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