qtbuttonpropertybrowser.cpp
Go to the documentation of this file.
1/****************************************************************************
2**
3** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the Qt Solutions component.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** You may use this file under the terms of the BSD license as follows:
10**
11** "Redistribution and use in source and binary forms, with or without
12** modification, are permitted provided that the following conditions are
13** met:
14** * Redistributions of source code must retain the above copyright
15** notice, this list of conditions and the following disclaimer.
16** * Redistributions in binary form must reproduce the above copyright
17** notice, this list of conditions and the following disclaimer in
18** the documentation and/or other materials provided with the
19** distribution.
20** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
21** of its contributors may be used to endorse or promote products derived
22** from this software without specific prior written permission.
23**
24**
25** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41
43
44#include <QGridLayout>
45#include <QLabel>
46#include <QMap>
47#include <QSet>
48#include <QStyle>
49#include <QTimer>
50#include <QToolButton>
51
52QT_BEGIN_NAMESPACE
53
55{
57 Q_DECLARE_PUBLIC(QtButtonPropertyBrowser)
58public:
59 void init(QWidget* parent);
60
64
65 QWidget*
66 createEditor(QtProperty* property, QWidget* parent) const
67 {
68 return q_ptr->createEditor(property, parent);
69 }
70
72 void slotUpdate();
73 void slotToggled(bool checked);
74
76 {
78 widget(0),
79 label(0),
80 widgetLabel(0),
81 button(0),
82 container(0),
83 layout(0),
84 /*line(0), */ parent(0),
85 expanded(false)
86 {
87 }
88
89 QWidget* widget; // can be null
90 QLabel* label; // main label with property name
91 QLabel* widgetLabel; // label substitute showing the current value if there is no widget
92 QToolButton* button; // expandable button for items with children
93 QWidget* container; // container which is expanded when the button is clicked
94 QGridLayout* layout; // layout in container
96 QList<WidgetItem*> children;
98 };
99
100private:
101 void updateLater();
102 void updateItem(WidgetItem* item);
103 void insertRow(QGridLayout* layout, int row) const;
104 void removeRow(QGridLayout* layout, int row) const;
105 int gridRow(WidgetItem* item) const;
106 int gridSpan(WidgetItem* item) const;
107 void setExpanded(WidgetItem* item, bool expanded);
108 QToolButton* createButton(QWidget* panret = 0) const;
109
110 QMap<QtBrowserItem*, WidgetItem*> m_indexToItem;
111 QMap<WidgetItem*, QtBrowserItem*> m_itemToIndex;
112 QMap<QWidget*, WidgetItem*> m_widgetToItem;
113 QMap<QObject*, WidgetItem*> m_buttonToItem;
114 QGridLayout* m_mainLayout;
115 QList<WidgetItem*> m_children;
116 QList<WidgetItem*> m_recreateQueue;
117};
118
119QToolButton*
120QtButtonPropertyBrowserPrivate::createButton(QWidget* parent) const
121{
122 QToolButton* button = new QToolButton(parent);
123 button->setCheckable(true);
124 button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
125 button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
126 button->setArrowType(Qt::DownArrow);
127 button->setIconSize(QSize(3, 16));
128 /*
129 QIcon icon;
130 icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowDown), QIcon::Normal, QIcon::Off);
131 icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowUp), QIcon::Normal, QIcon::On);
132 button->setIcon(icon);
133 */
134 return button;
135}
136
137int
138QtButtonPropertyBrowserPrivate::gridRow(WidgetItem* item) const
139{
140 QList<WidgetItem*> siblings;
141
142 if (item->parent)
143 {
144 siblings = item->parent->children;
145 }
146 else
147 {
148 siblings = m_children;
149 }
150
151 int row = 0;
152 QListIterator<WidgetItem*> it(siblings);
153
154 while (it.hasNext())
155 {
156 WidgetItem* sibling = it.next();
157
158 if (sibling == item)
159 {
160 return row;
161 }
162
163 row += gridSpan(sibling);
164 }
165
166 return -1;
167}
168
169int
170QtButtonPropertyBrowserPrivate::gridSpan(WidgetItem* item) const
171{
172 if (item->container && item->expanded)
173 {
174 return 2;
175 }
176
177 return 1;
178}
179
180void
182{
183 m_mainLayout = new QGridLayout();
184 parent->setLayout(m_mainLayout);
185 QLayoutItem* item = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
186 m_mainLayout->addItem(item, 0, 0);
187}
188
189void
191{
192 QWidget* editor = qobject_cast<QWidget*>(q_ptr->sender());
193
194 if (!editor)
195 {
196 return;
197 }
198
199 if (!m_widgetToItem.contains(editor))
200 {
201 return;
202 }
203
204 m_widgetToItem[editor]->widget = 0;
205 m_widgetToItem.remove(editor);
206}
207
208void
210{
211 QListIterator<WidgetItem*> itItem(m_recreateQueue);
212
213 while (itItem.hasNext())
214 {
215 WidgetItem* item = itItem.next();
216
217 WidgetItem* parent = item->parent;
218 QWidget* w = 0;
219 QGridLayout* l = 0;
220 const int oldRow = gridRow(item);
221
222 if (parent)
223 {
224 w = parent->container;
225 l = parent->layout;
226 }
227 else
228 {
229 w = q_ptr;
230 l = m_mainLayout;
231 }
232
233 int span = 1;
234
235 if (!item->widget && !item->widgetLabel)
236 {
237 span = 2;
238 }
239
240 item->label = new QLabel(w);
241 item->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
242 l->addWidget(item->label, oldRow, 0, 1, span);
243
244 updateItem(item);
245 }
246
247 m_recreateQueue.clear();
248}
249
250void
251QtButtonPropertyBrowserPrivate::setExpanded(WidgetItem* item, bool expanded)
252{
253 if (item->expanded == expanded)
254 {
255 return;
256 }
257
258 if (!item->container)
259 {
260 return;
261 }
262
263 item->expanded = expanded;
264 const int row = gridRow(item);
265 WidgetItem* parent = item->parent;
266 QGridLayout* l = 0;
267
268 if (parent)
269 {
270 l = parent->layout;
271 }
272 else
273 {
274 l = m_mainLayout;
275 }
276
277 if (expanded)
278 {
279 insertRow(l, row + 1);
280 l->addWidget(item->container, row + 1, 0, 1, 2);
281 item->container->show();
282 }
283 else
284 {
285 l->removeWidget(item->container);
286 item->container->hide();
287 removeRow(l, row + 1);
288 }
289
290 item->button->setChecked(expanded);
291 item->button->setArrowType(expanded ? Qt::UpArrow : Qt::DownArrow);
292}
293
294void
296{
297 WidgetItem* item = m_buttonToItem.value(q_ptr->sender());
298
299 if (!item)
300 {
301 return;
302 }
303
304 setExpanded(item, checked);
305
306 if (checked)
307 {
308 emit q_ptr->expanded(m_itemToIndex.value(item));
309 }
310 else
311 {
312 emit q_ptr->collapsed(m_itemToIndex.value(item));
313 }
314}
315
316void
317QtButtonPropertyBrowserPrivate::updateLater()
318{
319 QTimer::singleShot(0, q_ptr, SLOT(slotUpdate()));
320}
321
322void
324{
325 WidgetItem* afterItem = m_indexToItem.value(afterIndex);
326 WidgetItem* parentItem = m_indexToItem.value(index->parent());
327
328 WidgetItem* newItem = new WidgetItem();
329 newItem->parent = parentItem;
330
331 QGridLayout* layout = 0;
332 QWidget* parentWidget = 0;
333 int row = -1;
334
335 if (!afterItem)
336 {
337 row = 0;
338
339 if (parentItem)
340 {
341 parentItem->children.insert(0, newItem);
342 }
343 else
344 {
345 m_children.insert(0, newItem);
346 }
347 }
348 else
349 {
350 row = gridRow(afterItem) + gridSpan(afterItem);
351
352 if (parentItem)
353 {
354 parentItem->children.insert(parentItem->children.indexOf(afterItem) + 1, newItem);
355 }
356 else
357 {
358 m_children.insert(m_children.indexOf(afterItem) + 1, newItem);
359 }
360 }
361
362 if (!parentItem)
363 {
364 layout = m_mainLayout;
365 parentWidget = q_ptr;
366 }
367 else
368 {
369 if (!parentItem->container)
370 {
371 m_recreateQueue.removeAll(parentItem);
372 WidgetItem* grandParent = parentItem->parent;
373 QGridLayout* l = 0;
374 const int oldRow = gridRow(parentItem);
375
376 if (grandParent)
377 {
378 l = grandParent->layout;
379 }
380 else
381 {
382 l = m_mainLayout;
383 }
384
385 QFrame* container = new QFrame();
386 container->setFrameShape(QFrame::Panel);
387 container->setFrameShadow(QFrame::Raised);
388 parentItem->container = container;
389 parentItem->button = createButton();
390 m_buttonToItem[parentItem->button] = parentItem;
391 q_ptr->connect(
392 parentItem->button, SIGNAL(toggled(bool)), q_ptr, SLOT(slotToggled(bool)));
393 parentItem->layout = new QGridLayout();
394 container->setLayout(parentItem->layout);
395
396 if (parentItem->label)
397 {
398 l->removeWidget(parentItem->label);
399 delete parentItem->label;
400 parentItem->label = 0;
401 }
402
403 int span = 1;
404
405 if (!parentItem->widget && !parentItem->widgetLabel)
406 {
407 span = 2;
408 }
409
410 l->addWidget(parentItem->button, oldRow, 0, 1, span);
411 updateItem(parentItem);
412 }
413
414 layout = parentItem->layout;
415 parentWidget = parentItem->container;
416 }
417
418 newItem->label = new QLabel(parentWidget);
419 newItem->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
420 newItem->widget = createEditor(index->property(), parentWidget);
421
422 if (newItem->widget)
423 {
424 QObject::connect(newItem->widget, SIGNAL(destroyed()), q_ptr, SLOT(slotEditorDestroyed()));
425 m_widgetToItem[newItem->widget] = newItem;
426 }
427 else if (index->property()->hasValue())
428 {
429 newItem->widgetLabel = new QLabel(parentWidget);
430 newItem->widgetLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
431 }
432
433 insertRow(layout, row);
434 int span = 1;
435
436 if (newItem->widget)
437 {
438 layout->addWidget(newItem->widget, row, 1);
439 }
440 else if (newItem->widgetLabel)
441 {
442 layout->addWidget(newItem->widgetLabel, row, 1);
443 }
444 else
445 {
446 span = 2;
447 }
448
449 layout->addWidget(newItem->label, row, 0, span, 1);
450
451 m_itemToIndex[newItem] = index;
452 m_indexToItem[index] = newItem;
453
454 updateItem(newItem);
455}
456
457void
459{
460 WidgetItem* item = m_indexToItem.value(index);
461
462 m_indexToItem.remove(index);
463 m_itemToIndex.remove(item);
464
465 WidgetItem* parentItem = item->parent;
466
467 const int row = gridRow(item);
468
469 if (parentItem)
470 {
471 parentItem->children.removeAt(parentItem->children.indexOf(item));
472 }
473 else
474 {
475 m_children.removeAt(m_children.indexOf(item));
476 }
477
478 const int colSpan = gridSpan(item);
479
480 m_buttonToItem.remove(item->button);
481
482 if (item->widget)
483 {
484 delete item->widget;
485 }
486
487 if (item->label)
488 {
489 delete item->label;
490 }
491
492 if (item->widgetLabel)
493 {
494 delete item->widgetLabel;
495 }
496
497 if (item->button)
498 {
499 delete item->button;
500 }
501
502 if (item->container)
503 {
504 delete item->container;
505 }
506
507 if (!parentItem)
508 {
509 removeRow(m_mainLayout, row);
510
511 if (colSpan > 1)
512 {
513 removeRow(m_mainLayout, row);
514 }
515 }
516 else if (parentItem->children.count() != 0)
517 {
518 removeRow(parentItem->layout, row);
519
520 if (colSpan > 1)
521 {
522 removeRow(parentItem->layout, row);
523 }
524 }
525 else
526 {
527 const WidgetItem* grandParent = parentItem->parent;
528 QGridLayout* l = 0;
529
530 if (grandParent)
531 {
532 l = grandParent->layout;
533 }
534 else
535 {
536 l = m_mainLayout;
537 }
538
539 const int parentRow = gridRow(parentItem);
540 const int parentSpan = gridSpan(parentItem);
541
542 l->removeWidget(parentItem->button);
543 l->removeWidget(parentItem->container);
544 delete parentItem->button;
545 delete parentItem->container;
546 parentItem->button = 0;
547 parentItem->container = 0;
548 parentItem->layout = 0;
549
550 if (!m_recreateQueue.contains(parentItem))
551 {
552 m_recreateQueue.append(parentItem);
553 }
554
555 if (parentSpan > 1)
556 {
557 removeRow(l, parentRow + 1);
558 }
559
560 updateLater();
561 }
562
563 m_recreateQueue.removeAll(item);
564
565 delete item;
566}
567
568void
569QtButtonPropertyBrowserPrivate::insertRow(QGridLayout* layout, int row) const
570{
571 QMap<QLayoutItem*, QRect> itemToPos;
572 int idx = 0;
573
574 while (idx < layout->count())
575 {
576 int r, c, rs, cs;
577 layout->getItemPosition(idx, &r, &c, &rs, &cs);
578
579 if (r >= row)
580 {
581 itemToPos[layout->takeAt(idx)] = QRect(r + 1, c, rs, cs);
582 }
583 else
584 {
585 idx++;
586 }
587 }
588
589 const QMap<QLayoutItem*, QRect>::ConstIterator icend = itemToPos.constEnd();
590
591 for (QMap<QLayoutItem*, QRect>::ConstIterator it = itemToPos.constBegin(); it != icend; ++it)
592 {
593 const QRect r = it.value();
594 layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
595 }
596}
597
598void
599QtButtonPropertyBrowserPrivate::removeRow(QGridLayout* layout, int row) const
600{
601 QMap<QLayoutItem*, QRect> itemToPos;
602 int idx = 0;
603
604 while (idx < layout->count())
605 {
606 int r, c, rs, cs;
607 layout->getItemPosition(idx, &r, &c, &rs, &cs);
608
609 if (r > row)
610 {
611 itemToPos[layout->takeAt(idx)] = QRect(r - 1, c, rs, cs);
612 }
613 else
614 {
615 idx++;
616 }
617 }
618
619 const QMap<QLayoutItem*, QRect>::ConstIterator icend = itemToPos.constEnd();
620
621 for (QMap<QLayoutItem*, QRect>::ConstIterator it = itemToPos.constBegin(); it != icend; ++it)
622 {
623 const QRect r = it.value();
624 layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
625 }
626}
627
628void
630{
631 WidgetItem* item = m_indexToItem.value(index);
632
633 updateItem(item);
634}
635
636void
637QtButtonPropertyBrowserPrivate::updateItem(WidgetItem* item)
638{
639 QtProperty* property = m_itemToIndex[item]->property();
640
641 if (item->button)
642 {
643 QFont font = item->button->font();
644 font.setUnderline(property->isModified());
645 item->button->setFont(font);
646 item->button->setText(property->propertyName());
647 item->button->setToolTip(property->toolTip());
648 item->button->setStatusTip(property->statusTip());
649 item->button->setWhatsThis(property->whatsThis());
650 item->button->setEnabled(property->isEnabled());
651 }
652
653 if (item->label)
654 {
655 QFont font = item->label->font();
656 font.setUnderline(property->isModified());
657 item->label->setFont(font);
658 item->label->setText(property->propertyName());
659 item->label->setToolTip(property->toolTip());
660 item->label->setStatusTip(property->statusTip());
661 item->label->setWhatsThis(property->whatsThis());
662 item->label->setEnabled(property->isEnabled());
663 }
664
665 if (item->widgetLabel)
666 {
667 QFont font = item->widgetLabel->font();
668 font.setUnderline(false);
669 item->widgetLabel->setFont(font);
670 item->widgetLabel->setText(property->valueText());
671 item->widgetLabel->setToolTip(property->valueText());
672 item->widgetLabel->setEnabled(property->isEnabled());
673 }
674
675 if (item->widget)
676 {
677 QFont font = item->widget->font();
678 font.setUnderline(false);
679 item->widget->setFont(font);
680 item->widget->setEnabled(property->isEnabled());
681 item->widget->setToolTip(property->valueText());
682 }
683}
684
685/*!
686 \class QtButtonPropertyBrowser
687
688 \brief The QtButtonPropertyBrowser class provides a drop down QToolButton
689 based property browser.
690
691 A property browser is a widget that enables the user to edit a
692 given set of properties. Each property is represented by a label
693 specifying the property's name, and an editing widget (e.g. a line
694 edit or a combobox) holding its value. A property can have zero or
695 more subproperties.
696
697 QtButtonPropertyBrowser provides drop down button for all nested
698 properties, i.e. subproperties are enclosed by a container associated with
699 the drop down button. The parent property's name is displayed as button text. For example:
700
701 \image qtbuttonpropertybrowser.png
702
703 Use the QtAbstractPropertyBrowser API to add, insert and remove
704 properties from an instance of the QtButtonPropertyBrowser
705 class. The properties themselves are created and managed by
706 implementations of the QtAbstractPropertyManager class.
707
708 \sa QtTreePropertyBrowser, QtAbstractPropertyBrowser
709*/
710
711/*!
712 \fn void QtButtonPropertyBrowser::collapsed(QtBrowserItem *item)
713
714 This signal is emitted when the \a item is collapsed.
715
716 \sa expanded(), setExpanded()
717*/
718
719/*!
720 \fn void QtButtonPropertyBrowser::expanded(QtBrowserItem *item)
721
722 This signal is emitted when the \a item is expanded.
723
724 \sa collapsed(), setExpanded()
725*/
726
727/*!
728 Creates a property browser with the given \a parent.
729*/
732{
734 d_ptr->q_ptr = this;
735
736 d_ptr->init(this);
737}
738
739/*!
740 Destroys this property browser.
741
742 Note that the properties that were inserted into this browser are
743 \e not destroyed since they may still be used in other
744 browsers. The properties are owned by the manager that created
745 them.
746
747 \sa QtProperty, QtAbstractPropertyManager
748*/
750{
751 const QMap<QtButtonPropertyBrowserPrivate::WidgetItem*, QtBrowserItem*>::ConstIterator icend =
752 d_ptr->m_itemToIndex.constEnd();
753
754 for (QMap<QtButtonPropertyBrowserPrivate::WidgetItem*, QtBrowserItem*>::ConstIterator it =
755 d_ptr->m_itemToIndex.constBegin();
756 it != icend;
757 ++it)
758 {
759 delete it.key();
760 }
761
762 delete d_ptr;
763}
764
765/*!
766 \reimp
767*/
768void
770{
771 d_ptr->propertyInserted(item, afterItem);
772}
773
774/*!
775 \reimp
776*/
777void
779{
780 d_ptr->propertyRemoved(item);
781}
782
783/*!
784 \reimp
785*/
786void
788{
789 d_ptr->propertyChanged(item);
790}
791
792/*!
793 Sets the \a item to either collapse or expanded, depending on the value of \a expanded.
794
795 \sa isExpanded(), expanded(), collapsed()
796*/
797
798void
800{
801 QtButtonPropertyBrowserPrivate::WidgetItem* itm = d_ptr->m_indexToItem.value(item);
802
803 if (itm)
804 {
805 d_ptr->setExpanded(itm, expanded);
806 }
807}
808
809/*!
810 Returns true if the \a item is expanded; otherwise returns false.
811
812 \sa setExpanded()
813*/
814
815bool
817{
818 QtButtonPropertyBrowserPrivate::WidgetItem* itm = d_ptr->m_indexToItem.value(item);
819
820 if (itm)
821 {
822 return itm->expanded;
823 }
824
825 return false;
826}
827
828QT_END_NAMESPACE
829
830#include "moc_qtbuttonpropertybrowser.cpp"
uint8_t index
constexpr T c
QtAbstractPropertyBrowser(QWidget *parent=0)
The QtBrowserItem class represents a property in a property browser instance.
void propertyChanged(QtBrowserItem *index)
void propertyRemoved(QtBrowserItem *index)
void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex)
QWidget * createEditor(QtProperty *property, QWidget *parent) const
The QtButtonPropertyBrowser class provides a drop down QToolButton based property browser.
QtButtonPropertyBrowser(QWidget *parent=0)
void setExpanded(QtBrowserItem *item, bool expanded)
void itemRemoved(QtBrowserItem *item) override
void expanded(QtBrowserItem *item)
bool isExpanded(QtBrowserItem *item) const
void itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) override
void itemChanged(QtBrowserItem *item) override
The QtProperty class encapsulates an instance of a property.