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 QtWidgets module 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#include "qtreeview.h"
40
41#include <qheaderview.h>
42#include <qitemdelegate.h>
43#include <qapplication.h>
44#include <qscrollbar.h>
45#include <qpainter.h>
46#include <qstack.h>
47#include <qstyle.h>
48#include <qstyleoption.h>
49#include <qevent.h>
50#include <qpen.h>
51#include <qdebug.h>
52#include <QMetaMethod>
53#include <private/qscrollbar_p.h>
54#ifndef QT_NO_ACCESSIBILITY
55#include <qaccessible.h>
56#endif
57
58#include <private/qtreeview_p.h>
59#include <private/qheaderview_p.h>
60
61#include <algorithm>
62
63QT_BEGIN_NAMESPACE
64
65/*!
66 \class QTreeView
67 \brief The QTreeView class provides a default model/view implementation of a tree view.
68
69 \ingroup model-view
70 \ingroup advanced
71 \inmodule QtWidgets
72
73 \image windows-treeview.png
74
75 A QTreeView implements a tree representation of items from a
76 model. This class is used to provide standard hierarchical lists that
77 were previously provided by the \c QListView class, but using the more
78 flexible approach provided by Qt's model/view architecture.
79
80 The QTreeView class is one of the \l{Model/View Classes} and is part of
81 Qt's \l{Model/View Programming}{model/view framework}.
82
83 QTreeView implements the interfaces defined by the
84 QAbstractItemView class to allow it to display data provided by
85 models derived from the QAbstractItemModel class.
86
87 It is simple to construct a tree view displaying data from a
88 model. In the following example, the contents of a directory are
89 supplied by a QFileSystemModel and displayed as a tree:
90
91 \snippet shareddirmodel/main.cpp 3
92 \snippet shareddirmodel/main.cpp 6
93
94 The model/view architecture ensures that the contents of the tree view
95 are updated as the model changes.
96
97 Items that have children can be in an expanded (children are
98 visible) or collapsed (children are hidden) state. When this state
99 changes a collapsed() or expanded() signal is emitted with the
100 model index of the relevant item.
101
102 The amount of indentation used to indicate levels of hierarchy is
103 controlled by the \l indentation property.
104
105 Headers in tree views are constructed using the QHeaderView class and can
106 be hidden using \c{header()->hide()}. Note that each header is configured
107 with its \l{QHeaderView::}{stretchLastSection} property set to true,
108 ensuring that the view does not waste any of the space assigned to it for
109 its header. If this value is set to true, this property will override the
110 resize mode set on the last section in the header.
111
112 By default, all columns in a tree view are movable except the first. To
113 disable movement of these columns, use QHeaderView's
114 \l {QHeaderView::}{setSectionsMovable()} function. For more information
115 about rearranging sections, see \l {Moving Header Sections}.
116
117 \section1 Key Bindings
118
119 QTreeView supports a set of key bindings that enable the user to
120 navigate in the view and interact with the contents of items:
121
122 \table
123 \header \li Key \li Action
124 \row \li Up \li Moves the cursor to the item in the same column on
125 the previous row. If the parent of the current item has no more rows to
126 navigate to, the cursor moves to the relevant item in the last row
127 of the sibling that precedes the parent.
128 \row \li Down \li Moves the cursor to the item in the same column on
129 the next row. If the parent of the current item has no more rows to
130 navigate to, the cursor moves to the relevant item in the first row
131 of the sibling that follows the parent.
132 \row \li Left \li Hides the children of the current item (if present)
133 by collapsing a branch.
134 \row \li Minus \li Same as LeftArrow.
135 \row \li Right \li Reveals the children of the current item (if present)
136 by expanding a branch.
137 \row \li Plus \li Same as RightArrow.
138 \row \li Asterisk \li Expands all children of the current item (if present).
139 \row \li PageUp \li Moves the cursor up one page.
140 \row \li PageDown \li Moves the cursor down one page.
141 \row \li Home \li Moves the cursor to an item in the same column of the first
142 row of the first top-level item in the model.
143 \row \li End \li Moves the cursor to an item in the same column of the last
144 row of the last top-level item in the model.
145 \row \li F2 \li In editable models, this opens the current item for editing.
146 The Escape key can be used to cancel the editing process and revert
147 any changes to the data displayed.
148 \endtable
149
150 \omit
151 Describe the expanding/collapsing concept if not covered elsewhere.
152 \endomit
153
154 \section1 Improving Performance
155
156 It is possible to give the view hints about the data it is handling in order
157 to improve its performance when displaying large numbers of items. One approach
158 that can be taken for views that are intended to display items with equal heights
159 is to set the \l uniformRowHeights property to true.
160
161 \sa QListView, QTreeWidget, {View Classes}, QAbstractItemModel, QAbstractItemView,
162 {Dir View Example}
163*/
164
165
166/*!
167 \fn void QTreeView::expanded(const QModelIndex &index)
168
169 This signal is emitted when the item specified by \a index is expanded.
170*/
171
172
173/*!
174 \fn void QTreeView::collapsed(const QModelIndex &index)
175
176 This signal is emitted when the item specified by \a index is collapsed.
177*/
178
179/*!
180 Constructs a tree view with a \a parent to represent a model's
181 data. Use setModel() to set the model.
182
183 \sa QAbstractItemModel
184*/
185QTreeView::QTreeView(QWidget *parent)
186 : QAbstractItemView(*new QTreeViewPrivate, parent)
187{
188 Q_D(QTreeView);
189 d->initialize();
190}
191
192/*!
193 \internal
194*/
195QTreeView::QTreeView(QTreeViewPrivate &dd, QWidget *parent)
196 : QAbstractItemView(dd, parent)
197{
198 Q_D(QTreeView);
199 d->initialize();
200}
201
202/*!
203 Destroys the tree view.
204*/
205QTreeView::~QTreeView()
206{
207}
208
209/*!
210 \reimp
211*/
212void QTreeView::setModel(QAbstractItemModel *model)
213{
214 Q_D(QTreeView);
215 if (model == d->model)
216 return;
217 if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
218 disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
219 this, SLOT(rowsRemoved(QModelIndex,int,int)));
220
221 disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset()));
222 }
223
224 if (d->selectionModel) { // support row editing
225 disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
226 d->model, SLOT(submit()));
227 disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
228 this, SLOT(rowsRemoved(QModelIndex,int,int)));
229 disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset()));
230 }
231 d->viewItems.clear();
232 d->expandedIndexes.clear();
233 d->hiddenIndexes.clear();
234 d->header->setModel(model);
235 QAbstractItemView::setModel(model);
236
237 // QAbstractItemView connects to a private slot
238 disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
239 this, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
240 // do header layout after the tree
241 disconnect(d->model, SIGNAL(layoutChanged()),
242 d->header, SLOT(_q_layoutChanged()));
243 // QTreeView has a public slot for this
244 connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
245 this, SLOT(rowsRemoved(QModelIndex,int,int)));
246
247 connect(d->model, SIGNAL(modelAboutToBeReset()), SLOT(_q_modelAboutToBeReset()));
248
249 if (d->sortingEnabled)
250 d->_q_sortIndicatorChanged(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
251}
252
253/*!
254 \reimp
255*/
256void QTreeView::setRootIndex(const QModelIndex &index)
257{
258 Q_D(QTreeView);
259 d->header->setRootIndex(index);
260 QAbstractItemView::setRootIndex(index);
261}
262
263/*!
264 \reimp
265*/
266void QTreeView::setSelectionModel(QItemSelectionModel *selectionModel)
267{
268 Q_D(QTreeView);
269 Q_ASSERT(selectionModel);
270 if (d->selectionModel) {
271 // support row editing
272 disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
273 d->model, SLOT(submit()));
274 }
275
276 d->header->setSelectionModel(selectionModel);
277 QAbstractItemView::setSelectionModel(selectionModel);
278
279 if (d->selectionModel) {
280 // support row editing
281 connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
282 d->model, SLOT(submit()));
283 }
284}
285
286/*!
287 Returns the header for the tree view.
288
289 \sa QAbstractItemModel::headerData()
290*/
291QHeaderView *QTreeView::header() const
292{
293 Q_D(const QTreeView);
294 return d->header;
295}
296
297/*!
298 Sets the header for the tree view, to the given \a header.
299
300 The view takes ownership over the given \a header and deletes it
301 when a new header is set.
302
303 \sa QAbstractItemModel::headerData()
304*/
305void QTreeView::setHeader(QHeaderView *header)
306{
307 Q_D(QTreeView);
308 if (header == d->header || !header)
309 return;
310 if (d->header && d->header->parent() == this)
311 delete d->header;
312 d->header = header;
313 d->header->setParent(this);
314 d->header->setFirstSectionMovable(false);
315
316 if (!d->header->model()) {
317 d->header->setModel(d->model);
318 if (d->selectionModel)
319 d->header->setSelectionModel(d->selectionModel);
320 }
321
322 connect(d->header, SIGNAL(sectionResized(int,int,int)),
323 this, SLOT(columnResized(int,int,int)));
324 connect(d->header, SIGNAL(sectionMoved(int,int,int)),
325 this, SLOT(columnMoved()));
326 connect(d->header, SIGNAL(sectionCountChanged(int,int)),
327 this, SLOT(columnCountChanged(int,int)));
328 connect(d->header, SIGNAL(sectionHandleDoubleClicked(int)),
329 this, SLOT(resizeColumnToContents(int)));
330 connect(d->header, SIGNAL(geometriesChanged()),
331 this, SLOT(updateGeometries()));
332
333 setSortingEnabled(d->sortingEnabled);
334 d->updateGeometry();
335}
336
337/*!
338 \property QTreeView::autoExpandDelay
339 \brief The delay time before items in a tree are opened during a drag and drop operation.
340 \since 4.3
341
342 This property holds the amount of time in milliseconds that the user must wait over
343 a node before that node will automatically open or close. If the time is
344 set to less then 0 then it will not be activated.
345
346 By default, this property has a value of -1, meaning that auto-expansion is disabled.
347*/
348int QTreeView::autoExpandDelay() const
349{
350 Q_D(const QTreeView);
351 return d->autoExpandDelay;
352}
353
354void QTreeView::setAutoExpandDelay(int delay)
355{
356 Q_D(QTreeView);
357 d->autoExpandDelay = delay;
358}
359
360/*!
361 \property QTreeView::indentation
362 \brief indentation of the items in the tree view.
363
364 This property holds the indentation measured in pixels of the items for each
365 level in the tree view. For top-level items, the indentation specifies the
366 horizontal distance from the viewport edge to the items in the first column;
367 for child items, it specifies their indentation from their parent items.
368
369 By default, the value of this property is style dependent. Thus, when the style
370 changes, this property updates from it. Calling setIndentation() stops the updates,
371 calling resetIndentation() will restore default behavior.
372*/
373int QTreeView::indentation() const
374{
375 Q_D(const QTreeView);
376 return d->indent;
377}
378
379void QTreeView::setIndentation(int i)
380{
381 Q_D(QTreeView);
382 if (!d->customIndent || (i != d->indent)) {
383 d->indent = i;
384 d->customIndent = true;
385 d->viewport->update();
386 }
387}
388
389void QTreeView::resetIndentation()
390{
391 Q_D(QTreeView);
392 if (d->customIndent) {
393 d->updateIndentationFromStyle();
394 d->customIndent = false;
395 }
396}
397
398/*!
399 \property QTreeView::rootIsDecorated
400 \brief whether to show controls for expanding and collapsing top-level items
401
402 Items with children are typically shown with controls to expand and collapse
403 them, allowing their children to be shown or hidden. If this property is
404 false, these controls are not shown for top-level items. This can be used to
405 make a single level tree structure appear like a simple list of items.
406
407 By default, this property is \c true.
408*/
409bool QTreeView::rootIsDecorated() const
410{
411 Q_D(const QTreeView);
412 return d->rootDecoration;
413}
414
415void QTreeView::setRootIsDecorated(bool show)
416{
417 Q_D(QTreeView);
418 if (show != d->rootDecoration) {
419 d->rootDecoration = show;
420 d->viewport->update();
421 }
422}
423
424/*!
425 \property QTreeView::uniformRowHeights
426 \brief whether all items in the treeview have the same height
427
428 This property should only be set to true if it is guaranteed that all items
429 in the view has the same height. This enables the view to do some
430 optimizations.
431
432 The height is obtained from the first item in the view. It is updated
433 when the data changes on that item.
434
435 \note If the editor size hint is bigger than the cell size hint, then the
436 size hint of the editor will be used.
437
438 By default, this property is \c false.
439*/
440bool QTreeView::uniformRowHeights() const
441{
442 Q_D(const QTreeView);
443 return d->uniformRowHeights;
444}
445
446void QTreeView::setUniformRowHeights(bool uniform)
447{
448 Q_D(QTreeView);
449 d->uniformRowHeights = uniform;
450}
451
452/*!
453 \property QTreeView::itemsExpandable
454 \brief whether the items are expandable by the user.
455
456 This property holds whether the user can expand and collapse items
457 interactively.
458
459 By default, this property is \c true.
460
461*/
462bool QTreeView::itemsExpandable() const
463{
464 Q_D(const QTreeView);
465 return d->itemsExpandable;
466}
467
468void QTreeView::setItemsExpandable(bool enable)
469{
470 Q_D(QTreeView);
471 d->itemsExpandable = enable;
472}
473
474/*!
475 \property QTreeView::expandsOnDoubleClick
476 \since 4.4
477 \brief whether the items can be expanded by double-clicking.
478
479 This property holds whether the user can expand and collapse items
480 by double-clicking. The default value is true.
481
482 \sa itemsExpandable
483*/
484bool QTreeView::expandsOnDoubleClick() const
485{
486 Q_D(const QTreeView);
487 return d->expandsOnDoubleClick;
488}
489
490void QTreeView::setExpandsOnDoubleClick(bool enable)
491{
492 Q_D(QTreeView);
493 d->expandsOnDoubleClick = enable;
494}
495
496/*!
497 Returns the horizontal position of the \a column in the viewport.
498*/
499int QTreeView::columnViewportPosition(int column) const
500{
501 Q_D(const QTreeView);
502 return d->header->sectionViewportPosition(column);
503}
504
505/*!
506 Returns the width of the \a column.
507
508 \sa resizeColumnToContents(), setColumnWidth()
509*/
510int QTreeView::columnWidth(int column) const
511{
512 Q_D(const QTreeView);
513 return d->header->sectionSize(column);
514}
515
516/*!
517 \since 4.2
518
519 Sets the width of the given \a column to the \a width specified.
520
521 \sa columnWidth(), resizeColumnToContents()
522*/
523void QTreeView::setColumnWidth(int column, int width)
524{
525 Q_D(QTreeView);
526 d->header->resizeSection(column, width);
527}
528
529/*!
530 Returns the column in the tree view whose header covers the \a x
531 coordinate given.
532*/
533int QTreeView::columnAt(int x) const
534{
535 Q_D(const QTreeView);
536 return d->header->logicalIndexAt(x);
537}
538
539/*!
540 Returns \c true if the \a column is hidden; otherwise returns \c false.
541
542 \sa hideColumn(), isRowHidden()
543*/
544bool QTreeView::isColumnHidden(int column) const
545{
546 Q_D(const QTreeView);
547 return d->header->isSectionHidden(column);
548}
549
550/*!
551 If \a hide is true the \a column is hidden, otherwise the \a column is shown.
552
553 \sa hideColumn(), setRowHidden()
554*/
555void QTreeView::setColumnHidden(int column, bool hide)
556{
557 Q_D(QTreeView);
558 if (column < 0 || column >= d->header->count())
559 return;
560 d->header->setSectionHidden(column, hide);
561}
562
563/*!
564 \property QTreeView::headerHidden
565 \brief whether the header is shown or not.
566 \since 4.4
567
568 If this property is \c true, the header is not shown otherwise it is.
569 The default value is false.
570
571 \sa header()
572*/
573bool QTreeView::isHeaderHidden() const
574{
575 Q_D(const QTreeView);
576 return d->header->isHidden();
577}
578
579void QTreeView::setHeaderHidden(bool hide)
580{
581 Q_D(QTreeView);
582 d->header->setHidden(hide);
583}
584
585/*!
586 Returns \c true if the item in the given \a row of the \a parent is hidden;
587 otherwise returns \c false.
588
589 \sa setRowHidden(), isColumnHidden()
590*/
591bool QTreeView::isRowHidden(int row, const QModelIndex &parent) const
592{
593 Q_D(const QTreeView);
594 if (!d->model)
595 return false;
596 return d->isRowHidden(d->model->index(row, 0, parent));
597}
598
599/*!
600 If \a hide is true the \a row with the given \a parent is hidden, otherwise the \a row is shown.
601
602 \sa isRowHidden(), setColumnHidden()
603*/
604void QTreeView::setRowHidden(int row, const QModelIndex &parent, bool hide)
605{
606 Q_D(QTreeView);
607 if (!d->model)
608 return;
609 QModelIndex index = d->model->index(row, 0, parent);
610 if (!index.isValid())
611 return;
612
613 if (hide) {
614 d->hiddenIndexes.insert(index);
615 } else if(d->isPersistent(index)) { //if the index is not persistent, it cannot be in the set
616 d->hiddenIndexes.remove(index);
617 }
618
619 d->doDelayedItemsLayout();
620}
621
622/*!
623 \since 4.3
624
625 Returns \c true if the item in first column in the given \a row
626 of the \a parent is spanning all the columns; otherwise returns \c false.
627
628 \sa setFirstColumnSpanned()
629*/
630bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const
631{
632 Q_D(const QTreeView);
633 if (d->spanningIndexes.isEmpty() || !d->model)
634 return false;
635 const QModelIndex index = d->model->index(row, 0, parent);
636 return d->spanningIndexes.contains(index);
637}
638
639/*!
640 \since 4.3
641
642 If \a span is true the item in the first column in the \a row
643 with the given \a parent is set to span all columns, otherwise all items
644 on the \a row are shown.
645
646 \sa isFirstColumnSpanned()
647*/
648void QTreeView::setFirstColumnSpanned(int row, const QModelIndex &parent, bool span)
649{
650 Q_D(QTreeView);
651 if (!d->model)
652 return;
653 const QModelIndex index = d->model->index(row, 0, parent);
654 if (!index.isValid())
655 return;
656
657 if (span)
658 d->spanningIndexes.insert(index);
659 else
660 d->spanningIndexes.remove(index);
661
662 d->executePostedLayout();
663 int i = d->viewIndex(index);
664 if (i >= 0)
665 d->viewItems[i].spanning = span;
666
667 d->viewport->update();
668}
669
670/*!
671 \reimp
672*/
673void QTreeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
674{
675 Q_D(QTreeView);
676
677 // if we are going to do a complete relayout anyway, there is no need to update
678 if (d->delayedPendingLayout)
679 return;
680
681 // refresh the height cache here; we don't really lose anything by getting the size hint,
682 // since QAbstractItemView::dataChanged() will get the visualRect for the items anyway
683
684 bool sizeChanged = false;
685 int topViewIndex = d->viewIndex(topLeft);
686 if (topViewIndex == 0) {
687 int newDefaultItemHeight = indexRowSizeHint(topLeft);
688 sizeChanged = d->defaultItemHeight != newDefaultItemHeight;
689 d->defaultItemHeight = newDefaultItemHeight;
690 }
691
692 if (topViewIndex != -1) {
693 if (topLeft.row() == bottomRight.row()) {
694 int oldHeight = d->itemHeight(topViewIndex);
695 d->invalidateHeightCache(topViewIndex);
696 sizeChanged |= (oldHeight != d->itemHeight(topViewIndex));
697 if (topLeft.column() == 0)
698 d->viewItems[topViewIndex].hasChildren = d->hasVisibleChildren(topLeft);
699 } else {
700 int bottomViewIndex = d->viewIndex(bottomRight);
701 for (int i = topViewIndex; i <= bottomViewIndex; ++i) {
702 int oldHeight = d->itemHeight(i);
703 d->invalidateHeightCache(i);
704 sizeChanged |= (oldHeight != d->itemHeight(i));
705 if (topLeft.column() == 0)
706 d->viewItems[i].hasChildren = d->hasVisibleChildren(d->viewItems.at(i).index);
707 }
708 }
709 }
710
711 if (sizeChanged) {
712 d->updateScrollBars();
713 d->viewport->update();
714 }
715 QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
716}
717
718/*!
719 Hides the \a column given.
720
721 \note This function should only be called after the model has been
722 initialized, as the view needs to know the number of columns in order to
723 hide \a column.
724
725 \sa showColumn(), setColumnHidden()
726*/
727void QTreeView::hideColumn(int column)
728{
729 Q_D(QTreeView);
730 if (d->header->isSectionHidden(column))
731 return;
732 d->header->hideSection(column);
733 doItemsLayout();
734}
735
736/*!
737 Shows the given \a column in the tree view.
738
739 \sa hideColumn(), setColumnHidden()
740*/
741void QTreeView::showColumn(int column)
742{
743 Q_D(QTreeView);
744 if (!d->header->isSectionHidden(column))
745 return;
746 d->header->showSection(column);
747 doItemsLayout();
748}
749
750/*!
751 \fn void QTreeView::expand(const QModelIndex &index)
752
753 Expands the model item specified by the \a index.
754
755 \sa expanded()
756*/
757void QTreeView::expand(const QModelIndex &index)
758{
759 Q_D(QTreeView);
760 if (!d->isIndexValid(index))
761 return;
762 if (index.flags() & Qt::ItemNeverHasChildren)
763 return;
764 if (d->isIndexExpanded(index))
765 return;
766 if (d->delayedPendingLayout) {
767 //A complete relayout is going to be performed, just store the expanded index, no need to layout.
768 if (d->storeExpanded(index))
769 emit expanded(index);
770 return;
771 }
772
773 int i = d->viewIndex(index);
774 if (i != -1) { // is visible
775 d->expand(i, true);
776 if (!d->isAnimating()) {
777 updateGeometries();
778 d->viewport->update();
779 }
780 } else if (d->storeExpanded(index)) {
781 emit expanded(index);
782 }
783}
784
785/*!
786 \fn void QTreeView::collapse(const QModelIndex &index)
787
788 Collapses the model item specified by the \a index.
789
790 \sa collapsed()
791*/
792void QTreeView::collapse(const QModelIndex &index)
793{
794 Q_D(QTreeView);
795 if (!d->isIndexValid(index))
796 return;
797 if (!d->isIndexExpanded(index))
798 return;
799 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
800 d->delayedAutoScroll.stop();
801
802 if (d->delayedPendingLayout) {
803 //A complete relayout is going to be performed, just un-store the expanded index, no need to layout.
804 if (d->isPersistent(index) && d->expandedIndexes.remove(index))
805 emit collapsed(index);
806 return;
807 }
808 int i = d->viewIndex(index);
809 if (i != -1) { // is visible
810 d->collapse(i, true);
811 if (!d->isAnimating()) {
812 updateGeometries();
813 viewport()->update();
814 }
815 } else {
816 if (d->isPersistent(index) && d->expandedIndexes.remove(index))
817 emit collapsed(index);
818 }
819}
820
821/*!
822 \fn bool QTreeView::isExpanded(const QModelIndex &index) const
823
824 Returns \c true if the model item \a index is expanded; otherwise returns
825 false.
826
827 \sa expand(), expanded(), setExpanded()
828*/
829bool QTreeView::isExpanded(const QModelIndex &index) const
830{
831 Q_D(const QTreeView);
832 return d->isIndexExpanded(index);
833}
834
835/*!
836 Sets the item referred to by \a index to either collapse or expanded,
837 depending on the value of \a expanded.
838
839 \sa expanded(), expand(), isExpanded()
840*/
841void QTreeView::setExpanded(const QModelIndex &index, bool expanded)
842{
843 if (expanded)
844 this->expand(index);
845 else
846 this->collapse(index);
847}
848
849/*!
850 \since 4.2
851 \property QTreeView::sortingEnabled
852 \brief whether sorting is enabled
853
854 If this property is \c true, sorting is enabled for the tree; if the property
855 is false, sorting is not enabled. The default value is false.
856
857 \note In order to avoid performance issues, it is recommended that
858 sorting is enabled \e after inserting the items into the tree.
859 Alternatively, you could also insert the items into a list before inserting
860 the items into the tree.
861
862 \sa sortByColumn()
863*/
864
865void QTreeView::setSortingEnabled(bool enable)
866{
867 Q_D(QTreeView);
868 header()->setSortIndicatorShown(enable);
869 header()->setSectionsClickable(enable);
870 if (enable) {
871 //sortByColumn has to be called before we connect or set the sortingEnabled flag
872 // because otherwise it will not call sort on the model.
873 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
874 connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
875 this, SLOT(_q_sortIndicatorChanged(int,Qt::SortOrder)), Qt::UniqueConnection);
876 } else {
877 disconnect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
878 this, SLOT(_q_sortIndicatorChanged(int,Qt::SortOrder)));
879 }
880 d->sortingEnabled = enable;
881}
882
883bool QTreeView::isSortingEnabled() const
884{
885 Q_D(const QTreeView);
886 return d->sortingEnabled;
887}
888
889/*!
890 \since 4.2
891 \property QTreeView::animated
892 \brief whether animations are enabled
893
894 If this property is \c true the treeview will animate expansion
895 and collapsing of branches. If this property is \c false, the treeview
896 will expand or collapse branches immediately without showing
897 the animation.
898
899 By default, this property is \c false.
900*/
901
902void QTreeView::setAnimated(bool animate)
903{
904 Q_D(QTreeView);
905 d->animationsEnabled = animate;
906}
907
908bool QTreeView::isAnimated() const
909{
910 Q_D(const QTreeView);
911 return d->animationsEnabled;
912}
913
914/*!
915 \since 4.2
916 \property QTreeView::allColumnsShowFocus
917 \brief whether items should show keyboard focus using all columns
918
919 If this property is \c true all columns will show focus, otherwise only
920 one column will show focus.
921
922 The default is false.
923*/
924
925void QTreeView::setAllColumnsShowFocus(bool enable)
926{
927 Q_D(QTreeView);
928 if (d->allColumnsShowFocus == enable)
929 return;
930 d->allColumnsShowFocus = enable;
931 d->viewport->update();
932}
933
934bool QTreeView::allColumnsShowFocus() const
935{
936 Q_D(const QTreeView);
937 return d->allColumnsShowFocus;
938}
939
940/*!
941 \property QTreeView::wordWrap
942 \brief the item text word-wrapping policy
943 \since 4.3
944
945 If this property is \c true then the item text is wrapped where
946 necessary at word-breaks; otherwise it is not wrapped at all.
947 This property is \c false by default.
948
949 Note that even if wrapping is enabled, the cell will not be
950 expanded to fit all text. Ellipsis will be inserted according to
951 the current \l{QAbstractItemView::}{textElideMode}.
952*/
953void QTreeView::setWordWrap(bool on)
954{
955 Q_D(QTreeView);
956 if (d->wrapItemText == on)
957 return;
958 d->wrapItemText = on;
959 d->doDelayedItemsLayout();
960}
961
962bool QTreeView::wordWrap() const
963{
964 Q_D(const QTreeView);
965 return d->wrapItemText;
966}
967
968/*!
969 \since 5.2
970
971 This specifies that the tree structure should be placed at logical index \a index.
972 If \index is set to -1 then the tree will always follow visual index 0.
973
974 \sa treePosition(), QHeaderView::swapSections(), QHeaderView::moveSection()
975*/
976
977void QTreeView::setTreePosition(int index)
978{
979 Q_D(QTreeView);
980 d->treePosition = index;
981 d->viewport->update();
982}
983
984/*!
985 \since 5.2
986
987 Return the logical index the tree is set on. If the return value is -1 then the
988 tree is placed on the visual index 0.
989
990 \sa setTreePosition()
991*/
992
993int QTreeView::treePosition() const
994{
995 Q_D(const QTreeView);
996 return d->treePosition;
997}
998
999/*!
1000 \reimp
1001 */
1002void QTreeView::keyboardSearch(const QString &search)
1003{
1004 Q_D(QTreeView);
1005 if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root))
1006 return;
1007
1008 // Do a relayout nows, so that we can utilize viewItems
1009 d->executePostedLayout();
1010 if (d->viewItems.isEmpty())
1011 return;
1012
1013 QModelIndex start;
1014 if (currentIndex().isValid())
1015 start = currentIndex();
1016 else
1017 start = d->viewItems.at(0).index;
1018
1019 bool skipRow = false;
1020 bool keyboardTimeWasValid = d->keyboardInputTime.isValid();
1021 qint64 keyboardInputTimeElapsed;
1022 if (keyboardTimeWasValid)
1023 keyboardInputTimeElapsed = d->keyboardInputTime.restart();
1024 else
1025 d->keyboardInputTime.start();
1026 if (search.isEmpty() || !keyboardTimeWasValid
1027 || keyboardInputTimeElapsed > QApplication::keyboardInputInterval()) {
1028 d->keyboardInput = search;
1029 skipRow = currentIndex().isValid(); //if it is not valid we should really start at QModelIndex(0,0)
1030 } else {
1031 d->keyboardInput += search;
1032 }
1033
1034 // special case for searches with same key like 'aaaaa'
1035 bool sameKey = false;
1036 if (d->keyboardInput.length() > 1) {
1037 int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.length() - 1));
1038 sameKey = (c == d->keyboardInput.length());
1039 if (sameKey)
1040 skipRow = true;
1041 }
1042
1043 // skip if we are searching for the same key or a new search started
1044 if (skipRow) {
1045 if (indexBelow(start).isValid()) {
1046 start = indexBelow(start);
1047 } else {
1048 const int origCol = start.column();
1049 start = d->viewItems.at(0).index;
1050 if (origCol != start.column())
1051 start = start.sibling(start.row(), origCol);
1052 }
1053 }
1054
1055 int startIndex = d->viewIndex(start);
1056 if (startIndex <= -1)
1057 return;
1058
1059 int previousLevel = -1;
1060 int bestAbove = -1;
1061 int bestBelow = -1;
1062 QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput;
1063 for (int i = 0; i < d->viewItems.count(); ++i) {
1064 if ((int)d->viewItems.at(i).level > previousLevel) {
1065 QModelIndex searchFrom = d->viewItems.at(i).index;
1066 if (start.column() > 0)
1067 searchFrom = searchFrom.sibling(searchFrom.row(), start.column());
1068 if (searchFrom.parent() == start.parent())
1069 searchFrom = start;
1070 QModelIndexList match = d->model->match(searchFrom, Qt::DisplayRole, searchString);
1071 if (match.count()) {
1072 int hitIndex = d->viewIndex(match.at(0));
1073 if (hitIndex >= 0 && hitIndex < startIndex)
1074 bestAbove = bestAbove == -1 ? hitIndex : qMin(hitIndex, bestAbove);
1075 else if (hitIndex >= startIndex)
1076 bestBelow = bestBelow == -1 ? hitIndex : qMin(hitIndex, bestBelow);
1077 }
1078 }
1079 previousLevel = d->viewItems.at(i).level;
1080 }
1081
1082 QModelIndex index;
1083 if (bestBelow > -1)
1084 index = d->viewItems.at(bestBelow).index;
1085 else if (bestAbove > -1)
1086 index = d->viewItems.at(bestAbove).index;
1087
1088 if (start.column() > 0)
1089 index = index.sibling(index.row(), start.column());
1090
1091 if (index.isValid()) {
1092 QItemSelectionModel::SelectionFlags flags = (d->selectionMode == SingleSelection
1093 ? QItemSelectionModel::SelectionFlags(
1094 QItemSelectionModel::ClearAndSelect
1095 |d->selectionBehaviorFlags())
1096 : QItemSelectionModel::SelectionFlags(
1097 QItemSelectionModel::NoUpdate));
1098 selectionModel()->setCurrentIndex(index, flags);
1099 }
1100}
1101
1102/*!
1103 Returns the rectangle on the viewport occupied by the item at \a index.
1104 If the index is not visible or explicitly hidden, the returned rectangle is invalid.
1105*/
1106QRect QTreeView::visualRect(const QModelIndex &index) const
1107{
1108 Q_D(const QTreeView);
1109
1110 if (!d->isIndexValid(index) || isIndexHidden(index))
1111 return QRect();
1112
1113 d->executePostedLayout();
1114
1115 int vi = d->viewIndex(index);
1116 if (vi < 0)
1117 return QRect();
1118
1119 bool spanning = d->viewItems.at(vi).spanning;
1120
1121 // if we have a spanning item, make the selection stretch from left to right
1122 int x = (spanning ? 0 : columnViewportPosition(index.column()));
1123 int w = (spanning ? d->header->length() : columnWidth(index.column()));
1124 // handle indentation
1125 if (d->isTreePosition(index.column())) {
1126 int i = d->indentationForItem(vi);
1127 w -= i;
1128 if (!isRightToLeft())
1129 x += i;
1130 }
1131
1132 int y = d->coordinateForItem(vi);
1133 int h = d->itemHeight(vi);
1134
1135 return QRect(x, y, w, h);
1136}
1137
1138/*!
1139 Scroll the contents of the tree view until the given model item
1140 \a index is visible. The \a hint parameter specifies more
1141 precisely where the item should be located after the
1142 operation.
1143 If any of the parents of the model item are collapsed, they will
1144 be expanded to ensure that the model item is visible.
1145*/
1146void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint)
1147{
1148 Q_D(QTreeView);
1149
1150 if (!d->isIndexValid(index))
1151 return;
1152
1153 d->executePostedLayout();
1154 d->updateScrollBars();
1155
1156 // Expand all parents if the parent(s) of the node are not expanded.
1157 QModelIndex parent = index.parent();
1158 while (parent != d->root && parent.isValid() && state() == NoState && d->itemsExpandable) {
1159 if (!isExpanded(parent))
1160 expand(parent);
1161 parent = d->model->parent(parent);
1162 }
1163
1164 int item = d->viewIndex(index);
1165 if (item < 0)
1166 return;
1167
1168 QRect area = d->viewport->rect();
1169
1170 // vertical
1171 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
1172 int top = verticalScrollBar()->value();
1173 int bottom = top + verticalScrollBar()->pageStep();
1174 if (hint == EnsureVisible && item >= top && item < bottom) {
1175 // nothing to do
1176 } else if (hint == PositionAtTop || (hint == EnsureVisible && item < top)) {
1177 verticalScrollBar()->setValue(item);
1178 } else { // PositionAtBottom or PositionAtCenter
1179 const int currentItemHeight = d->itemHeight(item);
1180 int y = (hint == PositionAtCenter
1181 //we center on the current item with a preference to the top item (ie. -1)
1182 ? area.height() / 2 + currentItemHeight - 1
1183 //otherwise we simply take the whole space
1184 : area.height());
1185 if (y > currentItemHeight) {
1186 while (item >= 0) {
1187 y -= d->itemHeight(item);
1188 if (y < 0) { //there is no more space left
1189 item++;
1190 break;
1191 }
1192 item--;
1193 }
1194 }
1195 verticalScrollBar()->setValue(item);
1196 }
1197 } else { // ScrollPerPixel
1198 QRect rect(columnViewportPosition(index.column()),
1199 d->coordinateForItem(item), // ### slow for items outside the view
1200 columnWidth(index.column()),
1201 d->itemHeight(item));
1202
1203 if (rect.isEmpty()) {
1204 // nothing to do
1205 } else if (hint == EnsureVisible && area.contains(rect)) {
1206 d->viewport->update(rect);
1207 // nothing to do
1208 } else {
1209 bool above = (hint == EnsureVisible
1210 && (rect.top() < area.top()
1211 || area.height() < rect.height()));
1212 bool below = (hint == EnsureVisible
1213 && rect.bottom() > area.bottom()
1214 && rect.height() < area.height());
1215
1216 int verticalValue = verticalScrollBar()->value();
1217 if (hint == PositionAtTop || above)
1218 verticalValue += rect.top();
1219 else if (hint == PositionAtBottom || below)
1220 verticalValue += rect.bottom() - area.height();
1221 else if (hint == PositionAtCenter)
1222 verticalValue += rect.top() - ((area.height() - rect.height()) / 2);
1223 verticalScrollBar()->setValue(verticalValue);
1224 }
1225 }
1226 // horizontal
1227 int viewportWidth = d->viewport->width();
1228 int horizontalOffset = d->header->offset();
1229 int horizontalPosition = d->header->sectionPosition(index.column());
1230 int cellWidth = d->header->sectionSize(index.column());
1231
1232 if (hint == PositionAtCenter) {
1233 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
1234 } else {
1235 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
1236 horizontalScrollBar()->setValue(horizontalPosition);
1237 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
1238 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
1239 }
1240}
1241
1242/*!
1243 \reimp
1244*/
1245void QTreeView::timerEvent(QTimerEvent *event)
1246{
1247 Q_D(QTreeView);
1248 if (event->timerId() == d->columnResizeTimerID) {
1249 updateGeometries();
1250 killTimer(d->columnResizeTimerID);
1251 d->columnResizeTimerID = 0;
1252 QRect rect;
1253 int viewportHeight = d->viewport->height();
1254 int viewportWidth = d->viewport->width();
1255 for (int i = d->columnsToUpdate.size() - 1; i >= 0; --i) {
1256 int column = d->columnsToUpdate.at(i);
1257 int x = columnViewportPosition(column);
1258 if (isRightToLeft())
1259 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
1260 else
1261 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
1262 }
1263 d->viewport->update(rect.normalized());
1264 d->columnsToUpdate.clear();
1265 } else if (event->timerId() == d->openTimer.timerId()) {
1266 QPoint pos = d->viewport->mapFromGlobal(QCursor::pos());
1267 if (state() == QAbstractItemView::DraggingState
1268 && d->viewport->rect().contains(pos)) {
1269 QModelIndex index = indexAt(pos);
1270 setExpanded(index, !isExpanded(index));
1271 }
1272 d->openTimer.stop();
1273 }
1274
1275 QAbstractItemView::timerEvent(event);
1276}
1277
1278/*!
1279 \reimp
1280*/
1281#ifndef QT_NO_DRAGANDDROP
1282void QTreeView::dragMoveEvent(QDragMoveEvent *event)
1283{
1284 Q_D(QTreeView);
1285 if (d->autoExpandDelay >= 0)
1286 d->openTimer.start(d->autoExpandDelay, this);
1287 QAbstractItemView::dragMoveEvent(event);
1288}
1289#endif
1290
1291/*!
1292 \reimp
1293*/
1294bool QTreeView::viewportEvent(QEvent *event)
1295{
1296 Q_D(QTreeView);
1297 switch (event->type()) {
1298 case QEvent::HoverEnter:
1299 case QEvent::HoverLeave:
1300 case QEvent::HoverMove: {
1301 QHoverEvent *he = static_cast<QHoverEvent*>(event);
1302 int oldBranch = d->hoverBranch;
1303 d->hoverBranch = d->itemDecorationAt(he->pos());
1304 QModelIndex newIndex = indexAt(he->pos());
1305 if (d->hover != newIndex || d->hoverBranch != oldBranch) {
1306 // Update the whole hovered over row. No need to update the old hovered
1307 // row, that is taken care in superclass hover handling.
1308 QRect rect = visualRect(newIndex);
1309 rect.setX(0);
1310 rect.setWidth(viewport()->width());
1311 viewport()->update(rect);
1312 }
1313 break; }
1314 default:
1315 break;
1316 }
1317 return QAbstractItemView::viewportEvent(event);
1318}
1319
1320/*!
1321 \reimp
1322*/
1323void QTreeView::paintEvent(QPaintEvent *event)
1324{
1325 Q_D(QTreeView);
1326 d->executePostedLayout();
1327 QPainter painter(viewport());
1328#ifndef QT_NO_ANIMATION
1329 if (d->isAnimating()) {
1330 drawTree(&painter, event->region() - d->animatedOperation.rect());
1331 d->drawAnimatedOperation(&painter);
1332 } else
1333#endif //QT_NO_ANIMATION
1334 {
1335 drawTree(&painter, event->region());
1336#ifndef QT_NO_DRAGANDDROP
1337 d->paintDropIndicator(&painter);
1338#endif
1339 }
1340}
1341
1342int QTreeViewPrivate::logicalIndexForTree() const
1343{
1344 int index = treePosition;
1345 if (index < 0)
1346 index = header->logicalIndex(0);
1347 return index;
1348}
1349
1350void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItem *option, int y, int bottom) const
1351{
1352 Q_Q(const QTreeView);
1353 if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q))
1354 return;
1355 int rowHeight = defaultItemHeight;
1356 if (rowHeight <= 0) {
1357 rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height();
1358 if (rowHeight <= 0)
1359 return;
1360 }
1361 while (y <= bottom) {
1362 option->rect.setRect(0, y, viewport->width(), rowHeight);
1363 option->features.setFlag(QStyleOptionViewItem::Alternate, current & 1);
1364 ++current;
1365 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q);
1366 y += rowHeight;
1367 }
1368}
1369
1370bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos)
1371{
1372 Q_Q(QTreeView);
1373 // we want to handle mousePress in EditingState (persistent editors)
1374 if ((state != QAbstractItemView::NoState
1375 && state != QAbstractItemView::EditingState)
1376 || !viewport->rect().contains(pos))
1377 return true;
1378
1379 int i = itemDecorationAt(pos);
1380 if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) {
1381 if (viewItems.at(i).expanded)
1382 collapse(i, true);
1383 else
1384 expand(i, true);
1385 if (!isAnimating()) {
1386 q->updateGeometries();
1387 viewport->update();
1388 }
1389 return true;
1390 }
1391 return false;
1392}
1393
1394void QTreeViewPrivate::_q_modelDestroyed()
1395{
1396 //we need to clear the viewItems because it contains QModelIndexes to
1397 //the model currently being destroyed
1398 viewItems.clear();
1399 QAbstractItemViewPrivate::_q_modelDestroyed();
1400}
1401
1402/*!
1403 \reimp
1404
1405 We have a QTreeView way of knowing what elements are on the viewport
1406*/
1407QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
1408{
1409 Q_ASSERT(r);
1410 Q_Q(const QTreeView);
1411 if (spanningIndexes.isEmpty())
1412 return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r);
1413 QModelIndexList list;
1414 for (const QModelIndex &idx : indexes) {
1415 if (idx.column() > 0 && q->isFirstColumnSpanned(idx.row(), idx.parent()))
1416 continue;
1417 list << idx;
1418 }
1419 return QAbstractItemViewPrivate::draggablePaintPairs(list, r);
1420}
1421
1422void QTreeViewPrivate::adjustViewOptionsForIndex(QStyleOptionViewItem *option, const QModelIndex &current) const
1423{
1424 const int row = viewIndex(current); // get the index in viewItems[]
1425 option->state = option->state | (viewItems.at(row).expanded ? QStyle::State_Open : QStyle::State_None)
1426 | (viewItems.at(row).hasChildren ? QStyle::State_Children : QStyle::State_None)
1427 | (viewItems.at(row).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1428
1429 option->showDecorationSelected = (selectionBehavior & QTreeView::SelectRows)
1430 || option->showDecorationSelected;
1431
1432 QVector<int> logicalIndices; // index = visual index of visible columns only. data = logical index.
1433 QVector<QStyleOptionViewItem::ViewItemPosition> viewItemPosList; // vector of left/middle/end for each logicalIndex, visible columns only.
1434 const bool spanning = viewItems.at(row).spanning;
1435 const int left = (spanning ? header->visualIndex(0) : 0);
1436 const int right = (spanning ? header->visualIndex(0) : header->count() - 1 );
1437 calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
1438
1439 const int visualIndex = logicalIndices.indexOf(current.column());
1440 option->viewItemPosition = viewItemPosList.at(visualIndex);
1441}
1442
1443
1444/*!
1445 \since 4.2
1446 Draws the part of the tree intersecting the given \a region using the specified
1447 \a painter.
1448
1449 \sa paintEvent()
1450*/
1451void QTreeView::drawTree(QPainter *painter, const QRegion &region) const
1452{
1453 Q_D(const QTreeView);
1454 const QVector<QTreeViewItem> viewItems = d->viewItems;
1455
1456 QStyleOptionViewItem option = d->viewOptionsV1();
1457 const QStyle::State state = option.state;
1458 d->current = 0;
1459
1460 if (viewItems.count() == 0 || d->header->count() == 0 || !d->itemDelegate) {
1461 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1462 return;
1463 }
1464
1465 int firstVisibleItemOffset = 0;
1466 const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset);
1467 if (firstVisibleItem < 0) {
1468 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1469 return;
1470 }
1471
1472 const int viewportWidth = d->viewport->width();
1473
1474 QPoint hoverPos = d->viewport->mapFromGlobal(QCursor::pos());
1475 d->hoverBranch = d->itemDecorationAt(hoverPos);
1476
1477 QVector<int> drawn;
1478 bool multipleRects = (region.rectCount() > 1);
1479 for (const QRect &a : region) {
1480 const QRect area = (multipleRects
1481 ? QRect(0, a.y(), viewportWidth, a.height())
1482 : a);
1483 d->leftAndRight = d->startAndEndColumns(area);
1484
1485 int i = firstVisibleItem; // the first item at the top of the viewport
1486 int y = firstVisibleItemOffset; // we may only see part of the first item
1487
1488 // start at the top of the viewport and iterate down to the update area
1489 for (; i < viewItems.count(); ++i) {
1490 const int itemHeight = d->itemHeight(i);
1491 if (y + itemHeight > area.top())
1492 break;
1493 y += itemHeight;
1494 }
1495
1496 // paint the visible rows
1497 for (; i < viewItems.count() && y <= area.bottom(); ++i) {
1498 const int itemHeight = d->itemHeight(i);
1499 option.rect.setRect(0, y, viewportWidth, itemHeight);
1500 option.state = state | (viewItems.at(i).expanded ? QStyle::State_Open : QStyle::State_None)
1501 | (viewItems.at(i).hasChildren ? QStyle::State_Children : QStyle::State_None)
1502 | (viewItems.at(i).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1503 d->current = i;
1504 d->spanning = viewItems.at(i).spanning;
1505 if (!multipleRects || !drawn.contains(i)) {
1506 drawRow(painter, option, viewItems.at(i).index);
1507 if (multipleRects) // even if the rect only intersects the item,
1508 drawn.append(i); // the entire item will be painted
1509 }
1510 y += itemHeight;
1511 }
1512
1513 if (y <= area.bottom()) {
1514 d->current = i;
1515 d->paintAlternatingRowColors(painter, &option, y, area.bottom());
1516 }
1517 }
1518}
1519
1520/// ### move to QObject :)
1521static inline bool ancestorOf(QObject *widget, QObject *other)
1522{
1523 for (QObject *parent = other; parent != 0; parent = parent->parent()) {
1524 if (parent == widget)
1525 return true;
1526 }
1527 return false;
1528}
1529
1530void QTreeViewPrivate::calcLogicalIndices(QVector<int> *logicalIndices, QVector<QStyleOptionViewItem::ViewItemPosition> *itemPositions, int left, int right) const
1531{
1532 const int columnCount = header->count();
1533 /* 'left' and 'right' are the left-most and right-most visible visual indices.
1534 Compute the first visible logical indices before and after the left and right.
1535 We will use these values to determine the QStyleOptionViewItem::viewItemPosition. */
1536 int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1;
1537 for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) {
1538 int logicalIndex = header->logicalIndex(visualIndex);
1539 if (!header->isSectionHidden(logicalIndex)) {
1540 logicalIndexBeforeLeft = logicalIndex;
1541 break;
1542 }
1543 }
1544
1545 for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) {
1546 int logicalIndex = header->logicalIndex(visualIndex);
1547 if (!header->isSectionHidden(logicalIndex)) {
1548 if (visualIndex > right) {
1549 logicalIndexAfterRight = logicalIndex;
1550 break;
1551 }
1552 logicalIndices->append(logicalIndex);
1553 }
1554 }
1555
1556 itemPositions->resize(logicalIndices->count());
1557 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices->count(); ++currentLogicalSection) {
1558 const int headerSection = logicalIndices->at(currentLogicalSection);
1559 // determine the viewItemPosition depending on the position of column 0
1560 int nextLogicalSection = currentLogicalSection + 1 >= logicalIndices->count()
1561 ? logicalIndexAfterRight
1562 : logicalIndices->at(currentLogicalSection + 1);
1563 int prevLogicalSection = currentLogicalSection - 1 < 0
1564 ? logicalIndexBeforeLeft
1565 : logicalIndices->at(currentLogicalSection - 1);
1566 QStyleOptionViewItem::ViewItemPosition pos;
1567 if (columnCount == 1 || (nextLogicalSection == 0 && prevLogicalSection == -1)
1568 || (headerSection == 0 && nextLogicalSection == -1) || spanning)
1569 pos = QStyleOptionViewItem::OnlyOne;
1570 else if (isTreePosition(headerSection) || (nextLogicalSection != 0 && prevLogicalSection == -1))
1571 pos = QStyleOptionViewItem::Beginning;
1572 else if (nextLogicalSection == 0 || nextLogicalSection == -1)
1573 pos = QStyleOptionViewItem::End;
1574 else
1575 pos = QStyleOptionViewItem::Middle;
1576 (*itemPositions)[currentLogicalSection] = pos;
1577 }
1578}
1579
1580/*!
1581 \internal
1582 Get sizeHint width for single index (providing existing hint and style option) and index in viewIndex i.
1583*/
1584int QTreeViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option, int i) const
1585{
1586 QWidget *editor = editorForIndex(index).widget.data();
1587 if (editor && persistent.contains(editor)) {
1588 hint = qMax(hint, editor->sizeHint().width());
1589 int min = editor->minimumSize().width();
1590 int max = editor->maximumSize().width();
1591 hint = qBound(min, hint, max);
1592 }
1593 int xhint = delegateForIndex(index)->sizeHint(option, index).width();
1594 hint = qMax(hint, xhint + (isTreePosition(index.column()) ? indentationForItem(i) : 0));
1595 return hint;
1596}
1597
1598/*!
1599 Draws the row in the tree view that contains the model item \a index,
1600 using the \a painter given. The \a option controls how the item is
1601 displayed.
1602
1603 \sa setAlternatingRowColors()
1604*/
1605void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
1606 const QModelIndex &index) const
1607{
1608 Q_D(const QTreeView);
1609 QStyleOptionViewItem opt = option;
1610 const QPoint offset = d->scrollDelayOffset;
1611 const int y = option.rect.y() + offset.y();
1612 const QModelIndex parent = index.parent();
1613 const QHeaderView *header = d->header;
1614 const QModelIndex current = currentIndex();
1615 const QModelIndex hover = d->hover;
1616 const bool reverse = isRightToLeft();
1617 const QStyle::State state = opt.state;
1618 const bool spanning = d->spanning;
1619 const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first);
1620 const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second);
1621 const bool alternate = d->alternatingColors;
1622 const bool enabled = (state & QStyle::State_Enabled) != 0;
1623 const bool allColumnsShowFocus = d->allColumnsShowFocus;
1624
1625
1626 // when the row contains an index widget which has focus,
1627 // we want to paint the entire row as active
1628 bool indexWidgetHasFocus = false;
1629 if ((current.row() == index.row()) && !d->editorIndexHash.isEmpty()) {
1630 const int r = index.row();
1631 QWidget *fw = QApplication::focusWidget();
1632 for (int c = 0; c < header->count(); ++c) {
1633 QModelIndex idx = d->model->index(r, c, parent);
1634 if (QWidget *editor = indexWidget(idx)) {
1635 if (ancestorOf(editor, fw)) {
1636 indexWidgetHasFocus = true;
1637 break;
1638 }
1639 }
1640 }
1641 }
1642
1643 const bool widgetHasFocus = hasFocus();
1644 bool currentRowHasFocus = false;
1645 if (allColumnsShowFocus && widgetHasFocus && current.isValid()) {
1646 // check if the focus index is before or after the visible columns
1647 const int r = index.row();
1648 for (int c = 0; c < left && !currentRowHasFocus; ++c) {
1649 QModelIndex idx = d->model->index(r, c, parent);
1650 currentRowHasFocus = (idx == current);
1651 }
1652 QModelIndex parent = d->model->parent(index);
1653 for (int c = right; c < header->count() && !currentRowHasFocus; ++c) {
1654 currentRowHasFocus = (d->model->index(r, c, parent) == current);
1655 }
1656 }
1657
1658 // ### special case: treeviews with multiple columns draw
1659 // the selections differently than with only one column
1660 opt.showDecorationSelected = (d->selectionBehavior & SelectRows)
1661 || option.showDecorationSelected;
1662
1663 int width, height = option.rect.height();
1664 int position;
1665 QModelIndex modelIndex;
1666 const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1667 && index.parent() == hover.parent()
1668 && index.row() == hover.row();
1669
1670 QVector<int> logicalIndices;
1671 QVector<QStyleOptionViewItem::ViewItemPosition> viewItemPosList; // vector of left/middle/end for each logicalIndex
1672 d->calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
1673
1674 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection) {
1675 int headerSection = logicalIndices.at(currentLogicalSection);
1676 position = columnViewportPosition(headerSection) + offset.x();
1677 width = header->sectionSize(headerSection);
1678
1679 if (spanning) {
1680 int lastSection = header->logicalIndex(header->count() - 1);
1681 if (!reverse) {
1682 width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position;
1683 } else {
1684 width += position - columnViewportPosition(lastSection);
1685 position = columnViewportPosition(lastSection);
1686 }
1687 }
1688
1689 modelIndex = d->model->index(index.row(), headerSection, parent);
1690 if (!modelIndex.isValid())
1691 continue;
1692 opt.state = state;
1693
1694 opt.viewItemPosition = viewItemPosList.at(currentLogicalSection);
1695
1696 // fake activeness when row editor has focus
1697 if (indexWidgetHasFocus)
1698 opt.state |= QStyle::State_Active;
1699
1700 if (d->selectionModel->isSelected(modelIndex))
1701 opt.state |= QStyle::State_Selected;
1702 if (widgetHasFocus && (current == modelIndex)) {
1703 if (allColumnsShowFocus)
1704 currentRowHasFocus = true;
1705 else
1706 opt.state |= QStyle::State_HasFocus;
1707 }
1708 opt.state.setFlag(QStyle::State_MouseOver,
1709 (hoverRow || modelIndex == hover)
1710 && (option.showDecorationSelected || d->hoverBranch == -1));
1711
1712 if (enabled) {
1713 QPalette::ColorGroup cg;
1714 if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) {
1715 opt.state &= ~QStyle::State_Enabled;
1716 cg = QPalette::Disabled;
1717 } else if (opt.state & QStyle::State_Active) {
1718 cg = QPalette::Active;
1719 } else {
1720 cg = QPalette::Inactive;
1721 }
1722 opt.palette.setCurrentColorGroup(cg);
1723 }
1724
1725 if (alternate) {
1726 opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
1727 }
1728
1729 /* Prior to Qt 4.3, the background of the branch (in selected state and
1730 alternate row color was provided by the view. For backward compatibility,
1731 this is now delegated to the style using PE_PanelViewItemRow which
1732 does the appropriate fill */
1733 if (d->isTreePosition(headerSection)) {
1734 const int i = d->indentationForItem(d->current);
1735 QRect branches(reverse ? position + width - i : position, y, i, height);
1736 const bool setClipRect = branches.width() > width;
1737 if (setClipRect) {
1738 painter->save();
1739 painter->setClipRect(QRect(position, y, width, height));
1740 }
1741 // draw background for the branch (selection + alternate row)
1742 opt.rect = branches;
1743 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1744
1745 // draw background of the item (only alternate row). rest of the background
1746 // is provided by the delegate
1747 QStyle::State oldState = opt.state;
1748 opt.state &= ~QStyle::State_Selected;
1749 opt.rect.setRect(reverse ? position : i + position, y, width - i, height);
1750 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1751 opt.state = oldState;
1752
1753 if (d->indent != 0)
1754 drawBranches(painter, branches, index);
1755 if (setClipRect)
1756 painter->restore();
1757 } else {
1758 QStyle::State oldState = opt.state;
1759 opt.state &= ~QStyle::State_Selected;
1760 opt.rect.setRect(position, y, width, height);
1761 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1762 opt.state = oldState;
1763 }
1764
1765 d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex);
1766 }
1767
1768 if (currentRowHasFocus) {
1769 QStyleOptionFocusRect o;
1770 o.QStyleOption::operator=(option);
1771 o.state |= QStyle::State_KeyboardFocusChange;
1772 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
1773 ? QPalette::Normal : QPalette::Disabled;
1774 o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index)
1775 ? QPalette::Highlight : QPalette::Background);
1776 int x = 0;
1777 if (!option.showDecorationSelected)
1778 x = header->sectionPosition(0) + d->indentationForItem(d->current);
1779 QRect focusRect(x - header->offset(), y, header->length() - x, height);
1780 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect);
1781 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1782 // if we show focus on all columns and the first section is moved,
1783 // we have to split the focus rect into two rects
1784 if (allColumnsShowFocus && !option.showDecorationSelected
1785 && header->sectionsMoved() && (header->visualIndex(0) != 0)) {
1786 QRect sectionRect(0, y, header->sectionPosition(0), height);
1787 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect);
1788 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1789 }
1790 }
1791}
1792
1793/*!
1794 Draws the branches in the tree view on the same row as the model item
1795 \a index, using the \a painter given. The branches are drawn in the
1796 rectangle specified by \a rect.
1797*/
1798void QTreeView::drawBranches(QPainter *painter, const QRect &rect,
1799 const QModelIndex &index) const
1800{
1801 Q_D(const QTreeView);
1802 const bool reverse = isRightToLeft();
1803 const int indent = d->indent;
1804 const int outer = d->rootDecoration ? 0 : 1;
1805 const int item = d->current;
1806 const QTreeViewItem &viewItem = d->viewItems.at(item);
1807 int level = viewItem.level;
1808 QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height());
1809
1810 QModelIndex parent = index.parent();
1811 QModelIndex current = parent;
1812 QModelIndex ancestor = current.parent();
1813
1814 QStyleOptionViewItem opt = viewOptions();
1815 QStyle::State extraFlags = QStyle::State_None;
1816 if (isEnabled())
1817 extraFlags |= QStyle::State_Enabled;
1818 if (hasFocus())
1819 extraFlags |= QStyle::State_Active;
1820 QPoint oldBO = painter->brushOrigin();
1821 if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel)
1822 painter->setBrushOrigin(QPoint(0, verticalOffset()));
1823
1824 if (d->alternatingColors) {
1825 opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
1826 }
1827
1828 // When hovering over a row, pass State_Hover for painting the branch
1829 // indicators if it has the decoration (aka branch) selected.
1830 bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1831 && opt.showDecorationSelected
1832 && index.parent() == d->hover.parent()
1833 && index.row() == d->hover.row();
1834
1835 if (d->selectionModel->isSelected(index))
1836 extraFlags |= QStyle::State_Selected;
1837
1838 if (level >= outer) {
1839 // start with the innermost branch
1840 primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent);
1841 opt.rect = primitive;
1842
1843 const bool expanded = viewItem.expanded;
1844 const bool children = viewItem.hasChildren;
1845 bool moreSiblings = viewItem.hasMoreSiblings;
1846
1847 opt.state = QStyle::State_Item | extraFlags
1848 | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None)
1849 | (children ? QStyle::State_Children : QStyle::State_None)
1850 | (expanded ? QStyle::State_Open : QStyle::State_None);
1851 opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
1852
1853 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1854 }
1855 // then go out level by level
1856 for (--level; level >= outer; --level) { // we have already drawn the innermost branch
1857 primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent);
1858 opt.rect = primitive;
1859 opt.state = extraFlags;
1860 bool moreSiblings = false;
1861 if (d->hiddenIndexes.isEmpty()) {
1862 moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row());
1863 } else {
1864 int successor = item + viewItem.total + 1;
1865 while (successor < d->viewItems.size()
1866 && d->viewItems.at(successor).level >= uint(level)) {
1867 const QTreeViewItem &successorItem = d->viewItems.at(successor);
1868 if (successorItem.level == uint(level)) {
1869 moreSiblings = true;
1870 break;
1871 }
1872 successor += successorItem.total + 1;
1873 }
1874 }
1875 if (moreSiblings)
1876 opt.state |= QStyle::State_Sibling;
1877 opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
1878
1879 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1880 current = ancestor;
1881 ancestor = current.parent();
1882 }
1883 painter->setBrushOrigin(oldBO);
1884}
1885
1886/*!
1887 \reimp
1888*/
1889void QTreeView::mousePressEvent(QMouseEvent *event)
1890{
1891 Q_D(QTreeView);
1892 bool handled = false;
1893 if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonPress)
1894 handled = d->expandOrCollapseItemAtPos(event->pos());
1895 if (!handled && d->itemDecorationAt(event->pos()) == -1)
1896 QAbstractItemView::mousePressEvent(event);
1897}
1898
1899/*!
1900 \reimp
1901*/
1902void QTreeView::mouseReleaseEvent(QMouseEvent *event)
1903{
1904 Q_D(QTreeView);
1905 if (d->itemDecorationAt(event->pos()) == -1) {
1906 QAbstractItemView::mouseReleaseEvent(event);
1907 } else {
1908 if (state() == QAbstractItemView::DragSelectingState || state() == QAbstractItemView::DraggingState)
1909 setState(QAbstractItemView::NoState);
1910 if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonRelease)
1911 d->expandOrCollapseItemAtPos(event->pos());
1912 }
1913}
1914
1915/*!
1916 \reimp
1917*/
1918void QTreeView::mouseDoubleClickEvent(QMouseEvent *event)
1919{
1920 Q_D(QTreeView);
1921 if (state() != NoState || !d->viewport->rect().contains(event->pos()))
1922 return;
1923
1924 int i = d->itemDecorationAt(event->pos());
1925 if (i == -1) {
1926 i = d->itemAtCoordinate(event->y());
1927 if (i == -1)
1928 return; // user clicked outside the items
1929
1930 const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index;
1931 const QPersistentModelIndex persistent = indexAt(event->pos());
1932
1933 if (d->pressedIndex != persistent) {
1934 mousePressEvent(event);
1935 return;
1936 }
1937
1938 // signal handlers may change the model
1939 emit doubleClicked(persistent);
1940
1941 if (!persistent.isValid())
1942 return;
1943
1944 if (edit(persistent, DoubleClicked, event) || state() != NoState)
1945 return; // the double click triggered editing
1946
1947 if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this))
1948 emit activated(persistent);
1949
1950 d->executePostedLayout(); // we need to make sure viewItems is updated
1951 if (d->itemsExpandable
1952 && d->expandsOnDoubleClick
1953 && d->hasVisibleChildren(persistent)) {
1954 if (!((i < d->viewItems.count()) && (d->viewItems.at(i).index == firstColumnIndex))) {
1955 // find the new index of the item
1956 for (i = 0; i < d->viewItems.count(); ++i) {
1957 if (d->viewItems.at(i).index == firstColumnIndex)
1958 break;
1959 }
1960 if (i == d->viewItems.count())
1961 return;
1962 }
1963 if (d->viewItems.at(i).expanded)
1964 d->collapse(i, true);
1965 else
1966 d->expand(i, true);
1967 updateGeometries();
1968 viewport()->update();
1969 }
1970 }
1971}
1972
1973/*!
1974 \reimp
1975*/
1976void QTreeView::mouseMoveEvent(QMouseEvent *event)
1977{
1978 Q_D(QTreeView);
1979 if (d->itemDecorationAt(event->pos()) == -1) // ### what about expanding/collapsing state ?
1980 QAbstractItemView::mouseMoveEvent(event);
1981}
1982
1983/*!
1984 \reimp
1985*/
1986void QTreeView::keyPressEvent(QKeyEvent *event)
1987{
1988 Q_D(QTreeView);
1989 QModelIndex current = currentIndex();
1990 //this is the management of the expansion
1991 if (d->isIndexValid(current) && d->model && d->itemsExpandable) {
1992 switch (event->key()) {
1993 case Qt::Key_Asterisk: {
1994 QStack<QModelIndex> parents;
1995 parents.push(current);
1996 while (!parents.isEmpty()) {
1997 QModelIndex parent = parents.pop();
1998 for (int row = 0; row < d->model->rowCount(parent); ++row) {
1999 QModelIndex child = d->model->index(row, 0, parent);
2000 if (!d->isIndexValid(child))
2001 break;
2002 parents.push(child);
2003 expand(child);
2004 }
2005 }
2006 expand(current);
2007 break; }
2008 case Qt::Key_Plus:
2009 expand(current);
2010 break;
2011 case Qt::Key_Minus:
2012 collapse(current);
2013 break;
2014 }
2015 }
2016
2017 QAbstractItemView::keyPressEvent(event);
2018}
2019
2020/*!
2021 \reimp
2022*/
2023QModelIndex QTreeView::indexAt(const QPoint &point) const
2024{
2025 Q_D(const QTreeView);
2026 d->executePostedLayout();
2027
2028 int visualIndex = d->itemAtCoordinate(point.y());
2029 QModelIndex idx = d->modelIndex(visualIndex);
2030 if (!idx.isValid())
2031 return QModelIndex();
2032
2033 if (d->viewItems.at(visualIndex).spanning)
2034 return idx;
2035
2036 int column = d->columnAt(point.x());
2037 if (column == idx.column())
2038 return idx;
2039 if (column < 0)
2040 return QModelIndex();
2041 return idx.sibling(idx.row(), column);
2042}
2043
2044/*!
2045 Returns the model index of the item above \a index.
2046*/
2047QModelIndex QTreeView::indexAbove(const QModelIndex &index) const
2048{
2049 Q_D(const QTreeView);
2050 if (!d->isIndexValid(index))
2051 return QModelIndex();
2052 d->executePostedLayout();
2053 int i = d->viewIndex(index);
2054 if (--i < 0)
2055 return QModelIndex();
2056 const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
2057 return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
2058}
2059
2060/*!
2061 Returns the model index of the item below \a index.
2062*/
2063QModelIndex QTreeView::indexBelow(const QModelIndex &index) const
2064{
2065 Q_D(const QTreeView);
2066 if (!d->isIndexValid(index))
2067 return QModelIndex();
2068 d->executePostedLayout();
2069 int i = d->viewIndex(index);
2070 if (++i >= d->viewItems.count())
2071 return QModelIndex();
2072 const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
2073 return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
2074}
2075
2076/*!
2077 \internal
2078
2079 Lays out the items in the tree view.
2080*/
2081void QTreeView::doItemsLayout()
2082{
2083 Q_D(QTreeView);
2084 if (!d->customIndent) {
2085 // ### Qt 6: move to event()
2086 // QAbstractItemView calls this method in case of a style change,
2087 // so update the indentation here if it wasn't set manually.
2088 d->updateIndentationFromStyle();
2089 }
2090 if (d->hasRemovedItems) {
2091 //clean the QSet that may contains old (and this invalid) indexes
2092 d->hasRemovedItems = false;
2093 QSet<QPersistentModelIndex>::iterator it = d->expandedIndexes.begin();
2094 while (it != d->expandedIndexes.end()) {
2095 if (!it->isValid())
2096 it = d->expandedIndexes.erase(it);
2097 else
2098 ++it;
2099 }
2100 it = d->hiddenIndexes.begin();
2101 while (it != d->hiddenIndexes.end()) {
2102 if (!it->isValid())
2103 it = d->hiddenIndexes.erase(it);
2104 else
2105 ++it;
2106 }
2107 }
2108 d->viewItems.clear(); // prepare for new layout
2109 QModelIndex parent = d->root;
2110 if (d->model->hasChildren(parent)) {
2111 d->layout(-1);
2112 }
2113 QAbstractItemView::doItemsLayout();
2114 d->header->doItemsLayout();
2115}
2116
2117/*!
2118 \reimp
2119*/
2120void QTreeView::reset()
2121{
2122 Q_D(QTreeView);
2123 d->expandedIndexes.clear();
2124 d->hiddenIndexes.clear();
2125 d->spanningIndexes.clear();
2126 d->viewItems.clear();
2127 QAbstractItemView::reset();
2128}
2129
2130/*!
2131 Returns the horizontal offset of the items in the treeview.
2132
2133 Note that the tree view uses the horizontal header section
2134 positions to determine the positions of columns in the view.
2135
2136 \sa verticalOffset()
2137*/
2138int QTreeView::horizontalOffset() const
2139{
2140 Q_D(const QTreeView);
2141 return d->header->offset();
2142}
2143
2144/*!
2145 Returns the vertical offset of the items in the tree view.
2146
2147 \sa horizontalOffset()
2148*/
2149int QTreeView::verticalOffset() const
2150{
2151 Q_D(const QTreeView);
2152 if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) {
2153 if (d->uniformRowHeights)
2154 return verticalScrollBar()->value() * d->defaultItemHeight;
2155 // If we are scrolling per item and have non-uniform row heights,
2156 // finding the vertical offset in pixels is going to be relatively slow.
2157 // ### find a faster way to do this
2158 d->executePostedLayout();
2159 int offset = 0;
2160 for (int i = 0; i < d->viewItems.count(); ++i) {
2161 if (i == verticalScrollBar()->value())
2162 return offset;
2163 offset += d->itemHeight(i);
2164 }
2165 return 0;
2166 }
2167 // scroll per pixel
2168 return verticalScrollBar()->value();
2169}
2170
2171/*!
2172 Move the cursor in the way described by \a cursorAction, using the
2173 information provided by the button \a modifiers.
2174*/
2175QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
2176{
2177 Q_D(QTreeView);
2178 Q_UNUSED(modifiers);
2179
2180 d->executePostedLayout();
2181
2182 QModelIndex current = currentIndex();
2183 if (!current.isValid()) {
2184 int i = d->below(-1);
2185 int c = 0;
2186 while (c < d->header->count() && d->header->isSectionHidden(d->header->logicalIndex(c)))
2187 ++c;
2188 if (i < d->viewItems.count() && c < d->header->count()) {
2189 return d->modelIndex(i, d->header->logicalIndex(c));
2190 }
2191 return QModelIndex();
2192 }
2193 int vi = -1;
2194#if 0 /* Used to be included in Qt4 for Q_WS_MAC */ && QT_CONFIG(style_mac)
2195 // Selection behavior is slightly different on the Mac.
2196 if (d->selectionMode == QAbstractItemView::ExtendedSelection
2197 && d->selectionModel
2198 && d->selectionModel->hasSelection()) {
2199
2200 const bool moveUpDown = (cursorAction == MoveUp || cursorAction == MoveDown);
2201 const bool moveNextPrev = (cursorAction == MoveNext || cursorAction == MovePrevious);
2202 const bool contiguousSelection = moveUpDown && (modifiers & Qt::ShiftModifier);
2203
2204 // Use the outermost index in the selection as the current index
2205 if (!contiguousSelection && (moveUpDown || moveNextPrev)) {
2206
2207 // Find outermost index.
2208 const bool useTopIndex = (cursorAction == MoveUp || cursorAction == MovePrevious);
2209 int index = useTopIndex ? INT_MAX : INT_MIN;
2210 const QItemSelection selection = d->selectionModel->selection();
2211 for (int i = 0; i < selection.count(); ++i) {
2212 const QItemSelectionRange &range = selection.at(i);
2213 int candidate = d->viewIndex(useTopIndex ? range.topLeft() : range.bottomRight());
2214 if (candidate >= 0)
2215 index = useTopIndex ? qMin(index, candidate) : qMax(index, candidate);
2216 }
2217
2218 if (index >= 0 && index < INT_MAX)
2219 vi = index;
2220 }
2221 }
2222#endif
2223 if (vi < 0)
2224 vi = qMax(0, d->viewIndex(current));
2225
2226 if (isRightToLeft()) {
2227 if (cursorAction == MoveRight)
2228 cursorAction = MoveLeft;
2229 else if (cursorAction == MoveLeft)
2230 cursorAction = MoveRight;
2231 }
2232 switch (cursorAction) {
2233 case MoveNext:
2234 case MoveDown:
2235#ifdef QT_KEYPAD_NAVIGATION
2236 if (vi == d->viewItems.count()-1 && QApplication::keypadNavigationEnabled())
2237 return d->model->index(0, current.column(), d->root);
2238#endif
2239 return d->modelIndex(d->below(vi), current.column());
2240 case MovePrevious:
2241 case MoveUp:
2242#ifdef QT_KEYPAD_NAVIGATION
2243 if (vi == 0 && QApplication::keypadNavigationEnabled())
2244 return d->modelIndex(d->viewItems.count() - 1, current.column());
2245#endif
2246 return d->modelIndex(d->above(vi), current.column());
2247 case MoveLeft: {
2248 QScrollBar *sb = horizontalScrollBar();
2249 if (vi < d->viewItems.count() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) {
2250 d->collapse(vi, true);
2251 d->moveCursorUpdatedView = true;
2252 } else {
2253 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this);
2254 if (descend) {
2255 QModelIndex par = current.parent();
2256 if (par.isValid() && par != rootIndex())
2257 return par;
2258 else
2259 descend = false;
2260 }
2261 if (!descend) {
2262 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2263 int visualColumn = d->header->visualIndex(current.column()) - 1;
2264 while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn)))
2265 visualColumn--;
2266 int newColumn = d->header->logicalIndex(visualColumn);
2267 QModelIndex next = current.sibling(current.row(), newColumn);
2268 if (next.isValid())
2269 return next;
2270 }
2271
2272 int oldValue = sb->value();
2273 sb->setValue(sb->value() - sb->singleStep());
2274 if (oldValue != sb->value())
2275 d->moveCursorUpdatedView = true;
2276 }
2277
2278 }
2279 updateGeometries();
2280 viewport()->update();
2281 break;
2282 }
2283 case MoveRight:
2284 if (vi < d->viewItems.count() && !d->viewItems.at(vi).expanded && d->itemsExpandable
2285 && d->hasVisibleChildren(d->viewItems.at(vi).index)) {
2286 d->expand(vi, true);
2287 d->moveCursorUpdatedView = true;
2288 } else {
2289 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this);
2290 if (descend) {
2291 QModelIndex idx = d->modelIndex(d->below(vi));
2292 if (idx.parent() == current)
2293 return idx;
2294 else
2295 descend = false;
2296 }
2297 if (!descend) {
2298 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2299 int visualColumn = d->header->visualIndex(current.column()) + 1;
2300 while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn)))
2301 visualColumn++;
2302 const int newColumn = d->header->logicalIndex(visualColumn);
2303 const QModelIndex next = current.sibling(current.row(), newColumn);
2304 if (next.isValid())
2305 return next;
2306 }
2307
2308 //last restort: we change the scrollbar value
2309 QScrollBar *sb = horizontalScrollBar();
2310 int oldValue = sb->value();
2311 sb->setValue(sb->value() + sb->singleStep());
2312 if (oldValue != sb->value())
2313 d->moveCursorUpdatedView = true;
2314 }
2315 }
2316 updateGeometries();
2317 viewport()->update();
2318 break;
2319 case MovePageUp:
2320 return d->modelIndex(d->pageUp(vi), current.column());
2321 case MovePageDown:
2322 return d->modelIndex(d->pageDown(vi), current.column());
2323 case MoveHome:
2324 return d->model->index(0, current.column(), d->root);
2325 case MoveEnd:
2326 return d->modelIndex(d->viewItems.count() - 1, current.column());
2327 }
2328 return current;
2329}
2330
2331/*!
2332 Applies the selection \a command to the items in or touched by the
2333 rectangle, \a rect.
2334
2335 \sa selectionCommand()
2336*/
2337void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2338{
2339 Q_D(QTreeView);
2340 if (!selectionModel() || rect.isNull())
2341 return;
2342
2343 d->executePostedLayout();
2344 QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right())
2345 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()));
2346 QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2347 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()));
2348 QModelIndex topLeft = indexAt(tl);
2349 QModelIndex bottomRight = indexAt(br);
2350 if (!topLeft.isValid() && !bottomRight.isValid()) {
2351 if (command & QItemSelectionModel::Clear)
2352 selectionModel()->clear();
2353 return;
2354 }
2355 if (!topLeft.isValid() && !d->viewItems.isEmpty())
2356 topLeft = d->viewItems.constFirst().index;
2357 if (!bottomRight.isValid() && !d->viewItems.isEmpty()) {
2358 const int column = d->header->logicalIndex(d->header->count() - 1);
2359 const QModelIndex index = d->viewItems.constLast().index;
2360 bottomRight = index.sibling(index.row(), column);
2361 }
2362
2363 if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight))
2364 return;
2365
2366 d->select(topLeft, bottomRight, command);
2367}
2368
2369/*!
2370 Returns the rectangle from the viewport of the items in the given
2371 \a selection.
2372
2373 Since 4.7, the returned region only contains rectangles intersecting
2374 (or included in) the viewport.
2375*/
2376QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const
2377{
2378 Q_D(const QTreeView);
2379 if (selection.isEmpty())
2380 return QRegion();
2381
2382 QRegion selectionRegion;
2383 const QRect &viewportRect = d->viewport->rect();
2384 for (const auto &range : selection) {
2385 if (!range.isValid())
2386 continue;
2387 QModelIndex parent = range.parent();
2388 QModelIndex leftIndex = range.topLeft();
2389 int columnCount = d->model->columnCount(parent);
2390 while (leftIndex.isValid() && isIndexHidden(leftIndex)) {
2391 if (leftIndex.column() + 1 < columnCount)
2392 leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent);
2393 else
2394 leftIndex = QModelIndex();
2395 }
2396 if (!leftIndex.isValid())
2397 continue;
2398 const QRect leftRect = visualRect(leftIndex);
2399 int top = leftRect.top();
2400 QModelIndex rightIndex = range.bottomRight();
2401 while (rightIndex.isValid() && isIndexHidden(rightIndex)) {
2402 if (rightIndex.column() - 1 >= 0)
2403 rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent);
2404 else
2405 rightIndex = QModelIndex();
2406 }
2407 if (!rightIndex.isValid())
2408 continue;
2409 const QRect rightRect = visualRect(rightIndex);
2410 int bottom = rightRect.bottom();
2411 if (top > bottom)
2412 qSwap<int>(top, bottom);
2413 int height = bottom - top + 1;
2414 if (d->header->sectionsMoved()) {
2415 for (int c = range.left(); c <= range.right(); ++c) {
2416 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2417 if (viewportRect.intersects(rangeRect))
2418 selectionRegion += rangeRect;
2419 }
2420 } else {
2421 QRect combined = leftRect|rightRect;
2422 combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left()));
2423 if (viewportRect.intersects(combined))
2424 selectionRegion += combined;
2425 }
2426 }
2427 return selectionRegion;
2428}
2429
2430/*!
2431 \reimp
2432*/
2433QModelIndexList QTreeView::selectedIndexes() const
2434{
2435 QModelIndexList viewSelected;
2436 QModelIndexList modelSelected;
2437 if (selectionModel())
2438 modelSelected = selectionModel()->selectedIndexes();
2439 for (int i = 0; i < modelSelected.count(); ++i) {
2440 // check that neither the parents nor the index is hidden before we add
2441 QModelIndex index = modelSelected.at(i);
2442 while (index.isValid() && !isIndexHidden(index))
2443 index = index.parent();
2444 if (index.isValid())
2445 continue;
2446 viewSelected.append(modelSelected.at(i));
2447 }
2448 return viewSelected;
2449}
2450
2451/*!
2452 Scrolls the contents of the tree view by (\a dx, \a dy).
2453*/
2454void QTreeView::scrollContentsBy(int dx, int dy)
2455{
2456 Q_D(QTreeView);
2457
2458 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
2459
2460 dx = isRightToLeft() ? -dx : dx;
2461 if (dx) {
2462 int oldOffset = d->header->offset();
2463 d->header->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
2464 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2465 int newOffset = d->header->offset();
2466 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
2467 }
2468 }
2469
2470 const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight;
2471 if (d->viewItems.isEmpty() || itemHeight == 0)
2472 return;
2473
2474 // guestimate the number of items in the viewport
2475 int viewCount = d->viewport->height() / itemHeight;
2476 int maxDeltaY = qMin(d->viewItems.count(), viewCount);
2477 // no need to do a lot of work if we are going to redraw the whole thing anyway
2478 if (qAbs(dy) > qAbs(maxDeltaY) && d->editorIndexHash.isEmpty()) {
2479 verticalScrollBar()->update();
2480 d->viewport->update();
2481 return;
2482 }
2483
2484 if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2485 int currentScrollbarValue = verticalScrollBar()->value();
2486 int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy)
2487 int currentViewIndex = currentScrollbarValue; // the first visible item
2488 int previousViewIndex = previousScrollbarValue;
2489 dy = 0;
2490 if (previousViewIndex < currentViewIndex) { // scrolling down
2491 for (int i = previousViewIndex; i < currentViewIndex; ++i) {
2492 if (i < d->viewItems.count())
2493 dy -= d->itemHeight(i);
2494 }
2495 } else if (previousViewIndex > currentViewIndex) { // scrolling up
2496 for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) {
2497 if (i < d->viewItems.count())
2498 dy += d->itemHeight(i);
2499 }
2500 }
2501 }
2502
2503 d->scrollContentsBy(dx, dy);
2504}
2505
2506/*!
2507 This slot is called whenever a column has been moved.
2508*/
2509void QTreeView::columnMoved()
2510{
2511 Q_D(QTreeView);
2512 updateEditorGeometries();
2513 d->viewport->update();
2514}
2515
2516/*!
2517 \internal
2518*/
2519void QTreeView::reexpand()
2520{
2521 // do nothing
2522}
2523
2524/*!
2525 Informs the view that the rows from the \a start row to the \a end row
2526 inclusive have been inserted into the \a parent model item.
2527*/
2528void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end)
2529{
2530 Q_D(QTreeView);
2531 // if we are going to do a complete relayout anyway, there is no need to update
2532 if (d->delayedPendingLayout) {
2533 QAbstractItemView::rowsInserted(parent, start, end);
2534 return;
2535 }
2536
2537 //don't add a hierarchy on a column != 0
2538 if (parent.column() != 0 && parent.isValid()) {
2539 QAbstractItemView::rowsInserted(parent, start, end);
2540 return;
2541 }
2542
2543 const int parentRowCount = d->model->rowCount(parent);
2544 const int delta = end - start + 1;
2545 if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) {
2546 QAbstractItemView::rowsInserted(parent, start, end);
2547 return;
2548 }
2549
2550 const int parentItem = d->viewIndex(parent);
2551 if (((parentItem != -1) && d->viewItems.at(parentItem).expanded)
2552 || (parent == d->root)) {
2553 d->doDelayedItemsLayout();
2554 } else if (parentItem != -1 && parentRowCount == delta) {
2555 // the parent just went from 0 children to more. update to re-paint the decoration
2556 d->viewItems[parentItem].hasChildren = true;
2557 viewport()->update();
2558 }
2559 QAbstractItemView::rowsInserted(parent, start, end);
2560}
2561
2562/*!
2563 Informs the view that the rows from the \a start row to the \a end row
2564 inclusive are about to removed from the given \a parent model item.
2565*/
2566void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
2567{
2568 Q_D(QTreeView);
2569 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
2570 d->viewItems.clear();
2571}
2572
2573/*!
2574 \since 4.1
2575
2576 Informs the view that the rows from the \a start row to the \a end row
2577 inclusive have been removed from the given \a parent model item.
2578*/
2579void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end)
2580{
2581 Q_D(QTreeView);
2582 d->viewItems.clear();
2583 d->doDelayedItemsLayout();
2584 d->hasRemovedItems = true;
2585 d->_q_rowsRemoved(parent, start, end);
2586}
2587
2588/*!
2589 Informs the tree view that the number of columns in the tree view has
2590 changed from \a oldCount to \a newCount.
2591*/
2592void QTreeView::columnCountChanged(int oldCount, int newCount)
2593{
2594 Q_D(QTreeView);
2595 if (oldCount == 0 && newCount > 0) {
2596 //if the first column has just been added we need to relayout.
2597 d->doDelayedItemsLayout();
2598 }
2599
2600 if (isVisible())
2601 updateGeometries();
2602 viewport()->update();
2603}
2604
2605/*!
2606 Resizes the \a column given to the size of its contents.
2607
2608 \sa columnWidth(), setColumnWidth(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
2609*/
2610void QTreeView::resizeColumnToContents(int column)
2611{
2612 Q_D(QTreeView);
2613 d->executePostedLayout();
2614 if (column < 0 || column >= d->header->count())
2615 return;
2616 int contents = sizeHintForColumn(column);
2617 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column);
2618 d->header->resizeSection(column, qMax(contents, header));
2619}
2620
2621/*!
2622 \obsolete
2623 \overload
2624
2625 Sorts the model by the values in the given \a column.
2626*/
2627void QTreeView::sortByColumn(int column)
2628{
2629 Q_D(QTreeView);
2630 sortByColumn(column, d->header->sortIndicatorOrder());
2631}
2632
2633/*!
2634 \since 4.2
2635
2636 Sets the model up for sorting by the values in the given \a column and \a order.
2637
2638 \a column may be -1, in which case no sort indicator will be shown
2639 and the model will return to its natural, unsorted order. Note that not
2640 all models support this and may even crash in this case.
2641
2642 \sa sortingEnabled
2643*/
2644void QTreeView::sortByColumn(int column, Qt::SortOrder order)
2645{
2646 Q_D(QTreeView);
2647
2648 //If sorting is enabled will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts
2649 d->header->setSortIndicator(column, order);
2650 //If sorting is not enabled, force to sort now.
2651 if (!d->sortingEnabled)
2652 d->model->sort(column, order);
2653}
2654
2655/*!
2656 \reimp
2657*/
2658void QTreeView::selectAll()
2659{
2660 Q_D(QTreeView);
2661 if (!selectionModel())
2662 return;
2663 SelectionMode mode = d->selectionMode;
2664 d->executePostedLayout(); //make sure we lay out the items
2665 if (mode != SingleSelection && mode != NoSelection && !d->viewItems.isEmpty()) {
2666 const QModelIndex &idx = d->viewItems.constLast().index;
2667 QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1);
2668 d->select(d->viewItems.constFirst().index, lastItemIndex,
2669 QItemSelectionModel::ClearAndSelect
2670 |QItemSelectionModel::Rows);
2671 }
2672}
2673
2674/*!
2675 \reimp
2676*/
2677QSize QTreeView::viewportSizeHint() const
2678{
2679 Q_D(const QTreeView);
2680 d->executePostedLayout(); // Make sure that viewItems are up to date.
2681
2682 if (d->viewItems.size() == 0)
2683 return QAbstractItemView::viewportSizeHint();
2684
2685 // Get rect for last item
2686 const QRect deepestRect = visualRect(d->viewItems.last().index);
2687
2688 if (!deepestRect.isValid())
2689 return QAbstractItemView::viewportSizeHint();
2690
2691 QSize result = QSize(d->header->length(), deepestRect.bottom() + 1);
2692
2693 // add size for header
2694 result += QSize(0, d->header->isVisible() ? d->header->height() : 0);
2695
2696 // add size for scrollbars
2697 result += QSize(verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0,
2698 horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0);
2699
2700 return result;
2701}
2702
2703/*!
2704 \since 4.2
2705 Expands all expandable items.
2706
2707 Warning: if the model contains a large number of items,
2708 this function will take some time to execute.
2709
2710 \sa collapseAll(), expand(), collapse(), setExpanded()
2711*/
2712void QTreeView::expandAll()
2713{
2714 Q_D(QTreeView);
2715 d->viewItems.clear();
2716 d->interruptDelayedItemsLayout();
2717 d->layout(-1, true);
2718 updateGeometries();
2719 d->viewport->update();
2720}
2721
2722/*!
2723 \since 4.2
2724
2725 Collapses all expanded items.
2726
2727 \sa expandAll(), expand(), collapse(), setExpanded()
2728*/
2729void QTreeView::collapseAll()
2730{
2731 Q_D(QTreeView);
2732 QSet<QPersistentModelIndex> old_expandedIndexes;
2733 old_expandedIndexes = d->expandedIndexes;
2734 d->expandedIndexes.clear();
2735 if (!signalsBlocked() && isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed))) {
2736 QSet<QPersistentModelIndex>::const_iterator i = old_expandedIndexes.constBegin();
2737 for (; i != old_expandedIndexes.constEnd(); ++i) {
2738 const QPersistentModelIndex &mi = (*i);
2739 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2740 emit collapsed(mi);
2741 }
2742 }
2743 doItemsLayout();
2744}
2745
2746/*!
2747 \since 4.3
2748 Expands all expandable items to the given \a depth.
2749
2750 \sa expandAll(), collapseAll(), expand(), collapse(), setExpanded()
2751*/
2752void QTreeView::expandToDepth(int depth)
2753{
2754 Q_D(QTreeView);
2755 d->viewItems.clear();
2756 QSet<QPersistentModelIndex> old_expandedIndexes;
2757 old_expandedIndexes = d->expandedIndexes;
2758 d->expandedIndexes.clear();
2759 d->interruptDelayedItemsLayout();
2760 d->layout(-1);
2761 for (int i = 0; i < d->viewItems.count(); ++i) {
2762 if (d->viewItems.at(i).level <= (uint)depth) {
2763 d->viewItems[i].expanded = true;
2764 d->layout(i);
2765 d->storeExpanded(d->viewItems.at(i).index);
2766 }
2767 }
2768
2769 bool someSignalEnabled = isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed));
2770 someSignalEnabled |= isSignalConnected(QMetaMethod::fromSignal(&QTreeView::expanded));
2771
2772 if (!signalsBlocked() && someSignalEnabled) {
2773 // emit signals
2774 QSet<QPersistentModelIndex> collapsedIndexes = old_expandedIndexes - d->expandedIndexes;
2775 QSet<QPersistentModelIndex>::const_iterator i = collapsedIndexes.constBegin();
2776 for (; i != collapsedIndexes.constEnd(); ++i) {
2777 const QPersistentModelIndex &mi = (*i);
2778 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2779 emit collapsed(mi);
2780 }
2781
2782 QSet<QPersistentModelIndex> expandedIndexs = d->expandedIndexes - old_expandedIndexes;
2783 i = expandedIndexs.constBegin();
2784 for (; i != expandedIndexs.constEnd(); ++i) {
2785 const QPersistentModelIndex &mi = (*i);
2786 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2787 emit expanded(mi);
2788 }
2789 }
2790
2791 updateGeometries();
2792 d->viewport->update();
2793}
2794
2795/*!
2796 This function is called whenever \a{column}'s size is changed in
2797 the header. \a oldSize and \a newSize give the previous size and
2798 the new size in pixels.
2799
2800 \sa setColumnWidth()
2801*/
2802void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */)
2803{
2804 Q_D(QTreeView);
2805 d->columnsToUpdate.append(column);
2806 if (d->columnResizeTimerID == 0)
2807 d->columnResizeTimerID = startTimer(0);
2808}
2809
2810/*!
2811 \reimp
2812*/
2813void QTreeView::updateGeometries()
2814{
2815 Q_D(QTreeView);
2816 if (d->header) {
2817 if (d->geometryRecursionBlock)
2818 return;
2819 d->geometryRecursionBlock = true;
2820 int height = 0;
2821 if (!d->header->isHidden()) {
2822 height = qMax(d->header->minimumHeight(), d->header->sizeHint().height());
2823 height = qMin(height, d->header->maximumHeight());
2824 }
2825 setViewportMargins(0, height, 0, 0);
2826 QRect vg = d->viewport->geometry();
2827 QRect geometryRect(vg.left(), vg.top() - height, vg.width(), height);
2828 d->header->setGeometry(geometryRect);
2829 QMetaObject::invokeMethod(d->header, "updateGeometries");
2830 d->updateScrollBars();
2831 d->geometryRecursionBlock = false;
2832 }
2833 QAbstractItemView::updateGeometries();
2834}
2835
2836/*!
2837 Returns the size hint for the \a column's width or -1 if there is no
2838 model.
2839
2840 If you need to set the width of a given column to a fixed value, call
2841 QHeaderView::resizeSection() on the view's header.
2842
2843 If you reimplement this function in a subclass, note that the value you
2844 return is only used when resizeColumnToContents() is called. In that case,
2845 if a larger column width is required by either the view's header or
2846 the item delegate, that width will be used instead.
2847
2848 \sa QWidget::sizeHint, header(), QHeaderView::resizeContentsPrecision()
2849*/
2850int QTreeView::sizeHintForColumn(int column) const
2851{
2852 Q_D(const QTreeView);
2853 d->executePostedLayout();
2854 if (d->viewItems.isEmpty())
2855 return -1;
2856 ensurePolished();
2857 int w = 0;
2858 QStyleOptionViewItem option = d->viewOptionsV1();
2859 const QVector<QTreeViewItem> viewItems = d->viewItems;
2860
2861 const int maximumProcessRows = d->header->resizeContentsPrecision(); // To avoid this to take forever.
2862
2863 int offset = 0;
2864 int start = d->firstVisibleItem(&offset);
2865 int end = d->lastVisibleItem(start, offset);
2866 if (start < 0 || end < 0 || end == viewItems.size() - 1) {
2867 end = viewItems.size() - 1;
2868 if (maximumProcessRows < 0) {
2869 start = 0;
2870 } else if (maximumProcessRows == 0) {
2871 start = qMax(0, end - 1);
2872 int remainingHeight = viewport()->height();
2873 while (start > 0 && remainingHeight > 0) {
2874 remainingHeight -= d->itemHeight(start);
2875 --start;
2876 }
2877 } else {
2878 start = qMax(0, end - maximumProcessRows);
2879 }
2880 }
2881
2882 int rowsProcessed = 0;
2883
2884 for (int i = start; i <= end; ++i) {
2885 if (viewItems.at(i).spanning)
2886 continue; // we have no good size hint
2887 QModelIndex index = viewItems.at(i).index;
2888 index = index.sibling(index.row(), column);
2889 w = d->widthHintForIndex(index, w, option, i);
2890 ++rowsProcessed;
2891 if (rowsProcessed == maximumProcessRows)
2892 break;
2893 }
2894
2895 --end;
2896 int actualBottom = viewItems.size() - 1;
2897
2898 if (maximumProcessRows == 0)
2899 rowsProcessed = 0; // skip the while loop
2900
2901 while (rowsProcessed != maximumProcessRows && (start > 0 || end < actualBottom)) {
2902 int idx = -1;
2903
2904 if ((rowsProcessed % 2 && start > 0) || end == actualBottom) {
2905 while (start > 0) {
2906 --start;
2907 if (viewItems.at(start).spanning)
2908 continue;
2909 idx = start;
2910 break;
2911 }
2912 } else {
2913 while (end < actualBottom) {
2914 ++end;
2915 if (viewItems.at(end).spanning)
2916 continue;
2917 idx = end;
2918 break;
2919 }
2920 }
2921 if (idx < 0)
2922 continue;
2923
2924 QModelIndex index = viewItems.at(idx).index;
2925 index = index.sibling(index.row(), column);
2926 w = d->widthHintForIndex(index, w, option, idx);
2927 ++rowsProcessed;
2928 }
2929 return w;
2930}
2931
2932/*!
2933 Returns the size hint for the row indicated by \a index.
2934
2935 \sa sizeHintForColumn(), uniformRowHeights()
2936*/
2937int QTreeView::indexRowSizeHint(const QModelIndex &index) const
2938{
2939 Q_D(const QTreeView);
2940 if (!d->isIndexValid(index) || !d->itemDelegate)
2941 return 0;
2942
2943 int start = -1;
2944 int end = -1;
2945 int indexRow = index.row();
2946 int count = d->header->count();
2947 bool emptyHeader = (count == 0);
2948 QModelIndex parent = index.parent();
2949
2950 if (count && isVisible()) {
2951 // If the sections have moved, we end up checking too many or too few
2952 start = d->header->visualIndexAt(0);
2953 } else {
2954 // If the header has not been laid out yet, we use the model directly
2955 count = d->model->columnCount(parent);
2956 }
2957
2958 if (isRightToLeft()) {
2959 start = (start == -1 ? count - 1 : start);
2960 end = 0;
2961 } else {
2962 start = (start == -1 ? 0 : start);
2963 end = count - 1;
2964 }
2965
2966 if (end < start)
2967 qSwap(end, start);
2968
2969 int height = -1;
2970 QStyleOptionViewItem option = d->viewOptionsV1();
2971 // ### If we want word wrapping in the items,
2972 // ### we need to go through all the columns
2973 // ### and set the width of the column
2974
2975 // Hack to speed up the function
2976 option.rect.setWidth(-1);
2977
2978 for (int column = start; column <= end; ++column) {
2979 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column);
2980 if (d->header->isSectionHidden(logicalColumn))
2981 continue;
2982 QModelIndex idx = d->model->index(indexRow, logicalColumn, parent);
2983 if (idx.isValid()) {
2984 QWidget *editor = d->editorForIndex(idx).widget.data();
2985 if (editor && d->persistent.contains(editor)) {
2986 height = qMax(height, editor->sizeHint().height());
2987 int min = editor->minimumSize().height();
2988 int max = editor->maximumSize().height();
2989 height = qBound(min, height, max);
2990 }
2991 int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height();
2992 height = qMax(height, hint);
2993 }
2994 }
2995
2996 return height;
2997}
2998
2999/*!
3000 \since 4.3
3001 Returns the height of the row indicated by the given \a index.
3002 \sa indexRowSizeHint()
3003*/
3004int QTreeView::rowHeight(const QModelIndex &index) const
3005{
3006 Q_D(const QTreeView);
3007 d->executePostedLayout();
3008 int i = d->viewIndex(index);
3009 if (i == -1)
3010 return 0;
3011 return d->itemHeight(i);
3012}
3013
3014/*!
3015 \internal
3016*/
3017void QTreeView::horizontalScrollbarAction(int action)
3018{
3019 QAbstractItemView::horizontalScrollbarAction(action);
3020}
3021
3022/*!
3023 \reimp
3024*/
3025bool QTreeView::isIndexHidden(const QModelIndex &index) const
3026{
3027 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent()));
3028}
3029
3030/*
3031 private implementation
3032*/
3033void QTreeViewPrivate::initialize()
3034{
3035 Q_Q(QTreeView);
3036
3037 updateIndentationFromStyle();
3038 updateStyledFrameWidths();
3039 q->setSelectionBehavior(QAbstractItemView::SelectRows);
3040 q->setSelectionMode(QAbstractItemView::SingleSelection);
3041 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
3042 q->setAttribute(Qt::WA_MacShowFocusRect);
3043
3044 QHeaderView *header = new QHeaderView(Qt::Horizontal, q);
3045 header->setSectionsMovable(true);
3046 header->setStretchLastSection(true);
3047 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
3048 q->setHeader(header);
3049#ifndef QT_NO_ANIMATION
3050 animationsEnabled = q->style()->styleHint(QStyle::SH_Widget_Animation_Duration, 0, q) > 0;
3051 QObject::connect(&animatedOperation, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation()));
3052#endif //QT_NO_ANIMATION
3053}
3054
3055void QTreeViewPrivate::expand(int item, bool emitSignal)
3056{
3057 Q_Q(QTreeView);
3058
3059 if (item == -1 || viewItems.at(item).expanded)
3060 return;
3061 const QModelIndex index = viewItems.at(item).index;
3062 if (index.flags() & Qt::ItemNeverHasChildren)
3063 return;
3064
3065#ifndef QT_NO_ANIMATION
3066 if (emitSignal && animationsEnabled)
3067 prepareAnimatedOperation(item, QVariantAnimation::Forward);
3068#endif //QT_NO_ANIMATION
3069 //if already animating, stateBeforeAnimation is set to the correct value
3070 if (state != QAbstractItemView::AnimatingState)
3071 stateBeforeAnimation = state;
3072 q->setState(QAbstractItemView::ExpandingState);
3073 storeExpanded(index);
3074 viewItems[item].expanded = true;
3075 layout(item);
3076 q->setState(stateBeforeAnimation);
3077
3078 if (model->canFetchMore(index))
3079 model->fetchMore(index);
3080 if (emitSignal) {
3081 emit q->expanded(index);
3082#ifndef QT_NO_ANIMATION
3083 if (animationsEnabled)
3084 beginAnimatedOperation();
3085#endif //QT_NO_ANIMATION
3086 }
3087}
3088
3089void QTreeViewPrivate::insertViewItems(int pos, int count, const QTreeViewItem &viewItem)
3090{
3091 viewItems.insert(pos, count, viewItem);
3092 QTreeViewItem *items = viewItems.data();
3093 for (int i = pos + count; i < viewItems.count(); i++)
3094 if (items[i].parentItem >= pos)
3095 items[i].parentItem += count;
3096}
3097
3098void QTreeViewPrivate::removeViewItems(int pos, int count)
3099{
3100 viewItems.remove(pos, count);
3101 QTreeViewItem *items = viewItems.data();
3102 for (int i = pos; i < viewItems.count(); i++)
3103 if (items[i].parentItem >= pos)
3104 items[i].parentItem -= count;
3105}
3106
3107#if 0
3108bool QTreeViewPrivate::checkViewItems() const
3109{
3110 for (int i = 0; i < viewItems.count(); ++i) {
3111 const QTreeViewItem &vi = viewItems.at(i);
3112 if (vi.parentItem == -1) {
3113 Q_ASSERT(!vi.index.parent().isValid() || vi.index.parent() == root);
3114 } else {
3115 Q_ASSERT(vi.index.parent() == viewItems.at(vi.parentItem).index);
3116 }
3117 }
3118 return true;
3119}
3120#endif
3121
3122void QTreeViewPrivate::collapse(int item, bool emitSignal)
3123{
3124 Q_Q(QTreeView);
3125
3126 if (item == -1 || expandedIndexes.isEmpty())
3127 return;
3128
3129 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
3130 delayedAutoScroll.stop();
3131
3132 int total = viewItems.at(item).total;
3133 const QModelIndex &modelIndex = viewItems.at(item).index;
3134 if (!isPersistent(modelIndex))
3135 return; // if the index is not persistent, no chances it is expanded
3136 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex);
3137 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false)
3138 return; // nothing to do
3139
3140#ifndef QT_NO_ANIMATION
3141 if (emitSignal && animationsEnabled)
3142 prepareAnimatedOperation(item, QVariantAnimation::Backward);
3143#endif //QT_NO_ANIMATION
3144
3145 //if already animating, stateBeforeAnimation is set to the correct value
3146 if (state != QAbstractItemView::AnimatingState)
3147 stateBeforeAnimation = state;
3148 q->setState(QAbstractItemView::CollapsingState);
3149 expandedIndexes.erase(it);
3150 viewItems[item].expanded = false;
3151 int index = item;
3152 while (index > -1) {
3153 viewItems[index].total -= total;
3154 index = viewItems[index].parentItem;
3155 }
3156 removeViewItems(item + 1, total); // collapse
3157 q->setState(stateBeforeAnimation);
3158
3159 if (emitSignal) {
3160 emit q->collapsed(modelIndex);
3161#ifndef QT_NO_ANIMATION
3162 if (animationsEnabled)
3163 beginAnimatedOperation();
3164#endif //QT_NO_ANIMATION
3165 }
3166}
3167
3168#ifndef QT_NO_ANIMATION
3169void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction)
3170{
3171 animatedOperation.item = item;
3172 animatedOperation.viewport = viewport;
3173 animatedOperation.setDirection(direction);
3174
3175 int top = coordinateForItem(item) + itemHeight(item);
3176 QRect rect = viewport->rect();
3177 rect.setTop(top);
3178 if (direction == QVariantAnimation::Backward) {
3179 const int limit = rect.height() * 2;
3180 int h = 0;
3181 int c = item + viewItems.at(item).total + 1;
3182 for (int i = item + 1; i < c && h < limit; ++i)
3183 h += itemHeight(i);
3184 rect.setHeight(h);
3185 animatedOperation.setEndValue(top + h);
3186 }
3187 animatedOperation.setStartValue(top);
3188 animatedOperation.before = renderTreeToPixmapForAnimation(rect);
3189}
3190
3191void QTreeViewPrivate::beginAnimatedOperation()
3192{
3193 Q_Q(QTreeView);
3194
3195 QRect rect = viewport->rect();
3196 rect.setTop(animatedOperation.top());
3197 if (animatedOperation.direction() == QVariantAnimation::Forward) {
3198 const int limit = rect.height() * 2;
3199 int h = 0;
3200 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1;
3201 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i)
3202 h += itemHeight(i);
3203 rect.setHeight(h);
3204 animatedOperation.setEndValue(animatedOperation.top() + h);
3205 }
3206
3207 if (!rect.isEmpty()) {
3208 animatedOperation.after = renderTreeToPixmapForAnimation(rect);
3209
3210 q->setState(QAbstractItemView::AnimatingState);
3211 animatedOperation.start(); //let's start the animation
3212 }
3213}
3214
3215void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const
3216{
3217 const int start = animatedOperation.startValue().toInt(),
3218 end = animatedOperation.endValue().toInt(),
3219 current = animatedOperation.currentValue().toInt();
3220 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward;
3221 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after;
3222 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height());
3223 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before;
3224 painter->drawPixmap(0, current, bottom);
3225}
3226
3227QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const
3228{
3229 Q_Q(const QTreeView);
3230 QPixmap pixmap(rect.size() * q->devicePixelRatio());
3231 pixmap.setDevicePixelRatio(q->devicePixelRatio());
3232 if (rect.size().isEmpty())
3233 return pixmap;
3234 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels.
3235 QPainter painter(&pixmap);
3236 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base());
3237 painter.translate(0, -rect.top());
3238 q->drawTree(&painter, QRegion(rect));
3239 painter.end();
3240
3241 //and now let's render the editors the editors
3242 QStyleOptionViewItem option = viewOptionsV1();
3243 for (QEditorIndexHash::const_iterator it = editorIndexHash.constBegin(); it != editorIndexHash.constEnd(); ++it) {
3244 QWidget *editor = it.key();
3245 const QModelIndex &index = it.value();
3246 option.rect = q->visualRect(index);
3247 if (option.rect.isValid()) {
3248
3249 if (QAbstractItemDelegate *delegate = delegateForIndex(index))
3250 delegate->updateEditorGeometry(editor, option, index);
3251
3252 const QPoint pos = editor->pos();
3253 if (rect.contains(pos)) {
3254 editor->render(&pixmap, pos - rect.topLeft());
3255 //the animation uses pixmap to display the treeview's content
3256 //the editor is rendered on this pixmap and thus can (should) be hidden
3257 editor->hide();
3258 }
3259 }
3260 }
3261
3262
3263 return pixmap;
3264}
3265
3266void QTreeViewPrivate::_q_endAnimatedOperation()
3267{
3268 Q_Q(QTreeView);
3269 q->setState(stateBeforeAnimation);
3270 q->updateGeometries();
3271 viewport->update();
3272}
3273#endif //QT_NO_ANIMATION
3274
3275void QTreeViewPrivate::_q_modelAboutToBeReset()
3276{
3277 viewItems.clear();
3278}
3279
3280void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
3281{
3282 if (start <= 0 && 0 <= end)
3283 viewItems.clear();
3284 QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(parent, start, end);
3285}
3286
3287void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end)
3288{
3289 if (start <= 0 && 0 <= end)
3290 doDelayedItemsLayout();
3291 QAbstractItemViewPrivate::_q_columnsRemoved(parent, start, end);
3292}
3293
3294/** \internal
3295 creates and initialize the viewItem structure of the children of the element \li
3296
3297 set \a recursiveExpanding if the function has to expand all the children (called from expandAll)
3298 \a afterIsUninitialized is when we recurse from layout(-1), it means all the items after 'i' are
3299 not yet initialized and need not to be moved
3300 */
3301void QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninitialized)
3302{
3303 Q_Q(QTreeView);
3304 QModelIndex current;
3305 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i);
3306
3307 if (i>=0 && !parent.isValid()) {
3308 //modelIndex() should never return something invalid for the real items.
3309 //This can happen if columncount has been set to 0.
3310 //To avoid infinite loop we stop here.
3311 return;
3312 }
3313
3314 int count = 0;
3315 if (model->hasChildren(parent)) {
3316 if (model->canFetchMore(parent))
3317 model->fetchMore(parent);
3318 count = model->rowCount(parent);
3319 }
3320
3321 bool expanding = true;
3322 if (i == -1) {
3323 if (uniformRowHeights) {
3324 QModelIndex index = model->index(0, 0, parent);
3325 defaultItemHeight = q->indexRowSizeHint(index);
3326 }
3327 viewItems.resize(count);
3328 afterIsUninitialized = true;
3329 } else if (viewItems[i].total != (uint)count) {
3330 if (!afterIsUninitialized)
3331 insertViewItems(i + 1, count, QTreeViewItem()); // expand
3332 else if (count > 0)
3333 viewItems.resize(viewItems.count() + count);
3334 } else {
3335 expanding = false;
3336 }
3337
3338 int first = i + 1;
3339 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0);
3340 int hidden = 0;
3341 int last = 0;
3342 int children = 0;
3343 QTreeViewItem *item = 0;
3344 for (int j = first; j <