File indexing completed on 2024-04-28 03:51:15

0001 /*.
0002     SPDX-FileCopyrightText: 2007 Vladimir Kuznetsov <ks.vladimir@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "itempalette.h"
0008 
0009 #include "worldmodel.h"
0010 #include "worldfactory.h"
0011 #include <stepcore/world.h>
0012 
0013 #include "settings.h"
0014 
0015 #include <QAction>
0016 #include <QActionGroup>
0017 #include <QEvent>
0018 #include <QIcon>
0019 #include <QPainter>
0020 #include <QScrollArea>
0021 #include <QScrollBar>
0022 #include <QStyleOption>
0023 #include <QToolButton>
0024 #include <QVBoxLayout>
0025 
0026 #include <KLocalizedString>
0027 
0028 class QPaintEvent;
0029 
0030 // Inspired by QToolBarSeparator
0031 class Separator: public QWidget
0032 {
0033 public:
0034     explicit Separator(QWidget* parent): QWidget(parent) {
0035         setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
0036         setProperty("isSeparator", true);
0037     }
0038 
0039     QSize sizeHint() const override {
0040         QStyleOption opt; opt.initFrom(this);
0041         const int extent = style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget());
0042         return QSize(extent, extent);
0043     }
0044 
0045     void paintEvent(QPaintEvent *) override {
0046         QPainter p(this); QStyleOption opt; opt.initFrom(this);
0047         style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget());
0048     }
0049 };
0050 
0051 class PaletteLayout: public QLayout
0052 {
0053 public:
0054     PaletteLayout(QWidget *parent, int margin = 0, int spacing = -1)
0055         : QLayout(parent) { setContentsMargins(margin, margin, margin, margin); setSpacing(spacing); resetCache(); }
0056     PaletteLayout(int spacing = -1) { setSpacing(spacing); resetCache(); }
0057     ~PaletteLayout() { QLayoutItem *item; while ((item = takeAt(0))) delete item; }
0058 
0059     void addItem(QLayoutItem *item) override { itemList.append(item); resetCache(); }
0060     int count() const override { return itemList.size(); }
0061     QLayoutItem* itemAt(int index) const override { return itemList.value(index); }
0062     QLayoutItem* takeAt(int index) override {
0063         resetCache();
0064         if (index >= 0 && index < itemList.size()) return itemList.takeAt(index);
0065         else return nullptr;
0066     }
0067 
0068     Qt::Orientations expandingDirections() const override { return Qt::Vertical; }
0069     bool hasHeightForWidth() const override { return true; }
0070 
0071     int heightForWidth(int width) const override {
0072         if(isCachedHeightForWidth && cachedHeightForWidth.width() == width) {
0073             return cachedHeightForWidth.height();
0074         } else {
0075             cachedHeightForWidth.setWidth(width);
0076             cachedHeightForWidth.setHeight(doLayout(QRect(0, 0, width, 0), true));
0077             isCachedHeightForWidth = true;
0078             return cachedHeightForWidth.height();
0079         }
0080     }
0081     
0082     void setGeometry(const QRect &rect) override {
0083         resetCache(); QLayout::setGeometry(rect); doLayout(rect, false);
0084     }
0085 
0086     QSize sizeHint() const override { return minimumSize(); }
0087 
0088     QSize minimumSize() const override {
0089         if(isCachedMinimumSize) return cachedMinimumSize;
0090         cachedMinimumSize = QSize();
0091         QLayoutItem *item;
0092         foreach (item, itemList)
0093             cachedMinimumSize = cachedMinimumSize.expandedTo(item->minimumSize());
0094         isCachedMinimumSize = true;
0095         return cachedMinimumSize;
0096     }
0097 
0098     void setOneLine(bool b) { oneLine = b; invalidate(); }
0099     bool isOneLine() const { return oneLine; }
0100 
0101     void invalidate() override { resetCache(); QLayout::invalidate(); }
0102 
0103 protected:
0104     void resetCache() { isCachedMinimumSize = false; isCachedHeightForWidth = false; }
0105 
0106     int doLayout(const QRect &rect, bool testOnly) const
0107     {
0108         int x = rect.x();
0109         int y = rect.y();
0110         int lineHeight = 0;
0111 
0112         if(oneLine) {
0113             foreach(QLayoutItem* item, itemList) {
0114                 y = y + lineHeight + spacing();
0115                 lineHeight = item->sizeHint().height();
0116                 if(!testOnly)
0117                     item->setGeometry(QRect(rect.x(), y, rect.width(), lineHeight));
0118             }
0119         } else {
0120             foreach(QLayoutItem* item, itemList) {
0121                 int w = item->sizeHint().width(); int h = item->sizeHint().height();
0122                 int nextX = x + item->sizeHint().width() + spacing();
0123                 if(item->widget() && item->widget()->property("isSeparator").toBool()) {
0124                     x = rect.x();
0125                     y = y + lineHeight + spacing();
0126                     nextX = x + rect.width();
0127                     w = rect.width();
0128                     lineHeight = 0;
0129                 } else if(nextX - spacing() > rect.right() && lineHeight > 0) {
0130                     x = rect.x();
0131                     y = y + lineHeight + spacing();
0132                     nextX = x + w + spacing();
0133                     lineHeight = 0;
0134                 }
0135 
0136                 if(!testOnly) item->setGeometry(QRect(x, y, w, h));
0137 
0138                 x = nextX;
0139                 lineHeight = qMax(lineHeight, h);
0140             }
0141         }
0142         return y + lineHeight - rect.y();
0143     }
0144 
0145     QList<QLayoutItem *> itemList;
0146     bool oneLine;
0147 
0148     mutable bool isCachedMinimumSize;
0149     mutable bool isCachedHeightForWidth;
0150     mutable QSize cachedMinimumSize;
0151     mutable QSize cachedHeightForWidth;
0152 };
0153 
0154 class PaletteScrollArea: public QScrollArea
0155 {
0156 public:
0157     PaletteScrollArea(QWidget* parent): QScrollArea(parent) {}
0158 
0159 protected:
0160     void resizeEvent(QResizeEvent* event) override {
0161         if(widget() && widget()->layout()) {
0162             QSize size(maximumViewportSize().width(),
0163                     widget()->layout()->heightForWidth(maximumViewportSize().width()));
0164             if(size.height() > maximumViewportSize().height()) {
0165                 int ext = style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
0166                 size.setWidth(maximumViewportSize().width() - 
0167                                 verticalScrollBar()->sizeHint().width() - ext);
0168                 size.setHeight(widget()->layout()->heightForWidth(size.width()));
0169             }
0170             widget()->resize(size);
0171         }
0172         QScrollArea::resizeEvent(event);
0173     }
0174 };
0175 
0176 ItemPalette::ItemPalette(WorldModel* worldModel, QWidget* parent)
0177     : QDockWidget(i18n("Palette"), parent), _worldModel(worldModel), _widget(nullptr)
0178 {
0179     setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
0180     //setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
0181 
0182     QWidget* topWidget = new QWidget(this);
0183 
0184     _scrollArea = new PaletteScrollArea(topWidget);
0185     _scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0186     _scrollArea->setFrameShape(QFrame::NoFrame);
0187 
0188     _widget = new QWidget(_scrollArea);
0189     _layout = new PaletteLayout(_widget);
0190     _layout->setSpacing(0);
0191     _layout->setOneLine(Settings::showButtonText());
0192 
0193     _actionGroup = new QActionGroup(this);
0194     _actionGroup->setExclusive(true);
0195 
0196     _pointerAction = new QAction(i18n("Pointer"), this);
0197     _pointerAction->setToolTip(i18n("Selection pointer"));
0198     _pointerAction->setIcon(QIcon::fromTheme(QStringLiteral("pointer")));
0199     _pointerAction->setCheckable(true);
0200     _pointerAction->setChecked(true);
0201     _pointerAction->setProperty("step_object", "Pointer");
0202     _actionGroup->addAction(_pointerAction);
0203     createToolButton(_pointerAction);
0204     createSeparator();
0205 
0206     foreach(const QString &name, _worldModel->worldFactory()->paletteMetaObjects()) {
0207         if(!name.isEmpty()) createObjectAction(_worldModel->worldFactory()->metaObject(name));
0208         else createSeparator();
0209     }
0210 
0211     _scrollArea->setWidget(_widget);
0212     _scrollArea->setMinimumWidth(_widget->minimumSizeHint().width());
0213 
0214     QVBoxLayout* topLayout = new QVBoxLayout(topWidget);
0215     topLayout->addWidget(_scrollArea);
0216     setWidget(topWidget);
0217 
0218     QObject::connect(_actionGroup, &QActionGroup::triggered, this, &ItemPalette::actionTriggered);
0219 
0220     QAction* showText = new QAction(i18n("Show text"), this);
0221     showText->setCheckable(true);
0222     showText->setChecked(Settings::showButtonText());
0223     QObject::connect(showText, &QAction::toggled, this, &ItemPalette::showButtonTextToggled);
0224 
0225     _widget->addAction(showText);
0226     _widget->setContextMenuPolicy(Qt::ActionsContextMenu);
0227 }
0228 
0229 void ItemPalette::createToolButton(QAction* action)
0230 {
0231     QToolButton* button = new QToolButton(_widget);
0232     button->setToolButtonStyle(Settings::showButtonText() ? 
0233                     Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly);
0234     button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0235     button->setAutoRaise(true);
0236     button->setIconSize(QSize(22,22));
0237     button->setDefaultAction(action);
0238     _toolButtons.append(button);
0239     _layout->addWidget(button);
0240 }
0241 
0242 void ItemPalette::createSeparator()
0243 {
0244     QAction* action = new QAction(this);
0245     action->setSeparator(true);
0246     _actionGroup->addAction(action);
0247     _layout->addWidget(new Separator(_widget));
0248 }
0249 
0250 void ItemPalette::createObjectAction(const StepCore::MetaObject* metaObject)
0251 {
0252     Q_ASSERT(metaObject && !metaObject->isAbstract());
0253 
0254     QAction* action = new QAction(metaObject->classNameTr(), this);
0255     action->setToolTip(metaObject->descriptionTr());
0256     action->setIcon(_worldModel->worldFactory()->objectIcon(metaObject));
0257     action->setCheckable(true);
0258     action->setProperty("step_object", metaObject->className());
0259     _actionGroup->addAction(action);
0260     createToolButton(action);
0261 }
0262 
0263 void ItemPalette::showButtonTextToggled(bool b)
0264 {
0265     Settings::setShowButtonText(b);
0266     Settings::self()->save();
0267     foreach(QToolButton* button, _toolButtons) {
0268         button->setToolButtonStyle(b ? Qt::ToolButtonTextBesideIcon :
0269                                        Qt::ToolButtonIconOnly);
0270     }
0271     _layout->setOneLine(b);
0272     _scrollArea->setMinimumWidth(_widget->minimumSizeHint().width());
0273 }
0274 
0275 void ItemPalette::actionTriggered(QAction* action)
0276 {
0277     emit beginAddItem(action->property("step_object").toString());
0278 }
0279 
0280 void ItemPalette::endAddItem(const QString& name, bool /*success*/)
0281 {
0282     if(name == _actionGroup->checkedAction()->property("step_object").toString())
0283         _pointerAction->setChecked(true);
0284 }
0285 
0286 bool ItemPalette::event(QEvent* event)
0287 {
0288     return QDockWidget::event(event);
0289 }
0290 
0291 #include "moc_itempalette.cpp"