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