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
1065 \sa itemDecorationRect()
1066*/
1067QRect QTreeView::visualRect(const QModelIndex &index) const
1068{
1069 Q_D(const QTreeView);
1070 return d->visualRect(index, QTreeViewPrivate::SingleSection);
1071}
1072
1073
1074/*!
1075 Returns the rectangle occupied by the expand/collapse indicator
1076 of the item at \a index. If the item has no such
1077 indicator, for example because it has no children or because root
1078 decoration is disabled, the returned rectangle is invalid. An invalid
1079 \a index also returns an invalid rectangle.
1080
1081 \since 6.12
1082 \sa visualRect(), QStyle::SE_TreeViewDisclosureItem
1083 */
1084QRect QTreeView::itemDecorationRect(const QModelIndex &index) const
1085{
1086 Q_D(const QTreeView);
1087 return d->itemDecorationRect(index);
1088}
1089
1090
1091/*!
1092 \internal
1093 \return the visual rectangle at \param index, according to \param rule.
1094 \list
1095 \li SingleSection
1096 The return value matches the section, which \a index points to.
1097 \li FullRow
1098 Return the rectangle of the entire row, no matter which section
1099 \a index points to.
1100 \li AddRowIndicatorToFirstSection
1101 Like SingleSection. If \index points to the first section, add the
1102 row indicator and its margins.
1103 \endlist
1104 */
1105QRect QTreeViewPrivate::visualRect(const QModelIndex &index, RectRule rule) const
1106{
1107 Q_Q(const QTreeView);
1108
1109 if (!isIndexValid(index))
1110 return QRect();
1111
1112 // Calculate the entire row's rectangle, even if one of the elements is hidden
1113 if (q->isIndexHidden(index) && rule != FullRow)
1114 return QRect();
1115
1116 executePostedLayout();
1117
1118 const int viewIndex = this->viewIndex(index);
1119 if (viewIndex < 0)
1120 return QRect();
1121
1122 const bool spanning = viewItems.at(viewIndex).spanning;
1123 const int column = index.column();
1124
1125 // if we have a spanning item, make the selection stretch from left to right
1126 int x = (spanning ? 0 : q->columnViewportPosition(column));
1127 int width = (spanning ? header->length() : q->columnWidth(column));
1128
1129 const bool addIndentation = isTreePosition(column) && (column > 0 || rule == SingleSection);
1130
1131 if (rule == FullRow) {
1132 x = 0;
1133 width = q->viewport()->width();
1134 } else if (addIndentation) {
1135 // calculate indentation
1136 const int indentation = indentationForItem(viewIndex);
1137 width -= indentation;
1138 if (!q->isRightToLeft())
1139 x += indentation;
1140 }
1141
1142 const int y = coordinateForItem(viewIndex);
1143 const int height = itemHeight(viewIndex);
1144
1145 return QRect(x, y, width, height);
1146}
1147
1148/*!
1149 Scroll the contents of the tree view until the given model item
1150 \a index is visible. The \a hint parameter specifies more
1151 precisely where the item should be located after the
1152 operation.
1153 If any of the parents of the model item are collapsed, they will
1154 be expanded to ensure that the model item is visible.
1155*/
1156void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint)
1157{
1158 Q_D(QTreeView);
1159
1160 if (!d->isIndexValid(index))
1161 return;
1162
1163 d->executePostedLayout();
1164 d->updateScrollBars();
1165
1166 // Expand all parents if the parent(s) of the node are not expanded.
1167 QModelIndex parent = index.parent();
1168 while (parent != d->root && parent.isValid() && state() == NoState && d->itemsExpandable) {
1169 if (!isExpanded(parent))
1170 expand(parent);
1171 parent = d->model->parent(parent);
1172 }
1173
1174 int item = d->viewIndex(index);
1175 if (item < 0)
1176 return;
1177
1178 QRect area = d->viewport->rect();
1179
1180 // vertical
1181 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
1182 int top = verticalScrollBar()->value();
1183 int bottom = top + verticalScrollBar()->pageStep();
1184 if (hint == EnsureVisible && item >= top && item < bottom) {
1185 // nothing to do
1186 } else if (hint == PositionAtTop || (hint == EnsureVisible && item < top)) {
1187 verticalScrollBar()->setValue(item);
1188 } else { // PositionAtBottom or PositionAtCenter
1189 const int currentItemHeight = d->itemHeight(item);
1190 int y = (hint == PositionAtCenter
1191 //we center on the current item with a preference to the top item (ie. -1)
1192 ? area.height() / 2 + currentItemHeight - 1
1193 //otherwise we simply take the whole space
1194 : area.height());
1195 if (y > currentItemHeight) {
1196 while (item >= 0) {
1197 y -= d->itemHeight(item);
1198 if (y < 0) { //there is no more space left
1199 item++;
1200 break;
1201 }
1202 item--;
1203 }
1204 }
1205 verticalScrollBar()->setValue(item);
1206 }
1207 } else { // ScrollPerPixel
1208 QRect rect(columnViewportPosition(index.column()),
1209 d->coordinateForItem(item), // ### slow for items outside the view
1210 columnWidth(index.column()),
1211 d->itemHeight(item));
1212
1213 if (rect.isEmpty()) {
1214 // nothing to do
1215 } else if (hint == EnsureVisible && area.contains(rect)) {
1216 d->viewport->update(rect);
1217 // nothing to do
1218 } else {
1219 bool above = (hint == EnsureVisible
1220 && (rect.top() < area.top()
1221 || area.height() < rect.height()));
1222 bool below = (hint == EnsureVisible
1223 && rect.bottom() > area.bottom()
1224 && rect.height() < area.height());
1225
1226 int verticalValue = verticalScrollBar()->value();
1227 if (hint == PositionAtTop || above)
1228 verticalValue += rect.top();
1229 else if (hint == PositionAtBottom || below)
1230 verticalValue += rect.bottom() - area.height();
1231 else if (hint == PositionAtCenter)
1232 verticalValue += rect.top() - ((area.height() - rect.height()) / 2);
1233 verticalScrollBar()->setValue(verticalValue);
1234 }
1235 }
1236 // horizontal
1237 int viewportWidth = d->viewport->width();
1238 int horizontalOffset = d->header->offset();
1239 int horizontalPosition = d->header->sectionPosition(index.column());
1240 int cellWidth = d->header->sectionSize(index.column());
1241
1242 if (hint == PositionAtCenter) {
1243 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
1244 } else {
1245 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
1246 horizontalScrollBar()->setValue(horizontalPosition);
1247 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
1248 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
1249 }
1250}
1251
1252/*!
1253 \reimp
1254*/
1255void QTreeView::changeEvent(QEvent *event)
1256{
1257 Q_D(QTreeView);
1258 if (event->type() == QEvent::StyleChange) {
1259 if (!d->customIndent) {
1260 // QAbstractItemView calls this method in case of a style change,
1261 // so update the indentation here if it wasn't set manually.
1262 d->updateIndentationFromStyle();
1263 }
1264 }
1265 QAbstractItemView::changeEvent(event);
1266}
1267
1268/*!
1269 \reimp
1270*/
1271void QTreeView::timerEvent(QTimerEvent *event)
1272{
1273 Q_D(QTreeView);
1274 if (event->id() == d->columnResizeTimer.id()) {
1275 updateGeometries();
1276 d->columnResizeTimer.stop();
1277 QRect rect;
1278 int viewportHeight = d->viewport->height();
1279 int viewportWidth = d->viewport->width();
1280 for (int i = d->columnsToUpdate.size() - 1; i >= 0; --i) {
1281 int column = d->columnsToUpdate.at(i);
1282 int x = columnViewportPosition(column);
1283 if (isRightToLeft())
1284 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
1285 else
1286 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
1287 }
1288 d->viewport->update(rect.normalized());
1289 d->columnsToUpdate.clear();
1290 } else if (event->timerId() == d->openTimer.timerId()) {
1291 QPoint pos = d->viewport->mapFromGlobal(QCursor::pos());
1292 if (state() == QAbstractItemView::DraggingState
1293 && d->viewport->rect().contains(pos)) {
1294 QModelIndex index = indexAt(pos);
1295 expand(index);
1296 }
1297 d->openTimer.stop();
1298 }
1299
1300 QAbstractItemView::timerEvent(event);
1301}
1302
1303/*!
1304 \reimp
1305*/
1306#if QT_CONFIG(draganddrop)
1307void QTreeView::dragMoveEvent(QDragMoveEvent *event)
1308{
1309 Q_D(QTreeView);
1310 if (d->autoExpandDelay >= 0)
1311 d->openTimer.start(d->autoExpandDelay, this);
1312 QAbstractItemView::dragMoveEvent(event);
1313}
1314#endif
1315
1316/*!
1317 \reimp
1318*/
1319bool QTreeView::viewportEvent(QEvent *event)
1320{
1321 Q_D(QTreeView);
1322 switch (event->type()) {
1323 case QEvent::HoverEnter:
1324 case QEvent::HoverLeave:
1325 case QEvent::HoverMove: {
1326 QHoverEvent *he = static_cast<QHoverEvent*>(event);
1327 const int oldBranch = d->hoverBranch;
1328 d->hoverBranch = d->itemDecorationAt(he->position().toPoint());
1329 QModelIndex newIndex = indexAt(he->position().toPoint());
1330 if (d->hover != newIndex || d->hoverBranch != oldBranch) {
1331 // Update the whole hovered over row. No need to update the old hovered
1332 // row, that is taken care in superclass hover handling.
1333 viewport()->update(d->visualRect(newIndex, QTreeViewPrivate::FullRow));
1334 }
1335 break; }
1336 default:
1337 break;
1338 }
1339 return QAbstractItemView::viewportEvent(event);
1340}
1341
1342/*!
1343 \reimp
1344*/
1345void QTreeView::paintEvent(QPaintEvent *event)
1346{
1347 Q_D(QTreeView);
1348 d->executePostedLayout();
1349 QPainter painter(viewport());
1350#if QT_CONFIG(animation)
1351 if (d->isAnimating()) {
1352 drawTree(&painter, event->region() - d->animatedOperation.rect());
1353 d->drawAnimatedOperation(&painter);
1354 } else
1355#endif // animation
1356 {
1357 drawTree(&painter, event->region());
1358#if QT_CONFIG(draganddrop)
1359 d->paintDropIndicator(&painter);
1360#endif
1361 }
1362}
1363
1364int QTreeViewPrivate::logicalIndexForTree() const
1365{
1366 int index = treePosition;
1367 if (index < 0)
1368 index = header->logicalIndex(0);
1369 return index;
1370}
1371
1372void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItem *option, int y, int bottom) const
1373{
1374 Q_Q(const QTreeView);
1375 if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q))
1376 return;
1377 int rowHeight = defaultItemHeight;
1378 if (rowHeight <= 0) {
1379 rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height();
1380 if (rowHeight <= 0)
1381 return;
1382 }
1383 while (y <= bottom) {
1384 option->rect.setRect(0, y, viewport->width(), rowHeight);
1385 option->features.setFlag(QStyleOptionViewItem::Alternate, current & 1);
1386 ++current;
1387 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q);
1388 y += rowHeight;
1389 }
1390}
1391
1392bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos)
1393{
1394 Q_Q(QTreeView);
1395 // we want to handle mousePress in EditingState (persistent editors)
1396 if ((state != QAbstractItemView::NoState
1397 && state != QAbstractItemView::EditingState)
1398 || !viewport->rect().contains(pos))
1399 return true;
1400
1401 int i = itemDecorationAt(pos);
1402 if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) {
1403 if (viewItems.at(i).expanded)
1404 collapse(i, true);
1405 else
1406 expand(i, true);
1407 if (!isAnimating()) {
1408 q->updateGeometries();
1409 viewport->update();
1410 }
1411 return true;
1412 }
1413 return false;
1414}
1415
1416void QTreeViewPrivate::modelDestroyed()
1417{
1418 //we need to clear the viewItems because it contains QModelIndexes to
1419 //the model currently being destroyed
1420 viewItems.clear();
1421 QAbstractItemViewPrivate::modelDestroyed();
1422}
1423
1424QRect QTreeViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const
1425{
1426 const auto parentIdx = topLeft.parent();
1427 executePostedLayout();
1428 QRect updateRect;
1429 int left = std::numeric_limits<int>::max();
1430 int right = std::numeric_limits<int>::min();
1431 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
1432 const auto idxCol0 = model->index(row, 0, parentIdx);
1433 if (isRowHidden(idxCol0))
1434 continue;
1435 QRect rowRect;
1436 if (left != std::numeric_limits<int>::max()) {
1437 // we already know left and right boundary of the rect to update
1438 rowRect = visualRect(idxCol0, FullRow);
1439 if (!rowRect.intersects(rect))
1440 continue;
1441 rowRect = QRect(left, rowRect.top(), right, rowRect.bottom());
1442 } else if (!spanningIndexes.isEmpty() && spanningIndexes.contains(idxCol0)) {
1443 // isFirstColumnSpanned re-creates the child index so take a shortcut here
1444 // spans the whole row, therefore ask for FullRow instead for every cell
1445 rowRect = visualRect(idxCol0, FullRow);
1446 if (!rowRect.intersects(rect))
1447 continue;
1448 } else {
1449 for (int col = topLeft.column(); col <= bottomRight.column(); ++col) {
1450 if (header->isSectionHidden(col))
1451 continue;
1452 const QModelIndex idx(model->index(row, col, parentIdx));
1453 const QRect idxRect = visualRect(idx, SingleSection);
1454 if (idxRect.isNull())
1455 continue;
1456 // early exit when complete row is out of viewport
1457 if (idxRect.top() > rect.bottom() || idxRect.bottom() < rect.top())
1458 break;
1459 if (!idxRect.intersects(rect))
1460 continue;
1461 rowRect = rowRect.united(idxRect);
1462 if (rowRect.left() < rect.left() && rowRect.right() > rect.right())
1463 break;
1464 }
1465 if (rowRect.isValid()) {
1466 left = std::min(left, rowRect.left());
1467 right = std::max(right, rowRect.right());
1468 }
1469 }
1470 updateRect = updateRect.united(rowRect);
1471 if (updateRect.contains(rect)) // already full rect covered?
1472 break;
1473 }
1474 return rect.intersected(updateRect);
1475}
1476
1477/*!
1478 \class QTreeViewPrivate
1479 \inmodule QtWidgets
1480 \internal
1481*/
1482
1483/*!
1484 \reimp
1485
1486 We have a QTreeView way of knowing what elements are on the viewport
1487*/
1488QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
1489{
1490 Q_ASSERT(r);
1491 Q_Q(const QTreeView);
1492 if (spanningIndexes.isEmpty())
1493 return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r);
1494 QModelIndexList list;
1495 for (const QModelIndex &idx : indexes) {
1496 if (idx.column() > 0 && q->isFirstColumnSpanned(idx.row(), idx.parent()))
1497 continue;
1498 list << idx;
1499 }
1500 return QAbstractItemViewPrivate::draggablePaintPairs(list, r);
1501}
1502
1503void QTreeViewPrivate::adjustViewOptionsForIndex(QStyleOptionViewItem *option, const QModelIndex &current) const
1504{
1505 const int row = viewIndex(current); // get the index in viewItems[]
1506 option->state = option->state | (viewItems.at(row).expanded ? QStyle::State_Open : QStyle::State_None)
1507 | (viewItems.at(row).hasChildren ? QStyle::State_Children : QStyle::State_None)
1508 | (viewItems.at(row).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1509
1510 option->showDecorationSelected = (selectionBehavior & QTreeView::SelectRows)
1511 || option->showDecorationSelected;
1512
1513 QList<int>
1514 logicalIndices; // index = visual index of visible columns only. data = logical index.
1515 QList<QStyleOptionViewItem::ViewItemPosition>
1516 viewItemPosList; // vector of left/middle/end for each logicalIndex, visible columns
1517 // only.
1518 const bool spanning = viewItems.at(row).spanning;
1519 const int left = (spanning ? header->visualIndex(0) : 0);
1520 const int right = (spanning ? header->visualIndex(0) : header->count() - 1 );
1521 calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
1522
1523 const int visualIndex = logicalIndices.indexOf(current.column());
1524 option->viewItemPosition = viewItemPosList.at(visualIndex);
1525}
1526
1527
1528/*!
1529 Draws the part of the tree intersecting the given \a region using the specified
1530 \a painter.
1531
1532 \sa paintEvent()
1533*/
1534void QTreeView::drawTree(QPainter *painter, const QRegion &region) const
1535{
1536 Q_D(const QTreeView);
1537 // d->viewItems changes when posted layouts are executed in itemDecorationAt, so don't copy
1538 const QList<QTreeViewItem> &viewItems = d->viewItems;
1539
1540 QStyleOptionViewItem option;
1541 initViewItemOption(&option);
1542 const QStyle::State state = option.state;
1543 d->current = 0;
1544
1545 if (viewItems.size() == 0 || d->header->count() == 0 || !d->itemDelegate) {
1546 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1547 return;
1548 }
1549
1550 int firstVisibleItemOffset = 0;
1551 const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset);
1552 if (firstVisibleItem < 0) {
1553 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1554 return;
1555 }
1556
1557 const int viewportWidth = d->viewport->width();
1558
1559 QPoint hoverPos = d->viewport->mapFromGlobal(QCursor::pos());
1560 d->hoverBranch = d->itemDecorationAt(hoverPos);
1561
1562 QList<int> drawn;
1563 bool multipleRects = (region.rectCount() > 1);
1564 for (const QRect &a : region) {
1565 const QRect area = (multipleRects
1566 ? QRect(0, a.y(), viewportWidth, a.height())
1567 : a);
1568 d->leftAndRight = d->startAndEndColumns(area);
1569
1570 int i = firstVisibleItem; // the first item at the top of the viewport
1571 int y = firstVisibleItemOffset; // we may only see part of the first item
1572
1573 // start at the top of the viewport and iterate down to the update area
1574 for (; i < viewItems.size(); ++i) {
1575 const int itemHeight = d->itemHeight(i);
1576 if (y + itemHeight > area.top())
1577 break;
1578 y += itemHeight;
1579 }
1580
1581 // paint the visible rows
1582 for (; i < viewItems.size() && y <= area.bottom(); ++i) {
1583 const QModelIndex &index = viewItems.at(i).index;
1584 const int itemHeight = d->itemHeight(i);
1585 option.rect = d->visualRect(index, QTreeViewPrivate::FullRow);
1586 option.state = state | (viewItems.at(i).expanded ? QStyle::State_Open : QStyle::State_None)
1587 | (viewItems.at(i).hasChildren ? QStyle::State_Children : QStyle::State_None)
1588 | (viewItems.at(i).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1589 d->current = i;
1590 d->spanning = viewItems.at(i).spanning;
1591 if (!multipleRects || !drawn.contains(i)) {
1592 drawRow(painter, option, viewItems.at(i).index);
1593 if (multipleRects) // even if the rect only intersects the item,
1594 drawn.append(i); // the entire item will be painted
1595 }
1596 y += itemHeight;
1597 }
1598
1599 if (y <= area.bottom()) {
1600 d->current = i;
1601 d->paintAlternatingRowColors(painter, &option, y, area.bottom());
1602 }
1603 }
1604}
1605
1606/// ### move to QObject :)
1607static inline bool ancestorOf(QObject *widget, QObject *other)
1608{
1609 for (QObject *parent = other; parent != nullptr; parent = parent->parent()) {
1610 if (parent == widget)
1611 return true;
1612 }
1613 return false;
1614}
1615
1616void QTreeViewPrivate::calcLogicalIndices(
1617 QList<int> *logicalIndices, QList<QStyleOptionViewItem::ViewItemPosition> *itemPositions,
1618 int left, int right) const
1619{
1620 const int columnCount = header->count();
1621 /* 'left' and 'right' are the left-most and right-most visible visual indices.
1622 Compute the first visible logical indices before and after the left and right.
1623 We will use these values to determine the QStyleOptionViewItem::viewItemPosition. */
1624 int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1;
1625 for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) {
1626 int logicalIndex = header->logicalIndex(visualIndex);
1627 if (!header->isSectionHidden(logicalIndex)) {
1628 logicalIndexBeforeLeft = logicalIndex;
1629 break;
1630 }
1631 }
1632
1633 for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) {
1634 int logicalIndex = header->logicalIndex(visualIndex);
1635 if (!header->isSectionHidden(logicalIndex)) {
1636 if (visualIndex > right) {
1637 logicalIndexAfterRight = logicalIndex;
1638 break;
1639 }
1640 logicalIndices->append(logicalIndex);
1641 }
1642 }
1643
1644 const auto indicesCount = logicalIndices->size();
1645 itemPositions->resize(indicesCount);
1646 for (qsizetype currentLogicalSection = 0; currentLogicalSection < indicesCount; ++currentLogicalSection) {
1647 // determine the viewItemPosition depending on the position of column 0
1648 int nextLogicalSection = currentLogicalSection + 1 >= indicesCount
1649 ? logicalIndexAfterRight
1650 : logicalIndices->at(currentLogicalSection + 1);
1651 int prevLogicalSection = currentLogicalSection - 1 < 0
1652 ? logicalIndexBeforeLeft
1653 : logicalIndices->at(currentLogicalSection - 1);
1654 const int headerSection = logicalIndices->at(currentLogicalSection);
1655 QStyleOptionViewItem::ViewItemPosition pos;
1656 if ((nextLogicalSection == -1 && prevLogicalSection == -1) || spanning) {
1657 pos = QStyleOptionViewItem::OnlyOne;
1658 } else if ((nextLogicalSection != 0 && prevLogicalSection == -1) || isTreePosition(headerSection))
1659 pos = QStyleOptionViewItem::Beginning;
1660 else if (nextLogicalSection == 0 || nextLogicalSection == -1)
1661 pos = QStyleOptionViewItem::End;
1662 else
1663 pos = QStyleOptionViewItem::Middle;
1664 (*itemPositions)[currentLogicalSection] = pos;
1665 }
1666}
1667
1668/*!
1669 \internal
1670 Get sizeHint width for single index (providing existing hint and style option) and index in viewIndex i.
1671*/
1672int QTreeViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option, int i) const
1673{
1674 Q_Q(const QTreeView);
1675 QWidget *editor = editorForIndex(index).widget.data();
1676 if (editor && persistent.contains(editor)) {
1677 hint = qMax(hint, editor->sizeHint().width());
1678 int min = editor->minimumSize().width();
1679 int max = editor->maximumSize().width();
1680 hint = qBound(min, hint, max);
1681 }
1682 int xhint = q->itemDelegateForIndex(index)->sizeHint(option, index).width();
1683 hint = qMax(hint, xhint + (isTreePosition(index.column()) ? indentationForItem(i) : 0));
1684 return hint;
1685}
1686
1687/*!
1688 Draws the row in the tree view that contains the model item \a index,
1689 using the \a painter given. The \a option controls how the item is
1690 displayed.
1691
1692 \sa setAlternatingRowColors()
1693*/
1694void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
1695 const QModelIndex &index) const
1696{
1697 Q_D(const QTreeView);
1698 QStyleOptionViewItem opt = option;
1699 const QPoint offset = d->scrollDelayOffset;
1700 const int y = option.rect.y() + offset.y();
1701 const QModelIndex parent = index.parent();
1702 const QHeaderView *header = d->header;
1703 const QModelIndex current = currentIndex();
1704 const QModelIndex hover = d->hover;
1705 const bool reverse = isRightToLeft();
1706 const QStyle::State state = opt.state;
1707 const bool spanning = d->spanning;
1708 const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first);
1709 const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second);
1710 const bool alternate = d->alternatingColors;
1711 const bool enabled = (state & QStyle::State_Enabled) != 0;
1712 const bool allColumnsShowFocus = d->allColumnsShowFocus;
1713
1714
1715 // when the row contains an index widget which has focus,
1716 // we want to paint the entire row as active
1717 bool indexWidgetHasFocus = false;
1718 if ((current.row() == index.row()) && !d->editorIndexHash.isEmpty()) {
1719 const int r = index.row();
1720 QWidget *fw = QApplication::focusWidget();
1721 for (int c = 0; c < header->count(); ++c) {
1722 QModelIndex idx = d->model->index(r, c, parent);
1723 if (QWidget *editor = indexWidget(idx)) {
1724 if (ancestorOf(editor, fw)) {
1725 indexWidgetHasFocus = true;
1726 break;
1727 }
1728 }
1729 }
1730 }
1731
1732 const bool widgetHasFocus = hasFocus();
1733 bool currentRowHasFocus = false;
1734 if (allColumnsShowFocus && widgetHasFocus && current.isValid()) {
1735 // check if the focus index is before or after the visible columns
1736 const int r = index.row();
1737 for (int c = 0; c < left && !currentRowHasFocus; ++c) {
1738 QModelIndex idx = d->model->index(r, c, parent);
1739 currentRowHasFocus = (idx == current);
1740 }
1741 QModelIndex parent = d->model->parent(index);
1742 for (int c = right; c < header->count() && !currentRowHasFocus; ++c) {
1743 currentRowHasFocus = (d->model->index(r, c, parent) == current);
1744 }
1745 }
1746
1747 // ### special case: if we select entire rows, then we need to draw the
1748 // selection in the first column all the way to the second column, rather
1749 // than just around the item text. We abuse showDecorationSelected to
1750 // indicate this to the style. Below we will reset this value temporarily
1751 // to only respect the styleHint while we are rendering the decoration.
1752 opt.showDecorationSelected = (d->selectionBehavior & SelectRows)
1753 || option.showDecorationSelected;
1754
1755 int width, height = option.rect.height();
1756 int position;
1757 QModelIndex modelIndex;
1758 const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1759 && index.parent() == hover.parent()
1760 && index.row() == hover.row();
1761
1762 QList<int> logicalIndices;
1763 QList<QStyleOptionViewItem::ViewItemPosition>
1764 viewItemPosList; // vector of left/middle/end for each logicalIndex
1765 d->calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
1766
1767 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.size(); ++currentLogicalSection) {
1768 int headerSection = logicalIndices.at(currentLogicalSection);
1769 position = columnViewportPosition(headerSection) + offset.x();
1770 width = header->sectionSize(headerSection);
1771
1772 if (spanning) {
1773 int lastSection = header->logicalIndex(header->count() - 1);
1774 if (!reverse) {
1775 width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position;
1776 } else {
1777 width += position - columnViewportPosition(lastSection);
1778 position = columnViewportPosition(lastSection);
1779 }
1780 }
1781
1782 modelIndex = d->model->index(index.row(), headerSection, parent);
1783 if (!modelIndex.isValid())
1784 continue;
1785 opt.state = state;
1786
1787 opt.viewItemPosition = viewItemPosList.at(currentLogicalSection);
1788
1789 // fake activeness when row editor has focus
1790 if (indexWidgetHasFocus)
1791 opt.state |= QStyle::State_Active;
1792
1793 if (d->selectionModel->isSelected(modelIndex))
1794 opt.state |= QStyle::State_Selected;
1795 if (widgetHasFocus && (current == modelIndex)) {
1796 if (allColumnsShowFocus)
1797 currentRowHasFocus = true;
1798 else
1799 opt.state |= QStyle::State_HasFocus;
1800 }
1801 opt.state.setFlag(QStyle::State_MouseOver,
1802 (hoverRow || modelIndex == hover)
1803 && (option.showDecorationSelected || d->hoverBranch == -1));
1804
1805 if (enabled) {
1806 QPalette::ColorGroup cg;
1807 if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) {
1808 opt.state &= ~QStyle::State_Enabled;
1809 cg = QPalette::Disabled;
1810 } else if (opt.state & QStyle::State_Active) {
1811 cg = QPalette::Active;
1812 } else {
1813 cg = QPalette::Inactive;
1814 }
1815 opt.palette.setCurrentColorGroup(cg);
1816 }
1817
1818 if (alternate) {
1819 opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
1820 }
1821 opt.features &= ~QStyleOptionViewItem::IsDecoratedRootColumn;
1822
1823 /* Prior to Qt 4.3, the background of the branch (in selected state and
1824 alternate row color was provided by the view. For backward compatibility,
1825 this is now delegated to the style using PE_PanelItemViewRow which
1826 does the appropriate fill */
1827 if (d->isTreePosition(headerSection)) {
1828 const int i = d->indentationForItem(d->current);
1829 QRect branches(reverse ? position + width - i : position, y, i, height);
1830 const bool setClipRect = branches.width() > width;
1831 if (setClipRect) {
1832 painter->save();
1833 painter->setClipRect(QRect(position, y, width, height));
1834 }
1835 // draw background for the branch (selection + alternate row)
1836
1837 // We use showDecorationSelected both to store the style hint, and to indicate
1838 // that the entire row has to be selected (see overrides of the value if
1839 // selectionBehavior == SelectRow).
1840 // While we are only painting the background we don't care for the
1841 // selectionBehavior factor, so respect only the style value, and reset later.
1842 const bool oldShowDecorationSelected = opt.showDecorationSelected;
1843 opt.showDecorationSelected = style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected,
1844 &opt, this);
1845 opt.rect = branches;
1846 if (opt.rect.width() > 0) {
1847 // the root item also has a branch decoration
1848 opt.features |= QStyleOptionViewItem::IsDecoratedRootColumn;
1849 // we now want to draw the branch decoration
1850 opt.features |= QStyleOptionViewItem::IsDecorationForRootColumn;
1851 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1852 opt.features &= ~QStyleOptionViewItem::IsDecorationForRootColumn;
1853 }
1854
1855 // draw background of the item (only alternate row). rest of the background
1856 // is provided by the delegate
1857 QStyle::State oldState = opt.state;
1858 opt.state &= ~QStyle::State_Selected;
1859 opt.rect.setRect(reverse ? position : i + position, y, width - i, height);
1860 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1861 opt.state = oldState;
1862 opt.showDecorationSelected = oldShowDecorationSelected;
1863
1864 if (d->indent != 0)
1865 drawBranches(painter, branches, index);
1866 if (setClipRect)
1867 painter->restore();
1868 } else {
1869 QStyle::State oldState = opt.state;
1870 opt.state &= ~QStyle::State_Selected;
1871 opt.rect.setRect(position, y, width, height);
1872 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1873 opt.state = oldState;
1874 }
1875
1876 itemDelegateForIndex(modelIndex)->paint(painter, opt, modelIndex);
1877 }
1878
1879 if (currentRowHasFocus) {
1880 QStyleOptionFocusRect o;
1881 o.QStyleOption::operator=(option);
1882 o.state |= QStyle::State_KeyboardFocusChange;
1883 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
1884 ? QPalette::Normal : QPalette::Disabled;
1885 o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index)
1886 ? QPalette::Highlight : QPalette::Window);
1887 int x = 0;
1888 if (!option.showDecorationSelected)
1889 x = header->sectionPosition(0) + d->indentationForItem(d->current);
1890 QRect focusRect(x - header->offset(), y, header->length() - x, height);
1891 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect);
1892 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1893 // if we show focus on all columns and the first section is moved,
1894 // we have to split the focus rect into two rects
1895 if (allColumnsShowFocus && !option.showDecorationSelected
1896 && header->sectionsMoved() && (header->visualIndex(0) != 0)) {
1897 QRect sectionRect(0, y, header->sectionPosition(0), height);
1898 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect);
1899 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1900 }
1901 }
1902}
1903
1904/*!
1905 Draws the branches in the tree view on the same row as the model item
1906 \a index, using the \a painter given. The branches are drawn in the
1907 rectangle specified by \a rect.
1908*/
1909void QTreeView::drawBranches(QPainter *painter, const QRect &rect,
1910 const QModelIndex &index) const
1911{
1912 Q_D(const QTreeView);
1913 const bool reverse = isRightToLeft();
1914 const int indent = d->indent;
1915 const int outer = d->rootDecoration ? 0 : 1;
1916 const int item = d->current;
1917 const QTreeViewItem &viewItem = d->viewItems.at(item);
1918 int level = viewItem.level;
1919 QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height());
1920
1921 QModelIndex parent = index.parent();
1922 QModelIndex current = parent;
1923 QModelIndex ancestor = current.parent();
1924
1925 QStyleOptionViewItem opt;
1926 initViewItemOption(&opt);
1927 QStyle::State extraFlags = QStyle::State_None;
1928 if (isEnabled())
1929 extraFlags |= QStyle::State_Enabled;
1930 if (hasFocus())
1931 extraFlags |= QStyle::State_Active;
1932 QPainterStateGuard psg(painter, QPainterStateGuard::InitialState::NoSave);
1933 if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel) {
1934 psg.save();
1935 painter->setBrushOrigin(QPoint(0, verticalOffset()));
1936 }
1937
1938 if (d->alternatingColors) {
1939 opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
1940 }
1941
1942 // When hovering over a row, pass State_Hover for painting the branch
1943 // indicators if it has the decoration (aka branch) selected.
1944 bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1945 && opt.showDecorationSelected
1946 && index.parent() == d->hover.parent()
1947 && index.row() == d->hover.row();
1948
1949 if (d->selectionModel->isSelected(index))
1950 extraFlags |= QStyle::State_Selected;
1951
1952 if (level >= outer) {
1953 // start with the innermost branch
1954 primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent);
1955 opt.rect = primitive;
1956
1957 const bool expanded = viewItem.expanded;
1958 const bool children = viewItem.hasChildren;
1959 bool moreSiblings = viewItem.hasMoreSiblings;
1960
1961 opt.state = QStyle::State_Item | extraFlags
1962 | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None)
1963 | (children ? QStyle::State_Children : QStyle::State_None)
1964 | (expanded ? QStyle::State_Open : QStyle::State_None);
1965 opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
1966
1967 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1968 }
1969 // then go out level by level
1970 for (--level; level >= outer; --level) { // we have already drawn the innermost branch
1971 primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent);
1972 opt.rect = primitive;
1973 opt.state = extraFlags;
1974 bool moreSiblings = false;
1975 if (d->hiddenIndexes.isEmpty()) {
1976 moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row());
1977 } else {
1978 int successor = item + viewItem.total + 1;
1979 while (successor < d->viewItems.size()
1980 && d->viewItems.at(successor).level >= uint(level)) {
1981 const QTreeViewItem &successorItem = d->viewItems.at(successor);
1982 if (successorItem.level == uint(level)) {
1983 moreSiblings = true;
1984 break;
1985 }
1986 successor += successorItem.total + 1;
1987 }
1988 }
1989 if (moreSiblings)
1990 opt.state |= QStyle::State_Sibling;
1991 opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
1992
1993 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1994 current = ancestor;
1995 ancestor = current.parent();
1996 }
1997}
1998
1999/*!
2000 \reimp
2001*/
2002void QTreeView::mousePressEvent(QMouseEvent *event)
2003{
2004 Q_D(QTreeView);
2005 bool handled = false;
2006 if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, nullptr, this) == QEvent::MouseButtonPress)
2007 handled = d->expandOrCollapseItemAtPos(event->position().toPoint());
2008 if (!handled && d->itemDecorationAt(event->position().toPoint()) == -1)
2009 QAbstractItemView::mousePressEvent(event);
2010 else
2011 d->pressedIndex = QModelIndex();
2012}
2013
2014/*!
2015 \reimp
2016*/
2017void QTreeView::mouseReleaseEvent(QMouseEvent *event)
2018{
2019 Q_D(QTreeView);
2020 if (d->itemDecorationAt(event->position().toPoint()) == -1) {
2021 QAbstractItemView::mouseReleaseEvent(event);
2022 } else {
2023 if (state() == QAbstractItemView::DragSelectingState || state() == QAbstractItemView::DraggingState)
2024 setState(QAbstractItemView::NoState);
2025 if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, nullptr, this) == QEvent::MouseButtonRelease)
2026 d->expandOrCollapseItemAtPos(event->position().toPoint());
2027 }
2028}
2029
2030/*!
2031 \reimp
2032*/
2033void QTreeView::mouseDoubleClickEvent(QMouseEvent *event)
2034{
2035 Q_D(QTreeView);
2036 if (state() != NoState || !d->viewport->rect().contains(event->position().toPoint()))
2037 return;
2038
2039 int i = d->itemDecorationAt(event->position().toPoint());
2040 if (i == -1) {
2041 i = d->itemAtCoordinate(event->position().toPoint().y());
2042 if (i == -1)
2043 return; // user clicked outside the items
2044
2045 const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index;
2046 const QPersistentModelIndex persistent = indexAt(event->position().toPoint());
2047
2048 if (d->pressedIndex != persistent) {
2049 mousePressEvent(event);
2050 return;
2051 }
2052
2053 // signal handlers may change the model
2054 emit doubleClicked(persistent);
2055
2056 if (!persistent.isValid())
2057 return;
2058
2059 if (edit(persistent, DoubleClicked, event) || state() != NoState)
2060 return; // the double click triggered editing
2061
2062 if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this))
2063 emit activated(persistent);
2064
2065 d->releaseFromDoubleClick = true;
2066 d->executePostedLayout(); // we need to make sure viewItems is updated
2067 if (d->itemsExpandable
2068 && d->expandsOnDoubleClick
2069 && d->hasVisibleChildren(persistent)) {
2070 if (!((i < d->viewItems.size()) && (d->viewItems.at(i).index == firstColumnIndex))) {
2071 // find the new index of the item
2072 for (i = 0; i < d->viewItems.size(); ++i) {
2073 if (d->viewItems.at(i).index == firstColumnIndex)
2074 break;
2075 }
2076 if (i == d->viewItems.size())
2077 return;
2078 }
2079 if (d->viewItems.at(i).expanded)
2080 d->collapse(i, true);
2081 else
2082 d->expand(i, true);
2083 updateGeometries();
2084 viewport()->update();
2085 }
2086 }
2087}
2088
2089/*!
2090 \reimp
2091*/
2092void QTreeView::mouseMoveEvent(QMouseEvent *event)
2093{
2094 Q_D(QTreeView);
2095 if (d->itemDecorationAt(event->position().toPoint()) == -1) // ### what about expanding/collapsing state ?
2096 QAbstractItemView::mouseMoveEvent(event);
2097}
2098
2099/*!
2100 \reimp
2101*/
2102void QTreeView::keyPressEvent(QKeyEvent *event)
2103{
2104 Q_D(QTreeView);
2105 QModelIndex current = currentIndex();
2106 //this is the management of the expansion
2107 if (d->isIndexValid(current) && d->model && d->itemsExpandable) {
2108 switch (event->key()) {
2109 case Qt::Key_Asterisk: {
2110 expandRecursively(current);
2111 break; }
2112 case Qt::Key_Plus:
2113 expand(current);
2114 break;
2115 case Qt::Key_Minus:
2116 collapse(current);
2117 break;
2118 }
2119 }
2120
2121 QAbstractItemView::keyPressEvent(event);
2122}
2123
2124/*!
2125 \reimp
2126*/
2127QModelIndex QTreeView::indexAt(const QPoint &point) const
2128{
2129 Q_D(const QTreeView);
2130 d->executePostedLayout();
2131
2132 int visualIndex = d->itemAtCoordinate(point.y());
2133 QModelIndex idx = d->modelIndex(visualIndex);
2134 if (!idx.isValid())
2135 return QModelIndex();
2136
2137 if (d->viewItems.at(visualIndex).spanning)
2138 return idx;
2139
2140 int column = d->columnAt(point.x());
2141 if (column == idx.column())
2142 return idx;
2143 if (column < 0)
2144 return QModelIndex();
2145 return idx.sibling(idx.row(), column);
2146}
2147
2148/*!
2149 Returns the model index of the item above \a index.
2150*/
2151QModelIndex QTreeView::indexAbove(const QModelIndex &index) const
2152{
2153 Q_D(const QTreeView);
2154 if (!d->isIndexValid(index))
2155 return QModelIndex();
2156 d->executePostedLayout();
2157 int i = d->viewIndex(index);
2158 if (--i < 0)
2159 return QModelIndex();
2160 const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
2161 return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
2162}
2163
2164/*!
2165 Returns the model index of the item below \a index.
2166*/
2167QModelIndex QTreeView::indexBelow(const QModelIndex &index) const
2168{
2169 Q_D(const QTreeView);
2170 if (!d->isIndexValid(index))
2171 return QModelIndex();
2172 d->executePostedLayout();
2173 int i = d->viewIndex(index);
2174 if (++i >= d->viewItems.size())
2175 return QModelIndex();
2176 const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
2177 return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
2178}
2179
2180/*!
2181 \internal
2182
2183 Lays out the items in the tree view.
2184*/
2185void QTreeView::doItemsLayout()
2186{
2187 Q_D(QTreeView);
2188 d->doItemsLayout(QTreeViewPrivate::ExpandItems::RootOnly,
2189 QTreeViewPrivate::InitializeNewItems::Initialize);
2190}
2191
2192void QTreeViewPrivate::doItemsLayout(ExpandItems expandItems, InitializeNewItems initializeNewItems)
2193{
2194 Q_Q(QTreeView);
2195 if (hasRemovedItems) {
2196 //clean the QSet that may contains old (and this invalid) indexes
2197 hasRemovedItems = false;
2198 const auto pred = [](const QPersistentModelIndex &idx) { return !idx.isValid(); };
2199 expandedIndexes.removeIf(pred);
2200 hiddenIndexes.removeIf(pred);
2201 }
2202 viewItems.clear(); // prepare for new layout
2203 QModelIndex parent = root;
2204 if (model->hasChildren(parent))
2205 layout(-1, expandItems, initializeNewItems);
2206
2207 q->QAbstractItemView::doItemsLayout();
2208 header->doItemsLayout();
2209 // reset the accessibility representation of the view once control has
2210 // returned to the event loop. This avoids that we destroy UI tree elements
2211 // in the platform layer as part of a model-reset notification, while those
2212 // elements respond to a query (i.e. of rect, which results in a call to
2213 // doItemsLayout().
2214 QMetaObject::invokeMethod(q, [this]{ updateAccessibility(); }, Qt::QueuedConnection);
2215}
2216
2217/*!
2218 \reimp
2219*/
2220void QTreeView::reset()
2221{
2222 Q_D(QTreeView);
2223 d->expandedIndexes.clear();
2224 d->hiddenIndexes.clear();
2225 d->spanningIndexes.clear();
2226 d->viewItems.clear();
2227 QAbstractItemView::reset();
2228}
2229
2230/*!
2231 Returns the horizontal offset of the items in the treeview.
2232
2233 Note that the tree view uses the horizontal header section
2234 positions to determine the positions of columns in the view.
2235
2236 \sa verticalOffset()
2237*/
2238int QTreeView::horizontalOffset() const
2239{
2240 Q_D(const QTreeView);
2241 return d->header->offset();
2242}
2243
2244/*!
2245 Returns the vertical offset of the items in the tree view.
2246
2247 \sa horizontalOffset()
2248*/
2249int QTreeView::verticalOffset() const
2250{
2251 Q_D(const QTreeView);
2252 if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) {
2253 if (d->uniformRowHeights)
2254 return verticalScrollBar()->value() * d->defaultItemHeight;
2255 // If we are scrolling per item and have non-uniform row heights,
2256 // finding the vertical offset in pixels is going to be relatively slow.
2257 // ### find a faster way to do this
2258 d->executePostedLayout();
2259 int offset = 0;
2260 const int cnt = qMin(d->viewItems.size(), verticalScrollBar()->value());
2261 for (int i = 0; i < cnt; ++i)
2262 offset += d->itemHeight(i);
2263 return offset;
2264 }
2265 // scroll per pixel
2266 return verticalScrollBar()->value();
2267}
2268
2269/*!
2270 Move the cursor in the way described by \a cursorAction, using the
2271 information provided by the button \a modifiers.
2272*/
2273QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
2274{
2275 Q_D(QTreeView);
2276 Q_UNUSED(modifiers);
2277
2278 d->executePostedLayout();
2279
2280 QModelIndex current = currentIndex();
2281 if (!current.isValid()) {
2282 int i = d->below(-1);
2283 int c = 0;
2284 while (c < d->header->count() && d->header->isSectionHidden(d->header->logicalIndex(c)))
2285 ++c;
2286 if (i < d->viewItems.size() && c < d->header->count()) {
2287 return d->modelIndex(i, d->header->logicalIndex(c));
2288 }
2289 return QModelIndex();
2290 }
2291
2292 const int vi = qMax(0, d->viewIndex(current));
2293
2294 if (isRightToLeft()) {
2295 if (cursorAction == MoveRight)
2296 cursorAction = MoveLeft;
2297 else if (cursorAction == MoveLeft)
2298 cursorAction = MoveRight;
2299 }
2300 switch (cursorAction) {
2301 case MoveNext:
2302 case MoveDown:
2303 return d->modelIndex(d->below(vi), current.column());
2304 case MovePrevious:
2305 case MoveUp:
2306 return d->modelIndex(d->above(vi), current.column());
2307 case MoveLeft: {
2308 QScrollBar *sb = horizontalScrollBar();
2309 if (vi < d->viewItems.size() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) {
2310 d->collapse(vi, true);
2311 d->moveCursorUpdatedView = true;
2312 } else {
2313 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, nullptr, this);
2314 if (descend) {
2315 QModelIndex par = current.parent();
2316 if (par.isValid() && par != rootIndex())
2317 return par;
2318 else
2319 descend = false;
2320 }
2321 if (!descend) {
2322 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2323 int visualColumn = d->header->visualIndex(current.column()) - 1;
2324 while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn)))
2325 visualColumn--;
2326 int newColumn = d->header->logicalIndex(visualColumn);
2327 QModelIndex next = current.sibling(current.row(), newColumn);
2328 if (next.isValid())
2329 return next;
2330 }
2331
2332 int oldValue = sb->value();
2333 sb->setValue(sb->value() - sb->singleStep());
2334 if (oldValue != sb->value())
2335 d->moveCursorUpdatedView = true;
2336 }
2337
2338 }
2339 updateGeometries();
2340 viewport()->update();
2341 break;
2342 }
2343 case MoveRight:
2344 if (vi < d->viewItems.size() && !d->viewItems.at(vi).expanded && d->itemsExpandable
2345 && d->hasVisibleChildren(d->viewItems.at(vi).index)) {
2346 d->expand(vi, true);
2347 d->moveCursorUpdatedView = true;
2348 } else {
2349 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, nullptr, this);
2350 if (descend) {
2351 QModelIndex idx = d->modelIndex(d->below(vi));
2352 if (idx.parent() == current)
2353 return idx;
2354 else
2355 descend = false;
2356 }
2357 if (!descend) {
2358 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2359 int visualColumn = d->header->visualIndex(current.column()) + 1;
2360 while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn)))
2361 visualColumn++;
2362 const int newColumn = d->header->logicalIndex(visualColumn);
2363 const QModelIndex next = current.sibling(current.row(), newColumn);
2364 if (next.isValid())
2365 return next;
2366 }
2367
2368 //last restort: we change the scrollbar value
2369 QScrollBar *sb = horizontalScrollBar();
2370 int oldValue = sb->value();
2371 sb->setValue(sb->value() + sb->singleStep());
2372 if (oldValue != sb->value())
2373 d->moveCursorUpdatedView = true;
2374 }
2375 }
2376 updateGeometries();
2377 viewport()->update();
2378 break;
2379 case MovePageUp:
2380 return d->modelIndex(d->pageUp(vi), current.column());
2381 case MovePageDown:
2382 return d->modelIndex(d->pageDown(vi), current.column());
2383 case MoveHome:
2384 return d->modelIndex(d->itemForKeyHome(), current.column());
2385 case MoveEnd:
2386 return d->modelIndex(d->itemForKeyEnd(), current.column());
2387 }
2388 return current;
2389}
2390
2391/*!
2392 Applies the selection \a command to the items in or touched by the
2393 rectangle, \a rect.
2394
2395 \sa selectionCommand()
2396*/
2397void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2398{
2399 Q_D(QTreeView);
2400 if (!selectionModel() || rect.isNull())
2401 return;
2402
2403 d->executePostedLayout();
2404 QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right())
2405 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()));
2406 QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2407 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()));
2408 QModelIndex topLeft = indexAt(tl);
2409 QModelIndex bottomRight = indexAt(br);
2410 if (!topLeft.isValid() && !bottomRight.isValid()) {
2411 if (command & QItemSelectionModel::Clear)
2412 selectionModel()->clear();
2413 return;
2414 }
2415 if (!topLeft.isValid() && !d->viewItems.isEmpty())
2416 topLeft = d->viewItems.constFirst().index;
2417 if (!bottomRight.isValid() && !d->viewItems.isEmpty()) {
2418 const int column = d->header->logicalIndex(d->header->count() - 1);
2419 const QModelIndex index = d->viewItems.constLast().index;
2420 bottomRight = index.sibling(index.row(), column);
2421 }
2422
2423 if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight))
2424 return;
2425
2426 d->select(topLeft, bottomRight, command);
2427}
2428
2429/*!
2430 Returns the rectangle from the viewport of the items in the given
2431 \a selection.
2432
2433 Since 4.7, the returned region only contains rectangles intersecting
2434 (or included in) the viewport.
2435*/
2436QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const
2437{
2438 Q_D(const QTreeView);
2439 if (selection.isEmpty())
2440 return QRegion();
2441
2442 QRegion selectionRegion;
2443 const QRect &viewportRect = d->viewport->rect();
2444 for (const auto &range : selection) {
2445 if (!range.isValid())
2446 continue;
2447 QModelIndex parent = range.parent();
2448 QModelIndex leftIndex = range.topLeft();
2449 int columnCount = d->model->columnCount(parent);
2450 while (leftIndex.isValid() && isIndexHidden(leftIndex)) {
2451 if (leftIndex.column() + 1 < columnCount)
2452 leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent);
2453 else
2454 leftIndex = QModelIndex();
2455 }
2456 if (!leftIndex.isValid())
2457 continue;
2458 const QRect leftRect = d->visualRect(leftIndex, QTreeViewPrivate::SingleSection);
2459 int top = leftRect.top();
2460 QModelIndex rightIndex = range.bottomRight();
2461 while (rightIndex.isValid() && isIndexHidden(rightIndex)) {
2462 if (rightIndex.column() - 1 >= 0)
2463 rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent);
2464 else
2465 rightIndex = QModelIndex();
2466 }
2467 if (!rightIndex.isValid())
2468 continue;
2469 const QRect rightRect = d->visualRect(rightIndex, QTreeViewPrivate::SingleSection);
2470 int bottom = rightRect.bottom();
2471 if (top > bottom)
2472 qSwap<int>(top, bottom);
2473 int height = bottom - top + 1;
2474 if (d->header->sectionsMoved()) {
2475 for (int c = range.left(); c <= range.right(); ++c) {
2476 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2477 if (viewportRect.intersects(rangeRect))
2478 selectionRegion += rangeRect;
2479 }
2480 } else {
2481 QRect combined = leftRect|rightRect;
2482 combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left()));
2483 if (viewportRect.intersects(combined))
2484 selectionRegion += combined;
2485 }
2486 }
2487 return selectionRegion;
2488}
2489
2490/*!
2491 \reimp
2492*/
2493QModelIndexList QTreeView::selectedIndexes() const
2494{
2495 QModelIndexList viewSelected;
2496 QModelIndexList modelSelected;
2497 if (selectionModel())
2498 modelSelected = selectionModel()->selectedIndexes();
2499 for (int i = 0; i < modelSelected.size(); ++i) {
2500 // check that neither the parents nor the index is hidden before we add
2501 QModelIndex index = modelSelected.at(i);
2502 while (index.isValid() && !isIndexHidden(index))
2503 index = index.parent();
2504 if (index.isValid())
2505 continue;
2506 viewSelected.append(modelSelected.at(i));
2507 }
2508 return viewSelected;
2509}
2510
2511/*!
2512 Scrolls the contents of the tree view by (\a dx, \a dy).
2513*/
2514void QTreeView::scrollContentsBy(int dx, int dy)
2515{
2516 Q_D(QTreeView);
2517
2518 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
2519
2520 dx = isRightToLeft() ? -dx : dx;
2521 if (dx) {
2522 int oldOffset = d->header->offset();
2523 d->header->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
2524 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2525 int newOffset = d->header->offset();
2526 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
2527 }
2528 }
2529
2530 const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight;
2531 if (d->viewItems.isEmpty() || itemHeight == 0)
2532 return;
2533
2534 // guestimate the number of items in the viewport
2535 int viewCount = d->viewport->height() / itemHeight;
2536 int maxDeltaY = qMin(d->viewItems.size(), viewCount);
2537 // no need to do a lot of work if we are going to redraw the whole thing anyway
2538 if (qAbs(dy) > qAbs(maxDeltaY) && d->editorIndexHash.isEmpty()) {
2539 verticalScrollBar()->update();
2540 d->viewport->update();
2541 return;
2542 }
2543
2544 if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2545 int currentScrollbarValue = verticalScrollBar()->value();
2546 int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy)
2547 int currentViewIndex = currentScrollbarValue; // the first visible item
2548 int previousViewIndex = previousScrollbarValue;
2549 dy = 0;
2550 if (previousViewIndex < currentViewIndex) { // scrolling down
2551 for (int i = previousViewIndex; i < currentViewIndex; ++i) {
2552 if (i < d->viewItems.size())
2553 dy -= d->itemHeight(i);
2554 }
2555 } else if (previousViewIndex > currentViewIndex) { // scrolling up
2556 for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) {
2557 if (i < d->viewItems.size())
2558 dy += d->itemHeight(i);
2559 }
2560 }
2561 }
2562
2563 d->scrollContentsBy(dx, dy);
2564}
2565
2566/*!
2567 This slot is called whenever a column has been moved.
2568*/
2569void QTreeView::columnMoved()
2570{
2571 Q_D(QTreeView);
2572 updateEditorGeometries();
2573 d->viewport->update();
2574}
2575
2576/*!
2577 \internal
2578*/
2579void QTreeView::reexpand()
2580{
2581 // do nothing
2582}
2583
2584/*!
2585 Informs the view that the rows from the \a start row to the \a end row
2586 inclusive have been inserted into the \a parent model item.
2587*/
2588void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end)
2589{
2590 Q_D(QTreeView);
2591 // if we are going to do a complete relayout anyway, there is no need to update
2592 if (d->delayedPendingLayout) {
2593 QAbstractItemView::rowsInserted(parent, start, end);
2594 return;
2595 }
2596
2597 //don't add a hierarchy on a column != 0
2598 if (parent.column() != 0 && parent.isValid()) {
2599 QAbstractItemView::rowsInserted(parent, start, end);
2600 return;
2601 }
2602
2603 const int parentRowCount = d->model->rowCount(parent);
2604 const int delta = end - start + 1;
2605 if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) {
2606 QAbstractItemView::rowsInserted(parent, start, end);
2607 return;
2608 }
2609
2610 const int parentItem = d->viewIndex(parent);
2611 if (((parentItem != -1) && d->viewItems.at(parentItem).expanded)
2612 || (parent == d->root)) {
2613 d->doDelayedItemsLayout();
2614 } else if (parentItem != -1 && parentRowCount == delta) {
2615 // the parent just went from 0 children to more. update to re-paint the decoration
2616 d->viewItems[parentItem].hasChildren = true;
2617 viewport()->update();
2618 }
2619 QAbstractItemView::rowsInserted(parent, start, end);
2620}
2621
2622/*!
2623 Informs the view that the rows from the \a start row to the \a end row
2624 inclusive are about to removed from the given \a parent model item.
2625*/
2626void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
2627{
2628 Q_D(QTreeView);
2629 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
2630 d->viewItems.clear();
2631}
2632
2633/*!
2634 Informs the view that the rows from the \a start row to the \a end row
2635 inclusive have been removed from the given \a parent model item.
2636*/
2637void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end)
2638{
2639 Q_D(QTreeView);
2640 d->viewItems.clear();
2641 d->doDelayedItemsLayout();
2642 d->hasRemovedItems = true;
2643 d->rowsRemoved(parent, start, end);
2644}
2645
2646/*!
2647 Informs the tree view that the number of columns in the tree view has
2648 changed from \a oldCount to \a newCount.
2649*/
2650void QTreeView::columnCountChanged(int oldCount, int newCount)
2651{
2652 Q_D(QTreeView);
2653 if (oldCount == 0 && newCount > 0) {
2654 //if the first column has just been added we need to relayout.
2655 d->doDelayedItemsLayout();
2656 }
2657
2658 if (isVisible())
2659 updateGeometries();
2660 viewport()->update();
2661}
2662
2663/*!
2664 Resizes the \a column given to the size of its contents.
2665
2666 \sa columnWidth(), setColumnWidth(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
2667*/
2668void QTreeView::resizeColumnToContents(int column)
2669{
2670 Q_D(QTreeView);
2671 d->executePostedLayout();
2672 if (column < 0 || column >= d->header->count())
2673 return;
2674 int contents = sizeHintForColumn(column);
2675 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column);
2676 d->header->resizeSection(column, qMax(contents, header));
2677}
2678
2679/*!
2680 Sorts the model by the values in the given \a column and \a order.
2681
2682 \a column may be -1, in which case no sort indicator will be shown
2683 and the model will return to its natural, unsorted order. Note that not
2684 all models support this and may even crash in this case.
2685
2686 \sa sortingEnabled
2687*/
2688void QTreeView::sortByColumn(int column, Qt::SortOrder order)
2689{
2690 Q_D(QTreeView);
2691 if (column < -1)
2692 return;
2693 d->header->setSortIndicator(column, order);
2694 // If sorting is not enabled or has the same order as before, force to sort now
2695 // else sorting will be trigger through sortIndicatorChanged()
2696 if (!d->sortingEnabled ||
2697 (d->header->sortIndicatorSection() == column && d->header->sortIndicatorOrder() == order))
2698 d->model->sort(column, order);
2699}
2700
2701/*!
2702 \reimp
2703*/
2704void QTreeView::selectAll()
2705{
2706 Q_D(QTreeView);
2707 if (!selectionModel())
2708 return;
2709 SelectionMode mode = d->selectionMode;
2710 d->executePostedLayout(); //make sure we lay out the items
2711 if (mode != SingleSelection && mode != NoSelection && !d->viewItems.isEmpty()) {
2712 const QModelIndex &idx = d->viewItems.constLast().index;
2713 QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1);
2714 d->select(d->viewItems.constFirst().index, lastItemIndex,
2715 QItemSelectionModel::ClearAndSelect
2716 |QItemSelectionModel::Rows);
2717 }
2718}
2719
2720/*!
2721 \reimp
2722*/
2723QSize QTreeView::viewportSizeHint() const
2724{
2725 Q_D(const QTreeView);
2726 d->executePostedLayout(); // Make sure that viewItems are up to date.
2727
2728 if (d->viewItems.size() == 0)
2729 return QAbstractItemView::viewportSizeHint();
2730
2731 // Get rect for last item
2732 const QRect deepestRect = d->visualRect(d->viewItems.last().index,
2733 QTreeViewPrivate::SingleSection);
2734
2735 if (!deepestRect.isValid())
2736 return QAbstractItemView::viewportSizeHint();
2737
2738 QSize result = QSize(d->header->length(), deepestRect.bottom() + 1);
2739
2740 // add size for header
2741 result += QSize(0, d->header->isHidden() ? 0 : d->header->height());
2742
2743 return result;
2744}
2745
2746/*!
2747 Expands all expandable items.
2748
2749 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2750 data.
2751
2752 \warning If the model contains a large number of items,
2753 this function will take some time to execute.
2754
2755 \sa collapseAll(), expand(), collapse(), setExpanded()
2756*/
2757void QTreeView::expandAll()
2758{
2759 Q_D(QTreeView);
2760 d->viewItems.clear();
2761 d->interruptDelayedItemsLayout();
2762 d->doItemsLayout(QTreeViewPrivate::ExpandItems::AllLevels,
2763 QTreeViewPrivate::InitializeNewItems::Initialize);
2764 updateGeometries();
2765 d->viewport->update();
2766 d->updateAccessibility();
2767}
2768
2769/*!
2770 \since 5.13
2771 Expands the item at the given \a index and all its children to the
2772 given \a depth. The \a depth is relative to the given \a index.
2773 A \a depth of -1 will expand all children, a \a depth of 0 will
2774 only expand the given \a index.
2775
2776 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2777 data.
2778
2779 \warning If the model contains a large number of items,
2780 this function will take some time to execute.
2781
2782 \sa expandAll()
2783*/
2784void QTreeView::expandRecursively(const QModelIndex &index, int depth)
2785{
2786 Q_D(QTreeView);
2787
2788 if (depth < -1)
2789 return;
2790 // do layouting only once after expanding is done
2791 d->doDelayedItemsLayout();
2792 expand(index);
2793 if (depth == 0)
2794 return;
2795 QStack<std::pair<QModelIndex, int>> parents;
2796 parents.push({index, 0});
2797 while (!parents.isEmpty()) {
2798 const std::pair<QModelIndex, int> elem = parents.pop();
2799 const QModelIndex &parent = elem.first;
2800 const int curDepth = elem.second;
2801 const int rowCount = d->model->rowCount(parent);
2802 for (int row = 0; row < rowCount; ++row) {
2803 const QModelIndex child = d->model->index(row, 0, parent);
2804 if (!d->isIndexValid(child))
2805 break;
2806 if (depth == -1 || curDepth + 1 < depth)
2807 parents.push({child, curDepth + 1});
2808 if (d->isIndexExpanded(child))
2809 continue;
2810 if (d->storeExpanded(child))
2811 emit expanded(child);
2812 }
2813 }
2814}
2815
2816/*!
2817 Collapses all expanded items.
2818
2819 \sa expandAll(), expand(), collapse(), setExpanded()
2820*/
2821void QTreeView::collapseAll()
2822{
2823 Q_D(QTreeView);
2824 QSet<QPersistentModelIndex> old_expandedIndexes;
2825 old_expandedIndexes = d->expandedIndexes;
2826 d->expandedIndexes.clear();
2827 if (!signalsBlocked() && isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed))) {
2828 QSet<QPersistentModelIndex>::const_iterator i = old_expandedIndexes.constBegin();
2829 for (; i != old_expandedIndexes.constEnd(); ++i) {
2830 const QPersistentModelIndex &mi = (*i);
2831 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2832 emit collapsed(mi);
2833 }
2834 }
2835 doItemsLayout();
2836}
2837
2838/*!
2839 Expands all expandable items to the given \a depth.
2840
2841 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
2842 data.
2843
2844 \sa expandAll(), collapseAll(), expand(), collapse(), setExpanded()
2845*/
2846void QTreeView::expandToDepth(int depth)
2847{
2848 Q_D(QTreeView);
2849 d->viewItems.clear();
2850 QSet<QPersistentModelIndex> old_expandedIndexes;
2851 old_expandedIndexes = d->expandedIndexes;
2852 d->expandedIndexes.clear();
2853 d->interruptDelayedItemsLayout();
2854 d->layout(-1, QTreeViewPrivate::ExpandItems::RootOnly,
2855 QTreeViewPrivate::InitializeNewItems::Initialize);
2856 for (int i = 0; i < d->viewItems.size(); ++i) {
2857 if (q20::cmp_less_equal(d->viewItems.at(i).level, depth)) {
2858 d->viewItems[i].expanded = true;
2859 d->layout(i, QTreeViewPrivate::ExpandItems::RootOnly,
2860 QTreeViewPrivate::InitializeNewItems::Initialize);
2861 d->storeExpanded(d->viewItems.at(i).index);
2862 }
2863 }
2864
2865 bool someSignalEnabled = isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed));
2866 someSignalEnabled |= isSignalConnected(QMetaMethod::fromSignal(&QTreeView::expanded));
2867
2868 if (!signalsBlocked() && someSignalEnabled) {
2869 // emit signals
2870 QSet<QPersistentModelIndex> collapsedIndexes = old_expandedIndexes - d->expandedIndexes;
2871 QSet<QPersistentModelIndex>::const_iterator i = collapsedIndexes.constBegin();
2872 for (; i != collapsedIndexes.constEnd(); ++i) {
2873 const QPersistentModelIndex &mi = (*i);
2874 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2875 emit collapsed(mi);
2876 }
2877
2878 QSet<QPersistentModelIndex> expandedIndexs = d->expandedIndexes - old_expandedIndexes;
2879 i = expandedIndexs.constBegin();
2880 for (; i != expandedIndexs.constEnd(); ++i) {
2881 const QPersistentModelIndex &mi = (*i);
2882 if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
2883 emit expanded(mi);
2884 }
2885 }
2886
2887 updateGeometries();
2888 d->viewport->update();
2889 d->updateAccessibility();
2890}
2891
2892/*!
2893 This function is called whenever \a{column}'s size is changed in
2894 the header. \a oldSize and \a newSize give the previous size and
2895 the new size in pixels.
2896
2897 \sa setColumnWidth()
2898*/
2899void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */)
2900{
2901 Q_D(QTreeView);
2902 d->columnsToUpdate.append(column);
2903 if (!d->columnResizeTimer.isActive())
2904 d->columnResizeTimer.start(0ns, this);
2905}
2906
2907/*!
2908 \reimp
2909*/
2910void QTreeView::updateGeometries()
2911{
2912 Q_D(QTreeView);
2913 if (d->header) {
2914 if (d->geometryRecursionBlock)
2915 return;
2916 d->geometryRecursionBlock = true;
2917 int height = 0;
2918 if (!d->header->isHidden()) {
2919 height = qMax(d->header->minimumHeight(), d->header->sizeHint().height());
2920 height = qMin(height, d->header->maximumHeight());
2921 }
2922 setViewportMargins(0, height, 0, 0);
2923 QRect vg = d->viewport->geometry();
2924 QRect geometryRect(vg.left(), vg.top() - height, vg.width(), height);
2925 d->header->setGeometry(geometryRect);
2926 QMetaObject::invokeMethod(d->header, "updateGeometries");
2927 d->updateScrollBars();
2928 d->geometryRecursionBlock = false;
2929 }
2930 QAbstractItemView::updateGeometries();
2931}
2932
2933/*!
2934 Returns the size hint for the \a column's width or -1 if there is no
2935 model.
2936
2937 If you need to set the width of a given column to a fixed value, call
2938 QHeaderView::resizeSection() on the view's header.
2939
2940 If you reimplement this function in a subclass, note that the value you
2941 return is only used when resizeColumnToContents() is called. In that case,
2942 if a larger column width is required by either the view's header or
2943 the item delegate, that width will be used instead.
2944
2945 \sa QWidget::sizeHint, header(), QHeaderView::resizeContentsPrecision()
2946*/
2947int QTreeView::sizeHintForColumn(int column) const
2948{
2949 Q_D(const QTreeView);
2950 d->executePostedLayout();
2951 if (d->viewItems.isEmpty())
2952 return -1;
2953 ensurePolished();
2954 int w = 0;
2955 QStyleOptionViewItem option;
2956 initViewItemOption(&option);
2957 const QList<QTreeViewItem> viewItems = d->viewItems;
2958
2959 const int maximumProcessRows = d->header->resizeContentsPrecision(); // To avoid this to take forever.
2960
2961 int offset = 0;
2962 int start = d->firstVisibleItem(&offset);
2963 int end = d->lastVisibleItem(start, offset);
2964 if (start < 0 || end < 0 || end == viewItems.size() - 1) {
2965 end = viewItems.size() - 1;
2966 if (maximumProcessRows < 0) {
2967 start = 0;
2968 } else if (maximumProcessRows == 0) {
2969 start = qMax(0, end - 1);
2970 int remainingHeight = viewport()->height();
2971 while (start > 0 && remainingHeight > 0) {
2972 remainingHeight -= d->itemHeight(start);
2973 --start;
2974 }
2975 } else {
2976 start = qMax(0, end - maximumProcessRows);
2977 }
2978 }
2979
2980 int rowsProcessed = 0;
2981
2982 for (int i = start; i <= end; ++i) {
2983 if (viewItems.at(i).spanning)
2984 continue; // we have no good size hint
2985 QModelIndex index = viewItems.at(i).index;
2986 index = index.sibling(index.row(), column);
2987 w = d->widthHintForIndex(index, w, option, i);
2988 ++rowsProcessed;
2989 if (rowsProcessed == maximumProcessRows)
2990 break;
2991 }
2992
2993 --end;
2994 int actualBottom = viewItems.size() - 1;
2995
2996 if (maximumProcessRows == 0)
2997 rowsProcessed = 0; // skip the while loop
2998
2999 while (rowsProcessed != maximumProcessRows && (start > 0 || end < actualBottom)) {
3000 int idx = -1;
3001
3002 if ((rowsProcessed % 2 && start > 0) || end == actualBottom) {
3003 while (start > 0) {
3004 --start;
3005 if (viewItems.at(start).spanning)
3006 continue;
3007 idx = start;
3008 break;
3009 }
3010 } else {
3011 while (end < actualBottom) {
3012 ++end;
3013 if (viewItems.at(end).spanning)
3014 continue;
3015 idx = end;
3016 break;
3017 }
3018 }
3019 if (idx < 0)
3020 continue;
3021
3022 QModelIndex index = viewItems.at(idx).index;
3023 index = index.sibling(index.row(), column);
3024 w = d->widthHintForIndex(index, w, option, idx);
3025 ++rowsProcessed;
3026 }
3027 return w;
3028}
3029
3030/*!
3031 Returns the size hint for the row indicated by \a index.
3032
3033 \sa sizeHintForColumn(), uniformRowHeights()
3034*/
3035int QTreeView::indexRowSizeHint(const QModelIndex &index) const
3036{
3037 Q_D(const QTreeView);
3038 if (!d->isIndexValid(index) || !d->itemDelegate)
3039 return 0;
3040
3041 int start = -1;
3042 int end = -1;
3043 int indexRow = index.row();
3044 int count = d->header->count();
3045 bool emptyHeader = (count == 0);
3046 QModelIndex parent = index.parent();
3047
3048 if (count && isVisible()) {
3049 // If the sections have moved, we end up checking too many or too few
3050 start = d->header->visualIndexAt(0);
3051 } else {
3052 // If the header has not been laid out yet, we use the model directly
3053 count = d->model->columnCount(parent);
3054 }
3055
3056 if (isRightToLeft()) {
3057 start = (start == -1 ? count - 1 : start);
3058 end = 0;
3059 } else {
3060 start = (start == -1 ? 0 : start);
3061 end = count - 1;
3062 }
3063
3064 if (end < start)
3065 qSwap(end, start);
3066
3067 int height = -1;
3068 QStyleOptionViewItem option;
3069 initViewItemOption(&option);
3070 // ### If we want word wrapping in the items,
3071 // ### we need to go through all the columns
3072 // ### and set the width of the column
3073
3074 // Hack to speed up the function
3075 option.rect.setWidth(-1);
3076
3077 for (int column = start; column <= end; ++column) {
3078 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column);
3079 if (d->header->isSectionHidden(logicalColumn))
3080 continue;
3081 QModelIndex idx = d->model->index(indexRow, logicalColumn, parent);
3082 if (idx.isValid()) {
3083 QWidget *editor = d->editorForIndex(idx).widget.data();
3084 if (editor && d->persistent.contains(editor)) {
3085 height = qMax(height, editor->sizeHint().height());
3086 int min = editor->minimumSize().height();
3087 int max = editor->maximumSize().height();
3088 height = qBound(min, height, max);
3089 }
3090 int hint = itemDelegateForIndex(idx)->sizeHint(option, idx).height();
3091 height = qMax(height, hint);
3092 }
3093 }
3094
3095 return height;
3096}
3097
3098/*!
3099 Returns the height of the row indicated by the given \a index.
3100 \sa indexRowSizeHint()
3101*/
3102int QTreeView::rowHeight(const QModelIndex &index) const
3103{
3104 Q_D(const QTreeView);
3105 d->executePostedLayout();
3106 int i = d->viewIndex(index);
3107 if (i == -1)
3108 return 0;
3109 return d->itemHeight(i);
3110}
3111
3112/*!
3113 \internal
3114*/
3115void QTreeView::horizontalScrollbarAction(int action)
3116{
3117 QAbstractItemView::horizontalScrollbarAction(action);
3118}
3119
3120/*!
3121 \reimp
3122*/
3123bool QTreeView::isIndexHidden(const QModelIndex &index) const
3124{
3125 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent()));
3126}
3127
3128/*
3129 private implementation
3130*/
3131void QTreeViewPrivate::initialize()
3132{
3133 Q_Q(QTreeView);
3134
3135 updateIndentationFromStyle();
3136 updateStyledFrameWidths();
3137 q->setSelectionBehavior(QAbstractItemView::SelectRows);
3138 q->setSelectionMode(QAbstractItemView::SingleSelection);
3139 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
3140 q->setAttribute(Qt::WA_MacShowFocusRect);
3141
3142 QHeaderView *header = new QHeaderView(Qt::Horizontal, q);
3143 header->setSectionsMovable(true);
3144 header->setStretchLastSection(true);
3145 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
3146 q->setHeader(header);
3147#if QT_CONFIG(animation)
3148 animationsEnabled = q->style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, q) > 0;
3149 animationConnection =
3150 QObjectPrivate::connect(&animatedOperation, &QVariantAnimation::finished,
3151 this, &QTreeViewPrivate::endAnimatedOperation);
3152#endif // animation
3153}
3154
3155void QTreeViewPrivate::clearConnections()
3156{
3157 for (QMetaObject::Connection &connection : modelConnections)
3158 QObject::disconnect(connection);
3159 for (QMetaObject::Connection &connection : headerConnections)
3160 QObject::disconnect(connection);
3161 QObject::disconnect(selectionmodelConnection);
3162 QObject::disconnect(sortHeaderConnection);
3163#if QT_CONFIG(animation)
3164 QObject::disconnect(animationConnection);
3165#endif
3166}
3167
3168void QTreeViewPrivate::expand(int item, bool emitSignal)
3169{
3170 Q_Q(QTreeView);
3171
3172 if (item == -1 || viewItems.at(item).expanded)
3173 return;
3174 const QModelIndex index = viewItems.at(item).index;
3175 if (index.flags() & Qt::ItemNeverHasChildren)
3176 return;
3177
3178#if QT_CONFIG(animation)
3179 if (emitSignal && animationsEnabled)
3180 prepareAnimatedOperation(item, QVariantAnimation::Forward);
3181#endif // animation
3182 //if already animating, stateBeforeAnimation is set to the correct value
3183 if (state != QAbstractItemView::AnimatingState)
3184 stateBeforeAnimation = state;
3185 q->setState(QAbstractItemView::ExpandingState);
3186 storeExpanded(index);
3187 viewItems[item].expanded = true;
3188 layout(item, ExpandItems::RootOnly, InitializeNewItems::Initialize);
3189 q->setState(stateBeforeAnimation);
3190
3191 if (model->canFetchMore(index))
3192 model->fetchMore(index);
3193 if (emitSignal) {
3194 emit q->expanded(index);
3195#if QT_CONFIG(animation)
3196 if (animationsEnabled)
3197 beginAnimatedOperation();
3198#endif // animation
3199 }
3200 updateAccessibility();
3201}
3202
3203void QTreeViewPrivate::insertViewItems(int pos, int count, const QTreeViewItem &viewItem)
3204{
3205 viewItems.insert(pos, count, viewItem);
3206 QTreeViewItem *items = viewItems.data();
3207 for (int i = pos + count; i < viewItems.size(); i++)
3208 if (items[i].parentItem >= pos)
3209 items[i].parentItem += count;
3210}
3211
3212void QTreeViewPrivate::removeViewItems(int pos, int count)
3213{
3214 viewItems.remove(pos, count);
3215 QTreeViewItem *items = viewItems.data();
3216 for (int i = pos; i < viewItems.size(); i++)
3217 if (items[i].parentItem >= pos)
3218 items[i].parentItem -= count;
3219}
3220
3221void QTreeViewPrivate::collapse(int item, bool emitSignal)
3222{
3223 Q_Q(QTreeView);
3224
3225 if (item == -1 || expandedIndexes.isEmpty())
3226 return;
3227
3228 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
3229 delayedAutoScroll.stop();
3230
3231 int total = viewItems.at(item).total;
3232 const QModelIndex &modelIndex = viewItems.at(item).index;
3233 if (!isPersistent(modelIndex))
3234 return; // if the index is not persistent, no chances it is expanded
3235 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex);
3236 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false)
3237 return; // nothing to do
3238
3239#if QT_CONFIG(animation)
3240 if (emitSignal && animationsEnabled)
3241 prepareAnimatedOperation(item, QVariantAnimation::Backward);
3242#endif // animation
3243
3244 //if already animating, stateBeforeAnimation is set to the correct value
3245 if (state != QAbstractItemView::AnimatingState)
3246 stateBeforeAnimation = state;
3247 q->setState(QAbstractItemView::CollapsingState);
3248 expandedIndexes.erase(it);
3249 viewItems[item].expanded = false;
3250 int index = item;
3251 while (index > -1) {
3252 viewItems[index].total -= total;
3253 index = viewItems[index].parentItem;
3254 }
3255 removeViewItems(item + 1, total); // collapse
3256 q->setState(stateBeforeAnimation);
3257
3258 if (emitSignal) {
3259 emit q->collapsed(modelIndex);
3260#if QT_CONFIG(animation)
3261 if (animationsEnabled)
3262 beginAnimatedOperation();
3263#endif // animation
3264 }
3265}
3266
3267#if QT_CONFIG(animation)
3268void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction)
3269{
3270 animatedOperation.item = item;
3271 animatedOperation.viewport = viewport;
3272 animatedOperation.setDirection(direction);
3273
3274 int top = coordinateForItem(item) + itemHeight(item);
3275 QRect rect = viewport->rect();
3276 rect.setTop(top);
3277 if (direction == QVariantAnimation::Backward) {
3278 const int limit = rect.height() * 2;
3279 int h = 0;
3280 int c = item + viewItems.at(item).total + 1;
3281 for (int i = item + 1; i < c && h < limit; ++i)
3282 h += itemHeight(i);
3283 rect.setHeight(h);
3284 animatedOperation.setEndValue(top + h);
3285 }
3286 animatedOperation.setStartValue(top);
3287 animatedOperation.before = renderTreeToPixmapForAnimation(rect);
3288}
3289
3290void QTreeViewPrivate::beginAnimatedOperation()
3291{
3292 Q_Q(QTreeView);
3293
3294 QRect rect = viewport->rect();
3295 rect.setTop(animatedOperation.top());
3296 if (animatedOperation.direction() == QVariantAnimation::Forward) {
3297 const int limit = rect.height() * 2;
3298 int h = 0;
3299 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1;
3300 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i)
3301 h += itemHeight(i);
3302 rect.setHeight(h);
3303 animatedOperation.setEndValue(animatedOperation.top() + h);
3304 }
3305
3306 if (!rect.isEmpty()) {
3307 animatedOperation.after = renderTreeToPixmapForAnimation(rect);
3308
3309 q->setState(QAbstractItemView::AnimatingState);
3310 animatedOperation.start(); //let's start the animation
3311 }
3312}
3313
3314void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const
3315{
3316 const int start = animatedOperation.startValue().toInt(),
3317 end = animatedOperation.endValue().toInt(),
3318 current = animatedOperation.currentValue().toInt();
3319 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward;
3320 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after;
3321 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height());
3322 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before;
3323 painter->drawPixmap(0, current, bottom);
3324}
3325
3326QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const
3327{
3328 Q_Q(const QTreeView);
3329 QPixmap pixmap(rect.size() * q->devicePixelRatio());
3330 pixmap.setDevicePixelRatio(q->devicePixelRatio());
3331 if (rect.size().isEmpty())
3332 return pixmap;
3333 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels.
3334 QPainter painter(&pixmap);
3335 painter.setLayoutDirection(q->layoutDirection());
3336 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base());
3337 painter.translate(0, -rect.top());
3338 q->drawTree(&painter, QRegion(rect));
3339 painter.end();
3340
3341 //and now let's render the editors the editors
3342 QStyleOptionViewItem option;
3343 q->initViewItemOption(&option);
3344 for (QEditorIndexHash::const_iterator it = editorIndexHash.constBegin(); it != editorIndexHash.constEnd(); ++it) {
3345 QWidget *editor = it.key();
3346 const QModelIndex &index = it.value();
3347 option.rect = visualRect(index, SingleSection);
3348 if (option.rect.isValid()) {
3349
3350 if (QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index))
3351 delegate->updateEditorGeometry(editor, option, index);
3352
3353 const QPoint pos = editor->pos();
3354 if (rect.contains(pos)) {
3355 editor->render(&pixmap, pos - rect.topLeft());
3356 //the animation uses pixmap to display the treeview's content
3357 //the editor is rendered on this pixmap and thus can (should) be hidden
3358 editor->hide();
3359 }
3360 }
3361 }
3362
3363
3364 return pixmap;
3365}
3366
3367void QTreeViewPrivate::endAnimatedOperation()
3368{
3369 Q_Q(QTreeView);
3370 q->setState(stateBeforeAnimation);
3371 q->updateGeometries();
3372 viewport->update();
3373}
3374#endif // animation
3375
3376void QTreeViewPrivate::modelAboutToBeReset()
3377{
3378 viewItems.clear();
3379}
3380
3381void QTreeViewPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
3382{
3383 if (start <= 0 && 0 <= end)
3384 viewItems.clear();
3385 QAbstractItemViewPrivate::columnsAboutToBeRemoved(parent, start, end);
3386}
3387
3388void QTreeViewPrivate::columnsRemoved(const QModelIndex &parent, int start, int end)
3389{
3390 if (start <= 0 && 0 <= end)
3391 doDelayedItemsLayout();
3392 QAbstractItemViewPrivate::columnsRemoved(parent, start, end);
3393}
3394
3395void QTreeViewPrivate::updateAccessibility()
3396{
3397#if QT_CONFIG(accessibility)
3398 Q_Q(QTreeView);
3399 if (pendingAccessibilityUpdate) {
3400 pendingAccessibilityUpdate = false;
3401 if (QAccessible::isActive()) {
3402 QAccessibleTableModelChangeEvent event(q, QAccessibleTableModelChangeEvent::ModelReset);
3403 QAccessible::updateAccessibility(&event);
3404 }
3405 }
3406#endif
3407}
3408
3409
3410/** \internal
3411 creates and initialize the viewItem structure of the children of the element \li
3412
3413 set \a recursiveExpanding if the function has to expand all the children (called from expandAll)
3414 \a initializeNewItems is when we recurse from layout(-1), it means all the items after 'i' are
3415 not yet initialized and need not to be moved
3416 */
3417void QTreeViewPrivate::layout(int i, ExpandItems recursiveExpanding, InitializeNewItems initializeNewItems)
3418{
3419 Q_Q(QTreeView);
3420 QModelIndex current;
3421 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i);
3422
3423 if (i>=0 && !parent.isValid()) {
3424 //modelIndex() should never return something invalid for the real items.
3425 //This can happen if columncount has been set to 0.
3426 //To avoid infinite loop we stop here.
3427 return;
3428 }
3429
3430#if QT_CONFIG(accessibility)
3431 // QAccessibleTree's rowCount implementation uses viewItems.size(), so
3432 // we need to invalidate any cached accessibility data structures if
3433 // that value changes during the run of this function.
3434 const auto resetModelIfNeeded = qScopeGuard([oldViewItemsSize = viewItems.size(), this]{
3435 pendingAccessibilityUpdate |= oldViewItemsSize != viewItems.size();
3436 });
3437#endif
3438
3439 int count = 0;
3440 if (model->hasChildren(parent)) {
3441 if (model->canFetchMore(parent)) {
3442 // fetchMore first, otherwise we might not yet have any data for sizeHintForRow
3443 model->fetchMore(parent);
3444 // guestimate the number of items in the viewport, and fetch as many as might fit
3445 const int itemHeight = defaultItemHeight <= 0 ? q->sizeHintForRow(0) : defaultItemHeight;
3446 const int viewCount = itemHeight ? viewport->height() / itemHeight : 0;
3447 int lastCount = -1;
3448 while ((count = model->rowCount(parent)) < viewCount &&
3449 count != lastCount && model->canFetchMore(parent)) {
3450 model->fetchMore(parent);
3451 lastCount = count;
3452 }
3453 } else {
3454 count = model->rowCount(parent);
3455 }
3456 }
3457
3458 bool expanding = true;
3459 if (i == -1) {
3460 if (uniformRowHeights) {
3461 QModelIndex index = model->index(0, 0, parent);
3462 defaultItemHeight = q->indexRowSizeHint(index);
3463 }
3464 viewItems.resize(count);
3465 initializeNewItems = InitializeNewItems::DontInitialize;
3466 } else if (q20::cmp_not_equal(viewItems[i].total, count)) {
3467 if (initializeNewItems == InitializeNewItems::Initialize)
3468 insertViewItems(i + 1, count, QTreeViewItem()); // expand
3469 else if (count > 0)
3470 viewItems.resize(viewItems.size() + count);
3471 } else {
3472 expanding = false;
3473 }
3474
3475 int first = i + 1;
3476 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0);
3477 int hidden = 0;
3478 int last = 0;
3479 int children = 0;
3480 QTreeViewItem *item = nullptr;
3481 for (int j = first; j < first + count; ++j) {
3482 current = model->index(j - first, 0, parent);
3483 if (isRowHidden(current)) {
3484 ++hidden;
3485 last = j - hidden + children;
3486 } else {
3487 last = j - hidden + children;
3488 if (item)
3489 item->hasMoreSiblings = true;
3490 item = &viewItems[last];
3491 item->index = current;
3492 item->parentItem = i;
3493 item->level = level;
3494 item->height = 0;
3495 item->spanning = q->isFirstColumnSpanned(current.row(), parent);
3496 item->expanded = false;
3497 item->total = 0;
3498 item->hasMoreSiblings = false;
3499 if ((recursiveExpanding == ExpandItems::AllLevels
3500 && !(current.flags() & Qt::ItemNeverHasChildren))
3501 || isIndexExpanded(current)) {
3502 if (recursiveExpanding == ExpandItems::AllLevels && storeExpanded(current)
3503 && !q->signalsBlocked()) {
3504 emit q->expanded(current);
3505 }
3506 item->expanded = true;
3507 layout(last, recursiveExpanding, initializeNewItems);
3508 item = &viewItems[last];
3509 children += item->total;
3510 item->hasChildren = item->total > 0;
3511 last = j - hidden + children;
3512 } else {
3513 item->hasChildren = hasVisibleChildren(current);
3514 }
3515 }
3516 }
3517
3518 // remove hidden items
3519 if (hidden > 0) {
3520 if (initializeNewItems == InitializeNewItems::Initialize)
3521 removeViewItems(last + 1, hidden);
3522 else
3523 viewItems.resize(viewItems.size() - hidden);
3524 }
3525
3526 if (!expanding)
3527 return; // nothing changed
3528
3529 while (i > -1) {
3530 viewItems[i].total += count - hidden;
3531 i = viewItems[i].parentItem;
3532 }
3533}
3534
3535int QTreeViewPrivate::pageUp(int i) const
3536{
3537 int index = itemAtCoordinate(coordinateForItem(i) - viewport->height());
3538 while (isItemHiddenOrDisabled(index))
3539 index--;
3540 if (index == -1)
3541 index = 0;
3542 while (isItemHiddenOrDisabled(index))
3543 index++;
3544 return index >= viewItems.size() ? 0 : index;
3545}
3546
3547int QTreeViewPrivate::pageDown(int i) const
3548{
3549 int index = itemAtCoordinate(coordinateForItem(i) + viewport->height());
3550 while (isItemHiddenOrDisabled(index))
3551 index++;
3552 if (index == -1 || index >= viewItems.size())
3553 index = viewItems.size() - 1;
3554 while (isItemHiddenOrDisabled(index))
3555 index--;
3556 return index == -1 ? viewItems.size() - 1 : index;
3557}
3558
3559int QTreeViewPrivate::itemForKeyHome() const
3560{
3561 int index = 0;
3562 while (isItemHiddenOrDisabled(index))
3563 index++;
3564 return index >= viewItems.size() ? 0 : index;
3565}
3566
3567int QTreeViewPrivate::itemForKeyEnd() const
3568{
3569 int index = viewItems.size() - 1;
3570 while (isItemHiddenOrDisabled(index))
3571 index--;
3572 return index == -1 ? viewItems.size() - 1 : index;
3573}
3574
3575int QTreeViewPrivate::indentationForItem(int item) const
3576{
3577 if (item < 0 || item >= viewItems.size())
3578 return 0;
3579 int level = viewItems.at(item).level;
3580 if (rootDecoration)
3581 ++level;
3582 return level * indent;
3583}
3584
3585int QTreeViewPrivate::itemHeight(int item) const
3586{
3587 Q_ASSERT(item < viewItems.size());
3588 if (uniformRowHeights)
3589 return defaultItemHeight;
3590 if (viewItems.isEmpty())
3591 return 0;
3592 const QModelIndex &index = viewItems.at(item).index;
3593 if (!index.isValid())
3594 return 0;
3595 int height = viewItems.at(item).height;
3596 if (height <= 0) {
3597 height = q_func()->indexRowSizeHint(index);
3598 viewItems[item].height = height;
3599 }
3600 return qMax(height, 0);
3601}
3602
3603
3604/*!
3605 \internal
3606 Returns the viewport y coordinate for \a item.
3607*/
3608int QTreeViewPrivate::coordinateForItem(int item) const
3609{
3610 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3611 if (uniformRowHeights)
3612 return (item * defaultItemHeight) - vbar->value();
3613 // ### optimize (maybe do like QHeaderView by letting items have startposition)
3614 int y = 0;
3615 for (int i = 0; i < viewItems.size(); ++i) {
3616 if (i == item)
3617 return y - vbar->value();
3618 y += itemHeight(i);
3619 }
3620 } else { // ScrollPerItem
3621 int topViewItemIndex = vbar->value();
3622 if (uniformRowHeights)
3623 return defaultItemHeight * (item - topViewItemIndex);
3624 if (item >= topViewItemIndex) {
3625 // search in the visible area first and continue down
3626 // ### slow if the item is not visible
3627 int viewItemCoordinate = 0;
3628 int viewItemIndex = topViewItemIndex;
3629 while (viewItemIndex < viewItems.size()) {
3630 if (viewItemIndex == item)
3631 return viewItemCoordinate;
3632 viewItemCoordinate += itemHeight(viewItemIndex);
3633 ++viewItemIndex;
3634 }
3635 // below the last item in the view
3636 Q_ASSERT(false);
3637 return viewItemCoordinate;
3638 } else {
3639 // search the area above the viewport (used for editor widgets)
3640 int viewItemCoordinate = 0;
3641 for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) {
3642 if (viewItemIndex == item)
3643 return viewItemCoordinate;
3644 viewItemCoordinate -= itemHeight(viewItemIndex - 1);
3645 }
3646 return viewItemCoordinate;
3647 }
3648 }
3649 return 0;
3650}
3651
3652/*!
3653 \internal
3654 Returns the index of the view item at the
3655 given viewport \a coordinate.
3656
3657 \sa modelIndex()
3658*/
3659int QTreeViewPrivate::itemAtCoordinate(int coordinate) const
3660{
3661 const int itemCount = viewItems.size();
3662 if (itemCount == 0)
3663 return -1;
3664 if (uniformRowHeights && defaultItemHeight <= 0)
3665 return -1;
3666 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3667 if (uniformRowHeights) {
3668 const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight;
3669 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3670 }
3671 // ### optimize
3672 int viewItemCoordinate = 0;
3673 const int contentsCoordinate = coordinate + vbar->value();
3674 for (int viewItemIndex = 0; viewItemIndex < viewItems.size(); ++viewItemIndex) {
3675 viewItemCoordinate += itemHeight(viewItemIndex);
3676 if (viewItemCoordinate > contentsCoordinate)
3677 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3678 }
3679 } else { // ScrollPerItem
3680 int topViewItemIndex = vbar->value();
3681 if (uniformRowHeights) {
3682 if (coordinate < 0)
3683 coordinate -= defaultItemHeight - 1;
3684 const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight);
3685 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3686 }
3687 if (coordinate >= 0) {
3688 // the coordinate is in or below the viewport
3689 int viewItemCoordinate = 0;
3690 for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.size(); ++viewItemIndex) {
3691 viewItemCoordinate += itemHeight(viewItemIndex);
3692 if (viewItemCoordinate > coordinate)
3693 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3694 }
3695 } else {
3696 // the coordinate is above the viewport
3697 int viewItemCoordinate = 0;
3698 for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) {
3699 if (viewItemCoordinate <= coordinate)
3700 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3701 viewItemCoordinate -= itemHeight(viewItemIndex);
3702 }
3703 }
3704 }
3705 return -1;
3706}
3707
3708int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const
3709{
3710 if (!_index.isValid() || viewItems.isEmpty())
3711 return -1;
3712
3713 const int totalCount = viewItems.size();
3714 const QModelIndex index = _index.sibling(_index.row(), 0);
3715 const int row = index.row();
3716 const quintptr internalId = index.internalId();
3717
3718 // We start nearest to the lastViewedItem
3719 int localCount = qMin(lastViewedItem - 1, totalCount - lastViewedItem);
3720 for (int i = 0; i < localCount; ++i) {
3721 const QModelIndex &idx1 = viewItems.at(lastViewedItem + i).index;
3722 if (idx1.row() == row && idx1.internalId() == internalId) {
3723 lastViewedItem = lastViewedItem + i;
3724 return lastViewedItem;
3725 }
3726 const QModelIndex &idx2 = viewItems.at(lastViewedItem - i - 1).index;
3727 if (idx2.row() == row && idx2.internalId() == internalId) {
3728 lastViewedItem = lastViewedItem - i - 1;
3729 return lastViewedItem;
3730 }
3731 }
3732
3733 for (int j = qMax(0, lastViewedItem + localCount); j < totalCount; ++j) {
3734 const QModelIndex &idx = viewItems.at(j).index;
3735 if (idx.row() == row && idx.internalId() == internalId) {
3736 lastViewedItem = j;
3737 return j;
3738 }
3739 }
3740 for (int j = qMin(totalCount, lastViewedItem - localCount) - 1; j >= 0; --j) {
3741 const QModelIndex &idx = viewItems.at(j).index;
3742 if (idx.row() == row && idx.internalId() == internalId) {
3743 lastViewedItem = j;
3744 return j;
3745 }
3746 }
3747
3748 // nothing found
3749 return -1;
3750}
3751
3752QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const
3753{
3754 if (i < 0 || i >= viewItems.size())
3755 return QModelIndex();
3756
3757 QModelIndex ret = viewItems.at(i).index;
3758 if (column)
3759 ret = ret.sibling(ret.row(), column);
3760 return ret;
3761}
3762
3763int QTreeViewPrivate::firstVisibleItem(int *offset) const
3764{
3765 const int value = vbar->value();
3766 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3767 if (offset)
3768 *offset = 0;
3769 return (value < 0 || value >= viewItems.size()) ? -1 : value;
3770 }
3771 // ScrollMode == ScrollPerPixel
3772 if (uniformRowHeights) {
3773 if (!defaultItemHeight)
3774 return -1;
3775
3776 if (offset)
3777 *offset = -(value % defaultItemHeight);
3778 return value / defaultItemHeight;
3779 }
3780 int y = 0; // ### (maybe do like QHeaderView by letting items have startposition)
3781 for (int i = 0; i < viewItems.size(); ++i) {
3782 y += itemHeight(i); // the height value is cached
3783 if (y > value) {
3784 if (offset)
3785 *offset = y - value - itemHeight(i);
3786 return i;
3787 }
3788 }
3789 return -1;
3790}
3791
3792int QTreeViewPrivate::lastVisibleItem(int firstVisual, int offset) const
3793{
3794 if (firstVisual < 0 || offset < 0) {
3795 firstVisual = firstVisibleItem(&offset);
3796 if (firstVisual < 0)
3797 return -1;
3798 }
3799 int y = - offset;
3800 int value = viewport->height();
3801
3802 for (int i = firstVisual; i < viewItems.size(); ++i) {
3803 y += itemHeight(i); // the height value is cached
3804 if (y > value)
3805 return i;
3806 }
3807 return viewItems.size() - 1;
3808}
3809
3810int QTreeViewPrivate::columnAt(int x) const
3811{
3812 return header->logicalIndexAt(x);
3813}
3814
3815void QTreeViewPrivate::updateScrollBars()
3816{
3817 Q_Q(QTreeView);
3818 QSize viewportSize = viewport->size();
3819 if (!viewportSize.isValid())
3820 viewportSize = QSize(0, 0);
3821
3822 executePostedLayout();
3823 if (viewItems.isEmpty()) {
3824 q->doItemsLayout();
3825 }
3826
3827 int itemsInViewport = 0;
3828 if (uniformRowHeights) {
3829 if (defaultItemHeight <= 0)
3830 itemsInViewport = viewItems.size();
3831 else
3832 itemsInViewport = viewportSize.height() / defaultItemHeight;
3833 } else {
3834 const int itemsCount = viewItems.size();
3835 const int viewportHeight = viewportSize.height();
3836 for (int height = 0, item = itemsCount - 1; item >= 0; --item) {
3837 height += itemHeight(item);
3838 if (height > viewportHeight)
3839 break;
3840 ++itemsInViewport;
3841 }
3842 }
3843 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3844 if (!viewItems.isEmpty())
3845 itemsInViewport = qMax(1, itemsInViewport);
3846 vbar->setRange(0, viewItems.size() - itemsInViewport);
3847 vbar->setPageStep(itemsInViewport);
3848 vbar->setSingleStep(1);
3849 } else { // scroll per pixel
3850 int contentsHeight = 0;
3851 if (uniformRowHeights) {
3852 contentsHeight = defaultItemHeight * viewItems.size();
3853 } else { // ### (maybe do like QHeaderView by letting items have startposition)
3854 for (int i = 0; i < viewItems.size(); ++i)
3855 contentsHeight += itemHeight(i);
3856 }
3857 vbar->setRange(0, contentsHeight - viewportSize.height());
3858 vbar->setPageStep(viewportSize.height());
3859 vbar->d_func()->itemviewChangeSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2));
3860 }
3861
3862 const int columnCount = header->count();
3863 const int viewportWidth = viewportSize.width();
3864 int columnsInViewport = 0;
3865 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
3866 int logical = header->logicalIndex(column);
3867 width += header->sectionSize(logical);
3868 if (width > viewportWidth)
3869 break;
3870 ++columnsInViewport;
3871 }
3872 if (columnCount > 0)
3873 columnsInViewport = qMax(1, columnsInViewport);
3874 if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) {
3875 hbar->setRange(0, columnCount - columnsInViewport);
3876 hbar->setPageStep(columnsInViewport);
3877 hbar->setSingleStep(1);
3878 } else { // scroll per pixel
3879 const int horizontalLength = header->length();
3880 const QSize maxSize = q->maximumViewportSize();
3881 if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0)
3882 viewportSize = maxSize;
3883 hbar->setPageStep(viewportSize.width());
3884 hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0));
3885 hbar->d_func()->itemviewChangeSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2));
3886 }
3887}
3888
3889int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const
3890{
3891 Q_Q(const QTreeView);
3892 executePostedLayout();
3893 bool spanned = false;
3894 if (!spanningIndexes.isEmpty()) {
3895 const QModelIndex index = q->indexAt(pos);
3896 if (index.isValid())
3897 spanned = q->isFirstColumnSpanned(index.row(), index.parent());
3898 }
3899 const int column = spanned ? 0 : header->logicalIndexAt(pos.x());
3900 if (!isTreePosition(column))
3901 return -1; // no logical index at x
3902
3903 int viewItemIndex = itemAtCoordinate(pos.y());
3904 QRect returning = itemDecorationRect(modelIndex(viewItemIndex));
3905 if (!returning.contains(pos))
3906 return -1;
3907
3908 return viewItemIndex;
3909}
3910
3911QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const
3912{
3913 Q_Q(const QTreeView);
3914 if (!rootDecoration && index.parent() == root)
3915 return QRect(); // no decoration at root
3916
3917 int viewItemIndex = viewIndex(index);
3918 if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index))
3919 return QRect();
3920
3921 int itemIndentation = indentationForItem(viewItemIndex);
3922 int position = header->sectionViewportPosition(logicalIndexForTree());
3923 int size = header->sectionSize(logicalIndexForTree());
3924
3925 QRect rect;
3926 if (q->isRightToLeft())
3927 rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex),
3928 indent, itemHeight(viewItemIndex));
3929 else
3930 rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex),
3931 indent, itemHeight(viewItemIndex));
3932 QStyleOption opt;
3933 opt.initFrom(q);
3934 opt.rect = rect;
3935 return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q);
3936}
3937
3938QList<std::pair<int, int>> QTreeViewPrivate::columnRanges(const QModelIndex &topIndex,
3939 const QModelIndex &bottomIndex) const
3940{
3941 const int topVisual = header->visualIndex(topIndex.column()),
3942 bottomVisual = header->visualIndex(bottomIndex.column());
3943
3944 const int start = qMin(topVisual, bottomVisual);
3945 const int end = qMax(topVisual, bottomVisual);
3946
3947 QList<int> logicalIndexes;
3948
3949 //we iterate over the visual indexes to get the logical indexes
3950 for (int c = start; c <= end; c++) {
3951 const int logical = header->logicalIndex(c);
3952 if (!header->isSectionHidden(logical)) {
3953 logicalIndexes << logical;
3954 }
3955 }
3956 //let's sort the list
3957 std::sort(logicalIndexes.begin(), logicalIndexes.end());
3958
3959 QList<std::pair<int, int>> ret;
3960 std::pair<int, int> current;
3961 current.first = -2; // -1 is not enough because -1+1 = 0
3962 current.second = -2;
3963 for(int i = 0; i < logicalIndexes.size(); ++i) {
3964 const int logicalColumn = logicalIndexes.at(i);
3965 if (current.second + 1 != logicalColumn) {
3966 if (current.first != -2) {
3967 //let's save the current one
3968 ret += current;
3969 }
3970 //let's start a new one
3971 current.first = current.second = logicalColumn;
3972 } else {
3973 current.second++;
3974 }
3975 }
3976
3977 //let's get the last range
3978 if (current.first != -2) {
3979 ret += current;
3980 }
3981
3982 return ret;
3983}
3984
3985void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex,
3986 QItemSelectionModel::SelectionFlags command)
3987{
3988 Q_Q(QTreeView);
3989 QItemSelection selection;
3990 const int top = viewIndex(topIndex),
3991 bottom = viewIndex(bottomIndex);
3992
3993 const QList<std::pair<int, int>> colRanges = columnRanges(topIndex, bottomIndex);
3994 QList<std::pair<int, int>>::const_iterator it;
3995 for (it = colRanges.begin(); it != colRanges.end(); ++it) {
3996 const int left = (*it).first,
3997 right = (*it).second;
3998
3999 QModelIndex previous;
4000 QItemSelectionRange currentRange;
4001 QStack<QItemSelectionRange> rangeStack;
4002 for (int i = top; i <= bottom; ++i) {
4003 QModelIndex index = modelIndex(i);
4004 QModelIndex parent = index.parent();
4005 QModelIndex previousParent = previous.parent();
4006 if (previous.isValid() && parent == previousParent) {
4007 // same parent
4008 if (qAbs(previous.row() - index.row()) > 1) {
4009 //a hole (hidden index inside a range) has been detected
4010 if (currentRange.isValid()) {
4011 selection.append(currentRange);
4012 }
4013 //let's start a new range
4014 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4015 } else {
4016 QModelIndex tl = model->index(currentRange.top(), currentRange.left(),
4017 currentRange.parent());
4018 currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right));
4019 }
4020 } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) {
4021 // item is child of previous
4022 rangeStack.push(currentRange);
4023 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4024 } else {
4025 if (currentRange.isValid())
4026 selection.append(currentRange);
4027 if (rangeStack.isEmpty()) {
4028 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
4029 } else {
4030 currentRange = rangeStack.pop();
4031 index = currentRange.bottomRight(); //let's resume the range
4032 --i; //we process again the current item
4033 }
4034 }
4035 previous = index;
4036 }
4037 if (currentRange.isValid())
4038 selection.append(currentRange);
4039 for (int i = 0; i < rangeStack.size(); ++i)
4040 selection.append(rangeStack.at(i));
4041 }
4042 q->selectionModel()->select(selection, command);
4043}
4044
4045std::pair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const
4046{
4047 Q_Q(const QTreeView);
4048 int start = header->visualIndexAt(rect.left());
4049 int end = header->visualIndexAt(rect.right());
4050 if (q->isRightToLeft()) {
4051 start = (start == -1 ? header->count() - 1 : start);
4052 end = (end == -1 ? 0 : end);
4053 } else {
4054 start = (start == -1 ? 0 : start);
4055 end = (end == -1 ? header->count() - 1 : end);
4056 }
4057 return std::pair(qMin(start, end), qMax(start, end));
4058}
4059
4060bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const
4061{
4062 Q_Q(const QTreeView);
4063 if (parent.flags() & Qt::ItemNeverHasChildren)
4064 return false;
4065 if (model->hasChildren(parent)) {
4066 if (hiddenIndexes.isEmpty())
4067 return true;
4068 if (q->isIndexHidden(parent))
4069 return false;
4070 int rowCount = model->rowCount(parent);
4071 for (int i = 0; i < rowCount; ++i) {
4072 if (!q->isRowHidden(i, parent))
4073 return true;
4074 }
4075 if (rowCount == 0)
4076 return true;
4077 }
4078 return false;
4079}
4080
4081void QTreeViewPrivate::sortIndicatorChanged(int column, Qt::SortOrder order)
4082{
4083 model->sort(column, order);
4084}
4085
4086#if QT_CONFIG(accessibility)
4087int QTreeViewPrivate::accessibleChildIndex(const QModelIndex &index) const
4088{
4089 Q_Q(const QTreeView);
4090 Q_ASSERT(index.isValid());
4091
4092 int rowForIndex = q->visualIndex(index);
4093 if (rowForIndex < 0)
4094 return -1; // should happen rarely, if ever. Possibly when select all is performed
4095 // Note that this will include the header, even if its hidden.
4096 if (q->header())
4097 ++rowForIndex;
4098 const int columnCount = index.model()->columnCount();
4099
4100 const qulonglong childIndex = qulonglong(rowForIndex) * columnCount + index.column();
4101 const int result = q26::saturate_cast<int>(childIndex);
4102 if (!q20::cmp_equal(result, childIndex)) {
4103 qWarning("QTreeView: model is too large to be made accessible (%d < %llu)",
4104 result, childIndex);
4105 return -1;
4106 }
4107 return result;
4108}
4109#endif
4110
4111void QTreeViewPrivate::updateIndentationFromStyle()
4112{
4113 Q_Q(const QTreeView);
4114 indent = q->style()->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, q);
4115}
4116
4117/*!
4118 \reimp
4119 */
4120void QTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
4121{
4122 Q_D(QTreeView);
4123 QAbstractItemView::currentChanged(current, previous);
4124
4125 if (allColumnsShowFocus()) {
4126 if (previous.isValid())
4127 viewport()->update(d->visualRect(previous, QTreeViewPrivate::FullRow));
4128 if (current.isValid())
4129 viewport()->update(d->visualRect(current, QTreeViewPrivate::FullRow));
4130 }
4131#if QT_CONFIG(accessibility)
4132 if (QAccessible::isActive() && current.isValid() && hasFocus()) {
4133 Q_D(QTreeView);
4134
4135 const int entry = d->accessibleChildIndex(current);
4136 if (entry >= 0) {
4137 QAccessibleEvent event(this, QAccessible::Focus);
4138 event.setChild(entry);
4139 QAccessible::updateAccessibility(&event);
4140 }
4141 }
4142#endif
4143}
4144
4145/*!
4146 \reimp
4147 */
4148void QTreeView::selectionChanged(const QItemSelection &selected,
4149 const QItemSelection &deselected)
4150{
4151 QAbstractItemView::selectionChanged(selected, deselected);
4152#if QT_CONFIG(accessibility)
4153 if (QAccessible::isActive()) {
4154 Q_D(QTreeView);
4155
4156 // ### does not work properly for selection ranges.
4157 QModelIndex sel = selected.indexes().value(0);
4158 if (sel.isValid()) {
4159 int entry = d->accessibleChildIndex(sel);
4160 if (entry >= 0) {
4161 QAccessibleEvent event(this, QAccessible::SelectionAdd);
4162 event.setChild(entry);
4163 QAccessible::updateAccessibility(&event);
4164 }
4165 }
4166 QModelIndex desel = deselected.indexes().value(0);
4167 if (desel.isValid()) {
4168 int entry = d->accessibleChildIndex(desel);
4169 if (entry >= 0) {
4170 QAccessibleEvent event(this, QAccessible::SelectionRemove);
4171 event.setChild(entry);
4172 QAccessible::updateAccessibility(&event);
4173 }
4174 }
4175 }
4176#endif
4177}
4178
4179int QTreeView::visualIndex(const QModelIndex &index) const
4180{
4181 Q_D(const QTreeView);
4182 d->executePostedLayout();
4183 return d->viewIndex(index);
4184}
4185
4186/*!
4187 \internal
4188*/
4189
4190void QTreeView::verticalScrollbarValueChanged(int value)
4191{
4192 Q_D(QTreeView);
4193 if (!d->viewItems.isEmpty() && value == verticalScrollBar()->maximum()) {
4194 QModelIndex ret = d->viewItems.last().index;
4195 // Root index will be handled by base class implementation
4196 while (ret.isValid()) {
4197 if (isExpanded(ret) && d->model->canFetchMore(ret)) {
4198 d->model->fetchMore(ret);
4199 break;
4200 }
4201 ret = ret.parent();
4202 }
4203 }
4204 QAbstractItemView::verticalScrollbarValueChanged(value);
4205}
4206
4207QT_END_NAMESPACE
4208
4209#include "moc_qtreeview.cpp"
static bool ancestorOf(QObject *widget, QObject *other)