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 return d->modelIndex(d->below(vi), current.column());
2303 case MovePrevious:
2304 case MoveUp:
2305 return d->modelIndex(d->above(vi), current.column());
2306 case MoveLeft: {
2307 QScrollBar *sb = horizontalScrollBar();
2308 if (vi < d->viewItems.size() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) {
2309 d->collapse(vi, true);
2310 d->moveCursorUpdatedView = true;
2311 } else {
2312 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, nullptr, this);
2313 if (descend) {
2314 QModelIndex par = current.parent();
2315 if (par.isValid() && par != rootIndex())
2316 return par;
2317 else
2318 descend = false;
2319 }
2320 if (!descend) {
2321 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2322 int visualColumn = d->header->visualIndex(current.column()) - 1;
2323 while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn)))
2324 visualColumn--;
2325 int newColumn = d->header->logicalIndex(visualColumn);
2326 QModelIndex next = current.sibling(current.row(), newColumn);
2327 if (next.isValid())
2328 return next;
2329 }
2330
2331 int oldValue = sb->value();
2332 sb->setValue(sb->value() - sb->singleStep());
2333 if (oldValue != sb->value())
2334 d->moveCursorUpdatedView = true;
2335 }
2336
2337 }
2338 updateGeometries();
2339 viewport()->update();
2340 break;
2341 }
2342 case MoveRight:
2343 if (vi < d->viewItems.size() && !d->viewItems.at(vi).expanded && d->itemsExpandable
2344 && d->hasVisibleChildren(d->viewItems.at(vi).index)) {
2345 d->expand(vi, true);
2346 d->moveCursorUpdatedView = true;
2347 } else {
2348 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, nullptr, this);
2349 if (descend) {
2350 QModelIndex idx = d->modelIndex(d->below(vi));
2351 if (idx.parent() == current)
2352 return idx;
2353 else
2354 descend = false;
2355 }
2356 if (!descend) {
2357 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2358 int visualColumn = d->header->visualIndex(current.column()) + 1;
2359 while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn)))
2360 visualColumn++;
2361 const int newColumn = d->header->logicalIndex(visualColumn);
2362 const QModelIndex next = current.sibling(current.row(), newColumn);
2363 if (next.isValid())
2364 return next;
2365 }
2366
2367 //last restort: we change the scrollbar value
2368 QScrollBar *sb = horizontalScrollBar();
2369 int oldValue = sb->value();
2370 sb->setValue(sb->value() + sb->singleStep());
2371 if (oldValue != sb->value())
2372 d->moveCursorUpdatedView = true;
2373 }
2374 }
2375 updateGeometries();
2376 viewport()->update();
2377 break;
2378 case MovePageUp:
2379 return d->modelIndex(d->pageUp(vi), current.column());
2380 case MovePageDown:
2381 return d->modelIndex(d->pageDown(vi), current.column());
2382 case MoveHome:
2383 return d->modelIndex(d->itemForKeyHome(), current.column());
2384 case MoveEnd:
2385 return d->modelIndex(d->itemForKeyEnd(), current.column());
2386 }
2387 return current;
2388}
2389
2390/*!
2391 Applies the selection \a command to the items in or touched by the
2392 rectangle, \a rect.
2393
2394 \sa selectionCommand()
2395*/
2396void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2397{
2398 Q_D(QTreeView);
2399 if (!selectionModel() || rect.isNull())
2400 return;
2401
2402 d->executePostedLayout();
2403 QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right())
2404 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()));
2405 QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2406 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()));
2407 QModelIndex topLeft = indexAt(tl);
2408 QModelIndex bottomRight = indexAt(br);
2409 if (!topLeft.isValid() && !bottomRight.isValid()) {
2410 if (command & QItemSelectionModel::Clear)
2411 selectionModel()->clear();
2412 return;
2413 }
2414 if (!topLeft.isValid() && !d->viewItems.isEmpty())
2415 topLeft = d->viewItems.constFirst().index;
2416 if (!bottomRight.isValid() && !d->viewItems.isEmpty()) {
2417 const int column = d->header->logicalIndex(d->header->count() - 1);
2418 const QModelIndex index = d->viewItems.constLast().index;
2419 bottomRight = index.sibling(index.row(), column);
2420 }
2421
2422 if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight))
2423 return;
2424
2425 d->select(topLeft, bottomRight, command);
2426}
2427
2428/*!
2429 Returns the rectangle from the viewport of the items in the given
2430 \a selection.
2431
2432 Since 4.7, the returned region only contains rectangles intersecting
2433 (or included in) the viewport.
2434*/
2435QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const
2436{
2437 Q_D(const QTreeView);
2438 if (selection.isEmpty())
2439 return QRegion();
2440
2441 QRegion selectionRegion;
2442 const QRect &viewportRect = d->viewport->rect();
2443 for (const auto &range : selection) {
2444 if (!range.isValid())
2445 continue;
2446 QModelIndex parent = range.parent();
2447 QModelIndex leftIndex = range.topLeft();
2448 int columnCount = d->model->columnCount(parent);
2449 while (leftIndex.isValid() && isIndexHidden(leftIndex)) {
2450 if (leftIndex.column() + 1 < columnCount)
2451 leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent);
2452 else
2453 leftIndex = QModelIndex();
2454 }
2455 if (!leftIndex.isValid())
2456 continue;
2457 const QRect leftRect = d->visualRect(leftIndex, QTreeViewPrivate::SingleSection);
2458 int top = leftRect.top();
2459 QModelIndex rightIndex = range.bottomRight();
2460 while (rightIndex.isValid() && isIndexHidden(rightIndex)) {
2461 if (rightIndex.column() - 1 >= 0)
2462 rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent);
2463 else
2464 rightIndex = QModelIndex();
2465 }
2466 if (!rightIndex.isValid())
2467 continue;
2468 const QRect rightRect = d->visualRect(rightIndex, QTreeViewPrivate::SingleSection);
2469 int bottom = rightRect.bottom();
2470 if (top > bottom)
2471 qSwap<int>(top, bottom);
2472 int height = bottom - top + 1;
2473 if (d->header->sectionsMoved()) {
2474 for (int c = range.left(); c <= range.right(); ++c) {
2475 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2476 if (viewportRect.intersects(rangeRect))
2477 selectionRegion += rangeRect;
2478 }
2479 } else {
2480 QRect combined = leftRect|rightRect;
2481 combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left()));
2482 if (viewportRect.intersects(combined))
2483 selectionRegion += combined;
2484 }
2485 }
2486 return selectionRegion;
2487}
2488
2489/*!
2490 \reimp
2491*/
2492QModelIndexList QTreeView::selectedIndexes() const
2493{
2494 QModelIndexList viewSelected;
2495 QModelIndexList modelSelected;
2496 if (selectionModel())
2497 modelSelected = selectionModel()->selectedIndexes();
2498 for (int i = 0; i < modelSelected.size(); ++i) {
2499 // check that neither the parents nor the index is hidden before we add
2500 QModelIndex index = modelSelected.at(i);
2501 while (index.isValid() && !isIndexHidden(index))
2502 index = index.parent();
2503 if (index.isValid())
2504 continue;
2505 viewSelected.append(modelSelected.at(i));
2506 }
2507 return viewSelected;
2508}
2509
2510/*!
2511 Scrolls the contents of the tree view by (\a dx, \a dy).
2512*/
2513void QTreeView::scrollContentsBy(int dx, int dy)
2514{
2515 Q_D(QTreeView);
2516
2517 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
2518
2519 dx = isRightToLeft() ? -dx : dx;
2520 if (dx) {
2521 int oldOffset = d->header->offset();
2522 d->header->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
2523 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2524 int newOffset = d->header->offset();
2525 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
2526 }
2527 }
2528
2529 const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight;
2530 if (d->viewItems.isEmpty() || itemHeight == 0)
2531 return;
2532
2533 // guestimate the number of items in the viewport
2534 int viewCount = d->viewport->height() / itemHeight;
2535 int maxDeltaY = qMin(d->viewItems.size(), viewCount);
2536 // no need to do a lot of work if we are going to redraw the whole thing anyway
2537 if (qAbs(dy) > qAbs(maxDeltaY) && d->editorIndexHash.isEmpty()) {
2538 verticalScrollBar()->update();
2539 d->viewport->update();
2540 return;
2541 }
2542
2543 if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2544 int currentScrollbarValue = verticalScrollBar()->value();
2545 int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy)
2546 int currentViewIndex = currentScrollbarValue; // the first visible item
2547 int previousViewIndex = previousScrollbarValue;
2548 dy = 0;
2549 if (previousViewIndex < currentViewIndex) { // scrolling down
2550 for (int i = previousViewIndex; i < currentViewIndex; ++i) {
2551 if (i < d->viewItems.size())
2552 dy -= d->itemHeight(i);
2553 }
2554 } else if (previousViewIndex > currentViewIndex) { // scrolling up
2555 for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) {
2556 if (i < d->viewItems.size())
2557 dy += d->itemHeight(i);
2558 }
2559 }
2560 }
2561
2562 d->scrollContentsBy(dx, dy);
2563}
2564
2565/*!
2566 This slot is called whenever a column has been moved.
2567*/
2568void QTreeView::columnMoved()
2569{
2570 Q_D(QTreeView);
2571 updateEditorGeometries();
2572 d->viewport->update();
2573}
2574
2575/*!
2576 \internal
2577*/
2578void QTreeView::reexpand()
2579{
2580 // do nothing
2581}
2582
2583/*!
2584 Informs the view that the rows from the \a start row to the \a end row
2585 inclusive have been inserted into the \a parent model item.
2586*/
2587void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end)
2588{
2589 Q_D(QTreeView);
2590 // if we are going to do a complete relayout anyway, there is no need to update
2591 if (d->delayedPendingLayout) {
2592 QAbstractItemView::rowsInserted(parent, start, end);
2593 return;
2594 }
2595
2596 //don't add a hierarchy on a column != 0
2597 if (parent.column() != 0 && parent.isValid()) {
2598 QAbstractItemView::rowsInserted(parent, start, end);
2599 return;
2600 }
2601
2602 const int parentRowCount = d->model->rowCount(parent);
2603 const int delta = end - start + 1;
2604 if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) {
2605 QAbstractItemView::rowsInserted(parent, start, end);
2606 return;
2607 }
2608
2609 const int parentItem = d->viewIndex(parent);
2610 if (((parentItem != -1) && d->viewItems.at(parentItem).expanded)
2611 || (parent == d->root)) {
2612 d->doDelayedItemsLayout();
2613 } else if (parentItem != -1 && parentRowCount == delta) {
2614 // the parent just went from 0 children to more. update to re-paint the decoration
2615 d->viewItems[parentItem].hasChildren = true;
2616 viewport()->update();
2617 }
2618 QAbstractItemView::rowsInserted(parent, start, end);
2619}
2620
2621/*!
2622 Informs the view that the rows from the \a start row to the \a end row
2623 inclusive are about to removed from the given \a parent model item.
2624*/
2625void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
2626{
2627 Q_D(QTreeView);
2628 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
2629 d->viewItems.clear();
2630}
2631
2632/*!
2633 Informs the view that the rows from the \a start row to the \a end row
2634 inclusive have been removed from the given \a parent model item.
2635*/
2636void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end)
2637{
2638 Q_D(QTreeView);
2639 d->viewItems.clear();
2640 d->doDelayedItemsLayout();
2641 d->hasRemovedItems = true;
2642 d->rowsRemoved(parent, start, end);
2643}
2644
2645/*!
2646 Informs the tree view that the number of columns in the tree view has
2647 changed from \a oldCount to \a newCount.
2648*/
2649void QTreeView::columnCountChanged(int oldCount, int newCount)
2650{
2651 Q_D(QTreeView);
2652 if (oldCount == 0 && newCount > 0) {
2653 //if the first column has just been added we need to relayout.
2654 d->doDelayedItemsLayout();
2655 }
2656
2657 if (isVisible())
2658 updateGeometries();
2659 viewport()->update();
2660}
2661
2662/*!
2663 Resizes the \a column given to the size of its contents.
2664
2665 \sa columnWidth(), setColumnWidth(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
2666*/
2667void QTreeView::resizeColumnToContents(int column)
2668{
2669 Q_D(QTreeView);
2670 d->executePostedLayout();
2671 if (column < 0 || column >= d->header->count())
2672 return;
2673 int contents = sizeHintForColumn(column);
2674 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column);
2675 d->header->resizeSection(column, qMax(contents, header));
2676}
2677
2678/*!
2679 Sorts the model by the values in the given \a column and \a order.
2680
2681 \a column may be -1, in which case no sort indicator will be shown
2682 and the model will return to its natural, unsorted order. Note that not
2683 all models support this and may even crash in this case.
2684
2685 \sa sortingEnabled
2686*/
2687void QTreeView::sortByColumn(int column, Qt::SortOrder order)
2688{
2689 Q_D(QTreeView);
2690 if (column < -1)
2691 return;
2692 d->header->setSortIndicator(column, order);
2693 // If sorting is not enabled or has the same order as before, force to sort now
2694 // else sorting will be trigger through sortIndicatorChanged()
2695 if (!d->sortingEnabled ||
2696 (d->header->sortIndicatorSection() == column && d->header->sortIndicatorOrder() == order))
2697 d->model->sort(column, order);
2698}
2699
2700/*!
2701 \reimp
2702*/
2703void QTreeView::selectAll()
2704{
2705 Q_D(QTreeView);
2706 if (!selectionModel())
2707 return;
2708 SelectionMode mode = d->selectionMode;
2709 d->executePostedLayout(); //make sure we lay out the items
2710 if (mode != SingleSelection && mode != NoSelection && !d->viewItems.isEmpty()) {
2711 const QModelIndex &idx = d->viewItems.constLast().index;
2712 QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1);
2713 d->select(d->viewItems.constFirst().index, lastItemIndex,
2714 QItemSelectionModel::ClearAndSelect
2715 |QItemSelectionModel::Rows);
2716 }
2717}
2718
2719/*!
2720 \reimp
2721*/
2722QSize QTreeView::viewportSizeHint() const
2723{
2724 Q_D(const QTreeView);
2725 d->executePostedLayout(); // Make sure that viewItems are up to date.
2726
2727 if (d->viewItems.size() == 0)
2728 return QAbstractItemView::viewportSizeHint();
2729
2730 // Get rect for last item
2731 const QRect deepestRect = d->visualRect(d->viewItems.last().index,
2732 QTreeViewPrivate::SingleSection);
2733
2734 if (!deepestRect.isValid())
2735 return QAbstractItemView::viewportSizeHint();
2736
2737 QSize result = QSize(d->header->length(), deepestRect.bottom() + 1);
2738
2739 // add size for header
2740 result += QSize(0, d->header->isHidden() ? 0 : d->header->height());
2741
2742 return result;
2743}
2744
2745/*!
2746 Expands all expandable items.
2747
2748 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2749 data.
2750
2751 \warning If the model contains a large number of items,
2752 this function will take some time to execute.
2753
2754 \sa collapseAll(), expand(), collapse(), setExpanded()
2755*/
2756void QTreeView::expandAll()
2757{
2758 Q_D(QTreeView);
2759 d->viewItems.clear();
2760 d->interruptDelayedItemsLayout();
2761 d->layout(-1, true);
2762 updateGeometries();
2763 d->viewport->update();
2764 d->updateAccessibility();
2765}
2766
2767/*!
2768 \since 5.13
2769 Expands the item at the given \a index and all its children to the
2770 given \a depth. The \a depth is relative to the given \a index.
2771 A \a depth of -1 will expand all children, a \a depth of 0 will
2772 only expand the given \a index.
2773
2774 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2775 data.
2776
2777 \warning If the model contains a large number of items,
2778 this function will take some time to execute.
2779
2780 \sa expandAll()
2781*/
2782void QTreeView::expandRecursively(const QModelIndex &index, int depth)
2783{
2784 Q_D(QTreeView);
2785
2786 if (depth < -1)
2787 return;
2788 // do layouting only once after expanding is done
2789 d->doDelayedItemsLayout();
2790 expand(index);
2791 if (depth == 0)
2792 return;
2793 QStack<std::pair<QModelIndex, int>> parents;
2794 parents.push({index, 0});
2795 while (!parents.isEmpty()) {
2796 const std::pair<QModelIndex, int> elem = parents.pop();
2797 const QModelIndex &parent = elem.first;
2798 const int curDepth = elem.second;
2799 const int rowCount = d->model->rowCount(parent);
2800 for (int row = 0; row < rowCount; ++row) {
2801 const QModelIndex child = d->model->index(row, 0, parent);
2802 if (!d->isIndexValid(child))
2803 break;
2804 if (depth == -1 || curDepth + 1 < depth)
2805 parents.push({child, curDepth + 1});
2806 if (d->isIndexExpanded(child))
2807 continue;
2808 if (d->storeExpanded(child))
2809 emit expanded(child);
2810 }
2811 }
2812}
2813
2814/*!
2815 Collapses all expanded items.
2816
2817 \sa expandAll(), expand(), collapse(), setExpanded()
2818*/
2819void QTreeView::collapseAll()
2820{
2821 Q_D(QTreeView);
2822 QSet<QPersistentModelIndex> old_expandedIndexes;
2823 old_expandedIndexes = d->expandedIndexes;
2824 d->expandedIndexes.clear();
2825 if (!signalsBlocked() && isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed))) {
2826 QSet<QPersistentModelIndex>::const_iterator i = old_expandedIndexes.constBegin();
2827 for (; i != old_expandedIndexes.constEnd(); ++i) {
2828 const QPersistentModelIndex &mi = (*i);
2829 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2830 emit collapsed(mi);
2831 }
2832 }
2833 doItemsLayout();
2834}
2835
2836/*!
2837 Expands all expandable items to the given \a depth.
2838
2839 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2840 data.
2841
2842 \sa expandAll(), collapseAll(), expand(), collapse(), setExpanded()
2843*/
2844void QTreeView::expandToDepth(int depth)
2845{
2846 Q_D(QTreeView);
2847 d->viewItems.clear();
2848 QSet<QPersistentModelIndex> old_expandedIndexes;
2849 old_expandedIndexes = d->expandedIndexes;
2850 d->expandedIndexes.clear();
2851 d->interruptDelayedItemsLayout();
2852 d->layout(-1);
2853 for (int i = 0; i < d->viewItems.size(); ++i) {
2854 if (q20::cmp_less_equal(d->viewItems.at(i).level, depth)) {
2855 d->viewItems[i].expanded = true;
2856 d->layout(i);
2857 d->storeExpanded(d->viewItems.at(i).index);
2858 }
2859 }
2860
2861 bool someSignalEnabled = isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed));
2862 someSignalEnabled |= isSignalConnected(QMetaMethod::fromSignal(&QTreeView::expanded));
2863
2864 if (!signalsBlocked() && someSignalEnabled) {
2865 // emit signals
2866 QSet<QPersistentModelIndex> collapsedIndexes = old_expandedIndexes - d->expandedIndexes;
2867 QSet<QPersistentModelIndex>::const_iterator i = collapsedIndexes.constBegin();
2868 for (; i != collapsedIndexes.constEnd(); ++i) {
2869 const QPersistentModelIndex &mi = (*i);
2870 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2871 emit collapsed(mi);
2872 }
2873
2874 QSet<QPersistentModelIndex> expandedIndexs = d->expandedIndexes - old_expandedIndexes;
2875 i = expandedIndexs.constBegin();
2876 for (; i != expandedIndexs.constEnd(); ++i) {
2877 const QPersistentModelIndex &mi = (*i);
2878 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2879 emit expanded(mi);
2880 }
2881 }
2882
2883 updateGeometries();
2884 d->viewport->update();
2885 d->updateAccessibility();
2886}
2887
2888/*!
2889 This function is called whenever \a{column}'s size is changed in
2890 the header. \a oldSize and \a newSize give the previous size and
2891 the new size in pixels.
2892
2893 \sa setColumnWidth()
2894*/
2895void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */)
2896{
2897 Q_D(QTreeView);
2898 d->columnsToUpdate.append(column);
2899 if (!d->columnResizeTimer.isActive())
2900 d->columnResizeTimer.start(0ns, this);
2901}
2902
2903/*!
2904 \reimp
2905*/
2906void QTreeView::updateGeometries()
2907{
2908 Q_D(QTreeView);
2909 if (d->header) {
2910 if (d->geometryRecursionBlock)
2911 return;
2912 d->geometryRecursionBlock = true;
2913 int height = 0;
2914 if (!d->header->isHidden()) {
2915 height = qMax(d->header->minimumHeight(), d->header->sizeHint().height());
2916 height = qMin(height, d->header->maximumHeight());
2917 }
2918 setViewportMargins(0, height, 0, 0);
2919 QRect vg = d->viewport->geometry();
2920 QRect geometryRect(vg.left(), vg.top() - height, vg.width(), height);
2921 d->header->setGeometry(geometryRect);
2922 QMetaObject::invokeMethod(d->header, "updateGeometries");
2923 d->updateScrollBars();
2924 d->geometryRecursionBlock = false;
2925 }
2926 QAbstractItemView::updateGeometries();
2927}
2928
2929/*!
2930 Returns the size hint for the \a column's width or -1 if there is no
2931 model.
2932
2933 If you need to set the width of a given column to a fixed value, call
2934 QHeaderView::resizeSection() on the view's header.
2935
2936 If you reimplement this function in a subclass, note that the value you
2937 return is only used when resizeColumnToContents() is called. In that case,
2938 if a larger column width is required by either the view's header or
2939 the item delegate, that width will be used instead.
2940
2941 \sa QWidget::sizeHint, header(), QHeaderView::resizeContentsPrecision()
2942*/
2943int QTreeView::sizeHintForColumn(int column) const
2944{
2945 Q_D(const QTreeView);
2946 d->executePostedLayout();
2947 if (d->viewItems.isEmpty())
2948 return -1;
2949 ensurePolished();
2950 int w = 0;
2951 QStyleOptionViewItem option;
2952 initViewItemOption(&option);
2953 const QList<QTreeViewItem> viewItems = d->viewItems;
2954
2955 const int maximumProcessRows = d->header->resizeContentsPrecision(); // To avoid this to take forever.
2956
2957 int offset = 0;
2958 int start = d->firstVisibleItem(&offset);
2959 int end = d->lastVisibleItem(start, offset);
2960 if (start < 0 || end < 0 || end == viewItems.size() - 1) {
2961 end = viewItems.size() - 1;
2962 if (maximumProcessRows < 0) {
2963 start = 0;
2964 } else if (maximumProcessRows == 0) {
2965 start = qMax(0, end - 1);
2966 int remainingHeight = viewport()->height();
2967 while (start > 0 && remainingHeight > 0) {
2968 remainingHeight -= d->itemHeight(start);
2969 --start;
2970 }
2971 } else {
2972 start = qMax(0, end - maximumProcessRows);
2973 }
2974 }
2975
2976 int rowsProcessed = 0;
2977
2978 for (int i = start; i <= end; ++i) {
2979 if (viewItems.at(i).spanning)
2980 continue; // we have no good size hint
2981 QModelIndex index = viewItems.at(i).index;
2982 index = index.sibling(index.row(), column);
2983 w = d->widthHintForIndex(index, w, option, i);
2984 ++rowsProcessed;
2985 if (rowsProcessed == maximumProcessRows)
2986 break;
2987 }
2988
2989 --end;
2990 int actualBottom = viewItems.size() - 1;
2991
2992 if (maximumProcessRows == 0)
2993 rowsProcessed = 0; // skip the while loop
2994
2995 while (rowsProcessed != maximumProcessRows && (start > 0 || end < actualBottom)) {
2996 int idx = -1;
2997
2998 if ((rowsProcessed % 2 && start > 0) || end == actualBottom) {
2999 while (start > 0) {
3000 --start;
3001 if (viewItems.at(start).spanning)
3002 continue;
3003 idx = start;
3004 break;
3005 }
3006 } else {
3007 while (end < actualBottom) {
3008 ++end;
3009 if (viewItems.at(end).spanning)
3010 continue;
3011 idx = end;
3012 break;
3013 }
3014 }
3015 if (idx < 0)
3016 continue;
3017
3018 QModelIndex index = viewItems.at(idx).index;
3019 index = index.sibling(index.row(), column);
3020 w = d->widthHintForIndex(index, w, option, idx);
3021 ++rowsProcessed;
3022 }
3023 return w;
3024}
3025
3026/*!
3027 Returns the size hint for the row indicated by \a index.
3028
3029 \sa sizeHintForColumn(), uniformRowHeights()
3030*/
3031int QTreeView::indexRowSizeHint(const QModelIndex &index) const
3032{
3033 Q_D(const QTreeView);
3034 if (!d->isIndexValid(index) || !d->itemDelegate)
3035 return 0;
3036
3037 int start = -1;
3038 int end = -1;
3039 int indexRow = index.row();
3040 int count = d->header->count();
3041 bool emptyHeader = (count == 0);
3042 QModelIndex parent = index.parent();
3043
3044 if (count && isVisible()) {
3045 // If the sections have moved, we end up checking too many or too few
3046 start = d->header->visualIndexAt(0);
3047 } else {
3048 // If the header has not been laid out yet, we use the model directly
3049 count = d->model->columnCount(parent);
3050 }
3051
3052 if (isRightToLeft()) {
3053 start = (start == -1 ? count - 1 : start);
3054 end = 0;
3055 } else {
3056 start = (start == -1 ? 0 : start);
3057 end = count - 1;
3058 }
3059
3060 if (end < start)
3061 qSwap(end, start);
3062
3063 int height = -1;
3064 QStyleOptionViewItem option;
3065 initViewItemOption(&option);
3066 // ### If we want word wrapping in the items,
3067 // ### we need to go through all the columns
3068 // ### and set the width of the column
3069
3070 // Hack to speed up the function
3071 option.rect.setWidth(-1);
3072
3073 for (int column = start; column <= end; ++column) {
3074 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column);
3075 if (d->header->isSectionHidden(logicalColumn))
3076 continue;
3077 QModelIndex idx = d->model->index(indexRow, logicalColumn, parent);
3078 if (idx.isValid()) {
3079 QWidget *editor = d->editorForIndex(idx).widget.data();
3080 if (editor && d->persistent.contains(editor)) {
3081 height = qMax(height, editor->sizeHint().height());
3082 int min = editor->minimumSize().height();
3083 int max = editor->maximumSize().height();
3084 height = qBound(min, height, max);
3085 }
3086 int hint = itemDelegateForIndex(idx)->sizeHint(option, idx).height();
3087 height = qMax(height, hint);
3088 }
3089 }
3090
3091 return height;
3092}
3093
3094/*!
3095 Returns the height of the row indicated by the given \a index.
3096 \sa indexRowSizeHint()
3097*/
3098int QTreeView::rowHeight(const QModelIndex &index) const
3099{
3100 Q_D(const QTreeView);
3101 d->executePostedLayout();
3102 int i = d->viewIndex(index);
3103 if (i == -1)
3104 return 0;
3105 return d->itemHeight(i);
3106}
3107
3108/*!
3109 \internal
3110*/
3111void QTreeView::horizontalScrollbarAction(int action)
3112{
3113 QAbstractItemView::horizontalScrollbarAction(action);
3114}
3115
3116/*!
3117 \reimp
3118*/
3119bool QTreeView::isIndexHidden(const QModelIndex &index) const
3120{
3121 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent()));
3122}
3123
3124/*
3125 private implementation
3126*/
3127void QTreeViewPrivate::initialize()
3128{
3129 Q_Q(QTreeView);
3130
3131 updateIndentationFromStyle();
3132 updateStyledFrameWidths();
3133 q->setSelectionBehavior(QAbstractItemView::SelectRows);
3134 q->setSelectionMode(QAbstractItemView::SingleSelection);
3135 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
3136 q->setAttribute(Qt::WA_MacShowFocusRect);
3137
3138 QHeaderView *header = new QHeaderView(Qt::Horizontal, q);
3139 header->setSectionsMovable(true);
3140 header->setStretchLastSection(true);
3141 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
3142 q->setHeader(header);
3143#if QT_CONFIG(animation)
3144 animationsEnabled = q->style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, q) > 0;
3145 animationConnection =
3146 QObjectPrivate::connect(&animatedOperation, &QVariantAnimation::finished,
3147 this, &QTreeViewPrivate::endAnimatedOperation);
3148#endif // animation
3149}
3150
3151void QTreeViewPrivate::clearConnections()
3152{
3153 for (QMetaObject::Connection &connection : modelConnections)
3154 QObject::disconnect(connection);
3155 for (QMetaObject::Connection &connection : headerConnections)
3156 QObject::disconnect(connection);
3157 QObject::disconnect(selectionmodelConnection);
3158 QObject::disconnect(sortHeaderConnection);
3159#if QT_CONFIG(animation)
3160 QObject::disconnect(animationConnection);
3161#endif
3162}
3163
3164void QTreeViewPrivate::expand(int item, bool emitSignal)
3165{
3166 Q_Q(QTreeView);
3167
3168 if (item == -1 || viewItems.at(item).expanded)
3169 return;
3170 const QModelIndex index = viewItems.at(item).index;
3171 if (index.flags() & Qt::ItemNeverHasChildren)
3172 return;
3173
3174#if QT_CONFIG(animation)
3175 if (emitSignal && animationsEnabled)
3176 prepareAnimatedOperation(item, QVariantAnimation::Forward);
3177#endif // animation
3178 //if already animating, stateBeforeAnimation is set to the correct value
3179 if (state != QAbstractItemView::AnimatingState)
3180 stateBeforeAnimation = state;
3181 q->setState(QAbstractItemView::ExpandingState);
3182 storeExpanded(index);
3183 viewItems[item].expanded = true;
3184 layout(item);
3185 q->setState(stateBeforeAnimation);
3186
3187 if (model->canFetchMore(index))
3188 model->fetchMore(index);
3189 if (emitSignal) {
3190 emit q->expanded(index);
3191#if QT_CONFIG(animation)
3192 if (animationsEnabled)
3193 beginAnimatedOperation();
3194#endif // animation
3195 }
3196 updateAccessibility();
3197}
3198
3199void QTreeViewPrivate::insertViewItems(int pos, int count, const QTreeViewItem &viewItem)
3200{
3201 viewItems.insert(pos, count, viewItem);
3202 QTreeViewItem *items = viewItems.data();
3203 for (int i = pos + count; i < viewItems.size(); i++)
3204 if (items[i].parentItem >= pos)
3205 items[i].parentItem += count;
3206}
3207
3208void QTreeViewPrivate::removeViewItems(int pos, int count)
3209{
3210 viewItems.remove(pos, count);
3211 QTreeViewItem *items = viewItems.data();
3212 for (int i = pos; i < viewItems.size(); i++)
3213 if (items[i].parentItem >= pos)
3214 items[i].parentItem -= count;
3215}
3216
3217void QTreeViewPrivate::collapse(int item, bool emitSignal)
3218{
3219 Q_Q(QTreeView);
3220
3221 if (item == -1 || expandedIndexes.isEmpty())
3222 return;
3223
3224 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
3225 delayedAutoScroll.stop();
3226
3227 int total = viewItems.at(item).total;
3228 const QModelIndex &modelIndex = viewItems.at(item).index;
3229 if (!isPersistent(modelIndex))
3230 return; // if the index is not persistent, no chances it is expanded
3231 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex);
3232 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false)
3233 return; // nothing to do
3234
3235#if QT_CONFIG(animation)
3236 if (emitSignal && animationsEnabled)
3237 prepareAnimatedOperation(item, QVariantAnimation::Backward);
3238#endif // animation
3239
3240 //if already animating, stateBeforeAnimation is set to the correct value
3241 if (state != QAbstractItemView::AnimatingState)
3242 stateBeforeAnimation = state;
3243 q->setState(QAbstractItemView::CollapsingState);
3244 expandedIndexes.erase(it);
3245 viewItems[item].expanded = false;
3246 int index = item;
3247 while (index > -1) {
3248 viewItems[index].total -= total;
3249 index = viewItems[index].parentItem;
3250 }
3251 removeViewItems(item + 1, total); // collapse
3252 q->setState(stateBeforeAnimation);
3253
3254 if (emitSignal) {
3255 emit q->collapsed(modelIndex);
3256#if QT_CONFIG(animation)
3257 if (animationsEnabled)
3258 beginAnimatedOperation();
3259#endif // animation
3260 }
3261}
3262
3263#if QT_CONFIG(animation)
3264void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction)
3265{
3266 animatedOperation.item = item;
3267 animatedOperation.viewport = viewport;
3268 animatedOperation.setDirection(direction);
3269
3270 int top = coordinateForItem(item) + itemHeight(item);
3271 QRect rect = viewport->rect();
3272 rect.setTop(top);
3273 if (direction == QVariantAnimation::Backward) {
3274 const int limit = rect.height() * 2;
3275 int h = 0;
3276 int c = item + viewItems.at(item).total + 1;
3277 for (int i = item + 1; i < c && h < limit; ++i)
3278 h += itemHeight(i);
3279 rect.setHeight(h);
3280 animatedOperation.setEndValue(top + h);
3281 }
3282 animatedOperation.setStartValue(top);
3283 animatedOperation.before = renderTreeToPixmapForAnimation(rect);
3284}
3285
3286void QTreeViewPrivate::beginAnimatedOperation()
3287{
3288 Q_Q(QTreeView);
3289
3290 QRect rect = viewport->rect();
3291 rect.setTop(animatedOperation.top());
3292 if (animatedOperation.direction() == QVariantAnimation::Forward) {
3293 const int limit = rect.height() * 2;
3294 int h = 0;
3295 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1;
3296 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i)
3297 h += itemHeight(i);
3298 rect.setHeight(h);
3299 animatedOperation.setEndValue(animatedOperation.top() + h);
3300 }
3301
3302 if (!rect.isEmpty()) {
3303 animatedOperation.after = renderTreeToPixmapForAnimation(rect);
3304
3305 q->setState(QAbstractItemView::AnimatingState);
3306 animatedOperation.start(); //let's start the animation
3307 }
3308}
3309
3310void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const
3311{
3312 const int start = animatedOperation.startValue().toInt(),
3313 end = animatedOperation.endValue().toInt(),
3314 current = animatedOperation.currentValue().toInt();
3315 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward;
3316 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after;
3317 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height());
3318 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before;
3319 painter->drawPixmap(0, current, bottom);
3320}
3321
3322QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const
3323{
3324 Q_Q(const QTreeView);
3325 QPixmap pixmap(rect.size() * q->devicePixelRatio());
3326 pixmap.setDevicePixelRatio(q->devicePixelRatio());
3327 if (rect.size().isEmpty())
3328 return pixmap;
3329 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels.
3330 QPainter painter(&pixmap);
3331 painter.setLayoutDirection(q->layoutDirection());
3332 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base());
3333 painter.translate(0, -rect.top());
3334 q->drawTree(&painter, QRegion(rect));
3335 painter.end();
3336
3337 //and now let's render the editors the editors
3338 QStyleOptionViewItem option;
3339 q->initViewItemOption(&option);
3340 for (QEditorIndexHash::const_iterator it = editorIndexHash.constBegin(); it != editorIndexHash.constEnd(); ++it) {
3341 QWidget *editor = it.key();
3342 const QModelIndex &index = it.value();
3343 option.rect = visualRect(index, SingleSection);
3344 if (option.rect.isValid()) {
3345
3346 if (QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index))
3347 delegate->updateEditorGeometry(editor, option, index);
3348
3349 const QPoint pos = editor->pos();
3350 if (rect.contains(pos)) {
3351 editor->render(&pixmap, pos - rect.topLeft());
3352 //the animation uses pixmap to display the treeview's content
3353 //the editor is rendered on this pixmap and thus can (should) be hidden
3354 editor->hide();
3355 }
3356 }
3357 }
3358
3359
3360 return pixmap;
3361}
3362
3363void QTreeViewPrivate::endAnimatedOperation()
3364{
3365 Q_Q(QTreeView);
3366 q->setState(stateBeforeAnimation);
3367 q->updateGeometries();
3368 viewport->update();
3369}
3370#endif // animation
3371
3372void QTreeViewPrivate::modelAboutToBeReset()
3373{
3374 viewItems.clear();
3375}
3376
3377void QTreeViewPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
3378{
3379 if (start <= 0 && 0 <= end)
3380 viewItems.clear();
3381 QAbstractItemViewPrivate::columnsAboutToBeRemoved(parent, start, end);
3382}
3383
3384void QTreeViewPrivate::columnsRemoved(const QModelIndex &parent, int start, int end)
3385{
3386 if (start <= 0 && 0 <= end)
3387 doDelayedItemsLayout();
3388 QAbstractItemViewPrivate::columnsRemoved(parent, start, end);
3389}
3390
3391void QTreeViewPrivate::updateAccessibility()
3392{
3393#if QT_CONFIG(accessibility)
3394 Q_Q(QTreeView);
3395 if (pendingAccessibilityUpdate) {
3396 pendingAccessibilityUpdate = false;
3397 if (QAccessible::isActive()) {
3398 QAccessibleTableModelChangeEvent event(q, QAccessibleTableModelChangeEvent::ModelReset);
3399 QAccessible::updateAccessibility(&event);
3400 }
3401 }
3402#endif
3403}
3404
3405
3406/** \internal
3407 creates and initialize the viewItem structure of the children of the element \li
3408
3409 set \a recursiveExpanding if the function has to expand all the children (called from expandAll)
3410 \a afterIsUninitialized is when we recurse from layout(-1), it means all the items after 'i' are
3411 not yet initialized and need not to be moved
3412 */
3413void QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninitialized)
3414{
3415 Q_Q(QTreeView);
3416 QModelIndex current;
3417 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i);
3418
3419 if (i>=0 && !parent.isValid()) {
3420 //modelIndex() should never return something invalid for the real items.
3421 //This can happen if columncount has been set to 0.
3422 //To avoid infinite loop we stop here.
3423 return;
3424 }
3425
3426#if QT_CONFIG(accessibility)
3427 // QAccessibleTree's rowCount implementation uses viewItems.size(), so
3428 // we need to invalidate any cached accessibility data structures if
3429 // that value changes during the run of this function.
3430 const auto resetModelIfNeeded = qScopeGuard([oldViewItemsSize = viewItems.size(), this]{
3431 pendingAccessibilityUpdate |= oldViewItemsSize != viewItems.size();
3432 });
3433#endif
3434
3435 int count = 0;
3436 if (model->hasChildren(parent)) {
3437 if (model->canFetchMore(parent)) {
3438 // fetchMore first, otherwise we might not yet have any data for sizeHintForRow
3439 model->fetchMore(parent);
3440 // guestimate the number of items in the viewport, and fetch as many as might fit
3441 const int itemHeight = defaultItemHeight <= 0 ? q->sizeHintForRow(0) : defaultItemHeight;
3442 const int viewCount = itemHeight ? viewport->height() / itemHeight : 0;
3443 int lastCount = -1;
3444 while ((count = model->rowCount(parent)) < viewCount &&
3445 count != lastCount && model->canFetchMore(parent)) {
3446 model->fetchMore(parent);
3447 lastCount = count;
3448 }
3449 } else {
3450 count = model->rowCount(parent);
3451 }
3452 }
3453
3454 bool expanding = true;
3455 if (i == -1) {
3456 if (uniformRowHeights) {
3457 QModelIndex index = model->index(0, 0, parent);
3458 defaultItemHeight = q->indexRowSizeHint(index);
3459 }
3460 viewItems.resize(count);
3461 afterIsUninitialized = true;
3462 } else if (q20::cmp_not_equal(viewItems[i].total, count)) {
3463 if (!afterIsUninitialized)
3464 insertViewItems(i + 1, count, QTreeViewItem()); // expand
3465 else if (count > 0)
3466 viewItems.resize(viewItems.size() + count);
3467 } else {
3468 expanding = false;
3469 }
3470
3471 int first = i + 1;
3472 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0);
3473 int hidden = 0;
3474 int last = 0;
3475 int children = 0;
3476 QTreeViewItem *item = nullptr;
3477 for (int j = first; j < first + count; ++j) {
3478 current = model->index(j - first, 0, parent);
3479 if (isRowHidden(current)) {
3480 ++hidden;
3481 last = j - hidden + children;
3482 } else {
3483 last = j - hidden + children;
3484 if (item)
3485 item->hasMoreSiblings = true;
3486 item = &viewItems[last];
3487 item->index = current;
3488 item->parentItem = i;
3489 item->level = level;
3490 item->height = 0;
3491 item->spanning = q->isFirstColumnSpanned(current.row(), parent);
3492 item->expanded = false;
3493 item->total = 0;
3494 item->hasMoreSiblings = false;
3495 if ((recursiveExpanding && !(current.flags() & Qt::ItemNeverHasChildren)) || isIndexExpanded(current)) {
3496 if (recursiveExpanding && storeExpanded(current) && !q->signalsBlocked())
3497 emit q->expanded(current);
3498 item->expanded = true;
3499 layout(last, recursiveExpanding, afterIsUninitialized);
3500 item = &viewItems[last];
3501 children += item->total;
3502 item->hasChildren = item->total > 0;
3503 last = j - hidden + children;
3504 } else {
3505 item->hasChildren = hasVisibleChildren(current);
3506 }
3507 }
3508 }
3509
3510 // remove hidden items
3511 if (hidden > 0) {
3512 if (!afterIsUninitialized)
3513 removeViewItems(last + 1, hidden);
3514 else
3515 viewItems.resize(viewItems.size() - hidden);
3516 }
3517
3518 if (!expanding)
3519 return; // nothing changed
3520
3521 while (i > -1) {
3522 viewItems[i].total += count - hidden;
3523 i = viewItems[i].parentItem;
3524 }
3525}
3526
3527int QTreeViewPrivate::pageUp(int i) const
3528{
3529 int index = itemAtCoordinate(coordinateForItem(i) - viewport->height());
3530 while (isItemHiddenOrDisabled(index))
3531 index--;
3532 if (index == -1)
3533 index = 0;
3534 while (isItemHiddenOrDisabled(index))
3535 index++;
3536 return index >= viewItems.size() ? 0 : index;
3537}
3538
3539int QTreeViewPrivate::pageDown(int i) const
3540{
3541 int index = itemAtCoordinate(coordinateForItem(i) + viewport->height());
3542 while (isItemHiddenOrDisabled(index))
3543 index++;
3544 if (index == -1 || index >= viewItems.size())
3545 index = viewItems.size() - 1;
3546 while (isItemHiddenOrDisabled(index))
3547 index--;
3548 return index == -1 ? viewItems.size() - 1 : index;
3549}
3550
3551int QTreeViewPrivate::itemForKeyHome() const
3552{
3553 int index = 0;
3554 while (isItemHiddenOrDisabled(index))
3555 index++;
3556 return index >= viewItems.size() ? 0 : index;
3557}
3558
3559int QTreeViewPrivate::itemForKeyEnd() const
3560{
3561 int index = viewItems.size() - 1;
3562 while (isItemHiddenOrDisabled(index))
3563 index--;
3564 return index == -1 ? viewItems.size() - 1 : index;
3565}
3566
3567int QTreeViewPrivate::indentationForItem(int item) const
3568{
3569 if (item < 0 || item >= viewItems.size())
3570 return 0;
3571 int level = viewItems.at(item).level;
3572 if (rootDecoration)
3573 ++level;
3574 return level * indent;
3575}
3576
3577int QTreeViewPrivate::itemHeight(int item) const
3578{
3579 Q_ASSERT(item < viewItems.size());
3580 if (uniformRowHeights)
3581 return defaultItemHeight;
3582 if (viewItems.isEmpty())
3583 return 0;
3584 const QModelIndex &index = viewItems.at(item).index;
3585 if (!index.isValid())
3586 return 0;
3587 int height = viewItems.at(item).height;
3588 if (height <= 0) {
3589 height = q_func()->indexRowSizeHint(index);
3590 viewItems[item].height = height;
3591 }
3592 return qMax(height, 0);
3593}
3594
3595
3596/*!
3597 \internal
3598 Returns the viewport y coordinate for \a item.
3599*/
3600int QTreeViewPrivate::coordinateForItem(int item) const
3601{
3602 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3603 if (uniformRowHeights)
3604 return (item * defaultItemHeight) - vbar->value();
3605 // ### optimize (maybe do like QHeaderView by letting items have startposition)
3606 int y = 0;
3607 for (int i = 0; i < viewItems.size(); ++i) {
3608 if (i == item)
3609 return y - vbar->value();
3610 y += itemHeight(i);
3611 }
3612 } else { // ScrollPerItem
3613 int topViewItemIndex = vbar->value();
3614 if (uniformRowHeights)
3615 return defaultItemHeight * (item - topViewItemIndex);
3616 if (item >= topViewItemIndex) {
3617 // search in the visible area first and continue down
3618 // ### slow if the item is not visible
3619 int viewItemCoordinate = 0;
3620 int viewItemIndex = topViewItemIndex;
3621 while (viewItemIndex < viewItems.size()) {
3622 if (viewItemIndex == item)
3623 return viewItemCoordinate;
3624 viewItemCoordinate += itemHeight(viewItemIndex);
3625 ++viewItemIndex;
3626 }
3627 // below the last item in the view
3628 Q_ASSERT(false);
3629 return viewItemCoordinate;
3630 } else {
3631 // search the area above the viewport (used for editor widgets)
3632 int viewItemCoordinate = 0;
3633 for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) {
3634 if (viewItemIndex == item)
3635 return viewItemCoordinate;
3636 viewItemCoordinate -= itemHeight(viewItemIndex - 1);
3637 }
3638 return viewItemCoordinate;
3639 }
3640 }
3641 return 0;
3642}
3643
3644/*!
3645 \internal
3646 Returns the index of the view item at the
3647 given viewport \a coordinate.
3648
3649 \sa modelIndex()
3650*/
3651int QTreeViewPrivate::itemAtCoordinate(int coordinate) const
3652{
3653 const int itemCount = viewItems.size();
3654 if (itemCount == 0)
3655 return -1;
3656 if (uniformRowHeights && defaultItemHeight <= 0)
3657 return -1;
3658 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3659 if (uniformRowHeights) {
3660 const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight;
3661 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3662 }
3663 // ### optimize
3664 int viewItemCoordinate = 0;
3665 const int contentsCoordinate = coordinate + vbar->value();
3666 for (int viewItemIndex = 0; viewItemIndex < viewItems.size(); ++viewItemIndex) {
3667 viewItemCoordinate += itemHeight(viewItemIndex);
3668 if (viewItemCoordinate > contentsCoordinate)
3669 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3670 }
3671 } else { // ScrollPerItem
3672 int topViewItemIndex = vbar->value();
3673 if (uniformRowHeights) {
3674 if (coordinate < 0)
3675 coordinate -= defaultItemHeight - 1;
3676 const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight);
3677 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3678 }
3679 if (coordinate >= 0) {
3680 // the coordinate is in or below the viewport
3681 int viewItemCoordinate = 0;
3682 for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.size(); ++viewItemIndex) {
3683 viewItemCoordinate += itemHeight(viewItemIndex);
3684 if (viewItemCoordinate > coordinate)
3685 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3686 }
3687 } else {
3688 // the coordinate is above the viewport
3689 int viewItemCoordinate = 0;
3690 for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) {
3691 if (viewItemCoordinate <= coordinate)
3692 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3693 viewItemCoordinate -= itemHeight(viewItemIndex);
3694 }
3695 }
3696 }
3697 return -1;
3698}
3699
3700int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const
3701{
3702 if (!_index.isValid() || viewItems.isEmpty())
3703 return -1;
3704
3705 const int totalCount = viewItems.size();
3706 const QModelIndex index = _index.sibling(_index.row(), 0);
3707 const int row = index.row();
3708 const quintptr internalId = index.internalId();
3709
3710 // We start nearest to the lastViewedItem
3711 int localCount = qMin(lastViewedItem - 1, totalCount - lastViewedItem);
3712 for (int i = 0; i < localCount; ++i) {
3713 const QModelIndex &idx1 = viewItems.at(lastViewedItem + i).index;
3714 if (idx1.row() == row && idx1.internalId() == internalId) {
3715 lastViewedItem = lastViewedItem + i;
3716 return lastViewedItem;
3717 }
3718 const QModelIndex &idx2 = viewItems.at(lastViewedItem - i - 1).index;
3719 if (idx2.row() == row && idx2.internalId() == internalId) {
3720 lastViewedItem = lastViewedItem - i - 1;
3721 return lastViewedItem;
3722 }
3723 }
3724
3725 for (int j = qMax(0, lastViewedItem + localCount); j < totalCount; ++j) {
3726 const QModelIndex &idx = viewItems.at(j).index;
3727 if (idx.row() == row && idx.internalId() == internalId) {
3728 lastViewedItem = j;
3729 return j;
3730 }
3731 }
3732 for (int j = qMin(totalCount, lastViewedItem - localCount) - 1; j >= 0; --j) {
3733 const QModelIndex &idx = viewItems.at(j).index;
3734 if (idx.row() == row && idx.internalId() == internalId) {
3735 lastViewedItem = j;
3736 return j;
3737 }
3738 }
3739
3740 // nothing found
3741 return -1;
3742}
3743
3744QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const
3745{
3746 if (i < 0 || i >= viewItems.size())
3747 return QModelIndex();
3748
3749 QModelIndex ret = viewItems.at(i).index;
3750 if (column)
3751 ret = ret.sibling(ret.row(), column);
3752 return ret;
3753}
3754
3755int QTreeViewPrivate::firstVisibleItem(int *offset) const
3756{
3757 const int value = vbar->value();
3758 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3759 if (offset)
3760 *offset = 0;
3761 return (value < 0 || value >= viewItems.size()) ? -1 : value;
3762 }
3763 // ScrollMode == ScrollPerPixel
3764 if (uniformRowHeights) {
3765 if (!defaultItemHeight)
3766 return -1;
3767
3768 if (offset)
3769 *offset = -(value % defaultItemHeight);
3770 return value / defaultItemHeight;
3771 }
3772 int y = 0; // ### (maybe do like QHeaderView by letting items have startposition)
3773 for (int i = 0; i < viewItems.size(); ++i) {
3774 y += itemHeight(i); // the height value is cached
3775 if (y > value) {
3776 if (offset)
3777 *offset = y - value - itemHeight(i);
3778 return i;
3779 }
3780 }
3781 return -1;
3782}
3783
3784int QTreeViewPrivate::lastVisibleItem(int firstVisual, int offset) const
3785{
3786 if (firstVisual < 0 || offset < 0) {
3787 firstVisual = firstVisibleItem(&offset);
3788 if (firstVisual < 0)
3789 return -1;
3790 }
3791 int y = - offset;
3792 int value = viewport->height();
3793
3794 for (int i = firstVisual; i < viewItems.size(); ++i) {
3795 y += itemHeight(i); // the height value is cached
3796 if (y > value)
3797 return i;
3798 }
3799 return viewItems.size() - 1;
3800}
3801
3802int QTreeViewPrivate::columnAt(int x) const
3803{
3804 return header->logicalIndexAt(x);
3805}
3806
3807void QTreeViewPrivate::updateScrollBars()
3808{
3809 Q_Q(QTreeView);
3810 QSize viewportSize = viewport->size();
3811 if (!viewportSize.isValid())
3812 viewportSize = QSize(0, 0);
3813
3814 executePostedLayout();
3815 if (viewItems.isEmpty()) {
3816 q->doItemsLayout();
3817 }
3818
3819 int itemsInViewport = 0;
3820 if (uniformRowHeights) {
3821 if (defaultItemHeight <= 0)
3822 itemsInViewport = viewItems.size();
3823 else
3824 itemsInViewport = viewportSize.height() / defaultItemHeight;
3825 } else {
3826 const int itemsCount = viewItems.size();
3827 const int viewportHeight = viewportSize.height();
3828 for (int height = 0, item = itemsCount - 1; item >= 0; --item) {
3829 height += itemHeight(item);
3830 if (height > viewportHeight)
3831 break;
3832 ++itemsInViewport;
3833 }
3834 }
3835 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3836 if (!viewItems.isEmpty())
3837 itemsInViewport = qMax(1, itemsInViewport);
3838 vbar->setRange(0, viewItems.size() - itemsInViewport);
3839 vbar->setPageStep(itemsInViewport);
3840 vbar->setSingleStep(1);
3841 } else { // scroll per pixel
3842 int contentsHeight = 0;
3843 if (uniformRowHeights) {
3844 contentsHeight = defaultItemHeight * viewItems.size();
3845 } else { // ### (maybe do like QHeaderView by letting items have startposition)
3846 for (int i = 0; i < viewItems.size(); ++i)
3847 contentsHeight += itemHeight(i);
3848 }
3849 vbar->setRange(0, contentsHeight - viewportSize.height());
3850 vbar->setPageStep(viewportSize.height());
3851 vbar->d_func()->itemviewChangeSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2));
3852 }
3853
3854 const int columnCount = header->count();
3855 const int viewportWidth = viewportSize.width();
3856 int columnsInViewport = 0;
3857 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
3858 int logical = header->logicalIndex(column);
3859 width += header->sectionSize(logical);
3860 if (width > viewportWidth)
3861 break;
3862 ++columnsInViewport;
3863 }
3864 if (columnCount > 0)
3865 columnsInViewport = qMax(1, columnsInViewport);
3866 if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) {
3867 hbar->setRange(0, columnCount - columnsInViewport);
3868 hbar->setPageStep(columnsInViewport);
3869 hbar->setSingleStep(1);
3870 } else { // scroll per pixel
3871 const int horizontalLength = header->length();
3872 const QSize maxSize = q->maximumViewportSize();
3873 if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0)
3874 viewportSize = maxSize;
3875 hbar->setPageStep(viewportSize.width());
3876 hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0));
3877 hbar->d_func()->itemviewChangeSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2));
3878 }
3879}
3880
3881int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const
3882{
3883 Q_Q(const QTreeView);
3884 executePostedLayout();
3885 bool spanned = false;
3886 if (!spanningIndexes.isEmpty()) {
3887 const QModelIndex index = q->indexAt(pos);
3888 if (index.isValid())
3889 spanned = q->isFirstColumnSpanned(index.row(), index.parent());
3890 }
3891 const int column = spanned ? 0 : header->logicalIndexAt(pos.x());
3892 if (!isTreePosition(column))
3893 return -1; // no logical index at x
3894
3895 int viewItemIndex = itemAtCoordinate(pos.y());
3896 QRect returning = itemDecorationRect(modelIndex(viewItemIndex));
3897 if (!returning.contains(pos))
3898 return -1;
3899
3900 return viewItemIndex;
3901}
3902
3903QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const
3904{
3905 Q_Q(const QTreeView);
3906 if (!rootDecoration && index.parent() == root)
3907 return QRect(); // no decoration at root
3908
3909 int viewItemIndex = viewIndex(index);
3910 if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index))
3911 return QRect();
3912
3913 int itemIndentation = indentationForItem(viewItemIndex);
3914 int position = header->sectionViewportPosition(logicalIndexForTree());
3915 int size = header->sectionSize(logicalIndexForTree());
3916
3917 QRect rect;
3918 if (q->isRightToLeft())
3919 rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex),
3920 indent, itemHeight(viewItemIndex));
3921 else
3922 rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex),
3923 indent, itemHeight(viewItemIndex));
3924 QStyleOption opt;
3925 opt.initFrom(q);
3926 opt.rect = rect;
3927 return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q);
3928}
3929
3930QList<std::pair<int, int>> QTreeViewPrivate::columnRanges(const QModelIndex &topIndex,
3931 const QModelIndex &bottomIndex) const
3932{
3933 const int topVisual = header->visualIndex(topIndex.column()),
3934 bottomVisual = header->visualIndex(bottomIndex.column());
3935
3936 const int start = qMin(topVisual, bottomVisual);
3937 const int end = qMax(topVisual, bottomVisual);
3938
3939 QList<int> logicalIndexes;
3940
3941 //we iterate over the visual indexes to get the logical indexes
3942 for (int c = start; c <= end; c++) {
3943 const int logical = header->logicalIndex(c);
3944 if (!header->isSectionHidden(logical)) {
3945 logicalIndexes << logical;
3946 }
3947 }
3948 //let's sort the list
3949 std::sort(logicalIndexes.begin(), logicalIndexes.end());
3950
3951 QList<std::pair<int, int>> ret;
3952 std::pair<int, int> current;
3953 current.first = -2; // -1 is not enough because -1+1 = 0
3954 current.second = -2;
3955 for(int i = 0; i < logicalIndexes.size(); ++i) {
3956 const int logicalColumn = logicalIndexes.at(i);
3957 if (current.second + 1 != logicalColumn) {
3958 if (current.first != -2) {
3959 //let's save the current one
3960 ret += current;
3961 }
3962 //let's start a new one
3963 current.first = current.second = logicalColumn;
3964 } else {
3965 current.second++;
3966 }
3967 }
3968
3969 //let's get the last range
3970 if (current.first != -2) {
3971 ret += current;
3972 }
3973
3974 return ret;
3975}
3976
3977void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex,
3978 QItemSelectionModel::SelectionFlags command)
3979{
3980 Q_Q(QTreeView);
3981 QItemSelection selection;
3982 const int top = viewIndex(topIndex),
3983 bottom = viewIndex(bottomIndex);
3984
3985 const QList<std::pair<int, int>> colRanges = columnRanges(topIndex, bottomIndex);
3986 QList<std::pair<int, int>>::const_iterator it;
3987 for (it = colRanges.begin(); it != colRanges.end(); ++it) {
3988 const int left = (*it).first,
3989 right = (*it).second;
3990
3991 QModelIndex previous;
3992 QItemSelectionRange currentRange;
3993 QStack<QItemSelectionRange> rangeStack;
3994 for (int i = top; i <= bottom; ++i) {
3995 QModelIndex index = modelIndex(i);
3996 QModelIndex parent = index.parent();
3997 QModelIndex previousParent = previous.parent();
3998 if (previous.isValid() && parent == previousParent) {
3999 // same parent
4000 if (qAbs(previous.row() - index.row()) > 1) {
4001 //a hole (hidden index inside a range) has been detected
4002 if (currentRange.isValid()) {
4003 selection.append(currentRange);
4004 }
4005 //let's start a new range
4006 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4007 } else {
4008 QModelIndex tl = model->index(currentRange.top(), currentRange.left(),
4009 currentRange.parent());
4010 currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right));
4011 }
4012 } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) {
4013 // item is child of previous
4014 rangeStack.push(currentRange);
4015 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4016 } else {
4017 if (currentRange.isValid())
4018 selection.append(currentRange);
4019 if (rangeStack.isEmpty()) {
4020 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4021 } else {
4022 currentRange = rangeStack.pop();
4023 index = currentRange.bottomRight(); //let's resume the range
4024 --i; //we process again the current item
4025 }
4026 }
4027 previous = index;
4028 }
4029 if (currentRange.isValid())
4030 selection.append(currentRange);
4031 for (int i = 0; i < rangeStack.size(); ++i)
4032 selection.append(rangeStack.at(i));
4033 }
4034 q->selectionModel()->select(selection, command);
4035}
4036
4037std::pair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const
4038{
4039 Q_Q(const QTreeView);
4040 int start = header->visualIndexAt(rect.left());
4041 int end = header->visualIndexAt(rect.right());
4042 if (q->isRightToLeft()) {
4043 start = (start == -1 ? header->count() - 1 : start);
4044 end = (end == -1 ? 0 : end);
4045 } else {
4046 start = (start == -1 ? 0 : start);
4047 end = (end == -1 ? header->count() - 1 : end);
4048 }
4049 return std::pair(qMin(start, end), qMax(start, end));
4050}
4051
4052bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const
4053{
4054 Q_Q(const QTreeView);
4055 if (parent.flags() & Qt::ItemNeverHasChildren)
4056 return false;
4057 if (model->hasChildren(parent)) {
4058 if (hiddenIndexes.isEmpty())
4059 return true;
4060 if (q->isIndexHidden(parent))
4061 return false;
4062 int rowCount = model->rowCount(parent);
4063 for (int i = 0; i < rowCount; ++i) {
4064 if (!q->isRowHidden(i, parent))
4065 return true;
4066 }
4067 if (rowCount == 0)
4068 return true;
4069 }
4070 return false;
4071}
4072
4073void QTreeViewPrivate::sortIndicatorChanged(int column, Qt::SortOrder order)
4074{
4075 model->sort(column, order);
4076}
4077
4078#if QT_CONFIG(accessibility)
4079int QTreeViewPrivate::accessibleChildIndex(const QModelIndex &index) const
4080{
4081 Q_Q(const QTreeView);
4082 Q_ASSERT(index.isValid());
4083
4084 int rowForIndex = q->visualIndex(index);
4085 if (rowForIndex < 0)
4086 return -1; // should happen rarely, if ever. Possibly when select all is performed
4087 // Note that this will include the header, even if its hidden.
4088 if (q->header())
4089 ++rowForIndex;
4090 const int columnCount = index.model()->columnCount();
4091
4092 const qulonglong childIndex = qulonglong(rowForIndex) * columnCount + index.column();
4093 const int result = q26::saturate_cast<int>(childIndex);
4094 if (!q20::cmp_equal(result, childIndex)) {
4095 qWarning("QTreeView: model is too large to be made accessible (%d < %llu)",
4096 result, childIndex);
4097 return -1;
4098 }
4099 return result;
4100}
4101#endif
4102
4103void QTreeViewPrivate::updateIndentationFromStyle()
4104{
4105 Q_Q(const QTreeView);
4106 indent = q->style()->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, q);
4107}
4108
4109/*!
4110 \reimp
4111 */
4112void QTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
4113{
4114 Q_D(QTreeView);
4115 QAbstractItemView::currentChanged(current, previous);
4116
4117 if (allColumnsShowFocus()) {
4118 if (previous.isValid())
4119 viewport()->update(d->visualRect(previous, QTreeViewPrivate::FullRow));
4120 if (current.isValid())
4121 viewport()->update(d->visualRect(current, QTreeViewPrivate::FullRow));
4122 }
4123#if QT_CONFIG(accessibility)
4124 if (QAccessible::isActive() && current.isValid() && hasFocus()) {
4125 Q_D(QTreeView);
4126
4127 const int entry = d->accessibleChildIndex(current);
4128 if (entry >= 0) {
4129 QAccessibleEvent event(this, QAccessible::Focus);
4130 event.setChild(entry);
4131 QAccessible::updateAccessibility(&event);
4132 }
4133 }
4134#endif
4135}
4136
4137/*!
4138 \reimp
4139 */
4140void QTreeView::selectionChanged(const QItemSelection &selected,
4141 const QItemSelection &deselected)
4142{
4143 QAbstractItemView::selectionChanged(selected, deselected);
4144#if QT_CONFIG(accessibility)
4145 if (QAccessible::isActive()) {
4146 Q_D(QTreeView);
4147
4148 // ### does not work properly for selection ranges.
4149 QModelIndex sel = selected.indexes().value(0);
4150 if (sel.isValid()) {
4151 int entry = d->accessibleChildIndex(sel);
4152 if (entry >= 0) {
4153 QAccessibleEvent event(this, QAccessible::SelectionAdd);
4154 event.setChild(entry);
4155 QAccessible::updateAccessibility(&event);
4156 }
4157 }
4158 QModelIndex desel = deselected.indexes().value(0);
4159 if (desel.isValid()) {
4160 int entry = d->accessibleChildIndex(desel);
4161 if (entry >= 0) {
4162 QAccessibleEvent event(this, QAccessible::SelectionRemove);
4163 event.setChild(entry);
4164 QAccessible::updateAccessibility(&event);
4165 }
4166 }
4167 }
4168#endif
4169}
4170
4171int QTreeView::visualIndex(const QModelIndex &index) const
4172{
4173 Q_D(const QTreeView);
4174 d->executePostedLayout();
4175 return d->viewIndex(index);
4176}
4177
4178/*!
4179 \internal
4180*/
4181
4182void QTreeView::verticalScrollbarValueChanged(int value)
4183{
4184 Q_D(QTreeView);
4185 if (!d->viewItems.isEmpty() && value == verticalScrollBar()->maximum()) {
4186 QModelIndex ret = d->viewItems.last().index;
4187 // Root index will be handled by base class implementation
4188 while (ret.isValid()) {
4189 if (isExpanded(ret) && d->model->canFetchMore(ret)) {
4190 d->model->fetchMore(ret);
4191 break;
4192 }
4193 ret = ret.parent();
4194 }
4195 }
4196 QAbstractItemView::verticalScrollbarValueChanged(value);
4197}
4198
4199QT_END_NAMESPACE
4200
4201#include "moc_qtreeview.cpp"
static bool ancestorOf(QObject *widget, QObject *other)