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