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