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

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