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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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