Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtreeview.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4#include "qtreeview.h"
5
6#include <qheaderview.h>
7#include <qabstractitemdelegate.h>
8#include <qapplication.h>
9#include <qscrollbar.h>
10#include <qpainter.h>
11#include <qpainterstateguard.h>
12#include <qstack.h>
13#include <qstyle.h>
14#include <qstyleoption.h>
15#include <qevent.h>
16#include <qpen.h>
17#include <qdebug.h>
18#include <QMetaMethod>
19#include <private/qscrollbar_p.h>
20#if QT_CONFIG(accessibility)
21#include <qaccessible.h>
22#endif
23
24#include <private/qapplication_p.h>
25#include <private/qtreeview_p.h>
26#include <private/qheaderview_p.h>
27
28#include <algorithm>
29
30using namespace std::chrono_literals;
31
32QT_BEGIN_NAMESPACE
33
34/*!
35 \class QTreeView
36 \brief The QTreeView class provides a default model/view implementation of a tree view.
37
38 \ingroup model-view
39 \ingroup advanced
40 \inmodule QtWidgets
41
42 \image fusion-treeview.png
43 {Directory widget showing the contents using a tree view}
44
45 A QTreeView implements a tree representation of items from a
46 model. This class is used to provide standard hierarchical lists that
47 were previously provided by the \c QListView class, but using the more
48 flexible approach provided by Qt's model/view architecture.
49
50 The QTreeView class is one of the \l{Model/View Classes} and is part of
51 Qt's \l{Model/View Programming}{model/view framework}.
52
53 QTreeView implements the interfaces defined by the
54 QAbstractItemView class to allow it to display data provided by
55 models derived from the QAbstractItemModel class.
56
57 It is simple to construct a tree view displaying data from a
58 model. In the following example, the contents of a directory are
59 supplied by a QFileSystemModel and displayed as a tree:
60
61 \snippet shareddirmodel/main.cpp 3
62 \snippet shareddirmodel/main.cpp 6
63
64 The model/view architecture ensures that the contents of the tree view
65 are updated as the model changes.
66
67 Items that have children can be in an expanded (children are
68 visible) or collapsed (children are hidden) state. When this state
69 changes a collapsed() or expanded() signal is emitted with the
70 model index of the relevant item.
71
72 The amount of indentation used to indicate levels of hierarchy is
73 controlled by the \l indentation property.
74
75 Headers in tree views are constructed using the QHeaderView class and can
76 be hidden using \c{header()->hide()}. Note that each header is configured
77 with its \l{QHeaderView::}{stretchLastSection} property set to true,
78 ensuring that the view does not waste any of the space assigned to it for
79 its header. If this value is set to true, this property will override the
80 resize mode set on the last section in the header.
81
82 By default, all columns in a tree view are movable except the first. To
83 disable movement of these columns, use QHeaderView's
84 \l {QHeaderView::}{setSectionsMovable()} function. For more information
85 about rearranging sections, see \l {Moving Header Sections}.
86
87 \section1 Key Bindings
88
89 QTreeView supports a set of key bindings that enable the user to
90 navigate in the view and interact with the contents of items:
91
92 \table
93 \header \li Key \li Action
94 \row \li Up \li Moves the cursor to the item in the same column on
95 the previous row. If the parent of the current item has no more rows to
96 navigate to, the cursor moves to the relevant item in the last row
97 of the sibling that precedes the parent.
98 \row \li Down \li Moves the cursor to the item in the same column on
99 the next row. If the parent of the current item has no more rows to
100 navigate to, the cursor moves to the relevant item in the first row
101 of the sibling that follows the parent.
102 \row \li Left \li Hides the children of the current item (if present)
103 by collapsing a branch.
104 \row \li Minus \li Same as Left.
105 \row \li Right \li Reveals the children of the current item (if present)
106 by expanding a branch.
107 \row \li Plus \li Same as Right.
108 \row \li Asterisk \li Expands the current item and all its children
109 (if present).
110 \row \li PageUp \li Moves the cursor up one page.
111 \row \li PageDown \li Moves the cursor down one page.
112 \row \li Home \li Moves the cursor to an item in the same column of the first
113 row of the first top-level item in the model.
114 \row \li End \li Moves the cursor to an item in the same column of the last
115 row of the last top-level item in the model.
116 \row \li F2 \li In editable models, this opens the current item for editing.
117 The Escape key can be used to cancel the editing process and revert
118 any changes to the data displayed.
119 \endtable
120
121 \omit
122 Describe the expanding/collapsing concept if not covered elsewhere.
123 \endomit
124
125 \section1 Improving Performance
126
127 It is possible to give the view hints about the data it is handling in order
128 to improve its performance when displaying large numbers of items. One approach
129 that can be taken for views that are intended to display items with equal heights
130 is to set the \l uniformRowHeights property to true.
131
132 \sa QListView, QTreeWidget, {View Classes}, QAbstractItemModel, QAbstractItemView
133*/
134
135
136/*!
137 \fn void QTreeView::expanded(const QModelIndex &index)
138
139 This signal is emitted when the item specified by \a index is expanded.
140*/
142
143/*!
144 \fn void QTreeView::collapsed(const QModelIndex &index)
145
146 This signal is emitted when the item specified by \a index is collapsed.
147*/
148
149/*!
150 Constructs a tree view with a \a parent to represent a model's
151 data. Use setModel() to set the model.
152
153 \sa QAbstractItemModel
154*/
155QTreeView::QTreeView(QWidget *parent)
156 : QAbstractItemView(*new QTreeViewPrivate, parent)
157{
158 Q_D(QTreeView);
159 d->initialize();
160}
161
162/*!
163 \internal
164*/
165QTreeView::QTreeView(QTreeViewPrivate &dd, QWidget *parent)
166 : QAbstractItemView(dd, parent)
167{
168 Q_D(QTreeView);
169 d->initialize();
170}
171
172/*!
173 Destroys the tree view.
174*/
175QTreeView::~QTreeView()
176{
177 Q_D(QTreeView);
178 d->clearConnections();
179}
180
181/*!
182 \reimp
183*/
184void QTreeView::setModel(QAbstractItemModel *model)
185{
186 Q_D(QTreeView);
187 if (model == d->model)
188 return;
189 if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
190 for (QMetaObject::Connection &connection : d->modelConnections)
191 QObject::disconnect(connection);
192 }
193
194 if (d->selectionModel) { // support row editing
195 QObject::disconnect(d->selectionmodelConnection);
196 }
197 d->viewItems.clear();
198 d->expandedIndexes.clear();
199 d->hiddenIndexes.clear();
200 d->geometryRecursionBlock = true; // do not update geometries due to signals from the headers
201 d->header->setModel(model);
202 d->geometryRecursionBlock = false;
203 QAbstractItemView::setModel(model);
204
205 if (d->model) {
206 // QAbstractItemView connects to a private slot
207 QObjectPrivate::disconnect(d->model, &QAbstractItemModel::rowsRemoved,
208 d, &QAbstractItemViewPrivate::rowsRemoved);
209 // do header layout after the tree
210 QObjectPrivate::disconnect(d->model, &QAbstractItemModel::layoutChanged,
211 d->header->d_func(), &QAbstractItemViewPrivate::layoutChanged);
212
213 d->modelConnections = {
214 // QTreeView has a public slot for this
215 QObject::connect(d->model, &QAbstractItemModel::rowsRemoved,
216 this, &QTreeView::rowsRemoved),
217 QObjectPrivate::connect(d->model, &QAbstractItemModel::modelAboutToBeReset,
218 d, &QTreeViewPrivate::modelAboutToBeReset)
219 };
220 }
221 if (d->sortingEnabled)
222 d->sortIndicatorChanged(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
223}
224
225/*!
226 \reimp
227*/
228void QTreeView::setRootIndex(const QModelIndex &index)
229{
230 Q_D(QTreeView);
231 d->header->setRootIndex(index);
232 QAbstractItemView::setRootIndex(index);
233}
234
235/*!
236 \reimp
237*/
238void QTreeView::setSelectionModel(QItemSelectionModel *selectionModel)
239{
240 Q_D(QTreeView);
241 Q_ASSERT(selectionModel);
242 if (d->selectionModel) {
243 // support row editing
244 QObject::disconnect(d->selectionmodelConnection);
245 }
246
247 d->header->setSelectionModel(selectionModel);
248 QAbstractItemView::setSelectionModel(selectionModel);
249
250 if (d->selectionModel) {
251 // support row editing
252 d->selectionmodelConnection =
253 connect(d->selectionModel, &QItemSelectionModel::currentRowChanged,
254 d->model, &QAbstractItemModel::submit);
255 }
256}
257
258/*!
259 Returns the header for the tree view.
260
261 \sa QAbstractItemModel::headerData()
262*/
263QHeaderView *QTreeView::header() const
264{
265 Q_D(const QTreeView);
266 return d->header;
267}
268
269/*!
270 Sets the header for the tree view, to the given \a header.
271
272 The view takes ownership over the given \a header and deletes it
273 when a new header is set.
274
275 \sa QAbstractItemModel::headerData()
276*/
277void QTreeView::setHeader(QHeaderView *header)
278{
279 Q_D(QTreeView);
280 if (header == d->header || !header)
281 return;
282 if (d->header && d->header->parent() == this)
283 delete d->header;
284 d->header = header;
285 d->header->setParent(this);
286 d->header->setFirstSectionMovable(false);
287
288 if (!d->header->model()) {
289 d->header->setModel(d->model);
290 if (d->selectionModel)
291 d->header->setSelectionModel(d->selectionModel);
292 }
293
294 d->headerConnections = {
295 connect(d->header, &QHeaderView::sectionResized,
296 this, &QTreeView::columnResized),
297 connect(d->header, &QHeaderView::sectionMoved,
298 this, &QTreeView::columnMoved),
299 connect(d->header, &QHeaderView::sectionCountChanged,
300 this, &QTreeView::columnCountChanged),
301 connect(d->header, &QHeaderView::sectionHandleDoubleClicked,
302 this, &QTreeView::resizeColumnToContents),
303 connect(d->header, &QHeaderView::geometriesChanged,
304 this, &QTreeView::updateGeometries)
305 };
306
307 setSortingEnabled(d->sortingEnabled);
308 d->updateGeometry();
309}
310
311/*!
312 \property QTreeView::autoExpandDelay
313 \brief The delay time before items in a tree are opened during a drag and drop operation.
314
315 This property holds the amount of time in milliseconds that the user must wait over
316 a node before that node will automatically open. If the time is
317 set to less then 0 then it will not be activated.
318
319 By default, this property has a value of -1, meaning that auto-expansion is disabled.
320*/
321int QTreeView::autoExpandDelay() const
322{
323 Q_D(const QTreeView);
324 return d->autoExpandDelay;
325}
326
327void QTreeView::setAutoExpandDelay(int delay)
328{
329 Q_D(QTreeView);
330 d->autoExpandDelay = delay;
331}
332
333/*!
334 \property QTreeView::indentation
335 \brief indentation of the items in the tree view.
336
337 This property holds the indentation measured in pixels of the items for each
338 level in the tree view. For top-level items, the indentation specifies the
339 horizontal distance from the viewport edge to the items in the first column;
340 for child items, it specifies their indentation from their parent items.
341
342 By default, the value of this property is style dependent. Thus, when the style
343 changes, this property updates from it. Calling setIndentation() stops the updates,
344 calling resetIndentation() will restore default behavior.
345*/
346int QTreeView::indentation() const
347{
348 Q_D(const QTreeView);
349 return d->indent;
350}
351
352void QTreeView::setIndentation(int i)
353{
354 Q_D(QTreeView);
355 if (!d->customIndent || (i != d->indent)) {
356 d->indent = i;
357 d->customIndent = true;
358 d->viewport->update();
359 }
360}
361
362void QTreeView::resetIndentation()
363{
364 Q_D(QTreeView);
365 if (d->customIndent) {
366 d->updateIndentationFromStyle();
367 d->customIndent = false;
368 }
369}
370
371/*!
372 \property QTreeView::rootIsDecorated
373 \brief whether to show controls for expanding and collapsing top-level items
374
375 Items with children are typically shown with controls to expand and collapse
376 them, allowing their children to be shown or hidden. If this property is
377 false, these controls are not shown for top-level items. This can be used to
378 make a single level tree structure appear like a simple list of items.
379
380 By default, this property is \c true.
381*/
382bool QTreeView::rootIsDecorated() const
383{
384 Q_D(const QTreeView);
385 return d->rootDecoration;
386}
387
388void QTreeView::setRootIsDecorated(bool show)
389{
390 Q_D(QTreeView);
391 if (show != d->rootDecoration) {
392 d->rootDecoration = show;
393 d->viewport->update();
394 }
395}
396
397/*!
398 \property QTreeView::uniformRowHeights
399 \brief whether all items in the treeview have the same height
400
401 This property should only be set to true if it is guaranteed that all items
402 in the view has the same height. This enables the view to do some
403 optimizations.
404
405 The height is obtained from the first item in the view. It is updated
406 when the data changes on that item.
407
408 \note If the editor size hint is bigger than the cell size hint, then the
409 size hint of the editor will be used.
410
411 By default, this property is \c false.
412*/
413bool QTreeView::uniformRowHeights() const
414{
415 Q_D(const QTreeView);
416 return d->uniformRowHeights;
417}
418
419void QTreeView::setUniformRowHeights(bool uniform)
420{
421 Q_D(QTreeView);
422 d->uniformRowHeights = uniform;
423}
424
425/*!
426 \property QTreeView::itemsExpandable
427 \brief whether the items are expandable by the user.
428
429 This property holds whether the user can expand and collapse items
430 interactively.
431
432 By default, this property is \c true.
433
434*/
435bool QTreeView::itemsExpandable() const
436{
437 Q_D(const QTreeView);
438 return d->itemsExpandable;
439}
440
441void QTreeView::setItemsExpandable(bool enable)
442{
443 Q_D(QTreeView);
444 d->itemsExpandable = enable;
445}
446
447/*!
448 \property QTreeView::expandsOnDoubleClick
449 \brief whether the items can be expanded by double-clicking.
450
451 This property holds whether the user can expand and collapse items
452 by double-clicking. The default value is true.
453
454 \sa itemsExpandable
455*/
456bool QTreeView::expandsOnDoubleClick() const
457{
458 Q_D(const QTreeView);
459 return d->expandsOnDoubleClick;
460}
461
462void QTreeView::setExpandsOnDoubleClick(bool enable)
463{
464 Q_D(QTreeView);
465 d->expandsOnDoubleClick = enable;
466}
467
468/*!
469 Returns the horizontal position of the \a column in the viewport.
470*/
471int QTreeView::columnViewportPosition(int column) const
472{
473 Q_D(const QTreeView);
474 return d->header->sectionViewportPosition(column);
475}
476
477/*!
478 Returns the width of the \a column.
479
480 \sa resizeColumnToContents(), setColumnWidth()
481*/
482int QTreeView::columnWidth(int column) const
483{
484 Q_D(const QTreeView);
485 return d->header->sectionSize(column);
486}
487
488/*!
489 Sets the width of the given \a column to the \a width specified.
490
491 \sa columnWidth(), resizeColumnToContents()
492*/
493void QTreeView::setColumnWidth(int column, int width)
494{
495 Q_D(QTreeView);
496 d->header->resizeSection(column, width);
497}
498
499/*!
500 Returns the column in the tree view whose header covers the \a x
501 coordinate given.
502*/
503int QTreeView::columnAt(int x) const
504{
505 Q_D(const QTreeView);
506 return d->header->logicalIndexAt(x);
507}
508
509/*!
510 Returns \c true if the \a column is hidden; otherwise returns \c false.
511
512 \sa hideColumn(), isRowHidden()
513*/
514bool QTreeView::isColumnHidden(int column) const
515{
516 Q_D(const QTreeView);
517 return d->header->isSectionHidden(column);
518}
519
520/*!
521 If \a hide is true the \a column is hidden, otherwise the \a column is shown.
522
523 \sa hideColumn(), setRowHidden()
524*/
525void QTreeView::setColumnHidden(int column, bool hide)
526{
527 Q_D(QTreeView);
528 if (column < 0 || column >= d->header->count())
529 return;
530 d->header->setSectionHidden(column, hide);
531}
532
533/*!
534 \property QTreeView::headerHidden
535 \brief whether the header is shown or not.
536
537 If this property is \c true, the header is not shown otherwise it is.
538 The default value is false.
539
540 \sa header()
541*/
542bool QTreeView::isHeaderHidden() const
543{
544 Q_D(const QTreeView);
545 return d->header->isHidden();
546}
547
548void QTreeView::setHeaderHidden(bool hide)
549{
550 Q_D(QTreeView);
551 d->header->setHidden(hide);
552}
553
554/*!
555 Returns \c true if the item in the given \a row of the \a parent is hidden;
556 otherwise returns \c false.
557
558 \sa setRowHidden(), isColumnHidden()
559*/
560bool QTreeView::isRowHidden(int row, const QModelIndex &parent) const
561{
562 Q_D(const QTreeView);
563 if (!d->model)
564 return false;
565 return d->isRowHidden(d->model->index(row, 0, parent));
566}
567
568/*!
569 If \a hide is true the \a row with the given \a parent is hidden, otherwise the \a row is shown.
570
571 \sa isRowHidden(), setColumnHidden()
572*/
573void QTreeView::setRowHidden(int row, const QModelIndex &parent, bool hide)
574{
575 Q_D(QTreeView);
576 if (!d->model)
577 return;
578 QModelIndex index = d->model->index(row, 0, parent);
579 if (!index.isValid())
580 return;
581
582 if (hide) {
583 d->hiddenIndexes.insert(index);
584 } else if (d->isPersistent(index)) { //if the index is not persistent, it cannot be in the set
585 d->hiddenIndexes.remove(index);
586 }
587
588 d->doDelayedItemsLayout();
589}
590
591/*!
592 Returns \c true if the item in first column in the given \a row
593 of the \a parent is spanning all the columns; otherwise returns \c false.
594
595 \sa setFirstColumnSpanned()
596*/
597bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const
598{
599 Q_D(const QTreeView);
600 if (d->spanningIndexes.isEmpty() || !d->model)
601 return false;
602 const QModelIndex index = d->model->index(row, 0, parent);
603 return d->spanningIndexes.contains(index);
604}
605
606/*!
607 If \a span is true the item in the first column in the \a row
608 with the given \a parent is set to span all columns, otherwise all items
609 on the \a row are shown.
610
611 \sa isFirstColumnSpanned()
612*/
613void QTreeView::setFirstColumnSpanned(int row, const QModelIndex &parent, bool span)
614{
615 Q_D(QTreeView);
616 if (!d->model)
617 return;
618 const QModelIndex index = d->model->index(row, 0, parent);
619 if (!index.isValid())
620 return;
621
622 if (span)
623 d->spanningIndexes.insert(index);
624 else
625 d->spanningIndexes.remove(index);
626
627 d->executePostedLayout();
628 int i = d->viewIndex(index);
629 if (i >= 0)
630 d->viewItems[i].spanning = span;
631
632 d->viewport->update();
633}
634
635/*!
636 \reimp
637*/
638void QTreeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
639 const QList<int> &roles)
640{
641 Q_D(QTreeView);
642
643 // if we are going to do a complete relayout anyway, there is no need to update
644 if (d->delayedPendingLayout)
645 return;
646
647 // refresh the height cache here; we don't really lose anything by getting the size hint,
648 // since QAbstractItemView::dataChanged() will get the visualRect for the items anyway
649
650 bool sizeChanged = false;
651 int topViewIndex = d->viewIndex(topLeft);
652 if (topViewIndex == 0) {
653 int newDefaultItemHeight = indexRowSizeHint(topLeft);
654 sizeChanged = d->defaultItemHeight != newDefaultItemHeight;
655 d->defaultItemHeight = newDefaultItemHeight;
656 }
657
658 if (topViewIndex != -1) {
659 if (topLeft.row() == bottomRight.row()) {
660 int oldHeight = d->itemHeight(topViewIndex);
661 d->invalidateHeightCache(topViewIndex);
662 sizeChanged |= (oldHeight != d->itemHeight(topViewIndex));
663 if (topLeft.column() == 0)
664 d->viewItems[topViewIndex].hasChildren = d->hasVisibleChildren(topLeft);
665 } else {
666 int bottomViewIndex = d->viewIndex(bottomRight);
667 for (int i = topViewIndex; i <= bottomViewIndex; ++i) {
668 int oldHeight = d->itemHeight(i);
669 d->invalidateHeightCache(i);
670 sizeChanged |= (oldHeight != d->itemHeight(i));
671 if (topLeft.column() == 0)
672 d->viewItems[i].hasChildren = d->hasVisibleChildren(d->viewItems.at(i).index);
673 }
674 }
675 }
676
677 if (sizeChanged) {
678 d->updateScrollBars();
679 d->viewport->update();
680 }
681 QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
682}
683
684/*!
685 Hides the \a column given.
686
687 \note This function should only be called after the model has been
688 initialized, as the view needs to know the number of columns in order to
689 hide \a column.
690
691 \sa showColumn(), setColumnHidden()
692*/
693void QTreeView::hideColumn(int column)
694{
695 Q_D(QTreeView);
696 if (d->header->isSectionHidden(column))
697 return;
698 d->header->hideSection(column);
699 doItemsLayout();
700}
701
702/*!
703 Shows the given \a column in the tree view.
704
705 \sa hideColumn(), setColumnHidden()
706*/
707void QTreeView::showColumn(int column)
708{
709 Q_D(QTreeView);
710 if (!d->header->isSectionHidden(column))
711 return;
712 d->header->showSection(column);
713 doItemsLayout();
714}
715
716/*!
717 \fn void QTreeView::expand(const QModelIndex &index)
718
719 Expands the model item specified by the \a index.
720
721 \sa expanded()
722*/
723void QTreeView::expand(const QModelIndex &index)
724{
725 Q_D(QTreeView);
726 if (!d->isIndexValid(index))
727 return;
728 if (index.flags() & Qt::ItemNeverHasChildren)
729 return;
730 if (d->isIndexExpanded(index))
731 return;
732 if (d->delayedPendingLayout) {
733 //A complete relayout is going to be performed, just store the expanded index, no need to layout.
734 if (d->storeExpanded(index))
735 emit expanded(index);
736 return;
737 }
738
739 int i = d->viewIndex(index);
740 if (i != -1) { // is visible
741 d->expand(i, true);
742 if (!d->isAnimating()) {
743 updateGeometries();
744 d->viewport->update();
745 }
746 } else if (d->storeExpanded(index)) {
747 emit expanded(index);
748 }
749}
750
751/*!
752 \fn void QTreeView::collapse(const QModelIndex &index)
753
754 Collapses the model item specified by the \a index.
755
756 \sa collapsed()
757*/
758void QTreeView::collapse(const QModelIndex &index)
759{
760 Q_D(QTreeView);
761 if (!d->isIndexValid(index))
762 return;
763 if (!d->isIndexExpanded(index))
764 return;
765 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
766 d->delayedAutoScroll.stop();
767
768 if (d->delayedPendingLayout) {
769 //A complete relayout is going to be performed, just un-store the expanded index, no need to layout.
770 if (d->isPersistent(index) && d->expandedIndexes.remove(index))
771 emit collapsed(index);
772 return;
773 }
774 int i = d->viewIndex(index);
775 if (i != -1) { // is visible
776 d->collapse(i, true);
777 if (!d->isAnimating()) {
778 updateGeometries();
779 viewport()->update();
780 }
781 } else {
782 if (d->isPersistent(index) && d->expandedIndexes.remove(index))
783 emit collapsed(index);
784 }
785}
786
787/*!
788 \fn bool QTreeView::isExpanded(const QModelIndex &index) const
789
790 Returns \c true if the model item \a index is expanded; otherwise returns
791 false.
792
793 \sa expand(), expanded(), setExpanded()
794*/
795bool QTreeView::isExpanded(const QModelIndex &index) const
796{
797 Q_D(const QTreeView);
798 return d->isIndexExpanded(index);
799}
800
801/*!
802 Sets the item referred to by \a index to either collapse or expanded,
803 depending on the value of \a expanded.
804
805 \sa expanded(), expand(), isExpanded()
806*/
807void QTreeView::setExpanded(const QModelIndex &index, bool expanded)
808{
809 if (expanded)
810 this->expand(index);
811 else
812 this->collapse(index);
813}
814
815/*!
816 \property QTreeView::sortingEnabled
817 \brief whether sorting is enabled
818
819 If this property is \c true, sorting is enabled for the tree; if the property
820 is false, sorting is not enabled. The default value is false.
821
822 \note In order to avoid performance issues, it is recommended that
823 sorting is enabled \e after inserting the items into the tree.
824 Alternatively, you could also insert the items into a list before inserting
825 the items into the tree.
826
827 \sa sortByColumn()
828*/
829
830void QTreeView::setSortingEnabled(bool enable)
831{
832 Q_D(QTreeView);
833 header()->setSortIndicatorShown(enable);
834 header()->setSectionsClickable(enable);
835 if (enable) {
836 //sortByColumn has to be called before we connect or set the sortingEnabled flag
837 // because otherwise it will not call sort on the model.
838 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
839 d->sortHeaderConnection =
840 QObjectPrivate::connect(header(), &QHeaderView::sortIndicatorChanged,
841 d, &QTreeViewPrivate::sortIndicatorChanged,
842 Qt::UniqueConnection);
843 } else {
844 QObject::disconnect(d->sortHeaderConnection);
845 }
846 d->sortingEnabled = enable;
847}
848
849bool QTreeView::isSortingEnabled() const
850{
851 Q_D(const QTreeView);
852 return d->sortingEnabled;
853}
854
855/*!
856 \property QTreeView::animated
857 \brief whether animations are enabled
858
859 If this property is \c true the treeview will animate expansion
860 and collapsing of branches. If this property is \c false, the treeview
861 will expand or collapse branches immediately without showing
862 the animation.
863
864 By default, this property is \c false.
865*/
866
867void QTreeView::setAnimated(bool animate)
868{
869 Q_D(QTreeView);
870 d->animationsEnabled = animate;
871}
872
873bool QTreeView::isAnimated() const
874{
875 Q_D(const QTreeView);
876 return d->animationsEnabled;
877}
878
879/*!
880 \property QTreeView::allColumnsShowFocus
881 \brief whether items should show keyboard focus using all columns
882
883 If this property is \c true all columns will show focus, otherwise only
884 one column will show focus.
885
886 The default is false.
887*/
888
889void QTreeView::setAllColumnsShowFocus(bool enable)
890{
891 Q_D(QTreeView);
892 if (d->allColumnsShowFocus == enable)
893 return;
894 d->allColumnsShowFocus = enable;
895 d->viewport->update();
896}
897
898bool QTreeView::allColumnsShowFocus() const
899{
900 Q_D(const QTreeView);
901 return d->allColumnsShowFocus;
902}
903
904/*!
905 \property QTreeView::wordWrap
906 \brief the item text word-wrapping policy
907
908 If this property is \c true then the item text is wrapped where
909 necessary at word-breaks; otherwise it is not wrapped at all.
910 This property is \c false by default.
911
912 Note that even if wrapping is enabled, the cell will not be
913 expanded to fit all text. Ellipsis will be inserted according to
914 the current \l{QAbstractItemView::}{textElideMode}.
915*/
916void QTreeView::setWordWrap(bool on)
917{
918 Q_D(QTreeView);
919 if (d->wrapItemText == on)
920 return;
921 d->wrapItemText = on;
922 d->doDelayedItemsLayout();
923}
924
925bool QTreeView::wordWrap() const
926{
927 Q_D(const QTreeView);
928 return d->wrapItemText;
929}
930
931/*!
932 \since 5.2
933
934 This specifies that the tree structure should be placed at logical index \a index.
935 If \index is set to -1 then the tree will always follow visual index 0.
936
937 \sa treePosition(), QHeaderView::swapSections(), QHeaderView::moveSection()
938*/
939
940void QTreeView::setTreePosition(int index)
941{
942 Q_D(QTreeView);
943 d->treePosition = index;
944 d->viewport->update();
945}
946
947/*!
948 \since 5.2
949
950 Return the logical index the tree is set on. If the return value is -1 then the
951 tree is placed on the visual index 0.
952
953 \sa setTreePosition()
954*/
955
956int QTreeView::treePosition() const
957{
958 Q_D(const QTreeView);
959 return d->treePosition;
960}
961
962/*!
963 \reimp
964 */
965void QTreeView::keyboardSearch(const QString &search)
966{
967 Q_D(QTreeView);
968 if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root))
969 return;
970
971 // Do a relayout nows, so that we can utilize viewItems
972 d->executePostedLayout();
973 if (d->viewItems.isEmpty())
974 return;
975
976 QModelIndex start;
977 if (currentIndex().isValid())
978 start = currentIndex();
979 else
980 start = d->viewItems.at(0).index;
981
982 bool skipRow = false;
983 bool keyboardTimeWasValid = d->keyboardInputTime.isValid();
984 qint64 keyboardInputTimeElapsed;
985 if (keyboardTimeWasValid)
986 keyboardInputTimeElapsed = d->keyboardInputTime.restart();
987 else
988 d->keyboardInputTime.start();
989 if (search.isEmpty() || !keyboardTimeWasValid
990 || keyboardInputTimeElapsed > QApplication::keyboardInputInterval()) {
991 d->keyboardInput = search;
992 skipRow = currentIndex().isValid(); //if it is not valid we should really start at QModelIndex(0,0)
993 } else {
994 d->keyboardInput += search;
995 }
996
997 // special case for searches with same key like 'aaaaa'
998 bool sameKey = false;
999 if (d->keyboardInput.size() > 1) {
1000 int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.size() - 1));
1001 sameKey = (c == d->keyboardInput.size());
1002 if (sameKey)
1003 skipRow = true;
1004 }
1005
1006 // skip if we are searching for the same key or a new search started
1007 if (skipRow) {
1008 if (indexBelow(start).isValid()) {
1009 start = indexBelow(start);
1010 } else {
1011 const int origCol = start.column();
1012 start = d->viewItems.at(0).index;
1013 if (origCol != start.column())
1014 start = start.sibling(start.row(), origCol);
1015 }
1016 }
1017
1018 int startIndex = d->viewIndex(start);
1019 if (startIndex <= -1)
1020 return;
1021
1022 int previousLevel = -1;
1023 int bestAbove = -1;
1024 int bestBelow = -1;
1025 QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput;
1026 for (int i = 0; i < d->viewItems.size(); ++i) {
1027 if ((int)d->viewItems.at(i).level > previousLevel) {
1028 QModelIndex searchFrom = d->viewItems.at(i).index;
1029 if (start.column() > 0)
1030 searchFrom = searchFrom.sibling(searchFrom.row(), start.column());
1031 if (searchFrom.parent() == start.parent())
1032 searchFrom = start;
1033 QModelIndexList match = d->model->match(searchFrom, Qt::DisplayRole, searchString, 1,
1034 keyboardSearchFlags());
1035 if (match.size()) {
1036 int hitIndex = d->viewIndex(match.at(0));
1037 if (hitIndex >= 0 && hitIndex < startIndex)
1038 bestAbove = bestAbove == -1 ? hitIndex : qMin(hitIndex, bestAbove);
1039 else if (hitIndex >= startIndex)
1040 bestBelow = bestBelow == -1 ? hitIndex : qMin(hitIndex, bestBelow);
1041 }
1042 }
1043 previousLevel = d->viewItems.at(i).level;
1044 }
1045
1046 QModelIndex index;
1047 if (bestBelow > -1)
1048 index = d->viewItems.at(bestBelow).index;
1049 else if (bestAbove > -1)
1050 index = d->viewItems.at(bestAbove).index;
1051
1052 if (start.column() > 0)
1053 index = index.sibling(index.row(), start.column());
1054
1055 if (index.isValid())
1056 setCurrentIndex(index);
1057}
1058
1059/*!
1060 Returns the rectangle on the viewport occupied by the item at \a index.
1061 If the index is not visible or explicitly hidden, the returned rectangle is invalid.
1062*/
1063QRect QTreeView::visualRect(const QModelIndex &index) const
1064{
1065 Q_D(const QTreeView);
1066 return d->visualRect(index, QTreeViewPrivate::SingleSection);
1067}
1068
1069
1070/*!
1071 Returns the rect occupied by the decoration of the given item at \a index.
1072 If the index has no decoration, the returned rect is empty.
1073
1074 \since 6.12
1075 \sa QStyle::SE_TreeViewDisclosureItem
1076 */
1077QRect QTreeView::itemDecorationRect(const QModelIndex &index) const
1078{
1079 Q_D(const QTreeView);
1080 return d->itemDecorationRect(index);
1081}
1082
1083
1084/*!
1085 \internal
1086 \return the visual rectangle at \param index, according to \param rule.
1087 \list
1088 \li SingleSection
1089 The return value matches the section, which \a index points to.
1090 \li FullRow
1091 Return the rectangle of the entire row, no matter which section
1092 \a index points to.
1093 \li AddRowIndicatorToFirstSection
1094 Like SingleSection. If \index points to the first section, add the
1095 row indicator and its margins.
1096 \endlist
1097 */
1098QRect QTreeViewPrivate::visualRect(const QModelIndex &index, RectRule rule) const
1099{
1100 Q_Q(const QTreeView);
1101
1102 if (!isIndexValid(index))
1103 return QRect();
1104
1105 // Calculate the entire row's rectangle, even if one of the elements is hidden
1106 if (q->isIndexHidden(index) && rule != FullRow)
1107 return QRect();
1108
1109 executePostedLayout();
1110
1111 const int viewIndex = this->viewIndex(index);
1112 if (viewIndex < 0)
1113 return QRect();
1114
1115 const bool spanning = viewItems.at(viewIndex).spanning;
1116 const int column = index.column();
1117
1118 // if we have a spanning item, make the selection stretch from left to right
1119 int x = (spanning ? 0 : q->columnViewportPosition(column));
1120 int width = (spanning ? header->length() : q->columnWidth(column));
1121
1122 const bool addIndentation = isTreePosition(column) && (column > 0 || rule == SingleSection);
1123
1124 if (rule == FullRow) {
1125 x = 0;
1126 width = q->viewport()->width();
1127 } else if (addIndentation) {
1128 // calculate indentation
1129 const int indentation = indentationForItem(viewIndex);
1130 width -= indentation;
1131 if (!q->isRightToLeft())
1132 x += indentation;
1133 }
1134
1135 const int y = coordinateForItem(viewIndex);
1136 const int height = itemHeight(viewIndex);
1137
1138 return QRect(x, y, width, height);
1139}
1140
1141/*!
1142 Scroll the contents of the tree view until the given model item
1143 \a index is visible. The \a hint parameter specifies more
1144 precisely where the item should be located after the
1145 operation.
1146 If any of the parents of the model item are collapsed, they will
1147 be expanded to ensure that the model item is visible.
1148*/
1149void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint)
1150{
1151 Q_D(QTreeView);
1152
1153 if (!d->isIndexValid(index))
1154 return;
1155
1156 d->executePostedLayout();
1157 d->updateScrollBars();
1158
1159 // Expand all parents if the parent(s) of the node are not expanded.
1160 QModelIndex parent = index.parent();
1161 while (parent != d->root && parent.isValid() && state() == NoState && d->itemsExpandable) {
1162 if (!isExpanded(parent))
1163 expand(parent);
1164 parent = d->model->parent(parent);
1165 }
1166
1167 int item = d->viewIndex(index);
1168 if (item < 0)
1169 return;
1170
1171 QRect area = d->viewport->rect();
1172
1173 // vertical
1174 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
1175 int top = verticalScrollBar()->value();
1176 int bottom = top + verticalScrollBar()->pageStep();
1177 if (hint == EnsureVisible && item >= top && item < bottom) {
1178 // nothing to do
1179 } else if (hint == PositionAtTop || (hint == EnsureVisible && item < top)) {
1180 verticalScrollBar()->setValue(item);
1181 } else { // PositionAtBottom or PositionAtCenter
1182 const int currentItemHeight = d->itemHeight(item);
1183 int y = (hint == PositionAtCenter
1184 //we center on the current item with a preference to the top item (ie. -1)
1185 ? area.height() / 2 + currentItemHeight - 1
1186 //otherwise we simply take the whole space
1187 : area.height());
1188 if (y > currentItemHeight) {
1189 while (item >= 0) {
1190 y -= d->itemHeight(item);
1191 if (y < 0) { //there is no more space left
1192 item++;
1193 break;
1194 }
1195 item--;
1196 }
1197 }
1198 verticalScrollBar()->setValue(item);
1199 }
1200 } else { // ScrollPerPixel
1201 QRect rect(columnViewportPosition(index.column()),
1202 d->coordinateForItem(item), // ### slow for items outside the view
1203 columnWidth(index.column()),
1204 d->itemHeight(item));
1205
1206 if (rect.isEmpty()) {
1207 // nothing to do
1208 } else if (hint == EnsureVisible && area.contains(rect)) {
1209 d->viewport->update(rect);
1210 // nothing to do
1211 } else {
1212 bool above = (hint == EnsureVisible
1213 && (rect.top() < area.top()
1214 || area.height() < rect.height()));
1215 bool below = (hint == EnsureVisible
1216 && rect.bottom() > area.bottom()
1217 && rect.height() < area.height());
1218
1219 int verticalValue = verticalScrollBar()->value();
1220 if (hint == PositionAtTop || above)
1221 verticalValue += rect.top();
1222 else if (hint == PositionAtBottom || below)
1223 verticalValue += rect.bottom() - area.height();
1224 else if (hint == PositionAtCenter)
1225 verticalValue += rect.top() - ((area.height() - rect.height()) / 2);
1226 verticalScrollBar()->setValue(verticalValue);
1227 }
1228 }
1229 // horizontal
1230 int viewportWidth = d->viewport->width();
1231 int horizontalOffset = d->header->offset();
1232 int horizontalPosition = d->header->sectionPosition(index.column());
1233 int cellWidth = d->header->sectionSize(index.column());
1234
1235 if (hint == PositionAtCenter) {
1236 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
1237 } else {
1238 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
1239 horizontalScrollBar()->setValue(horizontalPosition);
1240 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
1241 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
1242 }
1243}
1244
1245/*!
1246 \reimp
1247*/
1248void QTreeView::changeEvent(QEvent *event)
1249{
1250 Q_D(QTreeView);
1251 if (event->type() == QEvent::StyleChange) {
1252 if (!d->customIndent) {
1253 // QAbstractItemView calls this method in case of a style change,
1254 // so update the indentation here if it wasn't set manually.
1255 d->updateIndentationFromStyle();
1256 }
1257 }
1258 QAbstractItemView::changeEvent(event);
1259}
1260
1261/*!
1262 \reimp
1263*/
1264void QTreeView::timerEvent(QTimerEvent *event)
1265{
1266 Q_D(QTreeView);
1267 if (event->id() == d->columnResizeTimer.id()) {
1268 updateGeometries();
1269 d->columnResizeTimer.stop();
1270 QRect rect;
1271 int viewportHeight = d->viewport->height();
1272 int viewportWidth = d->viewport->width();
1273 for (int i = d->columnsToUpdate.size() - 1; i >= 0; --i) {
1274 int column = d->columnsToUpdate.at(i);
1275 int x = columnViewportPosition(column);
1276 if (isRightToLeft())
1277 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
1278 else
1279 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
1280 }
1281 d->viewport->update(rect.normalized());
1282 d->columnsToUpdate.clear();
1283 } else if (event->timerId() == d->openTimer.timerId()) {
1284 QPoint pos = d->viewport->mapFromGlobal(QCursor::pos());
1285 if (state() == QAbstractItemView::DraggingState
1286 && d->viewport->rect().contains(pos)) {
1287 QModelIndex index = indexAt(pos);
1288 expand(index);
1289 }
1290 d->openTimer.stop();
1291 }
1292
1293 QAbstractItemView::timerEvent(event);
1294}
1295
1296/*!
1297 \reimp
1298*/
1299#if QT_CONFIG(draganddrop)
1300void QTreeView::dragMoveEvent(QDragMoveEvent *event)
1301{
1302 Q_D(QTreeView);
1303 if (d->autoExpandDelay >= 0)
1304 d->openTimer.start(d->autoExpandDelay, this);
1305 QAbstractItemView::dragMoveEvent(event);
1306}
1307#endif
1308
1309/*!
1310 \reimp
1311*/
1312bool QTreeView::viewportEvent(QEvent *event)
1313{
1314 Q_D(QTreeView);
1315 switch (event->type()) {
1316 case QEvent::HoverEnter:
1317 case QEvent::HoverLeave:
1318 case QEvent::HoverMove: {
1319 QHoverEvent *he = static_cast<QHoverEvent*>(event);
1320 const int oldBranch = d->hoverBranch;
1321 d->hoverBranch = d->itemDecorationAt(he->position().toPoint());
1322 QModelIndex newIndex = indexAt(he->position().toPoint());
1323 if (d->hover != newIndex || d->hoverBranch != oldBranch) {
1324 // Update the whole hovered over row. No need to update the old hovered
1325 // row, that is taken care in superclass hover handling.
1326 viewport()->update(d->visualRect(newIndex, QTreeViewPrivate::FullRow));
1327 }
1328 break; }
1329 default:
1330 break;
1331 }
1332 return QAbstractItemView::viewportEvent(event);
1333}
1334
1335/*!
1336 \reimp
1337*/
1338void QTreeView::paintEvent(QPaintEvent *event)
1339{
1340 Q_D(QTreeView);
1341 d->executePostedLayout();
1342 QPainter painter(viewport());
1343#if QT_CONFIG(animation)
1344 if (d->isAnimating()) {
1345 drawTree(&painter, event->region() - d->animatedOperation.rect());
1346 d->drawAnimatedOperation(&painter);
1347 } else
1348#endif // animation
1349 {
1350 drawTree(&painter, event->region());
1351#if QT_CONFIG(draganddrop)
1352 d->paintDropIndicator(&painter);
1353#endif
1354 }
1355}
1356
1357int QTreeViewPrivate::logicalIndexForTree() const
1358{
1359 int index = treePosition;
1360 if (index < 0)
1361 index = header->logicalIndex(0);
1362 return index;
1363}
1364
1365void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItem *option, int y, int bottom) const
1366{
1367 Q_Q(const QTreeView);
1368 if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q))
1369 return;
1370 int rowHeight = defaultItemHeight;
1371 if (rowHeight <= 0) {
1372 rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height();
1373 if (rowHeight <= 0)
1374 return;
1375 }
1376 while (y <= bottom) {
1377 option->rect.setRect(0, y, viewport->width(), rowHeight);
1378 option->features.setFlag(QStyleOptionViewItem::Alternate, current & 1);
1379 ++current;
1380 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q);
1381 y += rowHeight;
1382 }
1383}
1384
1385bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos)
1386{
1387 Q_Q(QTreeView);
1388 // we want to handle mousePress in EditingState (persistent editors)
1389 if ((state != QAbstractItemView::NoState
1390 && state != QAbstractItemView::EditingState)
1391 || !viewport->rect().contains(pos))
1392 return true;
1393
1394 int i = itemDecorationAt(pos);
1395 if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) {
1396 if (viewItems.at(i).expanded)
1397 collapse(i, true);
1398 else
1399 expand(i, true);
1400 if (!isAnimating()) {
1401 q->updateGeometries();
1402 viewport->update();
1403 }
1404 return true;
1405 }
1406 return false;
1407}
1408
1409void QTreeViewPrivate::modelDestroyed()
1410{
1411 //we need to clear the viewItems because it contains QModelIndexes to
1412 //the model currently being destroyed
1413 viewItems.clear();
1414 QAbstractItemViewPrivate::modelDestroyed();
1415}
1416
1417QRect QTreeViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const
1418{
1419 const auto parentIdx = topLeft.parent();
1420 executePostedLayout();
1421 QRect updateRect;
1422 int left = std::numeric_limits<int>::max();
1423 int right = std::numeric_limits<int>::min();
1424 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
1425 const auto idxCol0 = model->index(row, 0, parentIdx);
1426 if (isRowHidden(idxCol0))
1427 continue;
1428 QRect rowRect;
1429 if (left != std::numeric_limits<int>::max()) {
1430 // we already know left and right boundary of the rect to update
1431 rowRect = visualRect(idxCol0, FullRow);
1432 if (!rowRect.intersects(rect))
1433 continue;
1434 rowRect = QRect(left, rowRect.top(), right, rowRect.bottom());
1435 } else if (!spanningIndexes.isEmpty() && spanningIndexes.contains(idxCol0)) {
1436 // isFirstColumnSpanned re-creates the child index so take a shortcut here
1437 // spans the whole row, therefore ask for FullRow instead for every cell
1438 rowRect = visualRect(idxCol0, FullRow);
1439 if (!rowRect.intersects(rect))
1440 continue;
1441 } else {
1442 for (int col = topLeft.column(); col <= bottomRight.column(); ++col) {
1443 if (header->isSectionHidden(col))
1444 continue;
1445 const QModelIndex idx(model->index(row, col, parentIdx));
1446 const QRect idxRect = visualRect(idx, SingleSection);
1447 if (idxRect.isNull())
1448 continue;
1449 // early exit when complete row is out of viewport
1450 if (idxRect.top() > rect.bottom() || idxRect.bottom() < rect.top())
1451 break;
1452 if (!idxRect.intersects(rect))
1453 continue;
1454 rowRect = rowRect.united(idxRect);
1455 if (rowRect.left() < rect.left() && rowRect.right() > rect.right())
1456 break;
1457 }
1458 if (rowRect.isValid()) {
1459 left = std::min(left, rowRect.left());
1460 right = std::max(right, rowRect.right());
1461 }
1462 }
1463 updateRect = updateRect.united(rowRect);
1464 if (updateRect.contains(rect)) // already full rect covered?
1465 break;
1466 }
1467 return rect.intersected(updateRect);
1468}
1469
1470/*!
1471 \class QTreeViewPrivate
1472 \inmodule QtWidgets
1473 \internal
1474*/
1475
1476/*!
1477 \reimp
1478
1479 We have a QTreeView way of knowing what elements are on the viewport
1480*/
1481QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
1482{
1483 Q_ASSERT(r);
1484 Q_Q(const QTreeView);
1485 if (spanningIndexes.isEmpty())
1486 return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r);
1487 QModelIndexList list;
1488 for (const QModelIndex &idx : indexes) {
1489 if (idx.column() > 0 && q->isFirstColumnSpanned(idx.row(), idx.parent()))
1490 continue;
1491 list << idx;
1492 }
1493 return QAbstractItemViewPrivate::draggablePaintPairs(list, r);
1494}
1495
1496void QTreeViewPrivate::adjustViewOptionsForIndex(QStyleOptionViewItem *option, const QModelIndex &current) const
1497{
1498 const int row = viewIndex(current); // get the index in viewItems[]
1499 option->state = option->state | (viewItems.at(row).expanded ? QStyle::State_Open : QStyle::State_None)
1500 | (viewItems.at(row).hasChildren ? QStyle::State_Children : QStyle::State_None)
1501 | (viewItems.at(row).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1502
1503 option->showDecorationSelected = (selectionBehavior & QTreeView::SelectRows)
1504 || option->showDecorationSelected;
1505
1506 QList<int>
1507 logicalIndices; // index = visual index of visible columns only. data = logical index.
1508 QList<QStyleOptionViewItem::ViewItemPosition>
1509 viewItemPosList; // vector of left/middle/end for each logicalIndex, visible columns
1510 // only.
1511 const bool spanning = viewItems.at(row).spanning;
1512 const int left = (spanning ? header->visualIndex(0) : 0);
1513 const int right = (spanning ? header->visualIndex(0) : header->count() - 1 );
1514 calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
1515
1516 const int visualIndex = logicalIndices.indexOf(current.column());
1517 option->viewItemPosition = viewItemPosList.at(visualIndex);
1518}
1519
1520
1521/*!
1522 Draws the part of the tree intersecting the given \a region using the specified
1523 \a painter.
1524
1525 \sa paintEvent()
1526*/
1527void QTreeView::drawTree(QPainter *painter, const QRegion &region) const
1528{
1529 Q_D(const QTreeView);
1530 // d->viewItems changes when posted layouts are executed in itemDecorationAt, so don't copy
1531 const QList<QTreeViewItem> &viewItems = d->viewItems;
1532
1533 QStyleOptionViewItem option;
1534 initViewItemOption(&option);
1535 const QStyle::State state = option.state;
1536 d->current = 0;
1537
1538 if (viewItems.size() == 0 || d->header->count() == 0 || !d->itemDelegate) {
1539 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1540 return;
1541 }
1542
1543 int firstVisibleItemOffset = 0;
1544 const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset);
1545 if (firstVisibleItem < 0) {
1546 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1547 return;
1548 }
1549
1550 const int viewportWidth = d->viewport->width();
1551
1552 QPoint hoverPos = d->viewport->mapFromGlobal(QCursor::pos());
1553 d->hoverBranch = d->itemDecorationAt(hoverPos);
1554
1555 QList<int> drawn;
1556 bool multipleRects = (region.rectCount() > 1);
1557 for (const QRect &a : region) {
1558 const QRect area = (multipleRects
1559 ? QRect(0, a.y(), viewportWidth, a.height())
1560 : a);
1561 d->leftAndRight = d->startAndEndColumns(area);
1562
1563 int i = firstVisibleItem; // the first item at the top of the viewport
1564 int y = firstVisibleItemOffset; // we may only see part of the first item
1565
1566 // start at the top of the viewport and iterate down to the update area
1567 for (; i < viewItems.size(); ++i) {
1568 const int itemHeight = d->itemHeight(i);
1569 if (y + itemHeight > area.top())
1570 break;
1571 y += itemHeight;
1572 }
1573
1574 // paint the visible rows
1575 for (; i < viewItems.size() && y <= area.bottom(); ++i) {
1576 const QModelIndex &index = viewItems.at(i).index;
1577 const int itemHeight = d->itemHeight(i);
1578 option.rect = d->visualRect(index, QTreeViewPrivate::FullRow);
1579 option.state = state | (viewItems.at(i).expanded ? QStyle::State_Open : QStyle::State_None)
1580 | (viewItems.at(i).hasChildren ? QStyle::State_Children : QStyle::State_None)
1581 | (viewItems.at(i).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1582 d->current = i;
1583 d->spanning = viewItems.at(i).spanning;
1584 if (!multipleRects || !drawn.contains(i)) {
1585 drawRow(painter, option, viewItems.at(i).index);
1586 if (multipleRects) // even if the rect only intersects the item,
1587 drawn.append(i); // the entire item will be painted
1588 }
1589 y += itemHeight;
1590 }
1591
1592 if (y <= area.bottom()) {
1593 d->current = i;
1594 d->paintAlternatingRowColors(painter, &option, y, area.bottom());
1595 }
1596 }
1597}
1598
1599/// ### move to QObject :)
1600static inline bool ancestorOf(QObject *widget, QObject *other)
1601{
1602 for (QObject *parent = other; parent != nullptr; parent = parent->parent()) {
1603 if (parent == widget)
1604 return true;
1605 }
1606 return false;
1607}
1608
1609void QTreeViewPrivate::calcLogicalIndices(
1610 QList<int> *logicalIndices, QList<QStyleOptionViewItem::ViewItemPosition> *itemPositions,
1611 int left, int right) const
1612{
1613 const int columnCount = header->count();
1614 /* 'left' and 'right' are the left-most and right-most visible visual indices.
1615 Compute the first visible logical indices before and after the left and right.
1616 We will use these values to determine the QStyleOptionViewItem::viewItemPosition. */
1617 int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1;
1618 for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) {
1619 int logicalIndex = header->logicalIndex(visualIndex);
1620 if (!header->isSectionHidden(logicalIndex)) {
1621 logicalIndexBeforeLeft = logicalIndex;
1622 break;
1623 }
1624 }
1625
1626 for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) {
1627 int logicalIndex = header->logicalIndex(visualIndex);
1628 if (!header->isSectionHidden(logicalIndex)) {
1629 if (visualIndex > right) {
1630 logicalIndexAfterRight = logicalIndex;
1631 break;
1632 }
1633 logicalIndices->append(logicalIndex);
1634 }
1635 }
1636
1637 const auto indicesCount = logicalIndices->size();
1638 itemPositions->resize(indicesCount);
1639 for (qsizetype currentLogicalSection = 0; currentLogicalSection < indicesCount; ++currentLogicalSection) {
1640 // determine the viewItemPosition depending on the position of column 0
1641 int nextLogicalSection = currentLogicalSection + 1 >= indicesCount
1642 ? logicalIndexAfterRight
1643 : logicalIndices->at(currentLogicalSection + 1);
1644 int prevLogicalSection = currentLogicalSection - 1 < 0
1645 ? logicalIndexBeforeLeft
1646 : logicalIndices->at(currentLogicalSection - 1);
1647 const int headerSection = logicalIndices->at(currentLogicalSection);
1648 QStyleOptionViewItem::ViewItemPosition pos;
1649 if ((nextLogicalSection == -1 && prevLogicalSection == -1) || spanning) {
1650 pos = QStyleOptionViewItem::OnlyOne;
1651 } else if ((nextLogicalSection != 0 && prevLogicalSection == -1) || isTreePosition(headerSection))
1652 pos = QStyleOptionViewItem::Beginning;
1653 else if (nextLogicalSection == 0 || nextLogicalSection == -1)
1654 pos = QStyleOptionViewItem::End;
1655 else
1656 pos = QStyleOptionViewItem::Middle;
1657 (*itemPositions)[currentLogicalSection] = pos;
1658 }
1659}
1660
1661/*!
1662 \internal
1663 Get sizeHint width for single index (providing existing hint and style option) and index in viewIndex i.
1664*/
1665int QTreeViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option, int i) const
1666{
1667 Q_Q(const QTreeView);
1668 QWidget *editor = editorForIndex(index).widget.data();
1669 if (editor && persistent.contains(editor)) {
1670 hint = qMax(hint, editor->sizeHint().width());
1671 int min = editor->minimumSize().width();
1672 int max = editor->maximumSize().width();
1673 hint = qBound(min, hint, max);
1674 }
1675 int xhint = q->itemDelegateForIndex(index)->sizeHint(option, index).width();
1676 hint = qMax(hint, xhint + (isTreePosition(index.column()) ? indentationForItem(i) : 0));
1677 return hint;
1678}
1679
1680/*!
1681 Draws the row in the tree view that contains the model item \a index,
1682 using the \a painter given. The \a option controls how the item is
1683 displayed.
1684
1685 \sa setAlternatingRowColors()
1686*/
1687void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
1688 const QModelIndex &index) const
1689{
1690 Q_D(const QTreeView);
1691 QStyleOptionViewItem opt = option;
1692 const QPoint offset = d->scrollDelayOffset;
1693 const int y = option.rect.y() + offset.y();
1694 const QModelIndex parent = index.parent();
1695 const QHeaderView *header = d->header;
1696 const QModelIndex current = currentIndex();
1697 const QModelIndex hover = d->hover;
1698 const bool reverse = isRightToLeft();
1699 const QStyle::State state = opt.state;
1700 const bool spanning = d->spanning;
1701 const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first);
1702 const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second);
1703 const bool alternate = d->alternatingColors;
1704 const bool enabled = (state & QStyle::State_Enabled) != 0;
1705 const bool allColumnsShowFocus = d->allColumnsShowFocus;
1706
1707
1708 // when the row contains an index widget which has focus,
1709 // we want to paint the entire row as active
1710 bool indexWidgetHasFocus = false;
1711 if ((current.row() == index.row()) && !d->editorIndexHash.isEmpty()) {
1712 const int r = index.row();
1713 QWidget *fw = QApplication::focusWidget();
1714 for (int c = 0; c < header->count(); ++c) {
1715 QModelIndex idx = d->model->index(r, c, parent);
1716 if (QWidget *editor = indexWidget(idx)) {
1717 if (ancestorOf(editor, fw)) {
1718 indexWidgetHasFocus = true;
1719 break;
1720 }
1721 }
1722 }
1723 }
1724
1725 const bool widgetHasFocus = hasFocus();
1726 bool currentRowHasFocus = false;
1727 if (allColumnsShowFocus && widgetHasFocus && current.isValid()) {
1728 // check if the focus index is before or after the visible columns
1729 const int r = index.row();
1730 for (int c = 0; c < left && !currentRowHasFocus; ++c) {
1731 QModelIndex idx = d->model->index(r, c, parent);
1732 currentRowHasFocus = (idx == current);
1733 }
1734 QModelIndex parent = d->model->parent(index);
1735 for (int c = right; c < header->count() && !currentRowHasFocus; ++c) {
1736 currentRowHasFocus = (d->model->index(r, c, parent) == current);
1737 }
1738 }
1739
1740 // ### special case: if we select entire rows, then we need to draw the
1741 // selection in the first column all the way to the second column, rather
1742 // than just around the item text. We abuse showDecorationSelected to
1743 // indicate this to the style. Below we will reset this value temporarily
1744 // to only respect the styleHint while we are rendering the decoration.
1745 opt.showDecorationSelected = (d->selectionBehavior & SelectRows)
1746 || option.showDecorationSelected;
1747
1748 int width, height = option.rect.height();
1749 int position;
1750 QModelIndex modelIndex;
1751 const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1752 && index.parent() == hover.parent()
1753 && index.row() == hover.row();
1754
1755 QList<int> logicalIndices;
1756 QList<QStyleOptionViewItem::ViewItemPosition>
1757 viewItemPosList; // vector of left/middle/end for each logicalIndex
1758 d->calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
1759
1760 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.size(); ++currentLogicalSection) {
1761 int headerSection = logicalIndices.at(currentLogicalSection);
1762 position = columnViewportPosition(headerSection) + offset.x();
1763 width = header->sectionSize(headerSection);
1764
1765 if (spanning) {
1766 int lastSection = header->logicalIndex(header->count() - 1);
1767 if (!reverse) {
1768 width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position;
1769 } else {
1770 width += position - columnViewportPosition(lastSection);
1771 position = columnViewportPosition(lastSection);
1772 }
1773 }
1774
1775 modelIndex = d->model->index(index.row(), headerSection, parent);
1776 if (!modelIndex.isValid())
1777 continue;
1778 opt.state = state;
1779
1780 opt.viewItemPosition = viewItemPosList.at(currentLogicalSection);
1781
1782 // fake activeness when row editor has focus
1783 if (indexWidgetHasFocus)
1784 opt.state |= QStyle::State_Active;
1785
1786 if (d->selectionModel->isSelected(modelIndex))
1787 opt.state |= QStyle::State_Selected;
1788 if (widgetHasFocus && (current == modelIndex)) {
1789 if (allColumnsShowFocus)
1790 currentRowHasFocus = true;
1791 else
1792 opt.state |= QStyle::State_HasFocus;
1793 }
1794 opt.state.setFlag(QStyle::State_MouseOver,
1795 (hoverRow || modelIndex == hover)
1796 && (option.showDecorationSelected || d->hoverBranch == -1));
1797
1798 if (enabled) {
1799 QPalette::ColorGroup cg;
1800 if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) {
1801 opt.state &= ~QStyle::State_Enabled;
1802 cg = QPalette::Disabled;
1803 } else if (opt.state & QStyle::State_Active) {
1804 cg = QPalette::Active;
1805 } else {
1806 cg = QPalette::Inactive;
1807 }
1808 opt.palette.setCurrentColorGroup(cg);
1809 }
1810
1811 if (alternate) {
1812 opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
1813 }
1814 opt.features &= ~QStyleOptionViewItem::IsDecoratedRootColumn;
1815
1816 /* Prior to Qt 4.3, the background of the branch (in selected state and
1817 alternate row color was provided by the view. For backward compatibility,
1818 this is now delegated to the style using PE_PanelItemViewRow which
1819 does the appropriate fill */
1820 if (d->isTreePosition(headerSection)) {
1821 const int i = d->indentationForItem(d->current);
1822 QRect branches(reverse ? position + width - i : position, y, i, height);
1823 const bool setClipRect = branches.width() > width;
1824 if (setClipRect) {
1825 painter->save();
1826 painter->setClipRect(QRect(position, y, width, height));
1827 }
1828 // draw background for the branch (selection + alternate row)
1829
1830 // We use showDecorationSelected both to store the style hint, and to indicate
1831 // that the entire row has to be selected (see overrides of the value if
1832 // selectionBehavior == SelectRow).
1833 // While we are only painting the background we don't care for the
1834 // selectionBehavior factor, so respect only the style value, and reset later.
1835 const bool oldShowDecorationSelected = opt.showDecorationSelected;
1836 opt.showDecorationSelected = style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected,
1837 &opt, this);
1838 opt.rect = branches;
1839 if (opt.rect.width() > 0) {
1840 // the root item also has a branch decoration
1841 opt.features |= QStyleOptionViewItem::IsDecoratedRootColumn;
1842 // we now want to draw the branch decoration
1843 opt.features |= QStyleOptionViewItem::IsDecorationForRootColumn;
1844 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1845 opt.features &= ~QStyleOptionViewItem::IsDecorationForRootColumn;
1846 }
1847
1848 // draw background of the item (only alternate row). rest of the background
1849 // is provided by the delegate
1850 QStyle::State oldState = opt.state;
1851 opt.state &= ~QStyle::State_Selected;
1852 opt.rect.setRect(reverse ? position : i + position, y, width - i, height);
1853 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1854 opt.state = oldState;
1855 opt.showDecorationSelected = oldShowDecorationSelected;
1856
1857 if (d->indent != 0)
1858 drawBranches(painter, branches, index);
1859 if (setClipRect)
1860 painter->restore();
1861 } else {
1862 QStyle::State oldState = opt.state;
1863 opt.state &= ~QStyle::State_Selected;
1864 opt.rect.setRect(position, y, width, height);
1865 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1866 opt.state = oldState;
1867 }
1868
1869 itemDelegateForIndex(modelIndex)->paint(painter, opt, modelIndex);
1870 }
1871
1872 if (currentRowHasFocus) {
1873 QStyleOptionFocusRect o;
1874 o.QStyleOption::operator=(option);
1875 o.state |= QStyle::State_KeyboardFocusChange;
1876 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
1877 ? QPalette::Normal : QPalette::Disabled;
1878 o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index)
1879 ? QPalette::Highlight : QPalette::Window);
1880 int x = 0;
1881 if (!option.showDecorationSelected)
1882 x = header->sectionPosition(0) + d->indentationForItem(d->current);
1883 QRect focusRect(x - header->offset(), y, header->length() - x, height);
1884 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect);
1885 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1886 // if we show focus on all columns and the first section is moved,
1887 // we have to split the focus rect into two rects
1888 if (allColumnsShowFocus && !option.showDecorationSelected
1889 && header->sectionsMoved() && (header->visualIndex(0) != 0)) {
1890 QRect sectionRect(0, y, header->sectionPosition(0), height);
1891 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect);
1892 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1893 }
1894 }
1895}
1896
1897/*!
1898 Draws the branches in the tree view on the same row as the model item
1899 \a index, using the \a painter given. The branches are drawn in the
1900 rectangle specified by \a rect.
1901*/
1902void QTreeView::drawBranches(QPainter *painter, const QRect &rect,
1903 const QModelIndex &index) const
1904{
1905 Q_D(const QTreeView);
1906 const bool reverse = isRightToLeft();
1907 const int indent = d->indent;
1908 const int outer = d->rootDecoration ? 0 : 1;
1909 const int item = d->current;
1910 const QTreeViewItem &viewItem = d->viewItems.at(item);
1911 int level = viewItem.level;
1912 QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height());
1913
1914 QModelIndex parent = index.parent();
1915 QModelIndex current = parent;
1916 QModelIndex ancestor = current.parent();
1917
1918 QStyleOptionViewItem opt;
1919 initViewItemOption(&opt);
1920 QStyle::State extraFlags = QStyle::State_None;
1921 if (isEnabled())
1922 extraFlags |= QStyle::State_Enabled;
1923 if (hasFocus())
1924 extraFlags |= QStyle::State_Active;
1925 QPainterStateGuard psg(painter, QPainterStateGuard::InitialState::NoSave);
1926 if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel) {
1927 psg.save();
1928 painter->setBrushOrigin(QPoint(0, verticalOffset()));
1929 }
1930
1931 if (d->alternatingColors) {
1932 opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
1933 }
1934
1935 // When hovering over a row, pass State_Hover for painting the branch
1936 // indicators if it has the decoration (aka branch) selected.
1937 bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1938 && opt.showDecorationSelected
1939 && index.parent() == d->hover.parent()
1940 && index.row() == d->hover.row();
1941
1942 if (d->selectionModel->isSelected(index))
1943 extraFlags |= QStyle::State_Selected;
1944
1945 if (level >= outer) {
1946 // start with the innermost branch
1947 primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent);
1948 opt.rect = primitive;
1949
1950 const bool expanded = viewItem.expanded;
1951 const bool children = viewItem.hasChildren;
1952 bool moreSiblings = viewItem.hasMoreSiblings;
1953
1954 opt.state = QStyle::State_Item | extraFlags
1955 | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None)
1956 | (children ? QStyle::State_Children : QStyle::State_None)
1957 | (expanded ? QStyle::State_Open : QStyle::State_None);
1958 opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
1959
1960 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1961 }
1962 // then go out level by level
1963 for (--level; level >= outer; --level) { // we have already drawn the innermost branch
1964 primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent);
1965 opt.rect = primitive;
1966 opt.state = extraFlags;
1967 bool moreSiblings = false;
1968 if (d->hiddenIndexes.isEmpty()) {
1969 moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row());
1970 } else {
1971 int successor = item + viewItem.total + 1;
1972 while (successor < d->viewItems.size()
1973 && d->viewItems.at(successor).level >= uint(level)) {
1974 const QTreeViewItem &successorItem = d->viewItems.at(successor);
1975 if (successorItem.level == uint(level)) {
1976 moreSiblings = true;
1977 break;
1978 }
1979 successor += successorItem.total + 1;
1980 }
1981 }
1982 if (moreSiblings)
1983 opt.state |= QStyle::State_Sibling;
1984 opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
1985
1986 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1987 current = ancestor;
1988 ancestor = current.parent();
1989 }
1990}
1991
1992/*!
1993 \reimp
1994*/
1995void QTreeView::mousePressEvent(QMouseEvent *event)
1996{
1997 Q_D(QTreeView);
1998 bool handled = false;
1999 if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, nullptr, this) == QEvent::MouseButtonPress)
2000 handled = d->expandOrCollapseItemAtPos(event->position().toPoint());
2001 if (!handled && d->itemDecorationAt(event->position().toPoint()) == -1)
2002 QAbstractItemView::mousePressEvent(event);
2003 else
2004 d->pressedIndex = QModelIndex();
2005}
2006
2007/*!
2008 \reimp
2009*/
2010void QTreeView::mouseReleaseEvent(QMouseEvent *event)
2011{
2012 Q_D(QTreeView);
2013 if (d->itemDecorationAt(event->position().toPoint()) == -1) {
2014 QAbstractItemView::mouseReleaseEvent(event);
2015 } else {
2016 if (state() == QAbstractItemView::DragSelectingState || state() == QAbstractItemView::DraggingState)
2017 setState(QAbstractItemView::NoState);
2018 if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, nullptr, this) == QEvent::MouseButtonRelease)
2019 d->expandOrCollapseItemAtPos(event->position().toPoint());
2020 }
2021}
2022
2023/*!
2024 \reimp
2025*/
2026void QTreeView::mouseDoubleClickEvent(QMouseEvent *event)
2027{
2028 Q_D(QTreeView);
2029 if (state() != NoState || !d->viewport->rect().contains(event->position().toPoint()))
2030 return;
2031
2032 int i = d->itemDecorationAt(event->position().toPoint());
2033 if (i == -1) {
2034 i = d->itemAtCoordinate(event->position().toPoint().y());
2035 if (i == -1)
2036 return; // user clicked outside the items
2037
2038 const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index;
2039 const QPersistentModelIndex persistent = indexAt(event->position().toPoint());
2040
2041 if (d->pressedIndex != persistent) {
2042 mousePressEvent(event);
2043 return;
2044 }
2045
2046 // signal handlers may change the model
2047 emit doubleClicked(persistent);
2048
2049 if (!persistent.isValid())
2050 return;
2051
2052 if (edit(persistent, DoubleClicked, event) || state() != NoState)
2053 return; // the double click triggered editing
2054
2055 if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this))
2056 emit activated(persistent);
2057
2058 d->releaseFromDoubleClick = true;
2059 d->executePostedLayout(); // we need to make sure viewItems is updated
2060 if (d->itemsExpandable
2061 && d->expandsOnDoubleClick
2062 && d->hasVisibleChildren(persistent)) {
2063 if (!((i < d->viewItems.size()) && (d->viewItems.at(i).index == firstColumnIndex))) {
2064 // find the new index of the item
2065 for (i = 0; i < d->viewItems.size(); ++i) {
2066 if (d->viewItems.at(i).index == firstColumnIndex)
2067 break;
2068 }
2069 if (i == d->viewItems.size())
2070 return;
2071 }
2072 if (d->viewItems.at(i).expanded)
2073 d->collapse(i, true);
2074 else
2075 d->expand(i, true);
2076 updateGeometries();
2077 viewport()->update();
2078 }
2079 }
2080}
2081
2082/*!
2083 \reimp
2084*/
2085void QTreeView::mouseMoveEvent(QMouseEvent *event)
2086{
2087 Q_D(QTreeView);
2088 if (d->itemDecorationAt(event->position().toPoint()) == -1) // ### what about expanding/collapsing state ?
2089 QAbstractItemView::mouseMoveEvent(event);
2090}
2091
2092/*!
2093 \reimp
2094*/
2095void QTreeView::keyPressEvent(QKeyEvent *event)
2096{
2097 Q_D(QTreeView);
2098 QModelIndex current = currentIndex();
2099 //this is the management of the expansion
2100 if (d->isIndexValid(current) && d->model && d->itemsExpandable) {
2101 switch (event->key()) {
2102 case Qt::Key_Asterisk: {
2103 expandRecursively(current);
2104 break; }
2105 case Qt::Key_Plus:
2106 expand(current);
2107 break;
2108 case Qt::Key_Minus:
2109 collapse(current);
2110 break;
2111 }
2112 }
2113
2114 QAbstractItemView::keyPressEvent(event);
2115}
2116
2117/*!
2118 \reimp
2119*/
2120QModelIndex QTreeView::indexAt(const QPoint &point) const
2121{
2122 Q_D(const QTreeView);
2123 d->executePostedLayout();
2124
2125 int visualIndex = d->itemAtCoordinate(point.y());
2126 QModelIndex idx = d->modelIndex(visualIndex);
2127 if (!idx.isValid())
2128 return QModelIndex();
2129
2130 if (d->viewItems.at(visualIndex).spanning)
2131 return idx;
2132
2133 int column = d->columnAt(point.x());
2134 if (column == idx.column())
2135 return idx;
2136 if (column < 0)
2137 return QModelIndex();
2138 return idx.sibling(idx.row(), column);
2139}
2140
2141/*!
2142 Returns the model index of the item above \a index.
2143*/
2144QModelIndex QTreeView::indexAbove(const QModelIndex &index) const
2145{
2146 Q_D(const QTreeView);
2147 if (!d->isIndexValid(index))
2148 return QModelIndex();
2149 d->executePostedLayout();
2150 int i = d->viewIndex(index);
2151 if (--i < 0)
2152 return QModelIndex();
2153 const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
2154 return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
2155}
2156
2157/*!
2158 Returns the model index of the item below \a index.
2159*/
2160QModelIndex QTreeView::indexBelow(const QModelIndex &index) const
2161{
2162 Q_D(const QTreeView);
2163 if (!d->isIndexValid(index))
2164 return QModelIndex();
2165 d->executePostedLayout();
2166 int i = d->viewIndex(index);
2167 if (++i >= d->viewItems.size())
2168 return QModelIndex();
2169 const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
2170 return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
2171}
2172
2173/*!
2174 \internal
2175
2176 Lays out the items in the tree view.
2177*/
2178void QTreeView::doItemsLayout()
2179{
2180 Q_D(QTreeView);
2181 if (d->hasRemovedItems) {
2182 //clean the QSet that may contains old (and this invalid) indexes
2183 d->hasRemovedItems = false;
2184 QSet<QPersistentModelIndex>::iterator it = d->expandedIndexes.begin();
2185 while (it != d->expandedIndexes.end()) {
2186 if (!it->isValid())
2187 it = d->expandedIndexes.erase(it);
2188 else
2189 ++it;
2190 }
2191 it = d->hiddenIndexes.begin();
2192 while (it != d->hiddenIndexes.end()) {
2193 if (!it->isValid())
2194 it = d->hiddenIndexes.erase(it);
2195 else
2196 ++it;
2197 }
2198 }
2199 d->viewItems.clear(); // prepare for new layout
2200 QModelIndex parent = d->root;
2201 if (d->model->hasChildren(parent)) {
2202 d->layout(-1);
2203 }
2204 QAbstractItemView::doItemsLayout();
2205 d->header->doItemsLayout();
2206 // reset the accessibility representation of the view once control has
2207 // returned to the event loop. This avoids that we destroy UI tree elements
2208 // in the platform layer as part of a model-reset notification, while those
2209 // elements respond to a query (i.e. of rect, which results in a call to
2210 // doItemsLayout().
2211 QMetaObject::invokeMethod(this, [d]{ d->updateAccessibility(); }, Qt::QueuedConnection);
2212}
2213
2214/*!
2215 \reimp
2216*/
2217void QTreeView::reset()
2218{
2219 Q_D(QTreeView);
2220 d->expandedIndexes.clear();
2221 d->hiddenIndexes.clear();
2222 d->spanningIndexes.clear();
2223 d->viewItems.clear();
2224 QAbstractItemView::reset();
2225}
2226
2227/*!
2228 Returns the horizontal offset of the items in the treeview.
2229
2230 Note that the tree view uses the horizontal header section
2231 positions to determine the positions of columns in the view.
2232
2233 \sa verticalOffset()
2234*/
2235int QTreeView::horizontalOffset() const
2236{
2237 Q_D(const QTreeView);
2238 return d->header->offset();
2239}
2240
2241/*!
2242 Returns the vertical offset of the items in the tree view.
2243
2244 \sa horizontalOffset()
2245*/
2246int QTreeView::verticalOffset() const
2247{
2248 Q_D(const QTreeView);
2249 if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) {
2250 if (d->uniformRowHeights)
2251 return verticalScrollBar()->value() * d->defaultItemHeight;
2252 // If we are scrolling per item and have non-uniform row heights,
2253 // finding the vertical offset in pixels is going to be relatively slow.
2254 // ### find a faster way to do this
2255 d->executePostedLayout();
2256 int offset = 0;
2257 const int cnt = qMin(d->viewItems.size(), verticalScrollBar()->value());
2258 for (int i = 0; i < cnt; ++i)
2259 offset += d->itemHeight(i);
2260 return offset;
2261 }
2262 // scroll per pixel
2263 return verticalScrollBar()->value();
2264}
2265
2266/*!
2267 Move the cursor in the way described by \a cursorAction, using the
2268 information provided by the button \a modifiers.
2269*/
2270QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
2271{
2272 Q_D(QTreeView);
2273 Q_UNUSED(modifiers);
2274
2275 d->executePostedLayout();
2276
2277 QModelIndex current = currentIndex();
2278 if (!current.isValid()) {
2279 int i = d->below(-1);
2280 int c = 0;
2281 while (c < d->header->count() && d->header->isSectionHidden(d->header->logicalIndex(c)))
2282 ++c;
2283 if (i < d->viewItems.size() && c < d->header->count()) {
2284 return d->modelIndex(i, d->header->logicalIndex(c));
2285 }
2286 return QModelIndex();
2287 }
2288
2289 const int vi = qMax(0, d->viewIndex(current));
2290
2291 if (isRightToLeft()) {
2292 if (cursorAction == MoveRight)
2293 cursorAction = MoveLeft;
2294 else if (cursorAction == MoveLeft)
2295 cursorAction = MoveRight;
2296 }
2297 switch (cursorAction) {
2298 case MoveNext:
2299 case MoveDown:
2300#ifdef QT_KEYPAD_NAVIGATION
2301 if (vi == d->viewItems.count()-1 && QApplicationPrivate::keypadNavigationEnabled())
2302 return d->model->index(0, current.column(), d->root);
2303#endif
2304 return d->modelIndex(d->below(vi), current.column());
2305 case MovePrevious:
2306 case MoveUp:
2307#ifdef QT_KEYPAD_NAVIGATION
2308 if (vi == 0 && QApplicationPrivate::keypadNavigationEnabled())
2309 return d->modelIndex(d->viewItems.count() - 1, current.column());
2310#endif
2311 return d->modelIndex(d->above(vi), current.column());
2312 case MoveLeft: {
2313 QScrollBar *sb = horizontalScrollBar();
2314 if (vi < d->viewItems.size() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) {
2315 d->collapse(vi, true);
2316 d->moveCursorUpdatedView = true;
2317 } else {
2318 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, nullptr, this);
2319 if (descend) {
2320 QModelIndex par = current.parent();
2321 if (par.isValid() && par != rootIndex())
2322 return par;
2323 else
2324 descend = false;
2325 }
2326 if (!descend) {
2327 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2328 int visualColumn = d->header->visualIndex(current.column()) - 1;
2329 while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn)))
2330 visualColumn--;
2331 int newColumn = d->header->logicalIndex(visualColumn);
2332 QModelIndex next = current.sibling(current.row(), newColumn);
2333 if (next.isValid())
2334 return next;
2335 }
2336
2337 int oldValue = sb->value();
2338 sb->setValue(sb->value() - sb->singleStep());
2339 if (oldValue != sb->value())
2340 d->moveCursorUpdatedView = true;
2341 }
2342
2343 }
2344 updateGeometries();
2345 viewport()->update();
2346 break;
2347 }
2348 case MoveRight:
2349 if (vi < d->viewItems.size() && !d->viewItems.at(vi).expanded && d->itemsExpandable
2350 && d->hasVisibleChildren(d->viewItems.at(vi).index)) {
2351 d->expand(vi, true);
2352 d->moveCursorUpdatedView = true;
2353 } else {
2354 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, nullptr, this);
2355 if (descend) {
2356 QModelIndex idx = d->modelIndex(d->below(vi));
2357 if (idx.parent() == current)
2358 return idx;
2359 else
2360 descend = false;
2361 }
2362 if (!descend) {
2363 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2364 int visualColumn = d->header->visualIndex(current.column()) + 1;
2365 while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn)))
2366 visualColumn++;
2367 const int newColumn = d->header->logicalIndex(visualColumn);
2368 const QModelIndex next = current.sibling(current.row(), newColumn);
2369 if (next.isValid())
2370 return next;
2371 }
2372
2373 //last restort: we change the scrollbar value
2374 QScrollBar *sb = horizontalScrollBar();
2375 int oldValue = sb->value();
2376 sb->setValue(sb->value() + sb->singleStep());
2377 if (oldValue != sb->value())
2378 d->moveCursorUpdatedView = true;
2379 }
2380 }
2381 updateGeometries();
2382 viewport()->update();
2383 break;
2384 case MovePageUp:
2385 return d->modelIndex(d->pageUp(vi), current.column());
2386 case MovePageDown:
2387 return d->modelIndex(d->pageDown(vi), current.column());
2388 case MoveHome:
2389 return d->modelIndex(d->itemForKeyHome(), current.column());
2390 case MoveEnd:
2391 return d->modelIndex(d->itemForKeyEnd(), current.column());
2392 }
2393 return current;
2394}
2395
2396/*!
2397 Applies the selection \a command to the items in or touched by the
2398 rectangle, \a rect.
2399
2400 \sa selectionCommand()
2401*/
2402void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2403{
2404 Q_D(QTreeView);
2405 if (!selectionModel() || rect.isNull())
2406 return;
2407
2408 d->executePostedLayout();
2409 QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right())
2410 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()));
2411 QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2412 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()));
2413 QModelIndex topLeft = indexAt(tl);
2414 QModelIndex bottomRight = indexAt(br);
2415 if (!topLeft.isValid() && !bottomRight.isValid()) {
2416 if (command & QItemSelectionModel::Clear)
2417 selectionModel()->clear();
2418 return;
2419 }
2420 if (!topLeft.isValid() && !d->viewItems.isEmpty())
2421 topLeft = d->viewItems.constFirst().index;
2422 if (!bottomRight.isValid() && !d->viewItems.isEmpty()) {
2423 const int column = d->header->logicalIndex(d->header->count() - 1);
2424 const QModelIndex index = d->viewItems.constLast().index;
2425 bottomRight = index.sibling(index.row(), column);
2426 }
2427
2428 if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight))
2429 return;
2430
2431 d->select(topLeft, bottomRight, command);
2432}
2433
2434/*!
2435 Returns the rectangle from the viewport of the items in the given
2436 \a selection.
2437
2438 Since 4.7, the returned region only contains rectangles intersecting
2439 (or included in) the viewport.
2440*/
2441QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const
2442{
2443 Q_D(const QTreeView);
2444 if (selection.isEmpty())
2445 return QRegion();
2446
2447 QRegion selectionRegion;
2448 const QRect &viewportRect = d->viewport->rect();
2449 for (const auto &range : selection) {
2450 if (!range.isValid())
2451 continue;
2452 QModelIndex parent = range.parent();
2453 QModelIndex leftIndex = range.topLeft();
2454 int columnCount = d->model->columnCount(parent);
2455 while (leftIndex.isValid() && isIndexHidden(leftIndex)) {
2456 if (leftIndex.column() + 1 < columnCount)
2457 leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent);
2458 else
2459 leftIndex = QModelIndex();
2460 }
2461 if (!leftIndex.isValid())
2462 continue;
2463 const QRect leftRect = d->visualRect(leftIndex, QTreeViewPrivate::SingleSection);
2464 int top = leftRect.top();
2465 QModelIndex rightIndex = range.bottomRight();
2466 while (rightIndex.isValid() && isIndexHidden(rightIndex)) {
2467 if (rightIndex.column() - 1 >= 0)
2468 rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent);
2469 else
2470 rightIndex = QModelIndex();
2471 }
2472 if (!rightIndex.isValid())
2473 continue;
2474 const QRect rightRect = d->visualRect(rightIndex, QTreeViewPrivate::SingleSection);
2475 int bottom = rightRect.bottom();
2476 if (top > bottom)
2477 qSwap<int>(top, bottom);
2478 int height = bottom - top + 1;
2479 if (d->header->sectionsMoved()) {
2480 for (int c = range.left(); c <= range.right(); ++c) {
2481 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2482 if (viewportRect.intersects(rangeRect))
2483 selectionRegion += rangeRect;
2484 }
2485 } else {
2486 QRect combined = leftRect|rightRect;
2487 combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left()));
2488 if (viewportRect.intersects(combined))
2489 selectionRegion += combined;
2490 }
2491 }
2492 return selectionRegion;
2493}
2494
2495/*!
2496 \reimp
2497*/
2498QModelIndexList QTreeView::selectedIndexes() const
2499{
2500 QModelIndexList viewSelected;
2501 QModelIndexList modelSelected;
2502 if (selectionModel())
2503 modelSelected = selectionModel()->selectedIndexes();
2504 for (int i = 0; i < modelSelected.size(); ++i) {
2505 // check that neither the parents nor the index is hidden before we add
2506 QModelIndex index = modelSelected.at(i);
2507 while (index.isValid() && !isIndexHidden(index))
2508 index = index.parent();
2509 if (index.isValid())
2510 continue;
2511 viewSelected.append(modelSelected.at(i));
2512 }
2513 return viewSelected;
2514}
2515
2516/*!
2517 Scrolls the contents of the tree view by (\a dx, \a dy).
2518*/
2519void QTreeView::scrollContentsBy(int dx, int dy)
2520{
2521 Q_D(QTreeView);
2522
2523 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
2524
2525 dx = isRightToLeft() ? -dx : dx;
2526 if (dx) {
2527 int oldOffset = d->header->offset();
2528 d->header->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
2529 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2530 int newOffset = d->header->offset();
2531 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
2532 }
2533 }
2534
2535 const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight;
2536 if (d->viewItems.isEmpty() || itemHeight == 0)
2537 return;
2538
2539 // guestimate the number of items in the viewport
2540 int viewCount = d->viewport->height() / itemHeight;
2541 int maxDeltaY = qMin(d->viewItems.size(), viewCount);
2542 // no need to do a lot of work if we are going to redraw the whole thing anyway
2543 if (qAbs(dy) > qAbs(maxDeltaY) && d->editorIndexHash.isEmpty()) {
2544 verticalScrollBar()->update();
2545 d->viewport->update();
2546 return;
2547 }
2548
2549 if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2550 int currentScrollbarValue = verticalScrollBar()->value();
2551 int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy)
2552 int currentViewIndex = currentScrollbarValue; // the first visible item
2553 int previousViewIndex = previousScrollbarValue;
2554 dy = 0;
2555 if (previousViewIndex < currentViewIndex) { // scrolling down
2556 for (int i = previousViewIndex; i < currentViewIndex; ++i) {
2557 if (i < d->viewItems.size())
2558 dy -= d->itemHeight(i);
2559 }
2560 } else if (previousViewIndex > currentViewIndex) { // scrolling up
2561 for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) {
2562 if (i < d->viewItems.size())
2563 dy += d->itemHeight(i);
2564 }
2565 }
2566 }
2567
2568 d->scrollContentsBy(dx, dy);
2569}
2570
2571/*!
2572 This slot is called whenever a column has been moved.
2573*/
2574void QTreeView::columnMoved()
2575{
2576 Q_D(QTreeView);
2577 updateEditorGeometries();
2578 d->viewport->update();
2579}
2580
2581/*!
2582 \internal
2583*/
2584void QTreeView::reexpand()
2585{
2586 // do nothing
2587}
2588
2589/*!
2590 Informs the view that the rows from the \a start row to the \a end row
2591 inclusive have been inserted into the \a parent model item.
2592*/
2593void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end)
2594{
2595 Q_D(QTreeView);
2596 // if we are going to do a complete relayout anyway, there is no need to update
2597 if (d->delayedPendingLayout) {
2598 QAbstractItemView::rowsInserted(parent, start, end);
2599 return;
2600 }
2601
2602 //don't add a hierarchy on a column != 0
2603 if (parent.column() != 0 && parent.isValid()) {
2604 QAbstractItemView::rowsInserted(parent, start, end);
2605 return;
2606 }
2607
2608 const int parentRowCount = d->model->rowCount(parent);
2609 const int delta = end - start + 1;
2610 if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) {
2611 QAbstractItemView::rowsInserted(parent, start, end);
2612 return;
2613 }
2614
2615 const int parentItem = d->viewIndex(parent);
2616 if (((parentItem != -1) && d->viewItems.at(parentItem).expanded)
2617 || (parent == d->root)) {
2618 d->doDelayedItemsLayout();
2619 } else if (parentItem != -1 && parentRowCount == delta) {
2620 // the parent just went from 0 children to more. update to re-paint the decoration
2621 d->viewItems[parentItem].hasChildren = true;
2622 viewport()->update();
2623 }
2624 QAbstractItemView::rowsInserted(parent, start, end);
2625}
2626
2627/*!
2628 Informs the view that the rows from the \a start row to the \a end row
2629 inclusive are about to removed from the given \a parent model item.
2630*/
2631void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
2632{
2633 Q_D(QTreeView);
2634 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
2635 d->viewItems.clear();
2636}
2637
2638/*!
2639 Informs the view that the rows from the \a start row to the \a end row
2640 inclusive have been removed from the given \a parent model item.
2641*/
2642void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end)
2643{
2644 Q_D(QTreeView);
2645 d->viewItems.clear();
2646 d->doDelayedItemsLayout();
2647 d->hasRemovedItems = true;
2648 d->rowsRemoved(parent, start, end);
2649}
2650
2651/*!
2652 Informs the tree view that the number of columns in the tree view has
2653 changed from \a oldCount to \a newCount.
2654*/
2655void QTreeView::columnCountChanged(int oldCount, int newCount)
2656{
2657 Q_D(QTreeView);
2658 if (oldCount == 0 && newCount > 0) {
2659 //if the first column has just been added we need to relayout.
2660 d->doDelayedItemsLayout();
2661 }
2662
2663 if (isVisible())
2664 updateGeometries();
2665 viewport()->update();
2666}
2667
2668/*!
2669 Resizes the \a column given to the size of its contents.
2670
2671 \sa columnWidth(), setColumnWidth(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
2672*/
2673void QTreeView::resizeColumnToContents(int column)
2674{
2675 Q_D(QTreeView);
2676 d->executePostedLayout();
2677 if (column < 0 || column >= d->header->count())
2678 return;
2679 int contents = sizeHintForColumn(column);
2680 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column);
2681 d->header->resizeSection(column, qMax(contents, header));
2682}
2683
2684/*!
2685 Sorts the model by the values in the given \a column and \a order.
2686
2687 \a column may be -1, in which case no sort indicator will be shown
2688 and the model will return to its natural, unsorted order. Note that not
2689 all models support this and may even crash in this case.
2690
2691 \sa sortingEnabled
2692*/
2693void QTreeView::sortByColumn(int column, Qt::SortOrder order)
2694{
2695 Q_D(QTreeView);
2696 if (column < -1)
2697 return;
2698 d->header->setSortIndicator(column, order);
2699 // If sorting is not enabled or has the same order as before, force to sort now
2700 // else sorting will be trigger through sortIndicatorChanged()
2701 if (!d->sortingEnabled ||
2702 (d->header->sortIndicatorSection() == column && d->header->sortIndicatorOrder() == order))
2703 d->model->sort(column, order);
2704}
2705
2706/*!
2707 \reimp
2708*/
2709void QTreeView::selectAll()
2710{
2711 Q_D(QTreeView);
2712 if (!selectionModel())
2713 return;
2714 SelectionMode mode = d->selectionMode;
2715 d->executePostedLayout(); //make sure we lay out the items
2716 if (mode != SingleSelection && mode != NoSelection && !d->viewItems.isEmpty()) {
2717 const QModelIndex &idx = d->viewItems.constLast().index;
2718 QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1);
2719 d->select(d->viewItems.constFirst().index, lastItemIndex,
2720 QItemSelectionModel::ClearAndSelect
2721 |QItemSelectionModel::Rows);
2722 }
2723}
2724
2725/*!
2726 \reimp
2727*/
2728QSize QTreeView::viewportSizeHint() const
2729{
2730 Q_D(const QTreeView);
2731 d->executePostedLayout(); // Make sure that viewItems are up to date.
2732
2733 if (d->viewItems.size() == 0)
2734 return QAbstractItemView::viewportSizeHint();
2735
2736 // Get rect for last item
2737 const QRect deepestRect = d->visualRect(d->viewItems.last().index,
2738 QTreeViewPrivate::SingleSection);
2739
2740 if (!deepestRect.isValid())
2741 return QAbstractItemView::viewportSizeHint();
2742
2743 QSize result = QSize(d->header->length(), deepestRect.bottom() + 1);
2744
2745 // add size for header
2746 result += QSize(0, d->header->isHidden() ? 0 : d->header->height());
2747
2748 return result;
2749}
2750
2751/*!
2752 Expands all expandable items.
2753
2754 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2755 data.
2756
2757 \warning If the model contains a large number of items,
2758 this function will take some time to execute.
2759
2760 \sa collapseAll(), expand(), collapse(), setExpanded()
2761*/
2762void QTreeView::expandAll()
2763{
2764 Q_D(QTreeView);
2765 d->viewItems.clear();
2766 d->interruptDelayedItemsLayout();
2767 d->layout(-1, true);
2768 updateGeometries();
2769 d->viewport->update();
2770 d->updateAccessibility();
2771}
2772
2773/*!
2774 \since 5.13
2775 Expands the item at the given \a index and all its children to the
2776 given \a depth. The \a depth is relative to the given \a index.
2777 A \a depth of -1 will expand all children, a \a depth of 0 will
2778 only expand the given \a index.
2779
2780 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2781 data.
2782
2783 \warning If the model contains a large number of items,
2784 this function will take some time to execute.
2785
2786 \sa expandAll()
2787*/
2788void QTreeView::expandRecursively(const QModelIndex &index, int depth)
2789{
2790 Q_D(QTreeView);
2791
2792 if (depth < -1)
2793 return;
2794 // do layouting only once after expanding is done
2795 d->doDelayedItemsLayout();
2796 expand(index);
2797 if (depth == 0)
2798 return;
2799 QStack<std::pair<QModelIndex, int>> parents;
2800 parents.push({index, 0});
2801 while (!parents.isEmpty()) {
2802 const std::pair<QModelIndex, int> elem = parents.pop();
2803 const QModelIndex &parent = elem.first;
2804 const int curDepth = elem.second;
2805 const int rowCount = d->model->rowCount(parent);
2806 for (int row = 0; row < rowCount; ++row) {
2807 const QModelIndex child = d->model->index(row, 0, parent);
2808 if (!d->isIndexValid(child))
2809 break;
2810 if (depth == -1 || curDepth + 1 < depth)
2811 parents.push({child, curDepth + 1});
2812 if (d->isIndexExpanded(child))
2813 continue;
2814 if (d->storeExpanded(child))
2815 emit expanded(child);
2816 }
2817 }
2818}
2819
2820/*!
2821 Collapses all expanded items.
2822
2823 \sa expandAll(), expand(), collapse(), setExpanded()
2824*/
2825void QTreeView::collapseAll()
2826{
2827 Q_D(QTreeView);
2828 QSet<QPersistentModelIndex> old_expandedIndexes;
2829 old_expandedIndexes = d->expandedIndexes;
2830 d->expandedIndexes.clear();
2831 if (!signalsBlocked() && isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed))) {
2832 QSet<QPersistentModelIndex>::const_iterator i = old_expandedIndexes.constBegin();
2833 for (; i != old_expandedIndexes.constEnd(); ++i) {
2834 const QPersistentModelIndex &mi = (*i);
2835 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2836 emit collapsed(mi);
2837 }
2838 }
2839 doItemsLayout();
2840}
2841
2842/*!
2843 Expands all expandable items to the given \a depth.
2844
2845 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2846 data.
2847
2848 \sa expandAll(), collapseAll(), expand(), collapse(), setExpanded()
2849*/
2850void QTreeView::expandToDepth(int depth)
2851{
2852 Q_D(QTreeView);
2853 d->viewItems.clear();
2854 QSet<QPersistentModelIndex> old_expandedIndexes;
2855 old_expandedIndexes = d->expandedIndexes;
2856 d->expandedIndexes.clear();
2857 d->interruptDelayedItemsLayout();
2858 d->layout(-1);
2859 for (int i = 0; i < d->viewItems.size(); ++i) {
2860 if (q20::cmp_less_equal(d->viewItems.at(i).level, depth)) {
2861 d->viewItems[i].expanded = true;
2862 d->layout(i);
2863 d->storeExpanded(d->viewItems.at(i).index);
2864 }
2865 }
2866
2867 bool someSignalEnabled = isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed));
2868 someSignalEnabled |= isSignalConnected(QMetaMethod::fromSignal(&QTreeView::expanded));
2869
2870 if (!signalsBlocked() && someSignalEnabled) {
2871 // emit signals
2872 QSet<QPersistentModelIndex> collapsedIndexes = old_expandedIndexes - d->expandedIndexes;
2873 QSet<QPersistentModelIndex>::const_iterator i = collapsedIndexes.constBegin();
2874 for (; i != collapsedIndexes.constEnd(); ++i) {
2875 const QPersistentModelIndex &mi = (*i);
2876 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2877 emit collapsed(mi);
2878 }
2879
2880 QSet<QPersistentModelIndex> expandedIndexs = d->expandedIndexes - old_expandedIndexes;
2881 i = expandedIndexs.constBegin();
2882 for (; i != expandedIndexs.constEnd(); ++i) {
2883 const QPersistentModelIndex &mi = (*i);
2884 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2885 emit expanded(mi);
2886 }
2887 }
2888
2889 updateGeometries();
2890 d->viewport->update();
2891 d->updateAccessibility();
2892}
2893
2894/*!
2895 This function is called whenever \a{column}'s size is changed in
2896 the header. \a oldSize and \a newSize give the previous size and
2897 the new size in pixels.
2898
2899 \sa setColumnWidth()
2900*/
2901void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */)
2902{
2903 Q_D(QTreeView);
2904 d->columnsToUpdate.append(column);
2905 if (!d->columnResizeTimer.isActive())
2906 d->columnResizeTimer.start(0ns, this);
2907}
2908
2909/*!
2910 \reimp
2911*/
2912void QTreeView::updateGeometries()
2913{
2914 Q_D(QTreeView);
2915 if (d->header) {
2916 if (d->geometryRecursionBlock)
2917 return;
2918 d->geometryRecursionBlock = true;
2919 int height = 0;
2920 if (!d->header->isHidden()) {
2921 height = qMax(d->header->minimumHeight(), d->header->sizeHint().height());
2922 height = qMin(height, d->header->maximumHeight());
2923 }
2924 setViewportMargins(0, height, 0, 0);
2925 QRect vg = d->viewport->geometry();
2926 QRect geometryRect(vg.left(), vg.top() - height, vg.width(), height);
2927 d->header->setGeometry(geometryRect);
2928 QMetaObject::invokeMethod(d->header, "updateGeometries");
2929 d->updateScrollBars();
2930 d->geometryRecursionBlock = false;
2931 }
2932 QAbstractItemView::updateGeometries();
2933}
2934
2935/*!
2936 Returns the size hint for the \a column's width or -1 if there is no
2937 model.
2938
2939 If you need to set the width of a given column to a fixed value, call
2940 QHeaderView::resizeSection() on the view's header.
2941
2942 If you reimplement this function in a subclass, note that the value you
2943 return is only used when resizeColumnToContents() is called. In that case,
2944 if a larger column width is required by either the view's header or
2945 the item delegate, that width will be used instead.
2946
2947 \sa QWidget::sizeHint, header(), QHeaderView::resizeContentsPrecision()
2948*/
2949int QTreeView::sizeHintForColumn(int column) const
2950{
2951 Q_D(const QTreeView);
2952 d->executePostedLayout();
2953 if (d->viewItems.isEmpty())
2954 return -1;
2955 ensurePolished();
2956 int w = 0;
2957 QStyleOptionViewItem option;
2958 initViewItemOption(&option);
2959 const QList<QTreeViewItem> viewItems = d->viewItems;
2960
2961 const int maximumProcessRows = d->header->resizeContentsPrecision(); // To avoid this to take forever.
2962
2963 int offset = 0;
2964 int start = d->firstVisibleItem(&offset);
2965 int end = d->lastVisibleItem(start, offset);
2966 if (start < 0 || end < 0 || end == viewItems.size() - 1) {
2967 end = viewItems.size() - 1;
2968 if (maximumProcessRows < 0) {
2969 start = 0;
2970 } else if (maximumProcessRows == 0) {
2971 start = qMax(0, end - 1);
2972 int remainingHeight = viewport()->height();
2973 while (start > 0 && remainingHeight > 0) {
2974 remainingHeight -= d->itemHeight(start);
2975 --start;
2976 }
2977 } else {
2978 start = qMax(0, end - maximumProcessRows);
2979 }
2980 }
2981
2982 int rowsProcessed = 0;
2983
2984 for (int i = start; i <= end; ++i) {
2985 if (viewItems.at(i).spanning)
2986 continue; // we have no good size hint
2987 QModelIndex index = viewItems.at(i).index;
2988 index = index.sibling(index.row(), column);
2989 w = d->widthHintForIndex(index, w, option, i);
2990 ++rowsProcessed;
2991 if (rowsProcessed == maximumProcessRows)
2992 break;
2993 }
2994
2995 --end;
2996 int actualBottom = viewItems.size() - 1;
2997
2998 if (maximumProcessRows == 0)
2999 rowsProcessed = 0; // skip the while loop
3000
3001 while (rowsProcessed != maximumProcessRows && (start > 0 || end < actualBottom)) {
3002 int idx = -1;
3003
3004 if ((rowsProcessed % 2 && start > 0) || end == actualBottom) {
3005 while (start > 0) {
3006 --start;
3007 if (viewItems.at(start).spanning)
3008 continue;
3009 idx = start;
3010 break;
3011 }
3012 } else {
3013 while (end < actualBottom) {
3014 ++end;
3015 if (viewItems.at(end).spanning)
3016 continue;
3017 idx = end;
3018 break;
3019 }
3020 }
3021 if (idx < 0)
3022 continue;
3023
3024 QModelIndex index = viewItems.at(idx).index;
3025 index = index.sibling(index.row(), column);
3026 w = d->widthHintForIndex(index, w, option, idx);
3027 ++rowsProcessed;
3028 }
3029 return w;
3030}
3031
3032/*!
3033 Returns the size hint for the row indicated by \a index.
3034
3035 \sa sizeHintForColumn(), uniformRowHeights()
3036*/
3037int QTreeView::indexRowSizeHint(const QModelIndex &index) const
3038{
3039 Q_D(const QTreeView);
3040 if (!d->isIndexValid(index) || !d->itemDelegate)
3041 return 0;
3042
3043 int start = -1;
3044 int end = -1;
3045 int indexRow = index.row();
3046 int count = d->header->count();
3047 bool emptyHeader = (count == 0);
3048 QModelIndex parent = index.parent();
3049
3050 if (count && isVisible()) {
3051 // If the sections have moved, we end up checking too many or too few
3052 start = d->header->visualIndexAt(0);
3053 } else {
3054 // If the header has not been laid out yet, we use the model directly
3055 count = d->model->columnCount(parent);
3056 }
3057
3058 if (isRightToLeft()) {
3059 start = (start == -1 ? count - 1 : start);
3060 end = 0;
3061 } else {
3062 start = (start == -1 ? 0 : start);
3063 end = count - 1;
3064 }
3065
3066 if (end < start)
3067 qSwap(end, start);
3068
3069 int height = -1;
3070 QStyleOptionViewItem option;
3071 initViewItemOption(&option);
3072 // ### If we want word wrapping in the items,
3073 // ### we need to go through all the columns
3074 // ### and set the width of the column
3075
3076 // Hack to speed up the function
3077 option.rect.setWidth(-1);
3078
3079 for (int column = start; column <= end; ++column) {
3080 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column);
3081 if (d->header->isSectionHidden(logicalColumn))
3082 continue;
3083 QModelIndex idx = d->model->index(indexRow, logicalColumn, parent);
3084 if (idx.isValid()) {
3085 QWidget *editor = d->editorForIndex(idx).widget.data();
3086 if (editor && d->persistent.contains(editor)) {
3087 height = qMax(height, editor->sizeHint().height());
3088 int min = editor->minimumSize().height();
3089 int max = editor->maximumSize().height();
3090 height = qBound(min, height, max);
3091 }
3092 int hint = itemDelegateForIndex(idx)->sizeHint(option, idx).height();
3093 height = qMax(height, hint);
3094 }
3095 }
3096
3097 return height;
3098}
3099
3100/*!
3101 Returns the height of the row indicated by the given \a index.
3102 \sa indexRowSizeHint()
3103*/
3104int QTreeView::rowHeight(const QModelIndex &index) const
3105{
3106 Q_D(const QTreeView);
3107 d->executePostedLayout();
3108 int i = d->viewIndex(index);
3109 if (i == -1)
3110 return 0;
3111 return d->itemHeight(i);
3112}
3113
3114/*!
3115 \internal
3116*/
3117void QTreeView::horizontalScrollbarAction(int action)
3118{
3119 QAbstractItemView::horizontalScrollbarAction(action);
3120}
3121
3122/*!
3123 \reimp
3124*/
3125bool QTreeView::isIndexHidden(const QModelIndex &index) const
3126{
3127 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent()));
3128}
3129
3130/*
3131 private implementation
3132*/
3133void QTreeViewPrivate::initialize()
3134{
3135 Q_Q(QTreeView);
3136
3137 updateIndentationFromStyle();
3138 updateStyledFrameWidths();
3139 q->setSelectionBehavior(QAbstractItemView::SelectRows);
3140 q->setSelectionMode(QAbstractItemView::SingleSelection);
3141 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
3142 q->setAttribute(Qt::WA_MacShowFocusRect);
3143
3144 QHeaderView *header = new QHeaderView(Qt::Horizontal, q);
3145 header->setSectionsMovable(true);
3146 header->setStretchLastSection(true);
3147 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
3148 q->setHeader(header);
3149#if QT_CONFIG(animation)
3150 animationsEnabled = q->style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, q) > 0;
3151 animationConnection =
3152 QObjectPrivate::connect(&animatedOperation, &QVariantAnimation::finished,
3153 this, &QTreeViewPrivate::endAnimatedOperation);
3154#endif // animation
3155}
3156
3157void QTreeViewPrivate::clearConnections()
3158{
3159 for (QMetaObject::Connection &connection : modelConnections)
3160 QObject::disconnect(connection);
3161 for (QMetaObject::Connection &connection : headerConnections)
3162 QObject::disconnect(connection);
3163 QObject::disconnect(selectionmodelConnection);
3164 QObject::disconnect(sortHeaderConnection);
3165#if QT_CONFIG(animation)
3166 QObject::disconnect(animationConnection);
3167#endif
3168}
3169
3170void QTreeViewPrivate::expand(int item, bool emitSignal)
3171{
3172 Q_Q(QTreeView);
3173
3174 if (item == -1 || viewItems.at(item).expanded)
3175 return;
3176 const QModelIndex index = viewItems.at(item).index;
3177 if (index.flags() & Qt::ItemNeverHasChildren)
3178 return;
3179
3180#if QT_CONFIG(animation)
3181 if (emitSignal && animationsEnabled)
3182 prepareAnimatedOperation(item, QVariantAnimation::Forward);
3183#endif // animation
3184 //if already animating, stateBeforeAnimation is set to the correct value
3185 if (state != QAbstractItemView::AnimatingState)
3186 stateBeforeAnimation = state;
3187 q->setState(QAbstractItemView::ExpandingState);
3188 storeExpanded(index);
3189 viewItems[item].expanded = true;
3190 layout(item);
3191 q->setState(stateBeforeAnimation);
3192
3193 if (model->canFetchMore(index))
3194 model->fetchMore(index);
3195 if (emitSignal) {
3196 emit q->expanded(index);
3197#if QT_CONFIG(animation)
3198 if (animationsEnabled)
3199 beginAnimatedOperation();
3200#endif // animation
3201 }
3202 updateAccessibility();
3203}
3204
3205void QTreeViewPrivate::insertViewItems(int pos, int count, const QTreeViewItem &viewItem)
3206{
3207 viewItems.insert(pos, count, viewItem);
3208 QTreeViewItem *items = viewItems.data();
3209 for (int i = pos + count; i < viewItems.size(); i++)
3210 if (items[i].parentItem >= pos)
3211 items[i].parentItem += count;
3212}
3213
3214void QTreeViewPrivate::removeViewItems(int pos, int count)
3215{
3216 viewItems.remove(pos, count);
3217 QTreeViewItem *items = viewItems.data();
3218 for (int i = pos; i < viewItems.size(); i++)
3219 if (items[i].parentItem >= pos)
3220 items[i].parentItem -= count;
3221}
3222
3223void QTreeViewPrivate::collapse(int item, bool emitSignal)
3224{
3225 Q_Q(QTreeView);
3226
3227 if (item == -1 || expandedIndexes.isEmpty())
3228 return;
3229
3230 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
3231 delayedAutoScroll.stop();
3232
3233 int total = viewItems.at(item).total;
3234 const QModelIndex &modelIndex = viewItems.at(item).index;
3235 if (!isPersistent(modelIndex))
3236 return; // if the index is not persistent, no chances it is expanded
3237 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex);
3238 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false)
3239 return; // nothing to do
3240
3241#if QT_CONFIG(animation)
3242 if (emitSignal && animationsEnabled)
3243 prepareAnimatedOperation(item, QVariantAnimation::Backward);
3244#endif // animation
3245
3246 //if already animating, stateBeforeAnimation is set to the correct value
3247 if (state != QAbstractItemView::AnimatingState)
3248 stateBeforeAnimation = state;
3249 q->setState(QAbstractItemView::CollapsingState);
3250 expandedIndexes.erase(it);
3251 viewItems[item].expanded = false;
3252 int index = item;
3253 while (index > -1) {
3254 viewItems[index].total -= total;
3255 index = viewItems[index].parentItem;
3256 }
3257 removeViewItems(item + 1, total); // collapse
3258 q->setState(stateBeforeAnimation);
3259
3260 if (emitSignal) {
3261 emit q->collapsed(modelIndex);
3262#if QT_CONFIG(animation)
3263 if (animationsEnabled)
3264 beginAnimatedOperation();
3265#endif // animation
3266 }
3267}
3268
3269#if QT_CONFIG(animation)
3270void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction)
3271{
3272 animatedOperation.item = item;
3273 animatedOperation.viewport = viewport;
3274 animatedOperation.setDirection(direction);
3275
3276 int top = coordinateForItem(item) + itemHeight(item);
3277 QRect rect = viewport->rect();
3278 rect.setTop(top);
3279 if (direction == QVariantAnimation::Backward) {
3280 const int limit = rect.height() * 2;
3281 int h = 0;
3282 int c = item + viewItems.at(item).total + 1;
3283 for (int i = item + 1; i < c && h < limit; ++i)
3284 h += itemHeight(i);
3285 rect.setHeight(h);
3286 animatedOperation.setEndValue(top + h);
3287 }
3288 animatedOperation.setStartValue(top);
3289 animatedOperation.before = renderTreeToPixmapForAnimation(rect);
3290}
3291
3292void QTreeViewPrivate::beginAnimatedOperation()
3293{
3294 Q_Q(QTreeView);
3295
3296 QRect rect = viewport->rect();
3297 rect.setTop(animatedOperation.top());
3298 if (animatedOperation.direction() == QVariantAnimation::Forward) {
3299 const int limit = rect.height() * 2;
3300 int h = 0;
3301 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1;
3302 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i)
3303 h += itemHeight(i);
3304 rect.setHeight(h);
3305 animatedOperation.setEndValue(animatedOperation.top() + h);
3306 }
3307
3308 if (!rect.isEmpty()) {
3309 animatedOperation.after = renderTreeToPixmapForAnimation(rect);
3310
3311 q->setState(QAbstractItemView::AnimatingState);
3312 animatedOperation.start(); //let's start the animation
3313 }
3314}
3315
3316void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const
3317{
3318 const int start = animatedOperation.startValue().toInt(),
3319 end = animatedOperation.endValue().toInt(),
3320 current = animatedOperation.currentValue().toInt();
3321 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward;
3322 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after;
3323 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height());
3324 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before;
3325 painter->drawPixmap(0, current, bottom);
3326}
3327
3328QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const
3329{
3330 Q_Q(const QTreeView);
3331 QPixmap pixmap(rect.size() * q->devicePixelRatio());
3332 pixmap.setDevicePixelRatio(q->devicePixelRatio());
3333 if (rect.size().isEmpty())
3334 return pixmap;
3335 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels.
3336 QPainter painter(&pixmap);
3337 painter.setLayoutDirection(q->layoutDirection());
3338 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base());
3339 painter.translate(0, -rect.top());
3340 q->drawTree(&painter, QRegion(rect));
3341 painter.end();
3342
3343 //and now let's render the editors the editors
3344 QStyleOptionViewItem option;
3345 q->initViewItemOption(&option);
3346 for (QEditorIndexHash::const_iterator it = editorIndexHash.constBegin(); it != editorIndexHash.constEnd(); ++it) {
3347 QWidget *editor = it.key();
3348 const QModelIndex &index = it.value();
3349 option.rect = visualRect(index, SingleSection);
3350 if (option.rect.isValid()) {
3351
3352 if (QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index))
3353 delegate->updateEditorGeometry(editor, option, index);
3354
3355 const QPoint pos = editor->pos();
3356 if (rect.contains(pos)) {
3357 editor->render(&pixmap, pos - rect.topLeft());
3358 //the animation uses pixmap to display the treeview's content
3359 //the editor is rendered on this pixmap and thus can (should) be hidden
3360 editor->hide();
3361 }
3362 }
3363 }
3364
3365
3366 return pixmap;
3367}
3368
3369void QTreeViewPrivate::endAnimatedOperation()
3370{
3371 Q_Q(QTreeView);
3372 q->setState(stateBeforeAnimation);
3373 q->updateGeometries();
3374 viewport->update();
3375}
3376#endif // animation
3377
3378void QTreeViewPrivate::modelAboutToBeReset()
3379{
3380 viewItems.clear();
3381}
3382
3383void QTreeViewPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
3384{
3385 if (start <= 0 && 0 <= end)
3386 viewItems.clear();
3387 QAbstractItemViewPrivate::columnsAboutToBeRemoved(parent, start, end);
3388}
3389
3390void QTreeViewPrivate::columnsRemoved(const QModelIndex &parent, int start, int end)
3391{
3392 if (start <= 0 && 0 <= end)
3393 doDelayedItemsLayout();
3394 QAbstractItemViewPrivate::columnsRemoved(parent, start, end);
3395}
3396
3397void QTreeViewPrivate::updateAccessibility()
3398{
3399#if QT_CONFIG(accessibility)
3400 Q_Q(QTreeView);
3401 if (pendingAccessibilityUpdate) {
3402 pendingAccessibilityUpdate = false;
3403 if (QAccessible::isActive()) {
3404 QAccessibleTableModelChangeEvent event(q, QAccessibleTableModelChangeEvent::ModelReset);
3405 QAccessible::updateAccessibility(&event);
3406 }
3407 }
3408#endif
3409}
3410
3411
3412/** \internal
3413 creates and initialize the viewItem structure of the children of the element \li
3414
3415 set \a recursiveExpanding if the function has to expand all the children (called from expandAll)
3416 \a afterIsUninitialized is when we recurse from layout(-1), it means all the items after 'i' are
3417 not yet initialized and need not to be moved
3418 */
3419void QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninitialized)
3420{
3421 Q_Q(QTreeView);
3422 QModelIndex current;
3423 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i);
3424
3425 if (i>=0 && !parent.isValid()) {
3426 //modelIndex() should never return something invalid for the real items.
3427 //This can happen if columncount has been set to 0.
3428 //To avoid infinite loop we stop here.
3429 return;
3430 }
3431
3432#if QT_CONFIG(accessibility)
3433 // QAccessibleTree's rowCount implementation uses viewItems.size(), so
3434 // we need to invalidate any cached accessibility data structures if
3435 // that value changes during the run of this function.
3436 const auto resetModelIfNeeded = qScopeGuard([oldViewItemsSize = viewItems.size(), this]{
3437 pendingAccessibilityUpdate |= oldViewItemsSize != viewItems.size();
3438 });
3439#endif
3440
3441 int count = 0;
3442 if (model->hasChildren(parent)) {
3443 if (model->canFetchMore(parent)) {
3444 // fetchMore first, otherwise we might not yet have any data for sizeHintForRow
3445 model->fetchMore(parent);
3446 // guestimate the number of items in the viewport, and fetch as many as might fit
3447 const int itemHeight = defaultItemHeight <= 0 ? q->sizeHintForRow(0) : defaultItemHeight;
3448 const int viewCount = itemHeight ? viewport->height() / itemHeight : 0;
3449 int lastCount = -1;
3450 while ((count = model->rowCount(parent)) < viewCount &&
3451 count != lastCount && model->canFetchMore(parent)) {
3452 model->fetchMore(parent);
3453 lastCount = count;
3454 }
3455 } else {
3456 count = model->rowCount(parent);
3457 }
3458 }
3459
3460 bool expanding = true;
3461 if (i == -1) {
3462 if (uniformRowHeights) {
3463 QModelIndex index = model->index(0, 0, parent);
3464 defaultItemHeight = q->indexRowSizeHint(index);
3465 }
3466 viewItems.resize(count);
3467 afterIsUninitialized = true;
3468 } else if (q20::cmp_not_equal(viewItems[i].total, count)) {
3469 if (!afterIsUninitialized)
3470 insertViewItems(i + 1, count, QTreeViewItem()); // expand
3471 else if (count > 0)
3472 viewItems.resize(viewItems.size() + count);
3473 } else {
3474 expanding = false;
3475 }
3476
3477 int first = i + 1;
3478 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0);
3479 int hidden = 0;
3480 int last = 0;
3481 int children = 0;
3482 QTreeViewItem *item = nullptr;
3483 for (int j = first; j < first + count; ++j) {
3484 current = model->index(j - first, 0, parent);
3485 if (isRowHidden(current)) {
3486 ++hidden;
3487 last = j - hidden + children;
3488 } else {
3489 last = j - hidden + children;
3490 if (item)
3491 item->hasMoreSiblings = true;
3492 item = &viewItems[last];
3493 item->index = current;
3494 item->parentItem = i;
3495 item->level = level;
3496 item->height = 0;
3497 item->spanning = q->isFirstColumnSpanned(current.row(), parent);
3498 item->expanded = false;
3499 item->total = 0;
3500 item->hasMoreSiblings = false;
3501 if ((recursiveExpanding && !(current.flags() & Qt::ItemNeverHasChildren)) || isIndexExpanded(current)) {
3502 if (recursiveExpanding && storeExpanded(current) && !q->signalsBlocked())
3503 emit q->expanded(current);
3504 item->expanded = true;
3505 layout(last, recursiveExpanding, afterIsUninitialized);
3506 item = &viewItems[last];
3507 children += item->total;
3508 item->hasChildren = item->total > 0;
3509 last = j - hidden + children;
3510 } else {
3511 item->hasChildren = hasVisibleChildren(current);
3512 }
3513 }
3514 }
3515
3516 // remove hidden items
3517 if (hidden > 0) {
3518 if (!afterIsUninitialized)
3519 removeViewItems(last + 1, hidden);
3520 else
3521 viewItems.resize(viewItems.size() - hidden);
3522 }
3523
3524 if (!expanding)
3525 return; // nothing changed
3526
3527 while (i > -1) {
3528 viewItems[i].total += count - hidden;
3529 i = viewItems[i].parentItem;
3530 }
3531}
3532
3533int QTreeViewPrivate::pageUp(int i) const
3534{
3535 int index = itemAtCoordinate(coordinateForItem(i) - viewport->height());
3536 while (isItemHiddenOrDisabled(index))
3537 index--;
3538 if (index == -1)
3539 index = 0;
3540 while (isItemHiddenOrDisabled(index))
3541 index++;
3542 return index >= viewItems.size() ? 0 : index;
3543}
3544
3545int QTreeViewPrivate::pageDown(int i) const
3546{
3547 int index = itemAtCoordinate(coordinateForItem(i) + viewport->height());
3548 while (isItemHiddenOrDisabled(index))
3549 index++;
3550 if (index == -1 || index >= viewItems.size())
3551 index = viewItems.size() - 1;
3552 while (isItemHiddenOrDisabled(index))
3553 index--;
3554 return index == -1 ? viewItems.size() - 1 : index;
3555}
3556
3557int QTreeViewPrivate::itemForKeyHome() const
3558{
3559 int index = 0;
3560 while (isItemHiddenOrDisabled(index))
3561 index++;
3562 return index >= viewItems.size() ? 0 : index;
3563}
3564
3565int QTreeViewPrivate::itemForKeyEnd() const
3566{
3567 int index = viewItems.size() - 1;
3568 while (isItemHiddenOrDisabled(index))
3569 index--;
3570 return index == -1 ? viewItems.size() - 1 : index;
3571}
3572
3573int QTreeViewPrivate::indentationForItem(int item) const
3574{
3575 if (item < 0 || item >= viewItems.size())
3576 return 0;
3577 int level = viewItems.at(item).level;
3578 if (rootDecoration)
3579 ++level;
3580 return level * indent;
3581}
3582
3583int QTreeViewPrivate::itemHeight(int item) const
3584{
3585 Q_ASSERT(item < viewItems.size());
3586 if (uniformRowHeights)
3587 return defaultItemHeight;
3588 if (viewItems.isEmpty())
3589 return 0;
3590 const QModelIndex &index = viewItems.at(item).index;
3591 if (!index.isValid())
3592 return 0;
3593 int height = viewItems.at(item).height;
3594 if (height <= 0) {
3595 height = q_func()->indexRowSizeHint(index);
3596 viewItems[item].height = height;
3597 }
3598 return qMax(height, 0);
3599}
3600
3601
3602/*!
3603 \internal
3604 Returns the viewport y coordinate for \a item.
3605*/
3606int QTreeViewPrivate::coordinateForItem(int item) const
3607{
3608 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3609 if (uniformRowHeights)
3610 return (item * defaultItemHeight) - vbar->value();
3611 // ### optimize (maybe do like QHeaderView by letting items have startposition)
3612 int y = 0;
3613 for (int i = 0; i < viewItems.size(); ++i) {
3614 if (i == item)
3615 return y - vbar->value();
3616 y += itemHeight(i);
3617 }
3618 } else { // ScrollPerItem
3619 int topViewItemIndex = vbar->value();
3620 if (uniformRowHeights)
3621 return defaultItemHeight * (item - topViewItemIndex);
3622 if (item >= topViewItemIndex) {
3623 // search in the visible area first and continue down
3624 // ### slow if the item is not visible
3625 int viewItemCoordinate = 0;
3626 int viewItemIndex = topViewItemIndex;
3627 while (viewItemIndex < viewItems.size()) {
3628 if (viewItemIndex == item)
3629 return viewItemCoordinate;
3630 viewItemCoordinate += itemHeight(viewItemIndex);
3631 ++viewItemIndex;
3632 }
3633 // below the last item in the view
3634 Q_ASSERT(false);
3635 return viewItemCoordinate;
3636 } else {
3637 // search the area above the viewport (used for editor widgets)
3638 int viewItemCoordinate = 0;
3639 for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) {
3640 if (viewItemIndex == item)
3641 return viewItemCoordinate;
3642 viewItemCoordinate -= itemHeight(viewItemIndex - 1);
3643 }
3644 return viewItemCoordinate;
3645 }
3646 }
3647 return 0;
3648}
3649
3650/*!
3651 \internal
3652 Returns the index of the view item at the
3653 given viewport \a coordinate.
3654
3655 \sa modelIndex()
3656*/
3657int QTreeViewPrivate::itemAtCoordinate(int coordinate) const
3658{
3659 const int itemCount = viewItems.size();
3660 if (itemCount == 0)
3661 return -1;
3662 if (uniformRowHeights && defaultItemHeight <= 0)
3663 return -1;
3664 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3665 if (uniformRowHeights) {
3666 const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight;
3667 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3668 }
3669 // ### optimize
3670 int viewItemCoordinate = 0;
3671 const int contentsCoordinate = coordinate + vbar->value();
3672 for (int viewItemIndex = 0; viewItemIndex < viewItems.size(); ++viewItemIndex) {
3673 viewItemCoordinate += itemHeight(viewItemIndex);
3674 if (viewItemCoordinate > contentsCoordinate)
3675 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3676 }
3677 } else { // ScrollPerItem
3678 int topViewItemIndex = vbar->value();
3679 if (uniformRowHeights) {
3680 if (coordinate < 0)
3681 coordinate -= defaultItemHeight - 1;
3682 const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight);
3683 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3684 }
3685 if (coordinate >= 0) {
3686 // the coordinate is in or below the viewport
3687 int viewItemCoordinate = 0;
3688 for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.size(); ++viewItemIndex) {
3689 viewItemCoordinate += itemHeight(viewItemIndex);
3690 if (viewItemCoordinate > coordinate)
3691 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3692 }
3693 } else {
3694 // the coordinate is above the viewport
3695 int viewItemCoordinate = 0;
3696 for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) {
3697 if (viewItemCoordinate <= coordinate)
3698 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3699 viewItemCoordinate -= itemHeight(viewItemIndex);
3700 }
3701 }
3702 }
3703 return -1;
3704}
3705
3706int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const
3707{
3708 if (!_index.isValid() || viewItems.isEmpty())
3709 return -1;
3710
3711 const int totalCount = viewItems.size();
3712 const QModelIndex index = _index.sibling(_index.row(), 0);
3713 const int row = index.row();
3714 const quintptr internalId = index.internalId();
3715
3716 // We start nearest to the lastViewedItem
3717 int localCount = qMin(lastViewedItem - 1, totalCount - lastViewedItem);
3718 for (int i = 0; i < localCount; ++i) {
3719 const QModelIndex &idx1 = viewItems.at(lastViewedItem + i).index;
3720 if (idx1.row() == row && idx1.internalId() == internalId) {
3721 lastViewedItem = lastViewedItem + i;
3722 return lastViewedItem;
3723 }
3724 const QModelIndex &idx2 = viewItems.at(lastViewedItem - i - 1).index;
3725 if (idx2.row() == row && idx2.internalId() == internalId) {
3726 lastViewedItem = lastViewedItem - i - 1;
3727 return lastViewedItem;
3728 }
3729 }
3730
3731 for (int j = qMax(0, lastViewedItem + localCount); j < totalCount; ++j) {
3732 const QModelIndex &idx = viewItems.at(j).index;
3733 if (idx.row() == row && idx.internalId() == internalId) {
3734 lastViewedItem = j;
3735 return j;
3736 }
3737 }
3738 for (int j = qMin(totalCount, lastViewedItem - localCount) - 1; j >= 0; --j) {
3739 const QModelIndex &idx = viewItems.at(j).index;
3740 if (idx.row() == row && idx.internalId() == internalId) {
3741 lastViewedItem = j;
3742 return j;
3743 }
3744 }
3745
3746 // nothing found
3747 return -1;
3748}
3749
3750QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const
3751{
3752 if (i < 0 || i >= viewItems.size())
3753 return QModelIndex();
3754
3755 QModelIndex ret = viewItems.at(i).index;
3756 if (column)
3757 ret = ret.sibling(ret.row(), column);
3758 return ret;
3759}
3760
3761int QTreeViewPrivate::firstVisibleItem(int *offset) const
3762{
3763 const int value = vbar->value();
3764 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3765 if (offset)
3766 *offset = 0;
3767 return (value < 0 || value >= viewItems.size()) ? -1 : value;
3768 }
3769 // ScrollMode == ScrollPerPixel
3770 if (uniformRowHeights) {
3771 if (!defaultItemHeight)
3772 return -1;
3773
3774 if (offset)
3775 *offset = -(value % defaultItemHeight);
3776 return value / defaultItemHeight;
3777 }
3778 int y = 0; // ### (maybe do like QHeaderView by letting items have startposition)
3779 for (int i = 0; i < viewItems.size(); ++i) {
3780 y += itemHeight(i); // the height value is cached
3781 if (y > value) {
3782 if (offset)
3783 *offset = y - value - itemHeight(i);
3784 return i;
3785 }
3786 }
3787 return -1;
3788}
3789
3790int QTreeViewPrivate::lastVisibleItem(int firstVisual, int offset) const
3791{
3792 if (firstVisual < 0 || offset < 0) {
3793 firstVisual = firstVisibleItem(&offset);
3794 if (firstVisual < 0)
3795 return -1;
3796 }
3797 int y = - offset;
3798 int value = viewport->height();
3799
3800 for (int i = firstVisual; i < viewItems.size(); ++i) {
3801 y += itemHeight(i); // the height value is cached
3802 if (y > value)
3803 return i;
3804 }
3805 return viewItems.size() - 1;
3806}
3807
3808int QTreeViewPrivate::columnAt(int x) const
3809{
3810 return header->logicalIndexAt(x);
3811}
3812
3813void QTreeViewPrivate::updateScrollBars()
3814{
3815 Q_Q(QTreeView);
3816 QSize viewportSize = viewport->size();
3817 if (!viewportSize.isValid())
3818 viewportSize = QSize(0, 0);
3819
3820 executePostedLayout();
3821 if (viewItems.isEmpty()) {
3822 q->doItemsLayout();
3823 }
3824
3825 int itemsInViewport = 0;
3826 if (uniformRowHeights) {
3827 if (defaultItemHeight <= 0)
3828 itemsInViewport = viewItems.size();
3829 else
3830 itemsInViewport = viewportSize.height() / defaultItemHeight;
3831 } else {
3832 const int itemsCount = viewItems.size();
3833 const int viewportHeight = viewportSize.height();
3834 for (int height = 0, item = itemsCount - 1; item >= 0; --item) {
3835 height += itemHeight(item);
3836 if (height > viewportHeight)
3837 break;
3838 ++itemsInViewport;
3839 }
3840 }
3841 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3842 if (!viewItems.isEmpty())
3843 itemsInViewport = qMax(1, itemsInViewport);
3844 vbar->setRange(0, viewItems.size() - itemsInViewport);
3845 vbar->setPageStep(itemsInViewport);
3846 vbar->setSingleStep(1);
3847 } else { // scroll per pixel
3848 int contentsHeight = 0;
3849 if (uniformRowHeights) {
3850 contentsHeight = defaultItemHeight * viewItems.size();
3851 } else { // ### (maybe do like QHeaderView by letting items have startposition)
3852 for (int i = 0; i < viewItems.size(); ++i)
3853 contentsHeight += itemHeight(i);
3854 }
3855 vbar->setRange(0, contentsHeight - viewportSize.height());
3856 vbar->setPageStep(viewportSize.height());
3857 vbar->d_func()->itemviewChangeSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2));
3858 }
3859
3860 const int columnCount = header->count();
3861 const int viewportWidth = viewportSize.width();
3862 int columnsInViewport = 0;
3863 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
3864 int logical = header->logicalIndex(column);
3865 width += header->sectionSize(logical);
3866 if (width > viewportWidth)
3867 break;
3868 ++columnsInViewport;
3869 }
3870 if (columnCount > 0)
3871 columnsInViewport = qMax(1, columnsInViewport);
3872 if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) {
3873 hbar->setRange(0, columnCount - columnsInViewport);
3874 hbar->setPageStep(columnsInViewport);
3875 hbar->setSingleStep(1);
3876 } else { // scroll per pixel
3877 const int horizontalLength = header->length();
3878 const QSize maxSize = q->maximumViewportSize();
3879 if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0)
3880 viewportSize = maxSize;
3881 hbar->setPageStep(viewportSize.width());
3882 hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0));
3883 hbar->d_func()->itemviewChangeSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2));
3884 }
3885}
3886
3887int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const
3888{
3889 Q_Q(const QTreeView);
3890 executePostedLayout();
3891 bool spanned = false;
3892 if (!spanningIndexes.isEmpty()) {
3893 const QModelIndex index = q->indexAt(pos);
3894 if (index.isValid())
3895 spanned = q->isFirstColumnSpanned(index.row(), index.parent());
3896 }
3897 const int column = spanned ? 0 : header->logicalIndexAt(pos.x());
3898 if (!isTreePosition(column))
3899 return -1; // no logical index at x
3900
3901 int viewItemIndex = itemAtCoordinate(pos.y());
3902 QRect returning = itemDecorationRect(modelIndex(viewItemIndex));
3903 if (!returning.contains(pos))
3904 return -1;
3905
3906 return viewItemIndex;
3907}
3908
3909QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const
3910{
3911 Q_Q(const QTreeView);
3912 if (!rootDecoration && index.parent() == root)
3913 return QRect(); // no decoration at root
3914
3915 int viewItemIndex = viewIndex(index);
3916 if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index))
3917 return QRect();
3918
3919 int itemIndentation = indentationForItem(viewItemIndex);
3920 int position = header->sectionViewportPosition(logicalIndexForTree());
3921 int size = header->sectionSize(logicalIndexForTree());
3922
3923 QRect rect;
3924 if (q->isRightToLeft())
3925 rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex),
3926 indent, itemHeight(viewItemIndex));
3927 else
3928 rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex),
3929 indent, itemHeight(viewItemIndex));
3930 QStyleOption opt;
3931 opt.initFrom(q);
3932 opt.rect = rect;
3933 return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q);
3934}
3935
3936QList<std::pair<int, int>> QTreeViewPrivate::columnRanges(const QModelIndex &topIndex,
3937 const QModelIndex &bottomIndex) const
3938{
3939 const int topVisual = header->visualIndex(topIndex.column()),
3940 bottomVisual = header->visualIndex(bottomIndex.column());
3941
3942 const int start = qMin(topVisual, bottomVisual);
3943 const int end = qMax(topVisual, bottomVisual);
3944
3945 QList<int> logicalIndexes;
3946
3947 //we iterate over the visual indexes to get the logical indexes
3948 for (int c = start; c <= end; c++) {
3949 const int logical = header->logicalIndex(c);
3950 if (!header->isSectionHidden(logical)) {
3951 logicalIndexes << logical;
3952 }
3953 }
3954 //let's sort the list
3955 std::sort(logicalIndexes.begin(), logicalIndexes.end());
3956
3957 QList<std::pair<int, int>> ret;
3958 std::pair<int, int> current;
3959 current.first = -2; // -1 is not enough because -1+1 = 0
3960 current.second = -2;
3961 for(int i = 0; i < logicalIndexes.size(); ++i) {
3962 const int logicalColumn = logicalIndexes.at(i);
3963 if (current.second + 1 != logicalColumn) {
3964 if (current.first != -2) {
3965 //let's save the current one
3966 ret += current;
3967 }
3968 //let's start a new one
3969 current.first = current.second = logicalColumn;
3970 } else {
3971 current.second++;
3972 }
3973 }
3974
3975 //let's get the last range
3976 if (current.first != -2) {
3977 ret += current;
3978 }
3979
3980 return ret;
3981}
3982
3983void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex,
3984 QItemSelectionModel::SelectionFlags command)
3985{
3986 Q_Q(QTreeView);
3987 QItemSelection selection;
3988 const int top = viewIndex(topIndex),
3989 bottom = viewIndex(bottomIndex);
3990
3991 const QList<std::pair<int, int>> colRanges = columnRanges(topIndex, bottomIndex);
3992 QList<std::pair<int, int>>::const_iterator it;
3993 for (it = colRanges.begin(); it != colRanges.end(); ++it) {
3994 const int left = (*it).first,
3995 right = (*it).second;
3996
3997 QModelIndex previous;
3998 QItemSelectionRange currentRange;
3999 QStack<QItemSelectionRange> rangeStack;
4000 for (int i = top; i <= bottom; ++i) {
4001 QModelIndex index = modelIndex(i);
4002 QModelIndex parent = index.parent();
4003 QModelIndex previousParent = previous.parent();
4004 if (previous.isValid() && parent == previousParent) {
4005 // same parent
4006 if (qAbs(previous.row() - index.row()) > 1) {
4007 //a hole (hidden index inside a range) has been detected
4008 if (currentRange.isValid()) {
4009 selection.append(currentRange);
4010 }
4011 //let's start a new range
4012 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4013 } else {
4014 QModelIndex tl = model->index(currentRange.top(), currentRange.left(),
4015 currentRange.parent());
4016 currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right));
4017 }
4018 } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) {
4019 // item is child of previous
4020 rangeStack.push(currentRange);
4021 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4022 } else {
4023 if (currentRange.isValid())
4024 selection.append(currentRange);
4025 if (rangeStack.isEmpty()) {
4026 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4027 } else {
4028 currentRange = rangeStack.pop();
4029 index = currentRange.bottomRight(); //let's resume the range
4030 --i; //we process again the current item
4031 }
4032 }
4033 previous = index;
4034 }
4035 if (currentRange.isValid())
4036 selection.append(currentRange);
4037 for (int i = 0; i < rangeStack.size(); ++i)
4038 selection.append(rangeStack.at(i));
4039 }
4040 q->selectionModel()->select(selection, command);
4041}
4042
4043std::pair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const
4044{
4045 Q_Q(const QTreeView);
4046 int start = header->visualIndexAt(rect.left());
4047 int end = header->visualIndexAt(rect.right());
4048 if (q->isRightToLeft()) {
4049 start = (start == -1 ? header->count() - 1 : start);
4050 end = (end == -1 ? 0 : end);
4051 } else {
4052 start = (start == -1 ? 0 : start);
4053 end = (end == -1 ? header->count() - 1 : end);
4054 }
4055 return std::pair(qMin(start, end), qMax(start, end));
4056}
4057
4058bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const
4059{
4060 Q_Q(const QTreeView);
4061 if (parent.flags() & Qt::ItemNeverHasChildren)
4062 return false;
4063 if (model->hasChildren(parent)) {
4064 if (hiddenIndexes.isEmpty())
4065 return true;
4066 if (q->isIndexHidden(parent))
4067 return false;
4068 int rowCount = model->rowCount(parent);
4069 for (int i = 0; i < rowCount; ++i) {
4070 if (!q->isRowHidden(i, parent))
4071 return true;
4072 }
4073 if (rowCount == 0)
4074 return true;
4075 }
4076 return false;
4077}
4078
4079void QTreeViewPrivate::sortIndicatorChanged(int column, Qt::SortOrder order)
4080{
4081 model->sort(column, order);
4082}
4083
4084#if QT_CONFIG(accessibility)
4085int QTreeViewPrivate::accessibleChildIndex(const QModelIndex &index) const
4086{
4087 Q_Q(const QTreeView);
4088 Q_ASSERT(index.isValid());
4089
4090 // Note that this will include the header, even if its hidden.
4091 return (q->visualIndex(index) + (q->header() ? 1 : 0)) * index.model()->columnCount() + index.column();
4092}
4093#endif
4094
4095void QTreeViewPrivate::updateIndentationFromStyle()
4096{
4097 Q_Q(const QTreeView);
4098 indent = q->style()->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, q);
4099}
4100
4101/*!
4102 \reimp
4103 */
4104void QTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
4105{
4106 Q_D(QTreeView);
4107 QAbstractItemView::currentChanged(current, previous);
4108
4109 if (allColumnsShowFocus()) {
4110 if (previous.isValid())
4111 viewport()->update(d->visualRect(previous, QTreeViewPrivate::FullRow));
4112 if (current.isValid())
4113 viewport()->update(d->visualRect(current, QTreeViewPrivate::FullRow));
4114 }
4115#if QT_CONFIG(accessibility)
4116 if (QAccessible::isActive() && current.isValid() && hasFocus()) {
4117 Q_D(QTreeView);
4118
4119 const int entry = d->accessibleChildIndex(current);
4120 if (entry >= 0) {
4121 QAccessibleEvent event(this, QAccessible::Focus);
4122 event.setChild(entry);
4123 QAccessible::updateAccessibility(&event);
4124 }
4125 }
4126#endif
4127}
4128
4129/*!
4130 \reimp
4131 */
4132void QTreeView::selectionChanged(const QItemSelection &selected,
4133 const QItemSelection &deselected)
4134{
4135 QAbstractItemView::selectionChanged(selected, deselected);
4136#if QT_CONFIG(accessibility)
4137 if (QAccessible::isActive()) {
4138 Q_D(QTreeView);
4139
4140 // ### does not work properly for selection ranges.
4141 QModelIndex sel = selected.indexes().value(0);
4142 if (sel.isValid()) {
4143 int entry = d->accessibleChildIndex(sel);
4144 if (entry >= 0) {
4145 QAccessibleEvent event(this, QAccessible::SelectionAdd);
4146 event.setChild(entry);
4147 QAccessible::updateAccessibility(&event);
4148 }
4149 }
4150 QModelIndex desel = deselected.indexes().value(0);
4151 if (desel.isValid()) {
4152 int entry = d->accessibleChildIndex(desel);
4153 if (entry >= 0) {
4154 QAccessibleEvent event(this, QAccessible::SelectionRemove);
4155 event.setChild(entry);
4156 QAccessible::updateAccessibility(&event);
4157 }
4158 }
4159 }
4160#endif
4161}
4162
4163int QTreeView::visualIndex(const QModelIndex &index) const
4164{
4165 Q_D(const QTreeView);
4166 d->executePostedLayout();
4167 return d->viewIndex(index);
4168}
4169
4170/*!
4171 \internal
4172*/
4173
4174void QTreeView::verticalScrollbarValueChanged(int value)
4175{
4176 Q_D(QTreeView);
4177 if (!d->viewItems.isEmpty() && value == verticalScrollBar()->maximum()) {
4178 QModelIndex ret = d->viewItems.last().index;
4179 // Root index will be handled by base class implementation
4180 while (ret.isValid()) {
4181 if (isExpanded(ret) && d->model->canFetchMore(ret)) {
4182 d->model->fetchMore(ret);
4183 break;
4184 }
4185 ret = ret.parent();
4186 }
4187 }
4188 QAbstractItemView::verticalScrollbarValueChanged(value);
4189}
4190
4191QT_END_NAMESPACE
4192
4193#include "moc_qtreeview.cpp"
static bool ancestorOf(QObject *widget, QObject *other)