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