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