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 "qtgroupboxpropertybrowser.h"
41 #include <QtCore/QSet>
42 #include <QtWidgets/QGridLayout>
43 #include <QtWidgets/QLabel>
44 #include <QtWidgets/QGroupBox>
45 #include <QtCore/QTimer>
46 #include <QtCore/QMap>
47 
48 QT_BEGIN_NAMESPACE
49 
50 class QtGroupBoxPropertyBrowserPrivate
51 {
52     QtGroupBoxPropertyBrowser *q_ptr;
53     Q_DECLARE_PUBLIC(QtGroupBoxPropertyBrowser)
54 public:
55 
56     void init(QWidget *parent);
57 
58     void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex);
59     void propertyRemoved(QtBrowserItem *index);
60     void propertyChanged(QtBrowserItem *index);
createEditor(QtProperty * property,QWidget * parent) const61     QWidget *createEditor(QtProperty *property, QWidget *parent) const
62         { return q_ptr->createEditor(property, parent); }
63 
64     void slotEditorDestroyed();
65     void slotUpdate();
66 
67     struct WidgetItem
68     {
69         QWidget *widget{nullptr}; // can be null
70         QLabel *label{nullptr};
71         QLabel *widgetLabel{nullptr};
72         QGroupBox *groupBox{nullptr};
73         QGridLayout *layout{nullptr};
74         QFrame *line{nullptr};
75         WidgetItem *parent{nullptr};
76         QList<WidgetItem *> children;
77     };
78 private:
79     void updateLater();
80     void updateItem(WidgetItem *item);
81     void insertRow(QGridLayout *layout, int row) const;
82     void removeRow(QGridLayout *layout, int row) const;
83 
84     bool hasHeader(WidgetItem *item) const;
85 
86     QMap<QtBrowserItem *, WidgetItem *> m_indexToItem;
87     QMap<WidgetItem *, QtBrowserItem *> m_itemToIndex;
88     QMap<QWidget *, WidgetItem *> m_widgetToItem;
89     QGridLayout *m_mainLayout;
90     QList<WidgetItem *> m_children;
91     QList<WidgetItem *> m_recreateQueue;
92 };
93 
init(QWidget * parent)94 void QtGroupBoxPropertyBrowserPrivate::init(QWidget *parent)
95 {
96     m_mainLayout = new QGridLayout();
97     parent->setLayout(m_mainLayout);
98     QLayoutItem *item = new QSpacerItem(0, 0,
99                 QSizePolicy::Fixed, QSizePolicy::Expanding);
100     m_mainLayout->addItem(item, 0, 0);
101 }
102 
slotEditorDestroyed()103 void QtGroupBoxPropertyBrowserPrivate::slotEditorDestroyed()
104 {
105     QWidget *editor = qobject_cast<QWidget *>(q_ptr->sender());
106     if (!editor)
107         return;
108     if (!m_widgetToItem.contains(editor))
109         return;
110     m_widgetToItem[editor]->widget = 0;
111     m_widgetToItem.remove(editor);
112 }
113 
slotUpdate()114 void QtGroupBoxPropertyBrowserPrivate::slotUpdate()
115 {
116     for (WidgetItem *item : qAsConst(m_recreateQueue)) {
117         WidgetItem *par = item->parent;
118         QWidget *w = 0;
119         QGridLayout *l = 0;
120         int oldRow = -1;
121         if (!par) {
122             w = q_ptr;
123             l = m_mainLayout;
124             oldRow = m_children.indexOf(item);
125         } else {
126             w = par->groupBox;
127             l = par->layout;
128             oldRow = par->children.indexOf(item);
129             if (hasHeader(par))
130                 oldRow += 2;
131         }
132 
133         if (item->widget) {
134             item->widget->setParent(w);
135         } else if (item->widgetLabel) {
136             item->widgetLabel->setParent(w);
137         } else {
138             item->widgetLabel = new QLabel(w);
139         }
140         int span = 1;
141         if (item->widget)
142             l->addWidget(item->widget, oldRow, 1, 1, 1);
143         else if (item->widgetLabel)
144             l->addWidget(item->widgetLabel, oldRow, 1, 1, 1);
145         else
146             span = 2;
147         item->label = new QLabel(w);
148         item->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
149         l->addWidget(item->label, oldRow, 0, 1, span);
150 
151         updateItem(item);
152     }
153     m_recreateQueue.clear();
154 }
155 
updateLater()156 void QtGroupBoxPropertyBrowserPrivate::updateLater()
157 {
158     QTimer::singleShot(0, q_ptr, SLOT(slotUpdate()));
159 }
160 
propertyInserted(QtBrowserItem * index,QtBrowserItem * afterIndex)161 void QtGroupBoxPropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex)
162 {
163     WidgetItem *afterItem = m_indexToItem.value(afterIndex);
164     WidgetItem *parentItem = m_indexToItem.value(index->parent());
165 
166     WidgetItem *newItem = new WidgetItem();
167     newItem->parent = parentItem;
168 
169     QGridLayout *layout = 0;
170     QWidget *parentWidget = 0;
171     int row = -1;
172     if (!afterItem) {
173         row = 0;
174         if (parentItem)
175             parentItem->children.insert(0, newItem);
176         else
177             m_children.insert(0, newItem);
178     } else {
179         if (parentItem) {
180             row = parentItem->children.indexOf(afterItem) + 1;
181             parentItem->children.insert(row, newItem);
182         } else {
183             row = m_children.indexOf(afterItem) + 1;
184             m_children.insert(row, newItem);
185         }
186     }
187     if (parentItem && hasHeader(parentItem))
188         row += 2;
189 
190     if (!parentItem) {
191         layout = m_mainLayout;
192         parentWidget = q_ptr;;
193     } else {
194         if (!parentItem->groupBox) {
195             m_recreateQueue.removeAll(parentItem);
196             WidgetItem *par = parentItem->parent;
197             QWidget *w = 0;
198             QGridLayout *l = 0;
199             int oldRow = -1;
200             if (!par) {
201                 w = q_ptr;
202                 l = m_mainLayout;
203                 oldRow = m_children.indexOf(parentItem);
204             } else {
205                 w = par->groupBox;
206                 l = par->layout;
207                 oldRow = par->children.indexOf(parentItem);
208                 if (hasHeader(par))
209                     oldRow += 2;
210             }
211             parentItem->groupBox = new QGroupBox(w);
212             parentItem->layout = new QGridLayout();
213             parentItem->groupBox->setLayout(parentItem->layout);
214             if (parentItem->label) {
215                 l->removeWidget(parentItem->label);
216                 delete parentItem->label;
217                 parentItem->label = 0;
218             }
219             if (parentItem->widget) {
220                 l->removeWidget(parentItem->widget);
221                 parentItem->widget->setParent(parentItem->groupBox);
222                 parentItem->layout->addWidget(parentItem->widget, 0, 0, 1, 2);
223                 parentItem->line = new QFrame(parentItem->groupBox);
224             } else if (parentItem->widgetLabel) {
225                 l->removeWidget(parentItem->widgetLabel);
226                 delete parentItem->widgetLabel;
227                 parentItem->widgetLabel = 0;
228             }
229             if (parentItem->line) {
230                 parentItem->line->setFrameShape(QFrame::HLine);
231                 parentItem->line->setFrameShadow(QFrame::Sunken);
232                 parentItem->layout->addWidget(parentItem->line, 1, 0, 1, 2);
233             }
234             l->addWidget(parentItem->groupBox, oldRow, 0, 1, 2);
235             updateItem(parentItem);
236         }
237         layout = parentItem->layout;
238         parentWidget = parentItem->groupBox;
239     }
240 
241     newItem->label = new QLabel(parentWidget);
242     newItem->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
243     newItem->widget = createEditor(index->property(), parentWidget);
244     if (!newItem->widget) {
245         newItem->widgetLabel = new QLabel(parentWidget);
246     } else {
247         QObject::connect(newItem->widget, SIGNAL(destroyed()), q_ptr, SLOT(slotEditorDestroyed()));
248         m_widgetToItem[newItem->widget] = newItem;
249     }
250 
251     insertRow(layout, row);
252     int span = 1;
253     if (newItem->widget)
254         layout->addWidget(newItem->widget, row, 1);
255     else if (newItem->widgetLabel)
256         layout->addWidget(newItem->widgetLabel, row, 1);
257     else
258         span = 2;
259     layout->addWidget(newItem->label, row, 0, 1, span);
260 
261     m_itemToIndex[newItem] = index;
262     m_indexToItem[index] = newItem;
263 
264     updateItem(newItem);
265 }
266 
propertyRemoved(QtBrowserItem * index)267 void QtGroupBoxPropertyBrowserPrivate::propertyRemoved(QtBrowserItem *index)
268 {
269     WidgetItem *item = m_indexToItem.value(index);
270 
271     m_indexToItem.remove(index);
272     m_itemToIndex.remove(item);
273 
274     WidgetItem *parentItem = item->parent;
275 
276     int row = -1;
277 
278     if (parentItem) {
279         row = parentItem->children.indexOf(item);
280         parentItem->children.removeAt(row);
281         if (hasHeader(parentItem))
282             row += 2;
283     } else {
284         row = m_children.indexOf(item);
285         m_children.removeAt(row);
286     }
287 
288     if (item->widget)
289         delete item->widget;
290     if (item->label)
291         delete item->label;
292     if (item->widgetLabel)
293         delete item->widgetLabel;
294     if (item->groupBox)
295         delete item->groupBox;
296 
297     if (!parentItem) {
298         removeRow(m_mainLayout, row);
299     } else if (parentItem->children.count() != 0) {
300         removeRow(parentItem->layout, row);
301     } else {
302         WidgetItem *par = parentItem->parent;
303         QGridLayout *l = 0;
304         int oldRow = -1;
305         if (!par) {
306             l = m_mainLayout;
307             oldRow = m_children.indexOf(parentItem);
308         } else {
309             l = par->layout;
310             oldRow = par->children.indexOf(parentItem);
311             if (hasHeader(par))
312                 oldRow += 2;
313         }
314 
315         if (parentItem->widget) {
316             parentItem->widget->hide();
317             parentItem->widget->setParent(0);
318         } else if (parentItem->widgetLabel) {
319             parentItem->widgetLabel->hide();
320             parentItem->widgetLabel->setParent(0);
321         } else {
322             //parentItem->widgetLabel = new QLabel(w);
323         }
324         l->removeWidget(parentItem->groupBox);
325         delete parentItem->groupBox;
326         parentItem->groupBox = 0;
327         parentItem->line = 0;
328         parentItem->layout = 0;
329         if (!m_recreateQueue.contains(parentItem))
330             m_recreateQueue.append(parentItem);
331         updateLater();
332     }
333     m_recreateQueue.removeAll(item);
334 
335     delete item;
336 }
337 
insertRow(QGridLayout * layout,int row) const338 void QtGroupBoxPropertyBrowserPrivate::insertRow(QGridLayout *layout, int row) const
339 {
340     QMap<QLayoutItem *, QRect> itemToPos;
341     int idx = 0;
342     while (idx < layout->count()) {
343         int r, c, rs, cs;
344         layout->getItemPosition(idx, &r, &c, &rs, &cs);
345         if (r >= row) {
346             itemToPos[layout->takeAt(idx)] = QRect(r + 1, c, rs, cs);
347         } else {
348             idx++;
349         }
350     }
351 
352     const QMap<QLayoutItem *, QRect>::ConstIterator icend = itemToPos.constEnd();
353     for (QMap<QLayoutItem *, QRect>::ConstIterator it = itemToPos.constBegin(); it != icend; ++it) {
354         const QRect r = it.value();
355         layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
356     }
357 }
358 
removeRow(QGridLayout * layout,int row) const359 void QtGroupBoxPropertyBrowserPrivate::removeRow(QGridLayout *layout, int row) const
360 {
361     QMap<QLayoutItem *, QRect> itemToPos;
362     int idx = 0;
363     while (idx < layout->count()) {
364         int r, c, rs, cs;
365         layout->getItemPosition(idx, &r, &c, &rs, &cs);
366         if (r > row) {
367             itemToPos[layout->takeAt(idx)] = QRect(r - 1, c, rs, cs);
368         } else {
369             idx++;
370         }
371     }
372 
373     const QMap<QLayoutItem *, QRect>::ConstIterator icend = itemToPos.constEnd();
374     for (QMap<QLayoutItem *, QRect>::ConstIterator it = itemToPos.constBegin(); it != icend; ++it) {
375         const QRect r = it.value();
376         layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
377     }
378 }
379 
hasHeader(WidgetItem * item) const380 bool QtGroupBoxPropertyBrowserPrivate::hasHeader(WidgetItem *item) const
381 {
382     if (item->widget)
383         return true;
384     return false;
385 }
386 
propertyChanged(QtBrowserItem * index)387 void QtGroupBoxPropertyBrowserPrivate::propertyChanged(QtBrowserItem *index)
388 {
389     WidgetItem *item = m_indexToItem.value(index);
390 
391     updateItem(item);
392 }
393 
updateItem(WidgetItem * item)394 void QtGroupBoxPropertyBrowserPrivate::updateItem(WidgetItem *item)
395 {
396     QtProperty *property = m_itemToIndex[item]->property();
397     if (item->groupBox) {
398         QFont font = item->groupBox->font();
399         font.setUnderline(property->isModified());
400         item->groupBox->setFont(font);
401         item->groupBox->setTitle(property->propertyName());
402         item->groupBox->setToolTip(property->descriptionToolTip());
403         item->groupBox->setStatusTip(property->statusTip());
404         item->groupBox->setWhatsThis(property->whatsThis());
405         item->groupBox->setEnabled(property->isEnabled());
406     }
407     if (item->label) {
408         QFont font = item->label->font();
409         font.setUnderline(property->isModified());
410         item->label->setFont(font);
411         item->label->setText(property->propertyName());
412         item->label->setToolTip(property->descriptionToolTip());
413         item->label->setStatusTip(property->statusTip());
414         item->label->setWhatsThis(property->whatsThis());
415         item->label->setEnabled(property->isEnabled());
416     }
417     if (item->widgetLabel) {
418         QFont font = item->widgetLabel->font();
419         font.setUnderline(false);
420         item->widgetLabel->setFont(font);
421         item->widgetLabel->setText(property->valueText());
422         item->widgetLabel->setEnabled(property->isEnabled());
423     }
424     if (item->widget) {
425         QFont font = item->widget->font();
426         font.setUnderline(false);
427         item->widget->setFont(font);
428         item->widget->setEnabled(property->isEnabled());
429         const QString valueToolTip = property->valueToolTip();
430         item->widget->setToolTip(valueToolTip.isEmpty() ? property->valueText() : valueToolTip);
431     }
432     //item->setIcon(1, property->valueIcon());
433 }
434 
435 
436 
437 /*!
438     \class QtGroupBoxPropertyBrowser
439     \internal
440     \inmodule QtDesigner
441     \since 4.4
442 
443     \brief The QtGroupBoxPropertyBrowser class provides a QGroupBox
444     based property browser.
445 
446     A property browser is a widget that enables the user to edit a
447     given set of properties. Each property is represented by a label
448     specifying the property's name, and an editing widget (e.g. a line
449     edit or a combobox) holding its value. A property can have zero or
450     more subproperties.
451 
452     QtGroupBoxPropertyBrowser provides group boxes for all nested
453     properties, i.e. subproperties are enclosed by a group box with
454     the parent property's name as its title. For example:
455 
456     \image qtgroupboxpropertybrowser.png
457 
458     Use the QtAbstractPropertyBrowser API to add, insert and remove
459     properties from an instance of the QtGroupBoxPropertyBrowser
460     class. The properties themselves are created and managed by
461     implementations of the QtAbstractPropertyManager class.
462 
463     \sa QtTreePropertyBrowser, QtAbstractPropertyBrowser
464 */
465 
466 /*!
467     Creates a property browser with the given \a parent.
468 */
QtGroupBoxPropertyBrowser(QWidget * parent)469 QtGroupBoxPropertyBrowser::QtGroupBoxPropertyBrowser(QWidget *parent)
470     : QtAbstractPropertyBrowser(parent), d_ptr(new QtGroupBoxPropertyBrowserPrivate)
471 {
472     d_ptr->q_ptr = this;
473 
474     d_ptr->init(this);
475 }
476 
477 /*!
478     Destroys this property browser.
479 
480     Note that the properties that were inserted into this browser are
481     \e not destroyed since they may still be used in other
482     browsers. The properties are owned by the manager that created
483     them.
484 
485     \sa QtProperty, QtAbstractPropertyManager
486 */
~QtGroupBoxPropertyBrowser()487 QtGroupBoxPropertyBrowser::~QtGroupBoxPropertyBrowser()
488 {
489     const QMap<QtGroupBoxPropertyBrowserPrivate::WidgetItem *, QtBrowserItem *>::ConstIterator icend = d_ptr->m_itemToIndex.constEnd();
490     for (QMap<QtGroupBoxPropertyBrowserPrivate::WidgetItem *, QtBrowserItem *>::ConstIterator it = d_ptr->m_itemToIndex.constBegin(); it != icend; ++it)
491         delete it.key();
492 }
493 
494 /*!
495     \reimp
496 */
itemInserted(QtBrowserItem * item,QtBrowserItem * afterItem)497 void QtGroupBoxPropertyBrowser::itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem)
498 {
499     d_ptr->propertyInserted(item, afterItem);
500 }
501 
502 /*!
503     \reimp
504 */
itemRemoved(QtBrowserItem * item)505 void QtGroupBoxPropertyBrowser::itemRemoved(QtBrowserItem *item)
506 {
507     d_ptr->propertyRemoved(item);
508 }
509 
510 /*!
511     \reimp
512 */
itemChanged(QtBrowserItem * item)513 void QtGroupBoxPropertyBrowser::itemChanged(QtBrowserItem *item)
514 {
515     d_ptr->propertyChanged(item);
516 }
517 
518 QT_END_NAMESPACE
519 
520 #include "moc_qtgroupboxpropertybrowser.cpp"
521