File indexing completed on 2024-05-05 07:51:40
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"