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