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