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