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