1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qtbuttonpropertybrowser.h"
41 #include <QtCore/QSet>
42 #include <QtWidgets/QGridLayout>
43 #include <QtWidgets/QLabel>
44 #include <QtCore/QTimer>
45 #include <QtCore/QMap>
46 #include <QtWidgets/QToolButton>
47 #include <QtWidgets/QStyle>
48 
49 QT_BEGIN_NAMESPACE
50 
51 class QtButtonPropertyBrowserPrivate
52 {
53     QtButtonPropertyBrowser *q_ptr;
54     Q_DECLARE_PUBLIC(QtButtonPropertyBrowser)
55 public:
56 
57     void init(QWidget *parent);
58 
59     void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex);
60     void propertyRemoved(QtBrowserItem *index);
61     void propertyChanged(QtBrowserItem *index);
createEditor(QtProperty * property,QWidget * parent) const62     QWidget *createEditor(QtProperty *property, QWidget *parent) const
63         { return q_ptr->createEditor(property, parent); }
64 
65     void slotEditorDestroyed();
66     void slotUpdate();
67     void slotToggled(bool checked);
68 
69     struct WidgetItem
70     {
71         QWidget *widget{nullptr}; // can be null
72         QLabel *label{nullptr}; // main label with property name
73         QLabel *widgetLabel{nullptr}; // label substitute showing the current value if there is no widget
74         QToolButton *button{nullptr}; // expandable button for items with children
75         QWidget *container{nullptr}; // container which is expanded when the button is clicked
76         QGridLayout *layout{nullptr}; // layout in container
77         WidgetItem *parent{nullptr};
78         QList<WidgetItem *> children;
79         bool expanded{false};
80     };
81 private:
82     void updateLater();
83     void updateItem(WidgetItem *item);
84     void insertRow(QGridLayout *layout, int row) const;
85     void removeRow(QGridLayout *layout, int row) const;
86     int gridRow(WidgetItem *item) const;
87     int gridSpan(WidgetItem *item) const;
88     void setExpanded(WidgetItem *item, bool expanded);
89     QToolButton *createButton(QWidget *panret = 0) const;
90 
91     QMap<QtBrowserItem *, WidgetItem *> m_indexToItem;
92     QMap<WidgetItem *, QtBrowserItem *> m_itemToIndex;
93     QMap<QWidget *, WidgetItem *> m_widgetToItem;
94     QMap<QObject *, WidgetItem *> m_buttonToItem;
95     QGridLayout *m_mainLayout;
96     QList<WidgetItem *> m_children;
97     QList<WidgetItem *> m_recreateQueue;
98 };
99 
createButton(QWidget * parent) const100 QToolButton *QtButtonPropertyBrowserPrivate::createButton(QWidget *parent) const
101 {
102     QToolButton *button = new QToolButton(parent);
103     button->setCheckable(true);
104     button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
105     button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
106     button->setArrowType(Qt::DownArrow);
107     button->setIconSize(QSize(3, 16));
108     /*
109     QIcon icon;
110     icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowDown), QIcon::Normal, QIcon::Off);
111     icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowUp), QIcon::Normal, QIcon::On);
112     button->setIcon(icon);
113     */
114     return button;
115 }
116 
gridRow(WidgetItem * item) const117 int QtButtonPropertyBrowserPrivate::gridRow(WidgetItem *item) const
118 {
119     QList<WidgetItem *> siblings;
120     if (item->parent)
121         siblings = item->parent->children;
122     else
123         siblings = m_children;
124 
125     int row = 0;
126     for (WidgetItem *sibling : qAsConst(siblings)) {
127         if (sibling == item)
128             return row;
129         row += gridSpan(sibling);
130     }
131     return -1;
132 }
133 
gridSpan(WidgetItem * item) const134 int QtButtonPropertyBrowserPrivate::gridSpan(WidgetItem *item) const
135 {
136     if (item->container && item->expanded)
137         return 2;
138     return 1;
139 }
140 
init(QWidget * parent)141 void QtButtonPropertyBrowserPrivate::init(QWidget *parent)
142 {
143     m_mainLayout = new QGridLayout();
144     parent->setLayout(m_mainLayout);
145     QLayoutItem *item = new QSpacerItem(0, 0,
146                 QSizePolicy::Fixed, QSizePolicy::Expanding);
147     m_mainLayout->addItem(item, 0, 0);
148 }
149 
slotEditorDestroyed()150 void QtButtonPropertyBrowserPrivate::slotEditorDestroyed()
151 {
152     QWidget *editor = qobject_cast<QWidget *>(q_ptr->sender());
153     if (!editor)
154         return;
155     if (!m_widgetToItem.contains(editor))
156         return;
157     m_widgetToItem[editor]->widget = 0;
158     m_widgetToItem.remove(editor);
159 }
160 
slotUpdate()161 void QtButtonPropertyBrowserPrivate::slotUpdate()
162 {
163     for (WidgetItem *item : qAsConst(m_recreateQueue)) {
164         WidgetItem *parent = item->parent;
165         QWidget *w = 0;
166         QGridLayout *l = 0;
167         const int oldRow = gridRow(item);
168         if (parent) {
169             w = parent->container;
170             l = parent->layout;
171         } else {
172             w = q_ptr;
173             l = m_mainLayout;
174         }
175 
176         int span = 1;
177         if (!item->widget && !item->widgetLabel)
178             span = 2;
179         item->label = new QLabel(w);
180         item->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
181         l->addWidget(item->label, oldRow, 0, 1, span);
182 
183         updateItem(item);
184     }
185     m_recreateQueue.clear();
186 }
187 
setExpanded(WidgetItem * item,bool expanded)188 void QtButtonPropertyBrowserPrivate::setExpanded(WidgetItem *item, bool expanded)
189 {
190     if (item->expanded == expanded)
191         return;
192 
193     if (!item->container)
194         return;
195 
196     item->expanded = expanded;
197     const int row = gridRow(item);
198     WidgetItem *parent = item->parent;
199     QGridLayout *l = 0;
200     if (parent)
201         l = parent->layout;
202     else
203         l = m_mainLayout;
204 
205     if (expanded) {
206         insertRow(l, row + 1);
207         l->addWidget(item->container, row + 1, 0, 1, 2);
208         item->container->show();
209     } else {
210         l->removeWidget(item->container);
211         item->container->hide();
212         removeRow(l, row + 1);
213     }
214 
215     item->button->setChecked(expanded);
216     item->button->setArrowType(expanded ? Qt::UpArrow : Qt::DownArrow);
217 }
218 
slotToggled(bool checked)219 void QtButtonPropertyBrowserPrivate::slotToggled(bool checked)
220 {
221     WidgetItem *item = m_buttonToItem.value(q_ptr->sender());
222     if (!item)
223         return;
224 
225     setExpanded(item, checked);
226 
227     if (checked)
228         emit q_ptr->expanded(m_itemToIndex.value(item));
229     else
230         emit q_ptr->collapsed(m_itemToIndex.value(item));
231 }
232 
updateLater()233 void QtButtonPropertyBrowserPrivate::updateLater()
234 {
235     QTimer::singleShot(0, q_ptr, SLOT(slotUpdate()));
236 }
237 
propertyInserted(QtBrowserItem * index,QtBrowserItem * afterIndex)238 void QtButtonPropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex)
239 {
240     WidgetItem *afterItem = m_indexToItem.value(afterIndex);
241     WidgetItem *parentItem = m_indexToItem.value(index->parent());
242 
243     WidgetItem *newItem = new WidgetItem();
244     newItem->parent = parentItem;
245 
246     QGridLayout *layout = 0;
247     QWidget *parentWidget = 0;
248     int row = -1;
249     if (!afterItem) {
250         row = 0;
251         if (parentItem)
252             parentItem->children.insert(0, newItem);
253         else
254             m_children.insert(0, newItem);
255     } else {
256         row = gridRow(afterItem) + gridSpan(afterItem);
257         if (parentItem)
258             parentItem->children.insert(parentItem->children.indexOf(afterItem) + 1, newItem);
259         else
260             m_children.insert(m_children.indexOf(afterItem) + 1, newItem);
261     }
262 
263     if (!parentItem) {
264         layout = m_mainLayout;
265         parentWidget = q_ptr;
266     } else {
267         if (!parentItem->container) {
268             m_recreateQueue.removeAll(parentItem);
269             WidgetItem *grandParent = parentItem->parent;
270             QGridLayout *l = 0;
271             const int oldRow = gridRow(parentItem);
272             if (grandParent) {
273                 l = grandParent->layout;
274             } else {
275                 l = m_mainLayout;
276             }
277             QFrame *container = new QFrame();
278             container->setFrameShape(QFrame::Panel);
279             container->setFrameShadow(QFrame::Raised);
280             parentItem->container = container;
281             parentItem->button = createButton();
282             m_buttonToItem[parentItem->button] = parentItem;
283             q_ptr->connect(parentItem->button, SIGNAL(toggled(bool)), q_ptr, SLOT(slotToggled(bool)));
284             parentItem->layout = new QGridLayout();
285             container->setLayout(parentItem->layout);
286             if (parentItem->label) {
287                 l->removeWidget(parentItem->label);
288                 delete parentItem->label;
289                 parentItem->label = 0;
290             }
291             int span = 1;
292             if (!parentItem->widget && !parentItem->widgetLabel)
293                 span = 2;
294             l->addWidget(parentItem->button, oldRow, 0, 1, span);
295             updateItem(parentItem);
296         }
297         layout = parentItem->layout;
298         parentWidget = parentItem->container;
299     }
300 
301     newItem->label = new QLabel(parentWidget);
302     newItem->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
303     newItem->widget = createEditor(index->property(), parentWidget);
304     if (newItem->widget) {
305         QObject::connect(newItem->widget, SIGNAL(destroyed()), q_ptr, SLOT(slotEditorDestroyed()));
306         m_widgetToItem[newItem->widget] = newItem;
307     } else if (index->property()->hasValue()) {
308         newItem->widgetLabel = new QLabel(parentWidget);
309         newItem->widgetLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
310     }
311 
312     insertRow(layout, row);
313     int span = 1;
314     if (newItem->widget)
315         layout->addWidget(newItem->widget, row, 1);
316     else if (newItem->widgetLabel)
317         layout->addWidget(newItem->widgetLabel, row, 1);
318     else
319         span = 2;
320     layout->addWidget(newItem->label, row, 0, span, 1);
321 
322     m_itemToIndex[newItem] = index;
323     m_indexToItem[index] = newItem;
324 
325     updateItem(newItem);
326 }
327 
propertyRemoved(QtBrowserItem * index)328 void QtButtonPropertyBrowserPrivate::propertyRemoved(QtBrowserItem *index)
329 {
330     WidgetItem *item = m_indexToItem.value(index);
331 
332     m_indexToItem.remove(index);
333     m_itemToIndex.remove(item);
334 
335     WidgetItem *parentItem = item->parent;
336 
337     const int row = gridRow(item);
338 
339     if (parentItem)
340         parentItem->children.removeAt(parentItem->children.indexOf(item));
341     else
342         m_children.removeAt(m_children.indexOf(item));
343 
344     const int colSpan = gridSpan(item);
345 
346     m_buttonToItem.remove(item->button);
347 
348     if (item->widget)
349         delete item->widget;
350     if (item->label)
351         delete item->label;
352     if (item->widgetLabel)
353         delete item->widgetLabel;
354     if (item->button)
355         delete item->button;
356     if (item->container)
357         delete item->container;
358 
359     if (!parentItem) {
360         removeRow(m_mainLayout, row);
361         if (colSpan > 1)
362             removeRow(m_mainLayout, row);
363     } else if (parentItem->children.count() != 0) {
364         removeRow(parentItem->layout, row);
365         if (colSpan > 1)
366             removeRow(parentItem->layout, row);
367     } else {
368         const WidgetItem *grandParent = parentItem->parent;
369         QGridLayout *l = 0;
370         if (grandParent) {
371             l = grandParent->layout;
372         } else {
373             l = m_mainLayout;
374         }
375 
376         const int parentRow = gridRow(parentItem);
377         const int parentSpan = gridSpan(parentItem);
378 
379         l->removeWidget(parentItem->button);
380         l->removeWidget(parentItem->container);
381         delete parentItem->button;
382         delete parentItem->container;
383         parentItem->button = 0;
384         parentItem->container = 0;
385         parentItem->layout = 0;
386         if (!m_recreateQueue.contains(parentItem))
387             m_recreateQueue.append(parentItem);
388         if (parentSpan > 1)
389             removeRow(l, parentRow + 1);
390 
391         updateLater();
392     }
393     m_recreateQueue.removeAll(item);
394 
395     delete item;
396 }
397 
insertRow(QGridLayout * layout,int row) const398 void QtButtonPropertyBrowserPrivate::insertRow(QGridLayout *layout, int row) const
399 {
400     QMap<QLayoutItem *, QRect> itemToPos;
401     int idx = 0;
402     while (idx < layout->count()) {
403         int r, c, rs, cs;
404         layout->getItemPosition(idx, &r, &c, &rs, &cs);
405         if (r >= row) {
406             itemToPos[layout->takeAt(idx)] = QRect(r + 1, c, rs, cs);
407         } else {
408             idx++;
409         }
410     }
411 
412     for (auto it = itemToPos.constBegin(), icend = itemToPos.constEnd(); it != icend; ++it) {
413         const QRect r = it.value();
414         layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
415     }
416 }
417 
removeRow(QGridLayout * layout,int row) const418 void QtButtonPropertyBrowserPrivate::removeRow(QGridLayout *layout, int row) const
419 {
420     QMap<QLayoutItem *, QRect> itemToPos;
421     int idx = 0;
422     while (idx < layout->count()) {
423         int r, c, rs, cs;
424         layout->getItemPosition(idx, &r, &c, &rs, &cs);
425         if (r > row) {
426             itemToPos[layout->takeAt(idx)] = QRect(r - 1, c, rs, cs);
427         } else {
428             idx++;
429         }
430     }
431 
432     for (auto it = itemToPos.constBegin(), icend = itemToPos.constEnd(); it != icend; ++it) {
433         const QRect r = it.value();
434         layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
435     }
436 }
437 
propertyChanged(QtBrowserItem * index)438 void QtButtonPropertyBrowserPrivate::propertyChanged(QtBrowserItem *index)
439 {
440     WidgetItem *item = m_indexToItem.value(index);
441 
442     updateItem(item);
443 }
444 
updateItem(WidgetItem * item)445 void QtButtonPropertyBrowserPrivate::updateItem(WidgetItem *item)
446 {
447     QtProperty *property = m_itemToIndex[item]->property();
448     if (item->button) {
449         QFont font = item->button->font();
450         font.setUnderline(property->isModified());
451         item->button->setFont(font);
452         item->button->setText(property->propertyName());
453         item->button->setToolTip(property->descriptionToolTip());
454         item->button->setStatusTip(property->statusTip());
455         item->button->setWhatsThis(property->whatsThis());
456         item->button->setEnabled(property->isEnabled());
457     }
458     if (item->label) {
459         QFont font = item->label->font();
460         font.setUnderline(property->isModified());
461         item->label->setFont(font);
462         item->label->setText(property->propertyName());
463         item->label->setToolTip(property->descriptionToolTip());
464         item->label->setStatusTip(property->statusTip());
465         item->label->setWhatsThis(property->whatsThis());
466         item->label->setEnabled(property->isEnabled());
467     }
468     if (item->widgetLabel) {
469         QFont font = item->widgetLabel->font();
470         font.setUnderline(false);
471         item->widgetLabel->setFont(font);
472         item->widgetLabel->setText(property->valueText());
473         item->widgetLabel->setToolTip(property->valueText());
474         item->widgetLabel->setEnabled(property->isEnabled());
475     }
476     if (item->widget) {
477         QFont font = item->widget->font();
478         font.setUnderline(false);
479         item->widget->setFont(font);
480         item->widget->setEnabled(property->isEnabled());
481         const QString valueToolTip = property->valueToolTip();
482         item->widget->setToolTip(valueToolTip.isEmpty() ? property->valueText() : valueToolTip);
483     }
484 }
485 
486 
487 
488 /*!
489     \class QtButtonPropertyBrowser
490     \internal
491     \inmodule QtDesigner
492     \since 4.4
493 
494     \brief The QtButtonPropertyBrowser class provides a drop down QToolButton
495     based property browser.
496 
497     A property browser is a widget that enables the user to edit a
498     given set of properties. Each property is represented by a label
499     specifying the property's name, and an editing widget (e.g. a line
500     edit or a combobox) holding its value. A property can have zero or
501     more subproperties.
502 
503     QtButtonPropertyBrowser provides drop down button for all nested
504     properties, i.e. subproperties are enclosed by a container associated with
505     the drop down button. The parent property's name is displayed as button text. For example:
506 
507     \image qtbuttonpropertybrowser.png
508 
509     Use the QtAbstractPropertyBrowser API to add, insert and remove
510     properties from an instance of the QtButtonPropertyBrowser
511     class. The properties themselves are created and managed by
512     implementations of the QtAbstractPropertyManager class.
513 
514     \sa QtTreePropertyBrowser, QtAbstractPropertyBrowser
515 */
516 
517 /*!
518     \fn void QtButtonPropertyBrowser::collapsed(QtBrowserItem *item)
519 
520     This signal is emitted when the \a item is collapsed.
521 
522     \sa expanded(), setExpanded()
523 */
524 
525 /*!
526     \fn void QtButtonPropertyBrowser::expanded(QtBrowserItem *item)
527 
528     This signal is emitted when the \a item is expanded.
529 
530     \sa collapsed(), setExpanded()
531 */
532 
533 /*!
534     Creates a property browser with the given \a parent.
535 */
QtButtonPropertyBrowser(QWidget * parent)536 QtButtonPropertyBrowser::QtButtonPropertyBrowser(QWidget *parent)
537     : QtAbstractPropertyBrowser(parent), d_ptr(new QtButtonPropertyBrowserPrivate)
538 {
539     d_ptr->q_ptr = this;
540 
541     d_ptr->init(this);
542 }
543 
544 /*!
545     Destroys this property browser.
546 
547     Note that the properties that were inserted into this browser are
548     \e not destroyed since they may still be used in other
549     browsers. The properties are owned by the manager that created
550     them.
551 
552     \sa QtProperty, QtAbstractPropertyManager
553 */
~QtButtonPropertyBrowser()554 QtButtonPropertyBrowser::~QtButtonPropertyBrowser()
555 {
556     const QMap<QtButtonPropertyBrowserPrivate::WidgetItem *, QtBrowserItem *>::ConstIterator icend = d_ptr->m_itemToIndex.constEnd();
557     for (QMap<QtButtonPropertyBrowserPrivate::WidgetItem *, QtBrowserItem *>::ConstIterator  it =  d_ptr->m_itemToIndex.constBegin(); it != icend; ++it)
558         delete it.key();
559 }
560 
561 /*!
562     \reimp
563 */
itemInserted(QtBrowserItem * item,QtBrowserItem * afterItem)564 void QtButtonPropertyBrowser::itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem)
565 {
566     d_ptr->propertyInserted(item, afterItem);
567 }
568 
569 /*!
570     \reimp
571 */
itemRemoved(QtBrowserItem * item)572 void QtButtonPropertyBrowser::itemRemoved(QtBrowserItem *item)
573 {
574     d_ptr->propertyRemoved(item);
575 }
576 
577 /*!
578     \reimp
579 */
itemChanged(QtBrowserItem * item)580 void QtButtonPropertyBrowser::itemChanged(QtBrowserItem *item)
581 {
582     d_ptr->propertyChanged(item);
583 }
584 
585 /*!
586     Sets the \a item to either collapse or expanded, depending on the value of \a expanded.
587 
588     \sa isExpanded(), expanded(), collapsed()
589 */
590 
setExpanded(QtBrowserItem * item,bool expanded)591 void QtButtonPropertyBrowser::setExpanded(QtBrowserItem *item, bool expanded)
592 {
593     QtButtonPropertyBrowserPrivate::WidgetItem *itm = d_ptr->m_indexToItem.value(item);
594     if (itm)
595         d_ptr->setExpanded(itm, expanded);
596 }
597 
598 /*!
599     Returns true if the \a item is expanded; otherwise returns false.
600 
601     \sa setExpanded()
602 */
603 
isExpanded(QtBrowserItem * item) const604 bool QtButtonPropertyBrowser::isExpanded(QtBrowserItem *item) const
605 {
606     QtButtonPropertyBrowserPrivate::WidgetItem *itm = d_ptr->m_indexToItem.value(item);
607     if (itm)
608         return itm->expanded;
609     return false;
610 }
611 
612 QT_END_NAMESPACE
613 
614 #include "moc_qtbuttonpropertybrowser.cpp"
615