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