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