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 "qttreepropertybrowser.h"
5
6#include <QtCore/QHash>
7#include <QtGui/QFocusEvent>
8#include <QtGui/QIcon>
9#include <QtGui/QPainter>
10#include <QtGui/QPalette>
11#include <QtWidgets/QApplication>
12#include <QtWidgets/QHBoxLayout>
13#include <QtWidgets/QHeaderView>
14#include <QtWidgets/QItemDelegate>
15#include <QtWidgets/QStyle>
16#include <QtWidgets/QTreeWidget>
17
18QT_BEGIN_NAMESPACE
19
20class QtPropertyEditorView;
21
22class QtTreePropertyBrowserPrivate
23{
24 QtTreePropertyBrowser *q_ptr;
25 Q_DECLARE_PUBLIC(QtTreePropertyBrowser)
26
27public:
28 QtTreePropertyBrowserPrivate();
29 void init(QWidget *parent);
30
31 void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex);
32 void propertyRemoved(QtBrowserItem *index);
33 void propertyChanged(QtBrowserItem *index);
34 QWidget *createEditor(QtProperty *property, QWidget *parent) const
35 { return q_ptr->createEditor(property, parent); }
36 QtProperty *indexToProperty(const QModelIndex &index) const;
37 QTreeWidgetItem *indexToItem(const QModelIndex &index) const;
38 QtBrowserItem *indexToBrowserItem(const QModelIndex &index) const;
39 bool lastColumn(int column) const;
40 void disableItem(QTreeWidgetItem *item) const;
41 void enableItem(QTreeWidgetItem *item) const;
42 bool hasValue(QTreeWidgetItem *item) const;
43
44 void slotCollapsed(const QModelIndex &index);
45 void slotExpanded(const QModelIndex &index);
46
47 QColor calculatedBackgroundColor(QtBrowserItem *item) const;
48
49 QtPropertyEditorView *treeWidget() const { return m_treeWidget; }
50 bool markPropertiesWithoutValue() const { return m_markPropertiesWithoutValue; }
51
52 QtBrowserItem *currentItem() const;
53 void setCurrentItem(QtBrowserItem *browserItem, bool block);
54 void editItem(QtBrowserItem *browserItem);
55
56 void slotCurrentBrowserItemChanged(QtBrowserItem *item);
57 void slotCurrentTreeItemChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *);
58
59 QTreeWidgetItem *editedItem() const;
60
61private:
62 void updateItem(QTreeWidgetItem *item);
63
64 QHash<QtBrowserItem *, QTreeWidgetItem *> m_indexToItem;
65 QHash<QTreeWidgetItem *, QtBrowserItem *> m_itemToIndex;
66
67 QHash<QtBrowserItem *, QColor> m_indexToBackgroundColor;
68
69 QtPropertyEditorView *m_treeWidget;
70
71 bool m_headerVisible;
72 QtTreePropertyBrowser::ResizeMode m_resizeMode;
73 class QtPropertyEditorDelegate *m_delegate;
74 bool m_markPropertiesWithoutValue;
75 bool m_browserChangedBlocked;
76 QIcon m_expandIcon;
77};
78
79// ------------ QtPropertyEditorView
80class QtPropertyEditorView : public QTreeWidget
81{
82 Q_OBJECT
83public:
84 QtPropertyEditorView(QWidget *parent = 0);
85
86 void setEditorPrivate(QtTreePropertyBrowserPrivate *editorPrivate)
87 { m_editorPrivate = editorPrivate; }
88
89 QTreeWidgetItem *indexToItem(const QModelIndex &index) const
90 { return itemFromIndex(index); }
91
92protected:
93 void keyPressEvent(QKeyEvent *event) override;
94 void mousePressEvent(QMouseEvent *event) override;
95 void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
96
97private:
98 QtTreePropertyBrowserPrivate *m_editorPrivate;
99};
100
101QtPropertyEditorView::QtPropertyEditorView(QWidget *parent) :
102 QTreeWidget(parent),
103 m_editorPrivate(0)
104{
105 connect(sender: header(), signal: &QHeaderView::sectionDoubleClicked, context: this, slot: &QTreeView::resizeColumnToContents);
106}
107
108void QtPropertyEditorView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
109{
110 QStyleOptionViewItem opt = option;
111 bool hasValue = true;
112 if (m_editorPrivate) {
113 QtProperty *property = m_editorPrivate->indexToProperty(index);
114 if (property)
115 hasValue = property->hasValue();
116 }
117 if (!hasValue && m_editorPrivate->markPropertiesWithoutValue()) {
118 const QColor c = option.palette.color(cr: QPalette::Dark);
119 painter->fillRect(option.rect, color: c);
120 opt.palette.setColor(acr: QPalette::AlternateBase, acolor: c);
121 } else {
122 const QColor c = m_editorPrivate->calculatedBackgroundColor(item: m_editorPrivate->indexToBrowserItem(index));
123 if (c.isValid()) {
124 painter->fillRect(option.rect, color: c);
125 opt.palette.setColor(acr: QPalette::AlternateBase, acolor: c.lighter(f: 112));
126 }
127 }
128 QTreeWidget::drawRow(painter, options: opt, index);
129 QColor color = static_cast<QRgb>(QApplication::style()->styleHint(stylehint: QStyle::SH_Table_GridLineColor, opt: &opt));
130 painter->save();
131 painter->setPen(QPen(color));
132 painter->drawLine(x1: opt.rect.x(), y1: opt.rect.bottom(), x2: opt.rect.right(), y2: opt.rect.bottom());
133 painter->restore();
134}
135
136void QtPropertyEditorView::keyPressEvent(QKeyEvent *event)
137{
138 switch (event->key()) {
139 case Qt::Key_Return:
140 case Qt::Key_Enter:
141 case Qt::Key_Space: // Trigger Edit
142 if (!m_editorPrivate->editedItem())
143 if (const QTreeWidgetItem *item = currentItem())
144 if (item->columnCount() >= 2 && ((item->flags() & (Qt::ItemIsEditable | Qt::ItemIsEnabled)) == (Qt::ItemIsEditable | Qt::ItemIsEnabled))) {
145 event->accept();
146 // If the current position is at column 0, move to 1.
147 QModelIndex index = currentIndex();
148 if (index.column() == 0) {
149 index = index.sibling(arow: index.row(), acolumn: 1);
150 setCurrentIndex(index);
151 }
152 edit(index);
153 return;
154 }
155 break;
156 default:
157 break;
158 }
159 QTreeWidget::keyPressEvent(event);
160}
161
162void QtPropertyEditorView::mousePressEvent(QMouseEvent *event)
163{
164 QTreeWidget::mousePressEvent(event);
165 QTreeWidgetItem *item = itemAt(p: event->position().toPoint());
166
167 if (item) {
168 if ((item != m_editorPrivate->editedItem()) && (event->button() == Qt::LeftButton)
169 && (header()->logicalIndexAt(position: event->position().toPoint().x()) == 1)
170 && ((item->flags() & (Qt::ItemIsEditable | Qt::ItemIsEnabled)) == (Qt::ItemIsEditable | Qt::ItemIsEnabled))) {
171 editItem(item, column: 1);
172 } else if (!m_editorPrivate->hasValue(item) && m_editorPrivate->markPropertiesWithoutValue() && !rootIsDecorated()) {
173 if (event->position().toPoint().x() + header()->offset() < 20)
174 item->setExpanded(!item->isExpanded());
175 }
176 }
177}
178
179// ------------ QtPropertyEditorDelegate
180class QtPropertyEditorDelegate : public QItemDelegate
181{
182 Q_OBJECT
183public:
184 QtPropertyEditorDelegate(QObject *parent = 0)
185 : QItemDelegate(parent), m_editorPrivate(0), m_editedItem(0), m_editedWidget(0)
186 {}
187
188 void setEditorPrivate(QtTreePropertyBrowserPrivate *editorPrivate)
189 { m_editorPrivate = editorPrivate; }
190
191 QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
192 const QModelIndex &index) const override;
193
194 void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
195 const QModelIndex &index) const override;
196
197 void paint(QPainter *painter, const QStyleOptionViewItem &option,
198 const QModelIndex &index) const override;
199
200 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
201
202 void setModelData(QWidget *, QAbstractItemModel *,
203 const QModelIndex &) const override {}
204
205 void setEditorData(QWidget *, const QModelIndex &) const override {}
206
207 bool eventFilter(QObject *object, QEvent *event) override;
208 void closeEditor(QtProperty *property);
209
210 QTreeWidgetItem *editedItem() const { return m_editedItem; }
211
212private slots:
213 void slotEditorDestroyed(QObject *object);
214
215private:
216 int indentation(const QModelIndex &index) const;
217
218 using EditorToPropertyMap = QHash<QWidget *, QtProperty *>;
219 mutable EditorToPropertyMap m_editorToProperty;
220
221 using PropertyToEditorMap = QHash<QtProperty *, QWidget *>;
222 mutable PropertyToEditorMap m_propertyToEditor;
223 QtTreePropertyBrowserPrivate *m_editorPrivate;
224 mutable QTreeWidgetItem *m_editedItem;
225 mutable QWidget *m_editedWidget;
226};
227
228int QtPropertyEditorDelegate::indentation(const QModelIndex &index) const
229{
230 if (!m_editorPrivate)
231 return 0;
232
233 QTreeWidgetItem *item = m_editorPrivate->indexToItem(index);
234 int indent = 0;
235 while (item->parent()) {
236 item = item->parent();
237 ++indent;
238 }
239 if (m_editorPrivate->treeWidget()->rootIsDecorated())
240 ++indent;
241 return indent * m_editorPrivate->treeWidget()->indentation();
242}
243
244void QtPropertyEditorDelegate::slotEditorDestroyed(QObject *object)
245{
246 if (auto *w = qobject_cast<QWidget *>(o: object)) {
247 const auto it = m_editorToProperty.find(key: w);
248 if (it != m_editorToProperty.end()) {
249 m_propertyToEditor.remove(key: it.value());
250 m_editorToProperty.erase(it);
251 }
252 if (m_editedWidget == w) {
253 m_editedWidget = nullptr;
254 m_editedItem = nullptr;
255 }
256 }
257}
258
259void QtPropertyEditorDelegate::closeEditor(QtProperty *property)
260{
261 if (QWidget *w = m_propertyToEditor.value(key: property, defaultValue: nullptr))
262 w->deleteLater();
263}
264
265QWidget *QtPropertyEditorDelegate::createEditor(QWidget *parent,
266 const QStyleOptionViewItem &, const QModelIndex &index) const
267{
268 if (index.column() == 1 && m_editorPrivate) {
269 QtProperty *property = m_editorPrivate->indexToProperty(index);
270 QTreeWidgetItem *item = m_editorPrivate->indexToItem(index);
271 if (property && item && (item->flags() & Qt::ItemIsEnabled)) {
272 QWidget *editor = m_editorPrivate->createEditor(property, parent);
273 if (editor) {
274 editor->setAutoFillBackground(true);
275 editor->installEventFilter(filterObj: const_cast<QtPropertyEditorDelegate *>(this));
276 connect(sender: editor, signal: &QObject::destroyed,
277 context: this, slot: &QtPropertyEditorDelegate::slotEditorDestroyed);
278 m_propertyToEditor[property] = editor;
279 m_editorToProperty[editor] = property;
280 m_editedItem = item;
281 m_editedWidget = editor;
282 }
283 return editor;
284 }
285 }
286 return nullptr;
287}
288
289void QtPropertyEditorDelegate::updateEditorGeometry(QWidget *editor,
290 const QStyleOptionViewItem &option, const QModelIndex &index) const
291{
292 Q_UNUSED(index);
293 editor->setGeometry(option.rect.adjusted(xp1: 0, yp1: 0, xp2: 0, yp2: -1));
294}
295
296void QtPropertyEditorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
297 const QModelIndex &index) const
298{
299 bool hasValue = true;
300 if (m_editorPrivate) {
301 QtProperty *property = m_editorPrivate->indexToProperty(index);
302 if (property)
303 hasValue = property->hasValue();
304 }
305 QStyleOptionViewItem opt = option;
306 if ((m_editorPrivate && index.column() == 0) || !hasValue) {
307 QtProperty *property = m_editorPrivate->indexToProperty(index);
308 if (property && property->isModified()) {
309 opt.font.setBold(true);
310 opt.fontMetrics = QFontMetrics(opt.font);
311 }
312 }
313 QColor c;
314 if (!hasValue && m_editorPrivate->markPropertiesWithoutValue()) {
315 c = opt.palette.color(cr: QPalette::Dark);
316 opt.palette.setColor(acr: QPalette::Text, acolor: opt.palette.color(cr: QPalette::BrightText));
317 } else {
318 c = m_editorPrivate->calculatedBackgroundColor(item: m_editorPrivate->indexToBrowserItem(index));
319 if (c.isValid() && (opt.features & QStyleOptionViewItem::Alternate))
320 c = c.lighter(f: 112);
321 }
322 if (c.isValid())
323 painter->fillRect(option.rect, color: c);
324 opt.state &= ~QStyle::State_HasFocus;
325 QItemDelegate::paint(painter, option: opt, index);
326
327 opt.palette.setCurrentColorGroup(QPalette::Active);
328 QColor color = static_cast<QRgb>(QApplication::style()->styleHint(stylehint: QStyle::SH_Table_GridLineColor, opt: &opt));
329 painter->save();
330 painter->setPen(QPen(color));
331 if (!m_editorPrivate || (!m_editorPrivate->lastColumn(column: index.column()) && hasValue)) {
332 int right = (option.direction == Qt::LeftToRight) ? option.rect.right() : option.rect.left();
333 painter->drawLine(x1: right, y1: option.rect.y(), x2: right, y2: option.rect.bottom());
334 }
335 painter->restore();
336}
337
338QSize QtPropertyEditorDelegate::sizeHint(const QStyleOptionViewItem &option,
339 const QModelIndex &index) const
340{
341 return QItemDelegate::sizeHint(option, index) + QSize(3, 4);
342}
343
344bool QtPropertyEditorDelegate::eventFilter(QObject *object, QEvent *event)
345{
346 if (event->type() == QEvent::FocusOut) {
347 QFocusEvent *fe = static_cast<QFocusEvent *>(event);
348 if (fe->reason() == Qt::ActiveWindowFocusReason)
349 return false;
350 }
351 return QItemDelegate::eventFilter(object, event);
352}
353
354// -------- QtTreePropertyBrowserPrivate implementation
355QtTreePropertyBrowserPrivate::QtTreePropertyBrowserPrivate() :
356 m_treeWidget(0),
357 m_headerVisible(true),
358 m_resizeMode(QtTreePropertyBrowser::Stretch),
359 m_delegate(0),
360 m_markPropertiesWithoutValue(false),
361 m_browserChangedBlocked(false)
362{
363}
364
365// Draw an icon indicating opened/closing branches
366static QIcon drawIndicatorIcon(const QPalette &palette, QStyle *style)
367{
368 QPixmap pix(14, 14);
369 pix.fill(fillColor: Qt::transparent);
370 QStyleOption branchOption;
371 branchOption.rect = QRect(2, 2, 9, 9); // ### hardcoded in qcommonstyle.cpp
372 branchOption.palette = palette;
373 branchOption.state = QStyle::State_Children;
374
375 QPainter p;
376 // Draw closed state
377 p.begin(&pix);
378 style->drawPrimitive(pe: QStyle::PE_IndicatorBranch, opt: &branchOption, p: &p);
379 p.end();
380 QIcon rc = pix;
381 rc.addPixmap(pixmap: pix, mode: QIcon::Selected, state: QIcon::Off);
382 // Draw opened state
383 branchOption.state |= QStyle::State_Open;
384 pix.fill(fillColor: Qt::transparent);
385 p.begin(&pix);
386 style->drawPrimitive(pe: QStyle::PE_IndicatorBranch, opt: &branchOption, p: &p);
387 p.end();
388
389 rc.addPixmap(pixmap: pix, mode: QIcon::Normal, state: QIcon::On);
390 rc.addPixmap(pixmap: pix, mode: QIcon::Selected, state: QIcon::On);
391 return rc;
392}
393
394void QtTreePropertyBrowserPrivate::init(QWidget *parent)
395{
396 auto *layout = new QHBoxLayout(parent);
397 layout->setContentsMargins(QMargins());
398 m_treeWidget = new QtPropertyEditorView(parent);
399 m_treeWidget->setEditorPrivate(this);
400 m_treeWidget->setIconSize(QSize(18, 18));
401 layout->addWidget(m_treeWidget);
402
403 m_treeWidget->setColumnCount(2);
404 QStringList labels;
405 labels.append(t: QCoreApplication::translate(context: "QtTreePropertyBrowser", key: "Property"));
406 labels.append(t: QCoreApplication::translate(context: "QtTreePropertyBrowser", key: "Value"));
407 m_treeWidget->setHeaderLabels(labels);
408 m_treeWidget->setAlternatingRowColors(true);
409 m_treeWidget->setEditTriggers(QAbstractItemView::EditKeyPressed);
410 m_delegate = new QtPropertyEditorDelegate(parent);
411 m_delegate->setEditorPrivate(this);
412 m_treeWidget->setItemDelegate(m_delegate);
413 m_treeWidget->header()->setSectionsMovable(false);
414 m_treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
415
416 m_expandIcon = drawIndicatorIcon(palette: q_ptr->palette(), style: q_ptr->style());
417
418 QObject::connect(sender: m_treeWidget, signal: &QTreeView::collapsed,
419 context: q_ptr, slot: [this](const QModelIndex &index) { slotCollapsed(index); });
420 QObject::connect(sender: m_treeWidget, signal: &QTreeView::expanded,
421 context: q_ptr, slot: [this](const QModelIndex &index) { slotExpanded(index); });
422 QObject::connect(sender: m_treeWidget, signal: &QTreeWidget::currentItemChanged,
423 context: q_ptr, slot: [this](QTreeWidgetItem *current, QTreeWidgetItem *previous)
424 { slotCurrentTreeItemChanged(newItem: current, previous); });
425}
426
427QtBrowserItem *QtTreePropertyBrowserPrivate::currentItem() const
428{
429 if (QTreeWidgetItem *treeItem = m_treeWidget->currentItem())
430 return m_itemToIndex.value(key: treeItem);
431 return nullptr;
432}
433
434void QtTreePropertyBrowserPrivate::setCurrentItem(QtBrowserItem *browserItem, bool block)
435{
436 const bool blocked = block ? m_treeWidget->blockSignals(b: true) : false;
437 if (browserItem == nullptr)
438 m_treeWidget->setCurrentItem(nullptr);
439 else
440 m_treeWidget->setCurrentItem(m_indexToItem.value(key: browserItem));
441 if (block)
442 m_treeWidget->blockSignals(b: blocked);
443}
444
445QtProperty *QtTreePropertyBrowserPrivate::indexToProperty(const QModelIndex &index) const
446{
447 QTreeWidgetItem *item = m_treeWidget->indexToItem(index);
448 QtBrowserItem *idx = m_itemToIndex.value(key: item);
449 if (idx)
450 return idx->property();
451 return nullptr;
452}
453
454QtBrowserItem *QtTreePropertyBrowserPrivate::indexToBrowserItem(const QModelIndex &index) const
455{
456 QTreeWidgetItem *item = m_treeWidget->indexToItem(index);
457 return m_itemToIndex.value(key: item);
458}
459
460QTreeWidgetItem *QtTreePropertyBrowserPrivate::indexToItem(const QModelIndex &index) const
461{
462 return m_treeWidget->indexToItem(index);
463}
464
465bool QtTreePropertyBrowserPrivate::lastColumn(int column) const
466{
467 return m_treeWidget->header()->visualIndex(logicalIndex: column) == m_treeWidget->columnCount() - 1;
468}
469
470void QtTreePropertyBrowserPrivate::disableItem(QTreeWidgetItem *item) const
471{
472 Qt::ItemFlags flags = item->flags();
473 if (flags & Qt::ItemIsEnabled) {
474 flags &= ~Qt::ItemIsEnabled;
475 item->setFlags(flags);
476 m_delegate->closeEditor(property: m_itemToIndex[item]->property());
477 const int childCount = item->childCount();
478 for (int i = 0; i < childCount; i++) {
479 QTreeWidgetItem *child = item->child(index: i);
480 disableItem(item: child);
481 }
482 }
483}
484
485void QtTreePropertyBrowserPrivate::enableItem(QTreeWidgetItem *item) const
486{
487 Qt::ItemFlags flags = item->flags();
488 flags |= Qt::ItemIsEnabled;
489 item->setFlags(flags);
490 const int childCount = item->childCount();
491 for (int i = 0; i < childCount; i++) {
492 QTreeWidgetItem *child = item->child(index: i);
493 QtProperty *property = m_itemToIndex[child]->property();
494 if (property->isEnabled()) {
495 enableItem(item: child);
496 }
497 }
498}
499
500bool QtTreePropertyBrowserPrivate::hasValue(QTreeWidgetItem *item) const
501{
502 QtBrowserItem *browserItem = m_itemToIndex.value(key: item);
503 if (browserItem)
504 return browserItem->property()->hasValue();
505 return false;
506}
507
508void QtTreePropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex)
509{
510 QTreeWidgetItem *afterItem = m_indexToItem.value(key: afterIndex);
511 QTreeWidgetItem *parentItem = m_indexToItem.value(key: index->parent());
512
513 QTreeWidgetItem *newItem = nullptr;
514 if (parentItem) {
515 newItem = new QTreeWidgetItem(parentItem, afterItem);
516 } else {
517 newItem = new QTreeWidgetItem(m_treeWidget, afterItem);
518 }
519 m_itemToIndex[newItem] = index;
520 m_indexToItem[index] = newItem;
521
522 newItem->setFlags(newItem->flags() | Qt::ItemIsEditable);
523 newItem->setExpanded(true);
524
525 updateItem(item: newItem);
526}
527
528void QtTreePropertyBrowserPrivate::propertyRemoved(QtBrowserItem *index)
529{
530 QTreeWidgetItem *item = m_indexToItem.value(key: index);
531
532 if (m_treeWidget->currentItem() == item) {
533 m_treeWidget->setCurrentItem(0);
534 }
535
536 delete item;
537
538 m_indexToItem.remove(key: index);
539 m_itemToIndex.remove(key: item);
540 m_indexToBackgroundColor.remove(key: index);
541}
542
543void QtTreePropertyBrowserPrivate::propertyChanged(QtBrowserItem *index)
544{
545 QTreeWidgetItem *item = m_indexToItem.value(key: index);
546
547 updateItem(item);
548}
549
550void QtTreePropertyBrowserPrivate::updateItem(QTreeWidgetItem *item)
551{
552 QtProperty *property = m_itemToIndex[item]->property();
553 QIcon expandIcon;
554 if (property->hasValue()) {
555 const QString valueToolTip = property->valueToolTip();
556 const QString valueText = property->valueText();
557 item->setToolTip(column: 1, atoolTip: valueToolTip.isEmpty() ? valueText : valueToolTip);
558 item->setIcon(column: 1, aicon: property->valueIcon());
559 item->setText(column: 1, atext: valueText);
560 } else if (markPropertiesWithoutValue() && !m_treeWidget->rootIsDecorated()) {
561 expandIcon = m_expandIcon;
562 }
563 item->setIcon(column: 0, aicon: expandIcon);
564 item->setFirstColumnSpanned(!property->hasValue());
565 const QString descriptionToolTip = property->descriptionToolTip();
566 const QString propertyName = property->propertyName();
567 item->setToolTip(column: 0, atoolTip: descriptionToolTip.isEmpty() ? propertyName : descriptionToolTip);
568 item->setStatusTip(column: 0, astatusTip: property->statusTip());
569 item->setWhatsThis(column: 0, awhatsThis: property->whatsThis());
570 item->setText(column: 0, atext: propertyName);
571 bool wasEnabled = item->flags() & Qt::ItemIsEnabled;
572 bool isEnabled = wasEnabled;
573 if (property->isEnabled()) {
574 QTreeWidgetItem *parent = item->parent();
575 if (!parent || (parent->flags() & Qt::ItemIsEnabled))
576 isEnabled = true;
577 else
578 isEnabled = false;
579 } else {
580 isEnabled = false;
581 }
582 if (wasEnabled != isEnabled) {
583 if (isEnabled)
584 enableItem(item);
585 else
586 disableItem(item);
587 }
588 m_treeWidget->viewport()->update();
589}
590
591QColor QtTreePropertyBrowserPrivate::calculatedBackgroundColor(QtBrowserItem *item) const
592{
593 QtBrowserItem *i = item;
594 const auto itEnd = m_indexToBackgroundColor.constEnd();
595 while (i) {
596 auto it = m_indexToBackgroundColor.constFind(key: i);
597 if (it != itEnd)
598 return it.value();
599 i = i->parent();
600 }
601 return {};
602}
603
604void QtTreePropertyBrowserPrivate::slotCollapsed(const QModelIndex &index)
605{
606 QTreeWidgetItem *item = indexToItem(index);
607 QtBrowserItem *idx = m_itemToIndex.value(key: item);
608 if (item)
609 emit q_ptr->collapsed(item: idx);
610}
611
612void QtTreePropertyBrowserPrivate::slotExpanded(const QModelIndex &index)
613{
614 QTreeWidgetItem *item = indexToItem(index);
615 QtBrowserItem *idx = m_itemToIndex.value(key: item);
616 if (item)
617 emit q_ptr->expanded(item: idx);
618}
619
620void QtTreePropertyBrowserPrivate::slotCurrentBrowserItemChanged(QtBrowserItem *item)
621{
622 if (!m_browserChangedBlocked && item != currentItem())
623 setCurrentItem(browserItem: item, block: true);
624}
625
626void QtTreePropertyBrowserPrivate::slotCurrentTreeItemChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *)
627{
628 QtBrowserItem *browserItem = newItem ? m_itemToIndex.value(key: newItem) : 0;
629 m_browserChangedBlocked = true;
630 q_ptr->setCurrentItem(browserItem);
631 m_browserChangedBlocked = false;
632}
633
634QTreeWidgetItem *QtTreePropertyBrowserPrivate::editedItem() const
635{
636 return m_delegate->editedItem();
637}
638
639void QtTreePropertyBrowserPrivate::editItem(QtBrowserItem *browserItem)
640{
641 if (QTreeWidgetItem *treeItem = m_indexToItem.value(key: browserItem, defaultValue: nullptr)) {
642 m_treeWidget->setCurrentItem (item: treeItem, column: 1);
643 m_treeWidget->editItem(item: treeItem, column: 1);
644 }
645}
646
647/*!
648 \class QtTreePropertyBrowser
649 \internal
650 \inmodule QtDesigner
651 \since 4.4
652
653 \brief The QtTreePropertyBrowser class provides QTreeWidget based
654 property browser.
655
656 A property browser is a widget that enables the user to edit a
657 given set of properties. Each property is represented by a label
658 specifying the property's name, and an editing widget (e.g. a line
659 edit or a combobox) holding its value. A property can have zero or
660 more subproperties.
661
662 QtTreePropertyBrowser provides a tree based view for all nested
663 properties, i.e. properties that have subproperties can be in an
664 expanded (subproperties are visible) or collapsed (subproperties
665 are hidden) state. For example:
666
667 \image qttreepropertybrowser.png
668
669 Use the QtAbstractPropertyBrowser API to add, insert and remove
670 properties from an instance of the QtTreePropertyBrowser class.
671 The properties themselves are created and managed by
672 implementations of the QtAbstractPropertyManager class.
673
674 \sa QtGroupBoxPropertyBrowser, QtAbstractPropertyBrowser
675*/
676
677/*!
678 \fn void QtTreePropertyBrowser::collapsed(QtBrowserItem *item)
679
680 This signal is emitted when the \a item is collapsed.
681
682 \sa expanded(), setExpanded()
683*/
684
685/*!
686 \fn void QtTreePropertyBrowser::expanded(QtBrowserItem *item)
687
688 This signal is emitted when the \a item is expanded.
689
690 \sa collapsed(), setExpanded()
691*/
692
693/*!
694 Creates a property browser with the given \a parent.
695*/
696QtTreePropertyBrowser::QtTreePropertyBrowser(QWidget *parent)
697 : QtAbstractPropertyBrowser(parent), d_ptr(new QtTreePropertyBrowserPrivate)
698{
699 d_ptr->q_ptr = this;
700
701 d_ptr->init(parent: this);
702 QObject::connect(sender: this, signal: &QtAbstractPropertyBrowser::currentItemChanged,
703 context: this, slot: [this](QtBrowserItem *current)
704 { d_ptr->slotCurrentBrowserItemChanged(item: current); });
705}
706
707/*!
708 Destroys this property browser.
709
710 Note that the properties that were inserted into this browser are
711 \e not destroyed since they may still be used in other
712 browsers. The properties are owned by the manager that created
713 them.
714
715 \sa QtProperty, QtAbstractPropertyManager
716*/
717QtTreePropertyBrowser::~QtTreePropertyBrowser()
718{
719}
720
721/*!
722 \property QtTreePropertyBrowser::indentation
723 \brief indentation of the items in the tree view.
724*/
725int QtTreePropertyBrowser::indentation() const
726{
727 return d_ptr->m_treeWidget->indentation();
728}
729
730void QtTreePropertyBrowser::setIndentation(int i)
731{
732 d_ptr->m_treeWidget->setIndentation(i);
733}
734
735/*!
736 \property QtTreePropertyBrowser::rootIsDecorated
737 \brief whether to show controls for expanding and collapsing root items.
738*/
739bool QtTreePropertyBrowser::rootIsDecorated() const
740{
741 return d_ptr->m_treeWidget->rootIsDecorated();
742}
743
744void QtTreePropertyBrowser::setRootIsDecorated(bool show)
745{
746 d_ptr->m_treeWidget->setRootIsDecorated(show);
747 for (auto it = d_ptr->m_itemToIndex.cbegin(), end = d_ptr->m_itemToIndex.cend(); it != end; ++it) {
748 QtProperty *property = it.value()->property();
749 if (!property->hasValue())
750 d_ptr->updateItem(item: it.key());
751 }
752}
753
754/*!
755 \property QtTreePropertyBrowser::alternatingRowColors
756 \brief whether to draw the background using alternating colors.
757 By default this property is set to true.
758*/
759bool QtTreePropertyBrowser::alternatingRowColors() const
760{
761 return d_ptr->m_treeWidget->alternatingRowColors();
762}
763
764void QtTreePropertyBrowser::setAlternatingRowColors(bool enable)
765{
766 d_ptr->m_treeWidget->setAlternatingRowColors(enable);
767}
768
769/*!
770 \property QtTreePropertyBrowser::headerVisible
771 \brief whether to show the header.
772*/
773bool QtTreePropertyBrowser::isHeaderVisible() const
774{
775 return d_ptr->m_headerVisible;
776}
777
778void QtTreePropertyBrowser::setHeaderVisible(bool visible)
779{
780 if (d_ptr->m_headerVisible == visible)
781 return;
782
783 d_ptr->m_headerVisible = visible;
784 d_ptr->m_treeWidget->header()->setVisible(visible);
785}
786
787/*!
788 \enum QtTreePropertyBrowser::ResizeMode
789
790 The resize mode specifies the behavior of the header sections.
791
792 \value Interactive The user can resize the sections.
793 The sections can also be resized programmatically using setSplitterPosition().
794
795 \value Fixed The user cannot resize the section.
796 The section can only be resized programmatically using setSplitterPosition().
797
798 \value Stretch QHeaderView will automatically resize the section to fill the available space.
799 The size cannot be changed by the user or programmatically.
800
801 \value ResizeToContents QHeaderView will automatically resize the section to its optimal
802 size based on the contents of the entire column.
803 The size cannot be changed by the user or programmatically.
804
805 \sa setResizeMode()
806*/
807
808/*!
809 \property QtTreePropertyBrowser::resizeMode
810 \brief the resize mode of setions in the header.
811*/
812
813QtTreePropertyBrowser::ResizeMode QtTreePropertyBrowser::resizeMode() const
814{
815 return d_ptr->m_resizeMode;
816}
817
818void QtTreePropertyBrowser::setResizeMode(QtTreePropertyBrowser::ResizeMode mode)
819{
820 if (d_ptr->m_resizeMode == mode)
821 return;
822
823 d_ptr->m_resizeMode = mode;
824 QHeaderView::ResizeMode m = QHeaderView::Stretch;
825 switch (mode) {
826 case QtTreePropertyBrowser::Interactive: m = QHeaderView::Interactive; break;
827 case QtTreePropertyBrowser::Fixed: m = QHeaderView::Fixed; break;
828 case QtTreePropertyBrowser::ResizeToContents: m = QHeaderView::ResizeToContents; break;
829 case QtTreePropertyBrowser::Stretch:
830 default: m = QHeaderView::Stretch; break;
831 }
832 d_ptr->m_treeWidget->header()->setSectionResizeMode(m);
833}
834
835/*!
836 \property QtTreePropertyBrowser::splitterPosition
837 \brief the position of the splitter between the colunms.
838*/
839
840int QtTreePropertyBrowser::splitterPosition() const
841{
842 return d_ptr->m_treeWidget->header()->sectionSize(logicalIndex: 0);
843}
844
845void QtTreePropertyBrowser::setSplitterPosition(int position)
846{
847 d_ptr->m_treeWidget->header()->resizeSection(logicalIndex: 0, size: position);
848}
849
850/*!
851 Sets the \a item to either collapse or expanded, depending on the value of \a expanded.
852
853 \sa isExpanded(), expanded(), collapsed()
854*/
855
856void QtTreePropertyBrowser::setExpanded(QtBrowserItem *item, bool expanded)
857{
858 QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(key: item);
859 if (treeItem)
860 treeItem->setExpanded(expanded);
861}
862
863/*!
864 Returns true if the \a item is expanded; otherwise returns false.
865
866 \sa setExpanded()
867*/
868
869bool QtTreePropertyBrowser::isExpanded(QtBrowserItem *item) const
870{
871 QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(key: item);
872 if (treeItem)
873 return treeItem->isExpanded();
874 return false;
875}
876
877/*!
878 Returns true if the \a item is visible; otherwise returns false.
879
880 \sa setItemVisible()
881 \since 4.5
882*/
883
884bool QtTreePropertyBrowser::isItemVisible(QtBrowserItem *item) const
885{
886 if (const QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(key: item))
887 return !treeItem->isHidden();
888 return false;
889}
890
891/*!
892 Sets the \a item to be visible, depending on the value of \a visible.
893
894 \sa isItemVisible()
895 \since 4.5
896*/
897
898void QtTreePropertyBrowser::setItemVisible(QtBrowserItem *item, bool visible)
899{
900 if (QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(key: item))
901 treeItem->setHidden(!visible);
902}
903
904/*!
905 Sets the \a item's background color to \a color. Note that while item's background
906 is rendered every second row is being drawn with alternate color (which is a bit lighter than items \a color)
907
908 \sa backgroundColor(), calculatedBackgroundColor()
909*/
910
911void QtTreePropertyBrowser::setBackgroundColor(QtBrowserItem *item, const QColor &color)
912{
913 if (!d_ptr->m_indexToItem.contains(key: item))
914 return;
915 if (color.isValid())
916 d_ptr->m_indexToBackgroundColor[item] = color;
917 else
918 d_ptr->m_indexToBackgroundColor.remove(key: item);
919 d_ptr->m_treeWidget->viewport()->update();
920}
921
922/*!
923 Returns the \a item's color. If there is no color set for item it returns invalid color.
924
925 \sa calculatedBackgroundColor(), setBackgroundColor()
926*/
927
928QColor QtTreePropertyBrowser::backgroundColor(QtBrowserItem *item) const
929{
930 return d_ptr->m_indexToBackgroundColor.value(key: item);
931}
932
933/*!
934 Returns the \a item's color. If there is no color set for item it returns parent \a item's
935 color (if there is no color set for parent it returns grandparent's color and so on). In case
936 the color is not set for \a item and it's top level item it returns invalid color.
937
938 \sa backgroundColor(), setBackgroundColor()
939*/
940
941QColor QtTreePropertyBrowser::calculatedBackgroundColor(QtBrowserItem *item) const
942{
943 return d_ptr->calculatedBackgroundColor(item);
944}
945
946/*!
947 \property QtTreePropertyBrowser::propertiesWithoutValueMarked
948 \brief whether to enable or disable marking properties without value.
949
950 When marking is enabled the item's background is rendered in dark color and item's
951 foreground is rendered with light color.
952
953 \sa propertiesWithoutValueMarked()
954*/
955void QtTreePropertyBrowser::setPropertiesWithoutValueMarked(bool mark)
956{
957 if (d_ptr->m_markPropertiesWithoutValue == mark)
958 return;
959
960 d_ptr->m_markPropertiesWithoutValue = mark;
961 for (auto it = d_ptr->m_itemToIndex.cbegin(), end = d_ptr->m_itemToIndex.cend(); it != end; ++it) {
962 QtProperty *property = it.value()->property();
963 if (!property->hasValue())
964 d_ptr->updateItem(item: it.key());
965 }
966 d_ptr->m_treeWidget->viewport()->update();
967}
968
969bool QtTreePropertyBrowser::propertiesWithoutValueMarked() const
970{
971 return d_ptr->m_markPropertiesWithoutValue;
972}
973
974/*!
975 \reimp
976*/
977void QtTreePropertyBrowser::itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem)
978{
979 d_ptr->propertyInserted(index: item, afterIndex: afterItem);
980}
981
982/*!
983 \reimp
984*/
985void QtTreePropertyBrowser::itemRemoved(QtBrowserItem *item)
986{
987 d_ptr->propertyRemoved(index: item);
988}
989
990/*!
991 \reimp
992*/
993void QtTreePropertyBrowser::itemChanged(QtBrowserItem *item)
994{
995 d_ptr->propertyChanged(index: item);
996}
997
998/*!
999 Sets the current item to \a item and opens the relevant editor for it.
1000*/
1001void QtTreePropertyBrowser::editItem(QtBrowserItem *item)
1002{
1003 d_ptr->editItem(browserItem: item);
1004}
1005
1006QT_END_NAMESPACE
1007
1008#include "moc_qttreepropertybrowser.cpp"
1009#include "qttreepropertybrowser.moc"
1010

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