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

source code of qtbase/src/widgets/itemviews/qtreeview.cpp