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