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