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

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