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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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