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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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