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