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

source code of qttools/src/shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp