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