1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtGui module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
41 #include "qtreeview.h"
43 #ifndef QT_NO_TREEVIEW
44 #include <qheaderview.h>
45 #include <qitemdelegate.h>
46 #include <qapplication.h>
47 #include <qscrollbar.h>
51 #include <qstyleoption.h>
55 #ifndef QT_NO_ACCESSIBILITY
56 #include <qaccessible.h>
57 #include <qaccessible2.h>
60 #include <private/qtreeview_p.h>
66 \brief The QTreeView class provides a default model/view implementation of a tree view.
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.
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}.
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.
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:
88 \snippet doc/src/snippets/shareddirmodel/main.cpp 3
89 \snippet doc/src/snippets/shareddirmodel/main.cpp 6
91 The model/view architecture ensures that the contents of the tree view
92 are updated as the model changes.
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.
99 The amount of indentation used to indicate levels of hierarchy is
100 controlled by the \l indentation property.
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.
110 \section1 Key Bindings
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:
116 \header \li Key \li Action
117 \row \li Up \li 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 \li Down \li 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 \li Left \li Hides the children of the current item (if present)
126 by collapsing a branch.
127 \row \li Minus \li Same as LeftArrow.
128 \row \li Right \li Reveals the children of the current item (if present)
129 by expanding a branch.
130 \row \li Plus \li Same as RightArrow.
131 \row \li Asterisk \li Expands all children of the current item (if present).
132 \row \li PageUp \li Moves the cursor up one page.
133 \row \li PageDown \li Moves the cursor down one page.
134 \row \li Home \li 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 \li End \li 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 \li F2 \li 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.
144 Describe the expanding/collapsing concept if not covered elsewhere.
148 \row \li \inlineimage windowsxp-treeview.png Screenshot of a Windows XP style tree view
149 \li \inlineimage macintosh-treeview.png Screenshot of a Macintosh style tree view
150 \li \inlineimage plastique-treeview.png Screenshot of a Plastique style tree view
151 \row \li A \l{Windows XP Style Widget Gallery}{Windows XP style} tree view.
152 \li A \l{Macintosh Style Widget Gallery}{Macintosh style} tree view.
153 \li A \l{Plastique Style Widget Gallery}{Plastique style} tree view.
156 \section1 Improving Performance
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.
163 \sa QListView, QTreeWidget, {View Classes}, QAbstractItemModel, QAbstractItemView,
169 \fn void QTreeView::expanded(const QModelIndex &index)
171 This signal is emitted when the item specified by \a index is expanded.
176 \fn void QTreeView::collapsed(const QModelIndex &index)
178 This signal is emitted when the item specified by \a index is collapsed.
182 Constructs a tree view with a \a parent to represent a model's
183 data. Use setModel() to set the model.
185 \sa QAbstractItemModel
187 QTreeView::QTreeView(QWidget *parent)
188 : QAbstractItemView(*new QTreeViewPrivate, parent)
197 QTreeView::QTreeView(QTreeViewPrivate &dd, QWidget *parent)
198 : QAbstractItemView(dd, parent)
205 Destroys the tree view.
207 QTreeView::~QTreeView()
214 void QTreeView::setModel(QAbstractItemModel *model)
217 if (model == d->model)
219 if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
220 disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
221 this, SLOT(rowsRemoved(QModelIndex,int,int)));
223 disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset()));
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()));
233 d->viewItems.clear();
234 d->expandedIndexes.clear();
235 d->hiddenIndexes.clear();
236 d->header->setModel(model);
237 QAbstractItemView::setModel(model);
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)));
249 connect(d->model, SIGNAL(modelAboutToBeReset()), SLOT(_q_modelAboutToBeReset()));
251 if (d->sortingEnabled)
252 d->_q_sortIndicatorChanged(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
258 void QTreeView::setRootIndex(const QModelIndex &index)
261 d->header->setRootIndex(index);
262 QAbstractItemView::setRootIndex(index);
268 void QTreeView::setSelectionModel(QItemSelectionModel *selectionModel)
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()));
278 d->header->setSelectionModel(selectionModel);
279 QAbstractItemView::setSelectionModel(selectionModel);
281 if (d->selectionModel) {
282 // support row editing
283 connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
284 d->model, SLOT(submit()));
289 Returns the header for the tree view.
291 \sa QAbstractItemModel::headerData()
293 QHeaderView *QTreeView::header() const
295 Q_D(const QTreeView);
300 Sets the header for the tree view, to the given \a header.
302 The view takes ownership over the given \a header and deletes it
303 when a new header is set.
305 \sa QAbstractItemModel::headerData()
307 void QTreeView::setHeader(QHeaderView *header)
310 if (header == d->header || !header)
312 if (d->header && d->header->parent() == this)
315 d->header->setParent(this);
317 if (!d->header->model()) {
318 d->header->setModel(d->model);
319 if (d->selectionModel)
320 d->header->setSelectionModel(d->selectionModel);
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()));
334 setSortingEnabled(d->sortingEnabled);
338 \property QTreeView::autoExpandDelay
339 \brief The delay time before items in a tree are opened during a drag and drop operation.
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.
346 By default, this property has a value of -1, meaning that auto-expansion is disabled.
348 int QTreeView::autoExpandDelay() const
350 Q_D(const QTreeView);
351 return d->autoExpandDelay;
354 void QTreeView::setAutoExpandDelay(int delay)
357 d->autoExpandDelay = delay;
361 \property QTreeView::indentation
362 \brief indentation of the items in the tree view.
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.
369 By default, this property has a value of 20.
371 int QTreeView::indentation() const
373 Q_D(const QTreeView);
377 void QTreeView::setIndentation(int i)
380 if (i != d->indent) {
382 d->viewport->update();
387 \property QTreeView::rootIsDecorated
388 \brief whether to show controls for expanding and collapsing top-level items
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.
395 By default, this property is true.
397 bool QTreeView::rootIsDecorated() const
399 Q_D(const QTreeView);
400 return d->rootDecoration;
403 void QTreeView::setRootIsDecorated(bool show)
406 if (show != d->rootDecoration) {
407 d->rootDecoration = show;
408 d->viewport->update();
413 \property QTreeView::uniformRowHeights
414 \brief whether all items in the treeview have the same height
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
420 The height is obtained from the first item in the view. It is updated
421 when the data changes on that item.
423 By default, this property is false.
425 bool QTreeView::uniformRowHeights() const
427 Q_D(const QTreeView);
428 return d->uniformRowHeights;
431 void QTreeView::setUniformRowHeights(bool uniform)
434 d->uniformRowHeights = uniform;
438 \property QTreeView::itemsExpandable
439 \brief whether the items are expandable by the user.
441 This property holds whether the user can expand and collapse items
444 By default, this property is true.
447 bool QTreeView::itemsExpandable() const
449 Q_D(const QTreeView);
450 return d->itemsExpandable;
453 void QTreeView::setItemsExpandable(bool enable)
456 d->itemsExpandable = enable;
460 \property QTreeView::expandsOnDoubleClick
462 \brief whether the items can be expanded by double-clicking.
464 This property holds whether the user can expand and collapse items
465 by double-clicking. The default value is true.
469 bool QTreeView::expandsOnDoubleClick() const
471 Q_D(const QTreeView);
472 return d->expandsOnDoubleClick;
475 void QTreeView::setExpandsOnDoubleClick(bool enable)
478 d->expandsOnDoubleClick = enable;
482 Returns the horizontal position of the \a column in the viewport.
484 int QTreeView::columnViewportPosition(int column) const
486 Q_D(const QTreeView);
487 return d->header->sectionViewportPosition(column);
491 Returns the width of the \a column.
493 \sa resizeColumnToContents(), setColumnWidth()
495 int QTreeView::columnWidth(int column) const
497 Q_D(const QTreeView);
498 return d->header->sectionSize(column);
504 Sets the width of the given \a column to the \a width specified.
506 \sa columnWidth(), resizeColumnToContents()
508 void QTreeView::setColumnWidth(int column, int width)
511 d->header->resizeSection(column, width);
515 Returns the column in the tree view whose header covers the \a x
518 int QTreeView::columnAt(int x) const
520 Q_D(const QTreeView);
521 return d->header->logicalIndexAt(x);
525 Returns true if the \a column is hidden; otherwise returns false.
527 \sa hideColumn(), isRowHidden()
529 bool QTreeView::isColumnHidden(int column) const
531 Q_D(const QTreeView);
532 return d->header->isSectionHidden(column);
536 If \a hide is true the \a column is hidden, otherwise the \a column is shown.
538 \sa hideColumn(), setRowHidden()
540 void QTreeView::setColumnHidden(int column, bool hide)
543 if (column < 0 || column >= d->header->count())
545 d->header->setSectionHidden(column, hide);
549 \property QTreeView::headerHidden
550 \brief whether the header is shown or not.
553 If this property is true, the header is not shown otherwise it is.
554 The default value is false.
558 bool QTreeView::isHeaderHidden() const
560 Q_D(const QTreeView);
561 return d->header->isHidden();
564 void QTreeView::setHeaderHidden(bool hide)
567 d->header->setHidden(hide);
571 Returns true if the item in the given \a row of the \a parent is hidden;
572 otherwise returns false.
574 \sa setRowHidden(), isColumnHidden()
576 bool QTreeView::isRowHidden(int row, const QModelIndex &parent) const
578 Q_D(const QTreeView);
581 return d->isRowHidden(d->model->index(row, 0, parent));
585 If \a hide is true the \a row with the given \a parent is hidden, otherwise the \a row is shown.
587 \sa isRowHidden(), setColumnHidden()
589 void QTreeView::setRowHidden(int row, const QModelIndex &parent, bool hide)
594 QModelIndex index = d->model->index(row, 0, parent);
595 if (!index.isValid())
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);
604 d->doDelayedItemsLayout();
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.
613 \sa setFirstColumnSpanned()
615 bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const
617 Q_D(const QTreeView);
618 if (d->spanningIndexes.isEmpty() || !d->model)
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)
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.
634 \sa isFirstColumnSpanned()
636 void QTreeView::setFirstColumnSpanned(int row, const QModelIndex &parent, bool span)
641 QModelIndex index = d->model->index(row, 0, parent);
642 if (!index.isValid())
646 QPersistentModelIndex persistent(index);
647 if (!d->spanningIndexes.contains(persistent))
648 d->spanningIndexes.append(persistent);
650 QPersistentModelIndex persistent(index);
651 int i = d->spanningIndexes.indexOf(persistent);
653 d->spanningIndexes.remove(i);
656 d->executePostedLayout();
657 int i = d->viewIndex(index);
659 d->viewItems[i].spanning = span;
661 d->viewport->update();
667 void QTreeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QSet<int> &roles)
671 // if we are going to do a complete relayout anyway, there is no need to update
672 if (d->delayedPendingLayout)
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
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;
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);
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);
706 d->updateScrollBars();
707 d->viewport->update();
709 QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
713 Hides the \a column given.
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
719 \sa showColumn(), setColumnHidden()
721 void QTreeView::hideColumn(int column)
724 d->header->hideSection(column);
728 Shows the given \a column in the tree view.
730 \sa hideColumn(), setColumnHidden()
732 void QTreeView::showColumn(int column)
735 d->header->showSection(column);
739 \fn void QTreeView::expand(const QModelIndex &index)
741 Expands the model item specified by the \a index.
745 void QTreeView::expand(const QModelIndex &index)
748 if (!d->isIndexValid(index))
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);
757 int i = d->viewIndex(index);
758 if (i != -1) { // is visible
760 if (!d->isAnimating()) {
762 d->viewport->update();
764 } else if (d->storeExpanded(index)) {
765 emit expanded(index);
770 \fn void QTreeView::collapse(const QModelIndex &index)
772 Collapses the model item specified by the \a index.
776 void QTreeView::collapse(const QModelIndex &index)
779 if (!d->isIndexValid(index))
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();
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);
790 int i = d->viewIndex(index);
791 if (i != -1) { // is visible
792 d->collapse(i, true);
793 if (!d->isAnimating()) {
795 viewport()->update();
798 if (d->isPersistent(index) && d->expandedIndexes.remove(index))
799 emit collapsed(index);
804 \fn bool QTreeView::isExpanded(const QModelIndex &index) const
806 Returns true if the model item \a index is expanded; otherwise returns
809 \sa expand(), expanded(), setExpanded()
811 bool QTreeView::isExpanded(const QModelIndex &index) const
813 Q_D(const QTreeView);
814 return d->isIndexExpanded(index);
818 Sets the item referred to by \a index to either collapse or expanded,
819 depending on the value of \a expanded.
821 \sa expanded(), expand(), isExpanded()
823 void QTreeView::setExpanded(const QModelIndex &index, bool expanded)
828 this->collapse(index);
833 \property QTreeView::sortingEnabled
834 \brief whether sorting is enabled
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.
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.
847 void QTreeView::setSortingEnabled(bool enable)
850 header()->setSortIndicatorShown(enable);
851 header()->setClickable(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);
859 disconnect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
860 this, SLOT(_q_sortIndicatorChanged(int,Qt::SortOrder)));
862 d->sortingEnabled = enable;
865 bool QTreeView::isSortingEnabled() const
867 Q_D(const QTreeView);
868 return d->sortingEnabled;
873 \property QTreeView::animated
874 \brief whether animations are enabled
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
881 By default, this property is false.
884 void QTreeView::setAnimated(bool animate)
887 d->animationsEnabled = animate;
890 bool QTreeView::isAnimated() const
892 Q_D(const QTreeView);
893 return d->animationsEnabled;
898 \property QTreeView::allColumnsShowFocus
899 \brief whether items should show keyboard focus using all columns
901 If this property is true all columns will show focus, otherwise only
902 one column will show focus.
904 The default is false.
907 void QTreeView::setAllColumnsShowFocus(bool enable)
910 if (d->allColumnsShowFocus == enable)
912 d->allColumnsShowFocus = enable;
913 d->viewport->update();
916 bool QTreeView::allColumnsShowFocus() const
918 Q_D(const QTreeView);
919 return d->allColumnsShowFocus;
923 \property QTreeView::wordWrap
924 \brief the item text word-wrapping policy
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.
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}.
935 void QTreeView::setWordWrap(bool on)
938 if (d->wrapItemText == on)
940 d->wrapItemText = on;
941 d->doDelayedItemsLayout();
944 bool QTreeView::wordWrap() const
946 Q_D(const QTreeView);
947 return d->wrapItemText;
954 void QTreeView::keyboardSearch(const QString &search)
957 if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root))
961 if (currentIndex().isValid())
962 start = currentIndex();
964 start = d->model->index(0, 0, d->root);
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)
974 d->keyboardInput += search;
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());
986 // skip if we are searching for the same key or a new search started
988 if (indexBelow(start).isValid())
989 start = indexBelow(start);
991 start = d->model->index(0, start.column(), d->root);
994 d->executePostedLayout();
995 int startIndex = d->viewIndex(start);
996 if (startIndex <= -1)
999 int previousLevel = -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())
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);
1017 previousLevel = d->viewItems.at(i).level;
1022 index = d->viewItems.at(bestBelow).index;
1023 else if (bestAbove > -1)
1024 index = d->viewItems.at(bestAbove).index;
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);
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.
1041 QRect QTreeView::visualRect(const QModelIndex &index) const
1043 Q_D(const QTreeView);
1045 if (!d->isIndexValid(index) || isIndexHidden(index))
1048 d->executePostedLayout();
1050 int vi = d->viewIndex(index);
1054 bool spanning = d->viewItems.at(vi).spanning;
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);
1063 if (!isRightToLeft())
1067 int y = d->coordinateForItem(vi);
1068 int h = d->itemHeight(vi);
1070 return QRect(x, y, w, h);
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
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.
1081 void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint)
1085 if (!d->isIndexValid(index))
1088 d->executePostedLayout();
1089 d->updateScrollBars();
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))
1096 parent = d->model->parent(parent);
1099 int item = d->viewIndex(index);
1103 QRect area = d->viewport->rect();
1106 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
1107 int top = verticalScrollBar()->value();
1108 int bottom = top + verticalScrollBar()->pageStep();
1109 if (hint == EnsureVisible && item >= top && item < bottom) {
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
1120 if (y > currentItemHeight) {
1122 y -= d->itemHeight(item);
1123 if (y < 0) { //there is no more space left
1130 verticalScrollBar()->setValue(item);
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));
1138 if (rect.isEmpty()) {
1140 } else if (hint == EnsureVisible && area.contains(rect)) {
1141 d->viewport->update(rect);
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());
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);
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());
1167 if (hint == PositionAtCenter) {
1168 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
1170 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
1171 horizontalScrollBar()->setValue(horizontalPosition);
1172 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
1173 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
1180 void QTreeView::timerEvent(QTimerEvent *event)
1183 if (event->timerId() == d->columnResizeTimerID) {
1185 killTimer(d->columnResizeTimerID);
1186 d->columnResizeTimerID = 0;
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);
1196 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
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));
1207 d->openTimer.stop();
1210 QAbstractItemView::timerEvent(event);
1216 #ifndef QT_NO_DRAGANDDROP
1217 void QTreeView::dragMoveEvent(QDragMoveEvent *event)
1220 if (d->autoExpandDelay >= 0)
1221 d->openTimer.start(d->autoExpandDelay, this);
1222 QAbstractItemView::dragMoveEvent(event);
1229 bool QTreeView::viewportEvent(QEvent *event)
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 if (oldBranch != d->hoverBranch) {
1240 //we need to paint the whole items (including the decoration) so that when the user
1241 //moves the mouse over those elements they are updated
1242 if (oldBranch >= 0) {
1243 int y = d->coordinateForItem(oldBranch);
1244 int h = d->itemHeight(oldBranch);
1245 viewport()->update(QRect(0, y, viewport()->width(), h));
1247 if (d->hoverBranch >= 0) {
1248 int y = d->coordinateForItem(d->hoverBranch);
1249 int h = d->itemHeight(d->hoverBranch);
1250 viewport()->update(QRect(0, y, viewport()->width(), h));
1257 return QAbstractItemView::viewportEvent(event);
1263 void QTreeView::paintEvent(QPaintEvent *event)
1266 d->executePostedLayout();
1267 QPainter painter(viewport());
1268 #ifndef QT_NO_ANIMATION
1269 if (d->isAnimating()) {
1270 drawTree(&painter, event->region() - d->animatedOperation.rect());
1271 d->drawAnimatedOperation(&painter);
1273 #endif //QT_NO_ANIMATION
1275 drawTree(&painter, event->region());
1276 #ifndef QT_NO_DRAGANDDROP
1277 d->paintDropIndicator(&painter);
1282 void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const
1284 Q_Q(const QTreeView);
1285 if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q))
1287 int rowHeight = defaultItemHeight;
1288 if (rowHeight <= 0) {
1289 rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height();
1293 while (y <= bottom) {
1294 option->rect.setRect(0, y, viewport->width(), rowHeight);
1296 option->features |= QStyleOptionViewItemV2::Alternate;
1298 option->features &= ~QStyleOptionViewItemV2::Alternate;
1301 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q);
1306 bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos)
1309 // we want to handle mousePress in EditingState (persistent editors)
1310 if ((state != QAbstractItemView::NoState
1311 && state != QAbstractItemView::EditingState)
1312 || !viewport->rect().contains(pos))
1315 int i = itemDecorationAt(pos);
1316 if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) {
1317 if (viewItems.at(i).expanded)
1321 if (!isAnimating()) {
1322 q->updateGeometries();
1330 void QTreeViewPrivate::_q_modelDestroyed()
1332 //we need to clear that list because it contais QModelIndex to
1333 //the model currently being destroyed
1335 QAbstractItemViewPrivate::_q_modelDestroyed();
1341 We have a QTreeView way of knowing what elements are on the viewport
1343 QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
1346 return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r);
1347 Q_Q(const QTreeView);
1349 const QRect viewportRect = viewport->rect();
1351 int row = firstVisibleItem(&itemOffset);
1352 QPair<int, int> startEnd = startAndEndColumns(viewportRect);
1353 QVector<int> columns;
1354 for (int i = startEnd.first; i <= startEnd.second; ++i) {
1355 int logical = header->logicalIndex(i);
1356 if (!header->isSectionHidden(logical))
1359 QSet<QModelIndex> visibleIndexes;
1360 for (; itemOffset < viewportRect.bottom() && row < viewItems.count(); ++row) {
1361 const QModelIndex &index = viewItems.at(row).index;
1362 for (int colIndex = 0; colIndex < columns.count(); ++colIndex)
1363 visibleIndexes += index.sibling(index.row(), columns.at(colIndex));
1364 itemOffset += itemHeight(row);
1367 //now that we have the visible indexes, we can try to find those which are selected
1368 QItemViewPaintPairs ret;
1369 for (int i = 0; i < indexes.count(); ++i) {
1370 const QModelIndex &index = indexes.at(i);
1371 if (visibleIndexes.contains(index)) {
1372 const QRect current = q->visualRect(index);
1373 ret += qMakePair(current, index);
1377 rect &= viewportRect;
1381 void QTreeViewPrivate::adjustViewOptionsForIndex(QStyleOptionViewItemV4 *option, const QModelIndex ¤t) const
1383 const int row = current.row();
1384 option->state = option->state | (viewItems.at(row).expanded ? QStyle::State_Open : QStyle::State_None)
1385 | (viewItems.at(row).hasChildren ? QStyle::State_Children : QStyle::State_None)
1386 | (viewItems.at(row).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1388 option->showDecorationSelected = (selectionBehavior & QTreeView::SelectRows)
1389 || option->showDecorationSelected;
1391 QVector<int> logicalIndices; // index = visual index of visible columns only. data = logical index.
1392 QVector<QStyleOptionViewItemV4::ViewItemPosition> viewItemPosList; // vector of left/middle/end for each logicalIndex, visible columns only.
1393 calcLogicalIndices(&logicalIndices, &viewItemPosList);
1395 int columnIndex = 0;
1396 for (int visualIndex = 0; visualIndex < current.column(); ++visualIndex) {
1397 int logicalIndex = header->logicalIndex(visualIndex);
1398 if (!header->isSectionHidden(logicalIndex)) {
1403 option->viewItemPosition = viewItemPosList.at(columnIndex);
1409 Draws the part of the tree intersecting the given \a region using the specified
1414 void QTreeView::drawTree(QPainter *painter, const QRegion ®ion) const
1416 Q_D(const QTreeView);
1417 const QVector<QTreeViewItem> viewItems = d->viewItems;
1419 QStyleOptionViewItemV4 option = d->viewOptionsV4();
1420 const QStyle::State state = option.state;
1423 if (viewItems.count() == 0 || d->header->count() == 0 || !d->itemDelegate) {
1424 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1428 int firstVisibleItemOffset = 0;
1429 const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset);
1430 if (firstVisibleItem < 0) {
1431 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1435 const int viewportWidth = d->viewport->width();
1437 QVector<QRect> rects = region.rects();
1439 bool multipleRects = (rects.size() > 1);
1440 for (int a = 0; a < rects.size(); ++a) {
1441 const QRect area = (multipleRects
1442 ? QRect(0, rects.at(a).y(), viewportWidth, rects.at(a).height())
1444 d->leftAndRight = d->startAndEndColumns(area);
1446 int i = firstVisibleItem; // the first item at the top of the viewport
1447 int y = firstVisibleItemOffset; // we may only see part of the first item
1449 // start at the top of the viewport and iterate down to the update area
1450 for (; i < viewItems.count(); ++i) {
1451 const int itemHeight = d->itemHeight(i);
1452 if (y + itemHeight > area.top())
1457 // paint the visible rows
1458 for (; i < viewItems.count() && y <= area.bottom(); ++i) {
1459 const int itemHeight = d->itemHeight(i);
1460 option.rect.setRect(0, y, viewportWidth, itemHeight);
1461 option.state = state | (viewItems.at(i).expanded ? QStyle::State_Open : QStyle::State_None)
1462 | (viewItems.at(i).hasChildren ? QStyle::State_Children : QStyle::State_None)
1463 | (viewItems.at(i).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1465 d->spanning = viewItems.at(i).spanning;
1466 if (!multipleRects || !drawn.contains(i)) {
1467 drawRow(painter, option, viewItems.at(i).index);
1468 if (multipleRects) // even if the rect only intersects the item,
1469 drawn.append(i); // the entire item will be painted
1474 if (y <= area.bottom()) {
1476 d->paintAlternatingRowColors(painter, &option, y, area.bottom());
1481 /// ### move to QObject :)
1482 static inline bool ancestorOf(QObject *widget, QObject *other)
1484 for (QObject *parent = other; parent != 0; parent = parent->parent()) {
1485 if (parent == widget)
1491 void QTreeViewPrivate::calcLogicalIndices(QVector<int> *logicalIndices, QVector<QStyleOptionViewItemV4::ViewItemPosition> *itemPositions) const
1493 const int left = (spanning ? header->visualIndex(0) : leftAndRight.first);
1494 const int right = (spanning ? header->visualIndex(0) : leftAndRight.second);
1495 const int columnCount = header->count();
1496 /* 'left' and 'right' are the left-most and right-most visible visual indices.
1497 Compute the first visible logical indices before and after the left and right.
1498 We will use these values to determine the QStyleOptionViewItemV4::viewItemPosition. */
1499 int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1;
1500 for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) {
1501 int logicalIndex = header->logicalIndex(visualIndex);
1502 if (!header->isSectionHidden(logicalIndex)) {
1503 logicalIndexBeforeLeft = logicalIndex;
1508 for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) {
1509 int logicalIndex = header->logicalIndex(visualIndex);
1510 if (!header->isSectionHidden(logicalIndex)) {
1511 if (visualIndex > right) {
1512 logicalIndexAfterRight = logicalIndex;
1515 logicalIndices->append(logicalIndex);
1519 itemPositions->resize(logicalIndices->count());
1520 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices->count(); ++currentLogicalSection) {
1521 const int headerSection = logicalIndices->at(currentLogicalSection);
1522 // determine the viewItemPosition depending on the position of column 0
1523 int nextLogicalSection = currentLogicalSection + 1 >= logicalIndices->count()
1524 ? logicalIndexAfterRight
1525 : logicalIndices->at(currentLogicalSection + 1);
1526 int prevLogicalSection = currentLogicalSection - 1 < 0
1527 ? logicalIndexBeforeLeft
1528 : logicalIndices->at(currentLogicalSection - 1);
1529 QStyleOptionViewItemV4::ViewItemPosition pos;
1530 if (columnCount == 1 || (nextLogicalSection == 0 && prevLogicalSection == -1)
1531 || (headerSection == 0 && nextLogicalSection == -1) || spanning)
1532 pos = QStyleOptionViewItemV4::OnlyOne;
1533 else if (headerSection == 0 || (nextLogicalSection != 0 && prevLogicalSection == -1))
1534 pos = QStyleOptionViewItemV4::Beginning;
1535 else if (nextLogicalSection == 0 || nextLogicalSection == -1)
1536 pos = QStyleOptionViewItemV4::End;
1538 pos = QStyleOptionViewItemV4::Middle;
1539 (*itemPositions)[currentLogicalSection] = pos;
1545 Draws the row in the tree view that contains the model item \a index,
1546 using the \a painter given. The \a option control how the item is
1549 \sa setAlternatingRowColors()
1551 void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
1552 const QModelIndex &index) const
1554 Q_D(const QTreeView);
1555 QStyleOptionViewItemV4 opt = option;
1556 const QPoint offset = d->scrollDelayOffset;
1557 const int y = option.rect.y() + offset.y();
1558 const QModelIndex parent = index.parent();
1559 const QHeaderView *header = d->header;
1560 const QModelIndex current = currentIndex();
1561 const QModelIndex hover = d->hover;
1562 const bool reverse = isRightToLeft();
1563 const QStyle::State state = opt.state;
1564 const bool spanning = d->spanning;
1565 const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first);
1566 const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second);
1567 const bool alternate = d->alternatingColors;
1568 const bool enabled = (state & QStyle::State_Enabled) != 0;
1569 const bool allColumnsShowFocus = d->allColumnsShowFocus;
1572 // when the row contains an index widget which has focus,
1573 // we want to paint the entire row as active
1574 bool indexWidgetHasFocus = false;
1575 if ((current.row() == index.row()) && !d->editorIndexHash.isEmpty()) {
1576 const int r = index.row();
1577 QWidget *fw = QApplication::focusWidget();
1578 for (int c = 0; c < header->count(); ++c) {
1579 QModelIndex idx = d->model->index(r, c, parent);
1580 if (QWidget *editor = indexWidget(idx)) {
1581 if (ancestorOf(editor, fw)) {
1582 indexWidgetHasFocus = true;
1589 const bool widgetHasFocus = hasFocus();
1590 bool currentRowHasFocus = false;
1591 if (allColumnsShowFocus && widgetHasFocus && current.isValid()) {
1592 // check if the focus index is before or after the visible columns
1593 const int r = index.row();
1594 for (int c = 0; c < left && !currentRowHasFocus; ++c) {
1595 QModelIndex idx = d->model->index(r, c, parent);
1596 currentRowHasFocus = (idx == current);
1598 QModelIndex parent = d->model->parent(index);
1599 for (int c = right; c < header->count() && !currentRowHasFocus; ++c) {
1600 currentRowHasFocus = (d->model->index(r, c, parent) == current);
1604 // ### special case: treeviews with multiple columns draw
1605 // the selections differently than with only one column
1606 opt.showDecorationSelected = (d->selectionBehavior & SelectRows)
1607 || option.showDecorationSelected;
1609 int width, height = option.rect.height();
1611 QModelIndex modelIndex;
1612 const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1613 && index.parent() == hover.parent()
1614 && index.row() == hover.row();
1616 QVector<int> logicalIndices;
1617 QVector<QStyleOptionViewItemV4::ViewItemPosition> viewItemPosList; // vector of left/middle/end for each logicalIndex
1618 d->calcLogicalIndices(&logicalIndices, &viewItemPosList);
1620 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection) {
1621 int headerSection = logicalIndices.at(currentLogicalSection);
1622 position = columnViewportPosition(headerSection) + offset.x();
1623 width = header->sectionSize(headerSection);
1626 int lastSection = header->logicalIndex(header->count() - 1);
1628 width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position;
1630 width += position - columnViewportPosition(lastSection);
1631 position = columnViewportPosition(lastSection);
1635 modelIndex = d->model->index(index.row(), headerSection, parent);
1636 if (!modelIndex.isValid())
1640 opt.viewItemPosition = viewItemPosList.at(currentLogicalSection);
1642 // fake activeness when row editor has focus
1643 if (indexWidgetHasFocus)
1644 opt.state |= QStyle::State_Active;
1646 if (d->selectionModel->isSelected(modelIndex))
1647 opt.state |= QStyle::State_Selected;
1648 if (widgetHasFocus && (current == modelIndex)) {
1649 if (allColumnsShowFocus)
1650 currentRowHasFocus = true;
1652 opt.state |= QStyle::State_HasFocus;
1654 if ((hoverRow || modelIndex == hover)
1655 && (option.showDecorationSelected || (d->hoverBranch == -1)))
1656 opt.state |= QStyle::State_MouseOver;
1658 opt.state &= ~QStyle::State_MouseOver;
1661 QPalette::ColorGroup cg;
1662 if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) {
1663 opt.state &= ~QStyle::State_Enabled;
1664 cg = QPalette::Disabled;
1665 } else if (opt.state & QStyle::State_Active) {
1666 cg = QPalette::Active;
1668 cg = QPalette::Inactive;
1670 opt.palette.setCurrentColorGroup(cg);
1674 if (d->current & 1) {
1675 opt.features |= QStyleOptionViewItemV2::Alternate;
1677 opt.features &= ~QStyleOptionViewItemV2::Alternate;
1681 /* Prior to Qt 4.3, the background of the branch (in selected state and
1682 alternate row color was provided by the view. For backward compatibility,
1683 this is now delegated to the style using PE_PanelViewItemRow which
1684 does the appropriate fill */
1685 if (headerSection == 0) {
1686 const int i = d->indentationForItem(d->current);
1687 QRect branches(reverse ? position + width - i : position, y, i, height);
1688 const bool setClipRect = branches.width() > width;
1691 painter->setClipRect(QRect(position, y, width, height));
1693 // draw background for the branch (selection + alternate row)
1694 opt.rect = branches;
1695 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1697 // draw background of the item (only alternate row). rest of the background
1698 // is provided by the delegate
1699 QStyle::State oldState = opt.state;
1700 opt.state &= ~QStyle::State_Selected;
1701 opt.rect.setRect(reverse ? position : i + position, y, width - i, height);
1702 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1703 opt.state = oldState;
1705 drawBranches(painter, branches, index);
1709 QStyle::State oldState = opt.state;
1710 opt.state &= ~QStyle::State_Selected;
1711 opt.rect.setRect(position, y, width, height);
1712 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1713 opt.state = oldState;
1716 d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex);
1719 if (currentRowHasFocus) {
1720 QStyleOptionFocusRect o;
1721 o.QStyleOption::operator=(option);
1722 o.state |= QStyle::State_KeyboardFocusChange;
1723 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
1724 ? QPalette::Normal : QPalette::Disabled;
1725 o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index)
1726 ? QPalette::Highlight : QPalette::Background);
1728 if (!option.showDecorationSelected)
1729 x = header->sectionPosition(0) + d->indentationForItem(d->current);
1730 QRect focusRect(x - header->offset(), y, header->length() - x, height);
1731 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect);
1732 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1733 // if we show focus on all columns and the first section is moved,
1734 // we have to split the focus rect into two rects
1735 if (allColumnsShowFocus && !option.showDecorationSelected
1736 && header->sectionsMoved() && (header->visualIndex(0) != 0)) {
1737 QRect sectionRect(0, y, header->sectionPosition(0), height);
1738 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect);
1739 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1745 Draws the branches in the tree view on the same row as the model item
1746 \a index, using the \a painter given. The branches are drawn in the
1747 rectangle specified by \a rect.
1749 void QTreeView::drawBranches(QPainter *painter, const QRect &rect,
1750 const QModelIndex &index) const
1752 Q_D(const QTreeView);
1753 const bool reverse = isRightToLeft();
1754 const int indent = d->indent;
1755 const int outer = d->rootDecoration ? 0 : 1;
1756 const int item = d->current;
1757 const QTreeViewItem &viewItem = d->viewItems.at(item);
1758 int level = viewItem.level;
1759 QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height());
1761 QModelIndex parent = index.parent();
1762 QModelIndex current = parent;
1763 QModelIndex ancestor = current.parent();
1765 QStyleOptionViewItemV2 opt = viewOptions();
1766 QStyle::State extraFlags = QStyle::State_None;
1768 extraFlags |= QStyle::State_Enabled;
1769 if (window()->isActiveWindow())
1770 extraFlags |= QStyle::State_Active;
1771 QPoint oldBO = painter->brushOrigin();
1772 if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel)
1773 painter->setBrushOrigin(QPoint(0, verticalOffset()));
1775 if (d->alternatingColors) {
1776 if (d->current & 1) {
1777 opt.features |= QStyleOptionViewItemV2::Alternate;
1779 opt.features &= ~QStyleOptionViewItemV2::Alternate;
1783 // When hovering over a row, pass State_Hover for painting the branch
1784 // indicators if it has the decoration (aka branch) selected.
1785 bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1786 && opt.showDecorationSelected
1787 && index.parent() == d->hover.parent()
1788 && index.row() == d->hover.row();
1790 if (d->selectionModel->isSelected(index))
1791 extraFlags |= QStyle::State_Selected;
1793 if (level >= outer) {
1794 // start with the innermost branch
1795 primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent);
1796 opt.rect = primitive;
1798 const bool expanded = viewItem.expanded;
1799 const bool children = viewItem.hasChildren;
1800 bool moreSiblings = viewItem.hasMoreSiblings;
1802 opt.state = QStyle::State_Item | extraFlags
1803 | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None)
1804 | (children ? QStyle::State_Children : QStyle::State_None)
1805 | (expanded ? QStyle::State_Open : QStyle::State_None);
1806 if (hoverRow || item == d->hoverBranch)
1807 opt.state |= QStyle::State_MouseOver;
1809 opt.state &= ~QStyle::State_MouseOver;
1810 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1812 // then go out level by level
1813 for (--level; level >= outer; --level) { // we have already drawn the innermost branch
1814 primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent);
1815 opt.rect = primitive;
1816 opt.state = extraFlags;
1817 bool moreSiblings = false;
1818 if (d->hiddenIndexes.isEmpty()) {
1819 moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row());
1821 int successor = item + viewItem.total + 1;
1822 while (successor < d->viewItems.size()
1823 && d->viewItems.at(successor).level >= uint(level)) {
1824 const QTreeViewItem &successorItem = d->viewItems.at(successor);
1825 if (successorItem.level == uint(level)) {
1826 moreSiblings = true;
1829 successor += successorItem.total + 1;
1833 opt.state |= QStyle::State_Sibling;
1834 if (hoverRow || item == d->hoverBranch)
1835 opt.state |= QStyle::State_MouseOver;
1837 opt.state &= ~QStyle::State_MouseOver;
1838 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1840 ancestor = current.parent();
1842 painter->setBrushOrigin(oldBO);
1848 void QTreeView::mousePressEvent(QMouseEvent *event)
1851 bool handled = false;
1852 if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonPress)
1853 handled = d->expandOrCollapseItemAtPos(event->pos());
1854 if (!handled && d->itemDecorationAt(event->pos()) == -1)
1855 QAbstractItemView::mousePressEvent(event);
1861 void QTreeView::mouseReleaseEvent(QMouseEvent *event)
1864 if (d->itemDecorationAt(event->pos()) == -1) {
1865 QAbstractItemView::mouseReleaseEvent(event);
1867 if (state() == QAbstractItemView::DragSelectingState)
1868 setState(QAbstractItemView::NoState);
1869 if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonRelease)
1870 d->expandOrCollapseItemAtPos(event->pos());
1877 void QTreeView::mouseDoubleClickEvent(QMouseEvent *event)
1880 if (state() != NoState || !d->viewport->rect().contains(event->pos()))
1883 int i = d->itemDecorationAt(event->pos());
1885 i = d->itemAtCoordinate(event->y());
1887 return; // user clicked outside the items
1889 const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index;
1890 const QPersistentModelIndex persistent = indexAt(event->pos());
1892 if (d->pressedIndex != persistent) {
1893 mousePressEvent(event);
1897 // signal handlers may change the model
1898 emit doubleClicked(persistent);
1900 if (!persistent.isValid())
1903 if (edit(persistent, DoubleClicked, event) || state() != NoState)
1904 return; // the double click triggered editing
1906 if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this))
1907 emit activated(persistent);
1909 d->executePostedLayout(); // we need to make sure viewItems is updated
1910 if (d->itemsExpandable
1911 && d->expandsOnDoubleClick
1912 && d->hasVisibleChildren(persistent)) {
1913 if (!((i < d->viewItems.count()) && (d->viewItems.at(i).index == firstColumnIndex))) {
1914 // find the new index of the item
1915 for (i = 0; i < d->viewItems.count(); ++i) {
1916 if (d->viewItems.at(i).index == firstColumnIndex)
1919 if (i == d->viewItems.count())
1922 if (d->viewItems.at(i).expanded)
1923 d->collapse(i, true);
1927 viewport()->update();
1935 void QTreeView::mouseMoveEvent(QMouseEvent *event)
1938 if (d->itemDecorationAt(event->pos()) == -1) // ### what about expanding/collapsing state ?
1939 QAbstractItemView::mouseMoveEvent(event);
1945 void QTreeView::keyPressEvent(QKeyEvent *event)
1948 QModelIndex current = currentIndex();
1949 //this is the management of the expansion
1950 if (d->isIndexValid(current) && d->model && d->itemsExpandable) {
1951 switch (event->key()) {
1952 case Qt::Key_Asterisk: {
1953 QStack<QModelIndex> parents;
1954 parents.push(current);
1955 while (!parents.isEmpty()) {
1956 QModelIndex parent = parents.pop();
1957 for (int row = 0; row < d->model->rowCount(parent); ++row) {
1958 QModelIndex child = d->model->index(row, 0, parent);
1959 if (!d->isIndexValid(child))
1961 parents.push(child);
1976 QAbstractItemView::keyPressEvent(event);
1982 QModelIndex QTreeView::indexAt(const QPoint &point) const
1984 Q_D(const QTreeView);
1985 d->executePostedLayout();
1987 int visualIndex = d->itemAtCoordinate(point.y());
1988 QModelIndex idx = d->modelIndex(visualIndex);
1990 return QModelIndex();
1992 if (d->viewItems.at(visualIndex).spanning)
1995 int column = d->columnAt(point.x());
1996 if (column == idx.column())
1999 return QModelIndex();
2000 return idx.sibling(idx.row(), column);
2004 Returns the model index of the item above \a index.
2006 QModelIndex QTreeView::indexAbove(const QModelIndex &index) const
2008 Q_D(const QTreeView);
2009 if (!d->isIndexValid(index))
2010 return QModelIndex();
2011 d->executePostedLayout();
2012 int i = d->viewIndex(index);
2014 return QModelIndex();
2015 return d->viewItems.at(i).index;
2019 Returns the model index of the item below \a index.
2021 QModelIndex QTreeView::indexBelow(const QModelIndex &index) const
2023 Q_D(const QTreeView);
2024 if (!d->isIndexValid(index))
2025 return QModelIndex();
2026 d->executePostedLayout();
2027 int i = d->viewIndex(index);
2028 if (++i >= d->viewItems.count())
2029 return QModelIndex();
2030 return d->viewItems.at(i).index;
2036 Lays out the items in the tree view.
2038 void QTreeView::doItemsLayout()
2041 if (d->hasRemovedItems) {
2042 //clean the QSet that may contains old (and this invalid) indexes
2043 d->hasRemovedItems = false;
2044 QSet<QPersistentModelIndex>::iterator it = d->expandedIndexes.begin();
2045 while (it != d->expandedIndexes.constEnd()) {
2047 it = d->expandedIndexes.erase(it);
2051 it = d->hiddenIndexes.begin();
2052 while (it != d->hiddenIndexes.constEnd()) {
2054 it = d->hiddenIndexes.erase(it);
2059 d->viewItems.clear(); // prepare for new layout
2060 QModelIndex parent = d->root;
2061 if (d->model->hasChildren(parent)) {
2064 QAbstractItemView::doItemsLayout();
2065 d->header->doItemsLayout();
2071 void QTreeView::reset()
2074 d->expandedIndexes.clear();
2075 d->hiddenIndexes.clear();
2076 d->spanningIndexes.clear();
2077 d->viewItems.clear();
2078 QAbstractItemView::reset();
2082 Returns the horizontal offset of the items in the treeview.
2084 Note that the tree view uses the horizontal header section
2085 positions to determine the positions of columns in the view.
2087 \sa verticalOffset()
2089 int QTreeView::horizontalOffset() const
2091 Q_D(const QTreeView);
2092 return d->header->offset();
2096 Returns the vertical offset of the items in the tree view.
2098 \sa horizontalOffset()
2100 int QTreeView::verticalOffset() const
2102 Q_D(const QTreeView);
2103 if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) {
2104 if (d->uniformRowHeights)
2105 return verticalScrollBar()->value() * d->defaultItemHeight;
2106 // If we are scrolling per item and have non-uniform row heights,
2107 // finding the vertical offset in pixels is going to be relatively slow.
2108 // ### find a faster way to do this
2109 d->executePostedLayout();
2111 for (int i = 0; i < d->viewItems.count(); ++i) {
2112 if (i == verticalScrollBar()->value())
2114 offset += d->itemHeight(i);
2119 return verticalScrollBar()->value();
2123 Move the cursor in the way described by \a cursorAction, using the
2124 information provided by the button \a modifiers.
2126 QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
2129 Q_UNUSED(modifiers);
2131 d->executePostedLayout();
2133 QModelIndex current = currentIndex();
2134 if (!current.isValid()) {
2135 int i = d->below(-1);
2137 while (c < d->header->count() && d->header->isSectionHidden(c))
2139 if (i < d->viewItems.count() && c < d->header->count()) {
2140 return d->modelIndex(i, c);
2142 return QModelIndex();
2145 #if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC)
2146 // Selection behavior is slightly different on the Mac.
2147 if (d->selectionMode == QAbstractItemView::ExtendedSelection
2148 && d->selectionModel
2149 && d->selectionModel->hasSelection()) {
2151 const bool moveUpDown = (cursorAction == MoveUp || cursorAction == MoveDown);
2152 const bool moveNextPrev = (cursorAction == MoveNext || cursorAction == MovePrevious);
2153 const bool contiguousSelection = moveUpDown && (modifiers & Qt::ShiftModifier);
2155 // Use the outermost index in the selection as the current index
2156 if (!contiguousSelection && (moveUpDown || moveNextPrev)) {
2158 // Find outermost index.
2159 const bool useTopIndex = (cursorAction == MoveUp || cursorAction == MovePrevious);
2160 int index = useTopIndex ? INT_MAX : INT_MIN;
2161 const QItemSelection selection = d->selectionModel->selection();
2162 for (int i = 0; i < selection.count(); ++i) {
2163 const QItemSelectionRange &range = selection.at(i);
2164 int candidate = d->viewIndex(useTopIndex ? range.topLeft() : range.bottomRight());
2166 index = useTopIndex ? qMin(index, candidate) : qMax(index, candidate);
2169 if (index >= 0 && index < INT_MAX)
2175 vi = qMax(0, d->viewIndex(current));
2177 if (isRightToLeft()) {
2178 if (cursorAction == MoveRight)
2179 cursorAction = MoveLeft;
2180 else if (cursorAction == MoveLeft)
2181 cursorAction = MoveRight;
2183 switch (cursorAction) {
2186 #ifdef QT_KEYPAD_NAVIGATION
2187 if (vi == d->viewItems.count()-1 && QApplication::keypadNavigationEnabled())
2188 return d->model->index(0, current.column(), d->root);
2190 return d->modelIndex(d->below(vi), current.column());
2193 #ifdef QT_KEYPAD_NAVIGATION
2194 if (vi == 0 && QApplication::keypadNavigationEnabled())
2195 return d->modelIndex(d->viewItems.count() - 1, current.column());
2197 return d->modelIndex(d->above(vi), current.column());
2199 QScrollBar *sb = horizontalScrollBar();
2200 if (vi < d->viewItems.count() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) {
2201 d->collapse(vi, true);
2202 d->moveCursorUpdatedView = true;
2204 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this);
2206 QModelIndex par = current.parent();
2207 if (par.isValid() && par != rootIndex())
2213 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2214 int visualColumn = d->header->visualIndex(current.column()) - 1;
2215 while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn)))
2217 int newColumn = d->header->logicalIndex(visualColumn);
2218 QModelIndex next = current.sibling(current.row(), newColumn);
2223 int oldValue = sb->value();
2224 sb->setValue(sb->value() - sb->singleStep());
2225 if (oldValue != sb->value())
2226 d->moveCursorUpdatedView = true;
2231 viewport()->update();
2235 if (vi < d->viewItems.count() && !d->viewItems.at(vi).expanded && d->itemsExpandable
2236 && d->hasVisibleChildren(d->viewItems.at(vi).index)) {
2237 d->expand(vi, true);
2238 d->moveCursorUpdatedView = true;
2240 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this);
2242 QModelIndex idx = d->modelIndex(d->below(vi));
2243 if (idx.parent() == current)
2249 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2250 int visualColumn = d->header->visualIndex(current.column()) + 1;
2251 while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn)))
2254 QModelIndex next = current.sibling(current.row(), visualColumn);
2259 //last restort: we change the scrollbar value
2260 QScrollBar *sb = horizontalScrollBar();
2261 int oldValue = sb->value();
2262 sb->setValue(sb->value() + sb->singleStep());
2263 if (oldValue != sb->value())
2264 d->moveCursorUpdatedView = true;
2268 viewport()->update();
2271 return d->modelIndex(d->pageUp(vi), current.column());
2273 return d->modelIndex(d->pageDown(vi), current.column());
2275 return d->model->index(0, current.column(), d->root);
2277 return d->modelIndex(d->viewItems.count() - 1, current.column());
2283 Applies the selection \a command to the items in or touched by the
2286 \sa selectionCommand()
2288 void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2291 if (!selectionModel() || rect.isNull())
2294 d->executePostedLayout();
2295 QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right())
2296 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()));
2297 QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2298 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()));
2299 QModelIndex topLeft = indexAt(tl);
2300 QModelIndex bottomRight = indexAt(br);
2301 if (!topLeft.isValid() && !bottomRight.isValid()) {
2302 if (command & QItemSelectionModel::Clear)
2303 selectionModel()->clear();
2306 if (!topLeft.isValid() && !d->viewItems.isEmpty())
2307 topLeft = d->viewItems.first().index;
2308 if (!bottomRight.isValid() && !d->viewItems.isEmpty()) {
2309 const int column = d->header->logicalIndex(d->header->count() - 1);
2310 const QModelIndex index = d->viewItems.last().index;
2311 bottomRight = index.sibling(index.row(), column);
2314 if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight))
2317 d->select(topLeft, bottomRight, command);
2321 Returns the rectangle from the viewport of the items in the given
2324 Since 4.7, the returned region only contains rectangles intersecting
2325 (or included in) the viewport.
2327 QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const
2329 Q_D(const QTreeView);
2330 if (selection.isEmpty())
2333 QRegion selectionRegion;
2334 const QRect &viewportRect = d->viewport->rect();
2335 for (int i = 0; i < selection.count(); ++i) {
2336 QItemSelectionRange range = selection.at(i);
2337 if (!range.isValid())
2339 QModelIndex parent = range.parent();
2340 QModelIndex leftIndex = range.topLeft();
2341 int columnCount = d->model->columnCount(parent);
2342 while (leftIndex.isValid() && isIndexHidden(leftIndex)) {
2343 if (leftIndex.column() + 1 < columnCount)
2344 leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent);
2346 leftIndex = QModelIndex();
2348 if (!leftIndex.isValid())
2350 const QRect leftRect = visualRect(leftIndex);
2351 int top = leftRect.top();
2352 QModelIndex rightIndex = range.bottomRight();
2353 while (rightIndex.isValid() && isIndexHidden(rightIndex)) {
2354 if (rightIndex.column() - 1 >= 0)
2355 rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent);
2357 rightIndex = QModelIndex();
2359 if (!rightIndex.isValid())
2361 const QRect rightRect = visualRect(rightIndex);
2362 int bottom = rightRect.bottom();
2364 qSwap<int>(top, bottom);
2365 int height = bottom - top + 1;
2366 if (d->header->sectionsMoved()) {
2367 for (int c = range.left(); c <= range.right(); ++c) {
2368 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2369 if (viewportRect.intersects(rangeRect))
2370 selectionRegion += rangeRect;
2373 QRect combined = leftRect|rightRect;
2374 combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left()));
2375 if (viewportRect.intersects(combined))
2376 selectionRegion += combined;
2379 return selectionRegion;
2385 QModelIndexList QTreeView::selectedIndexes() const
2387 QModelIndexList viewSelected;
2388 QModelIndexList modelSelected;
2389 if (selectionModel())
2390 modelSelected = selectionModel()->selectedIndexes();
2391 for (int i = 0; i < modelSelected.count(); ++i) {
2392 // check that neither the parents nor the index is hidden before we add
2393 QModelIndex index = modelSelected.at(i);
2394 while (index.isValid() && !isIndexHidden(index))
2395 index = index.parent();
2396 if (index.isValid())
2398 viewSelected.append(modelSelected.at(i));
2400 return viewSelected;
2404 Scrolls the contents of the tree view by (\a dx, \a dy).
2406 void QTreeView::scrollContentsBy(int dx, int dy)
2410 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
2412 dx = isRightToLeft() ? -dx : dx;
2414 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2415 int oldOffset = d->header->offset();
2416 if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum())
2417 d->header->setOffsetToLastSection();
2419 d->header->setOffsetToSectionPosition(horizontalScrollBar()->value());
2420 int newOffset = d->header->offset();
2421 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
2423 d->header->setOffset(horizontalScrollBar()->value());
2427 const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight;
2428 if (d->viewItems.isEmpty() || itemHeight == 0)
2431 // guestimate the number of items in the viewport
2432 int viewCount = d->viewport->height() / itemHeight;
2433 int maxDeltaY = qMin(d->viewItems.count(), viewCount);
2434 // no need to do a lot of work if we are going to redraw the whole thing anyway
2435 if (qAbs(dy) > qAbs(maxDeltaY) && d->editorIndexHash.isEmpty()) {
2436 verticalScrollBar()->update();
2437 d->viewport->update();
2441 if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2442 int currentScrollbarValue = verticalScrollBar()->value();
2443 int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy)
2444 int currentViewIndex = currentScrollbarValue; // the first visible item
2445 int previousViewIndex = previousScrollbarValue;
2446 const QVector<QTreeViewItem> viewItems = d->viewItems;
2448 if (previousViewIndex < currentViewIndex) { // scrolling down
2449 for (int i = previousViewIndex; i < currentViewIndex; ++i) {
2450 if (i < d->viewItems.count())
2451 dy -= d->itemHeight(i);
2453 } else if (previousViewIndex > currentViewIndex) { // scrolling up
2454 for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) {
2455 if (i < d->viewItems.count())
2456 dy += d->itemHeight(i);
2461 d->scrollContentsBy(dx, dy);
2465 This slot is called whenever a column has been moved.
2467 void QTreeView::columnMoved()
2470 updateEditorGeometries();
2471 d->viewport->update();
2477 void QTreeView::reexpand()
2483 Informs the view that the rows from the \a start row to the \a end row
2484 inclusive have been inserted into the \a parent model item.
2486 void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end)
2489 // if we are going to do a complete relayout anyway, there is no need to update
2490 if (d->delayedPendingLayout) {
2491 QAbstractItemView::rowsInserted(parent, start, end);
2495 //don't add a hierarchy on a column != 0
2496 if (parent.column() != 0 && parent.isValid()) {
2497 QAbstractItemView::rowsInserted(parent, start, end);
2501 const int parentRowCount = d->model->rowCount(parent);
2502 const int delta = end - start + 1;
2503 if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) {
2504 QAbstractItemView::rowsInserted(parent, start, end);
2508 const int parentItem = d->viewIndex(parent);
2509 if (((parentItem != -1) && d->viewItems.at(parentItem).expanded)
2510 || (parent == d->root)) {
2511 d->doDelayedItemsLayout();
2512 } else if (parentItem != -1 && (d->model->rowCount(parent) == end - start + 1)) {
2513 // the parent just went from 0 children to more. update to re-paint the decoration
2514 d->viewItems[parentItem].hasChildren = true;
2515 viewport()->update();
2517 QAbstractItemView::rowsInserted(parent, start, end);
2521 Informs the view that the rows from the \a start row to the \a end row
2522 inclusive are about to removed from the given \a parent model item.
2524 void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
2527 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
2528 d->viewItems.clear();
2534 Informs the view that the rows from the \a start row to the \a end row
2535 inclusive have been removed from the given \a parent model item.
2537 void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end)
2540 d->viewItems.clear();
2541 d->doDelayedItemsLayout();
2542 d->hasRemovedItems = true;
2543 d->_q_rowsRemoved(parent, start, end);
2547 Informs the tree view that the number of columns in the tree view has
2548 changed from \a oldCount to \a newCount.
2550 void QTreeView::columnCountChanged(int oldCount, int newCount)
2553 if (oldCount == 0 && newCount > 0) {
2554 //if the first column has just been added we need to relayout.
2555 d->doDelayedItemsLayout();
2560 viewport()->update();
2564 Resizes the \a column given to the size of its contents.
2566 \sa columnWidth(), setColumnWidth()
2568 void QTreeView::resizeColumnToContents(int column)
2571 d->executePostedLayout();
2572 if (column < 0 || column >= d->header->count())
2574 int contents = sizeHintForColumn(column);
2575 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column);
2576 d->header->resizeSection(column, qMax(contents, header));
2583 Sorts the model by the values in the given \a column.
2585 void QTreeView::sortByColumn(int column)
2588 sortByColumn(column, d->header->sortIndicatorOrder());
2594 Sets the model up for sorting by the values in the given \a column and \a order.
2596 \a column may be -1, in which case no sort indicator will be shown
2597 and the model will return to its natural, unsorted order. Note that not
2598 all models support this and may even crash in this case.
2602 void QTreeView::sortByColumn(int column, Qt::SortOrder order)
2606 //If sorting is enabled will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts
2607 d->header->setSortIndicator(column, order);
2608 //If sorting is not enabled, force to sort now.
2609 if (!d->sortingEnabled)
2610 d->model->sort(column, order);
2616 void QTreeView::selectAll()
2619 if (!selectionModel())
2621 SelectionMode mode = d->selectionMode;
2622 d->executePostedLayout(); //make sure we lay out the items
2623 if (mode != SingleSelection && !d->viewItems.isEmpty()) {
2624 const QModelIndex &idx = d->viewItems.last().index;
2625 QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1);
2626 d->select(d->viewItems.first().index, lastItemIndex,
2627 QItemSelectionModel::ClearAndSelect
2628 |QItemSelectionModel::Rows);
2634 Expands all expandable items.
2636 Warning: if the model contains a large number of items,
2637 this function will take some time to execute.
2639 \sa collapseAll() expand() collapse() setExpanded()
2641 void QTreeView::expandAll()
2644 d->viewItems.clear();
2645 d->interruptDelayedItemsLayout();
2646 d->layout(-1, true);
2648 d->viewport->update();
2654 Collapses all expanded items.
2656 \sa expandAll() expand() collapse() setExpanded()
2658 void QTreeView::collapseAll()
2661 d->expandedIndexes.clear();
2667 Expands all expandable items to the given \a depth.
2669 \sa expandAll() collapseAll() expand() collapse() setExpanded()
2671 void QTreeView::expandToDepth(int depth)
2674 d->viewItems.clear();
2675 d->expandedIndexes.clear();
2676 d->interruptDelayedItemsLayout();
2678 for (int i = 0; i < d->viewItems.count(); ++i) {
2679 if (d->viewItems.at(i).level <= (uint)depth) {
2680 d->viewItems[i].expanded = true;
2682 d->storeExpanded(d->viewItems.at(i).index);
2686 d->viewport->update();
2690 This function is called whenever \a{column}'s size is changed in
2691 the header. \a oldSize and \a newSize give the previous size and
2692 the new size in pixels.
2694 \sa setColumnWidth()
2696 void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */)
2699 d->columnsToUpdate.append(column);
2700 if (d->columnResizeTimerID == 0)
2701 d->columnResizeTimerID = startTimer(0);
2707 void QTreeView::updateGeometries()
2711 if (d->geometryRecursionBlock)
2713 d->geometryRecursionBlock = true;
2714 QSize hint = d->header->isHidden() ? QSize(0, 0) : d->header->sizeHint();
2715 setViewportMargins(0, hint.height(), 0, 0);
2716 QRect vg = d->viewport->geometry();
2717 QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height());
2718 d->header->setGeometry(geometryRect);
2719 //d->header->setOffset(horizontalScrollBar()->value()); // ### bug ???
2720 QMetaObject::invokeMethod(d->header, "updateGeometries");
2721 d->updateScrollBars();
2722 d->geometryRecursionBlock = false;
2724 QAbstractItemView::updateGeometries();
2728 Returns the size hint for the \a column's width or -1 if there is no
2731 If you need to set the width of a given column to a fixed value, call
2732 QHeaderView::resizeSection() on the view's header.
2734 If you reimplement this function in a subclass, note that the value you
2735 return is only used when resizeColumnToContents() is called. In that case,
2736 if a larger column width is required by either the view's header or
2737 the item delegate, that width will be used instead.
2739 \sa QWidget::sizeHint, header()
2741 int QTreeView::sizeHintForColumn(int column) const
2743 Q_D(const QTreeView);
2744 d->executePostedLayout();
2745 if (d->viewItems.isEmpty())
2749 QStyleOptionViewItemV4 option = d->viewOptionsV4();
2750 const QVector<QTreeViewItem> viewItems = d->viewItems;
2753 int end = viewItems.count();
2754 if(end > 1000) { //if we have too many item this function would be too slow.
2755 //we get a good approximation by only iterate over 1000 items.
2756 start = qMax(0, d->firstVisibleItem() - 100);
2757 end = qMin(end, start + 900);
2760 for (int i = start; i < end; ++i) {
2761 if (viewItems.at(i).spanning)
2762 continue; // we have no good size hint
2763 QModelIndex index = viewItems.at(i).index;
2764 index = index.sibling(index.row(), column);
2765 QWidget *editor = d->editorForIndex(index).widget.data();
2766 if (editor && d->persistent.contains(editor)) {
2767 w = qMax(w, editor->sizeHint().width());
2768 int min = editor->minimumSize().width();
2769 int max = editor->maximumSize().width();
2770 w = qBound(min, w, max);
2772 int hint = d->delegateForIndex(index)->sizeHint(option, index).width();
2773 w = qMax(w, hint + (column == 0 ? d->indentationForItem(i) : 0));
2779 Returns the size hint for the row indicated by \a index.
2781 \sa sizeHintForColumn(), uniformRowHeights()
2783 int QTreeView::indexRowSizeHint(const QModelIndex &index) const
2785 Q_D(const QTreeView);
2786 if (!d->isIndexValid(index) || !d->itemDelegate)
2791 int indexRow = index.row();
2792 int count = d->header->count();
2793 bool emptyHeader = (count == 0);
2794 QModelIndex parent = index.parent();
2796 if (count && isVisible()) {
2797 // If the sections have moved, we end up checking too many or too few
2798 start = d->header->visualIndexAt(0);
2800 // If the header has not been laid out yet, we use the model directly
2801 count = d->model->columnCount(parent);
2804 if (isRightToLeft()) {
2805 start = (start == -1 ? count - 1 : start);
2808 start = (start == -1 ? 0 : start);
2816 QStyleOptionViewItemV4 option = d->viewOptionsV4();
2817 // ### If we want word wrapping in the items,
2818 // ### we need to go through all the columns
2819 // ### and set the width of the column
2821 // Hack to speed up the function
2822 option.rect.setWidth(-1);
2824 for (int column = start; column <= end; ++column) {
2825 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column);
2826 if (d->header->isSectionHidden(logicalColumn))
2828 QModelIndex idx = d->model->index(indexRow, logicalColumn, parent);
2829 if (idx.isValid()) {
2830 QWidget *editor = d->editorForIndex(idx).widget.data();
2831 if (editor && d->persistent.contains(editor)) {
2832 height = qMax(height, editor->sizeHint().height());
2833 int min = editor->minimumSize().height();
2834 int max = editor->maximumSize().height();
2835 height = qBound(min, height, max);
2837 int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height();
2838 height = qMax(height, hint);
2847 Returns the height of the row indicated by the given \a index.
2848 \sa indexRowSizeHint()
2850 int QTreeView::rowHeight(const QModelIndex &index) const
2852 Q_D(const QTreeView);
2853 d->executePostedLayout();
2854 int i = d->viewIndex(index);
2857 return d->itemHeight(i);
2863 void QTreeView::horizontalScrollbarAction(int action)
2865 QAbstractItemView::horizontalScrollbarAction(action);
2871 bool QTreeView::isIndexHidden(const QModelIndex &index) const
2873 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent()));
2877 private implementation
2879 void QTreeViewPrivate::initialize()
2882 updateStyledFrameWidths();
2883 q->setSelectionBehavior(QAbstractItemView::SelectRows);
2884 q->setSelectionMode(QAbstractItemView::SingleSelection);
2885 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
2886 q->setAttribute(Qt::WA_MacShowFocusRect);
2888 QHeaderView *header = new QHeaderView(Qt::Horizontal, q);
2889 header->setMovable(true);
2890 header->setStretchLastSection(true);
2891 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
2892 q->setHeader(header);
2893 #ifndef QT_NO_ANIMATION
2894 QObject::connect(&animatedOperation, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation()));
2895 #endif //QT_NO_ANIMATION
2898 void QTreeViewPrivate::expand(int item, bool emitSignal)
2902 if (item == -1 || viewItems.at(item).expanded)
2905 #ifndef QT_NO_ANIMATION
2906 if (emitSignal && animationsEnabled)
2907 prepareAnimatedOperation(item, QVariantAnimation::Forward);
2908 #endif //QT_NO_ANIMATION
2909 stateBeforeAnimation = state;
2910 q->setState(QAbstractItemView::ExpandingState);
2911 const QModelIndex index = viewItems.at(item).index;
2912 storeExpanded(index);
2913 viewItems[item].expanded = true;
2915 q->setState(stateBeforeAnimation);
2917 if (model->canFetchMore(index))
2918 model->fetchMore(index);
2920 emit q->expanded(index);
2921 #ifndef QT_NO_ANIMATION
2922 if (animationsEnabled)
2923 beginAnimatedOperation();
2924 #endif //QT_NO_ANIMATION
2928 void QTreeViewPrivate::insertViewItems(int pos, int count, const QTreeViewItem &viewItem)
2932 viewItems.insert(pos, count, viewItem);
2933 QTreeViewItem *items = viewItems.data();
2934 for (int i = pos + count; i < viewItems.count(); i++)
2935 if (items[i].parentItem >= pos)
2936 items[i].parentItem += count;
2937 #ifndef QT_NO_ACCESSIBILITY
2939 if (QAccessible::isActive()) {
2940 QAccessible::updateAccessibility(QAccessibleEvent(QAccessible::TableModelChanged, q, 0));
2946 void QTreeViewPrivate::removeViewItems(int pos, int count)
2950 viewItems.remove(pos, count);
2951 QTreeViewItem *items = viewItems.data();
2952 for (int i = pos; i < viewItems.count(); i++)
2953 if (items[i].parentItem >= pos)
2954 items[i].parentItem -= count;
2955 #ifndef QT_NO_ACCESSIBILITY
2957 if (QAccessible::isActive()) {
2958 QAccessible::updateAccessibility(QAccessibleEvent(QAccessible::TableModelChanged, q, 0));
2965 bool QTreeViewPrivate::checkViewItems() const
2967 for (int i = 0; i < viewItems.count(); ++i) {
2968 const QTreeViewItem &vi = viewItems.at(i);
2969 if (vi.parentItem == -1) {
2970 Q_ASSERT(!vi.index.parent().isValid() || vi.index.parent() == root);
2972 Q_ASSERT(vi.index.parent() == viewItems.at(vi.parentItem).index);
2979 void QTreeViewPrivate::collapse(int item, bool emitSignal)
2983 if (item == -1 || expandedIndexes.isEmpty())
2986 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
2987 delayedAutoScroll.stop();
2989 int total = viewItems.at(item).total;
2990 const QModelIndex &modelIndex = viewItems.at(item).index;
2991 if (!isPersistent(modelIndex))
2992 return; // if the index is not persistent, no chances it is expanded
2993 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex);
2994 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false)
2995 return; // nothing to do
2997 #ifndef QT_NO_ANIMATION
2998 if (emitSignal && animationsEnabled)
2999 prepareAnimatedOperation(item, QVariantAnimation::Backward);
3000 #endif //QT_NO_ANIMATION
3002 stateBeforeAnimation = state;
3003 q->setState(QAbstractItemView::CollapsingState);
3004 expandedIndexes.erase(it);
3005 viewItems[item].expanded = false;
3007 while (index > -1) {
3008 viewItems[index].total -= total;
3009 index = viewItems[index].parentItem;
3011 removeViewItems(item + 1, total); // collapse
3012 q->setState(stateBeforeAnimation);
3015 emit q->collapsed(modelIndex);
3016 #ifndef QT_NO_ANIMATION
3017 if (animationsEnabled)
3018 beginAnimatedOperation();
3019 #endif //QT_NO_ANIMATION
3023 #ifndef QT_NO_ANIMATION
3024 void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction)
3026 animatedOperation.item = item;
3027 animatedOperation.viewport = viewport;
3028 animatedOperation.setDirection(direction);
3030 int top = coordinateForItem(item) + itemHeight(item);
3031 QRect rect = viewport->rect();
3033 if (direction == QVariantAnimation::Backward) {
3034 const int limit = rect.height() * 2;
3036 int c = item + viewItems.at(item).total + 1;
3037 for (int i = item + 1; i < c && h < limit; ++i)
3040 animatedOperation.setEndValue(top + h);
3042 animatedOperation.setStartValue(top);
3043 animatedOperation.before = renderTreeToPixmapForAnimation(rect);
3046 void QTreeViewPrivate::beginAnimatedOperation()
3050 QRect rect = viewport->rect();
3051 rect.setTop(animatedOperation.top());
3052 if (animatedOperation.direction() == QVariantAnimation::Forward) {
3053 const int limit = rect.height() * 2;
3055 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1;
3056 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i)
3059 animatedOperation.setEndValue(animatedOperation.top() + h);
3062 if (!rect.isEmpty()) {
3063 animatedOperation.after = renderTreeToPixmapForAnimation(rect);
3065 q->setState(QAbstractItemView::AnimatingState);
3066 animatedOperation.start(); //let's start the animation
3070 void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const
3072 const int start = animatedOperation.startValue().toInt(),
3073 end = animatedOperation.endValue().toInt(),
3074 current = animatedOperation.currentValue().toInt();
3075 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward;
3076 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after;
3077 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height());
3078 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before;
3079 painter->drawPixmap(0, current, bottom);
3082 QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const
3084 Q_Q(const QTreeView);
3085 QPixmap pixmap(rect.size());
3086 if (rect.size().isEmpty())
3088 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels.
3089 QPainter painter(&pixmap);
3090 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base());
3091 painter.translate(0, -rect.top());
3092 q->drawTree(&painter, QRegion(rect));
3095 //and now let's render the editors the editors
3096 QStyleOptionViewItemV4 option = viewOptionsV4();
3097 for (QEditorIndexHash::const_iterator it = editorIndexHash.constBegin(); it != editorIndexHash.constEnd(); ++it) {
3098 QWidget *editor = it.key();
3099 const QModelIndex &index = it.value();
3100 option.rect = q->visualRect(index);
3101 if (option.rect.isValid()) {
3103 if (QAbstractItemDelegate *delegate = delegateForIndex(index))
3104 delegate->updateEditorGeometry(editor, option, index);
3106 const QPoint pos = editor->pos();
3107 if (rect.contains(pos)) {
3108 editor->render(&pixmap, pos - rect.topLeft());
3109 //the animation uses pixmap to display the treeview's content
3110 //the editor is rendered on this pixmap and thus can (should) be hidden
3120 void QTreeViewPrivate::_q_endAnimatedOperation()
3123 q->setState(stateBeforeAnimation);
3124 q->updateGeometries();
3127 #endif //QT_NO_ANIMATION
3129 void QTreeViewPrivate::_q_modelAboutToBeReset()
3134 void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
3136 if (start <= 0 && 0 <= end)
3138 QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(parent, start, end);
3141 void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end)
3143 if (start <= 0 && 0 <= end)
3144 doDelayedItemsLayout();
3145 QAbstractItemViewPrivate::_q_columnsRemoved(parent, start, end);
3149 creates and initialize the viewItem structure of the children of the element \li
3151 set \a recursiveExpanding if the function has to expand all the children (called from expandAll)
3152 \a afterIsUninitialized is when we recurse from layout(-1), it means all the items after 'i' are
3153 not yet initialized and need not to be moved
3155 void QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninitialized)
3158 QModelIndex current;
3159 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i);
3161 if (i>=0 && !parent.isValid()) {
3162 //modelIndex() should never return something invalid for the real items.
3163 //This can happen if columncount has been set to 0.
3164 //To avoid infinite loop we stop here.
3169 if (model->hasChildren(parent)) {
3170 if (model->canFetchMore(parent))
3171 model->fetchMore(parent);
3172 count = model->rowCount(parent);
3175 bool expanding = true;
3177 if (uniformRowHeights) {
3178 QModelIndex index = model->index(0, 0, parent);
3179 defaultItemHeight = q->indexRowSizeHint(index);
3181 viewItems.resize(count);
3182 afterIsUninitialized = true;
3183 } else if (viewItems[i].total != (uint)count) {
3184 if (!afterIsUninitialized)
3185 insertViewItems(i + 1, count, QTreeViewItem()); // expand
3187 viewItems.resize(viewItems.count() + count);
3193 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0);
3197 QTreeViewItem *item = 0;
3198 for (int j = first; j < first + count; ++j) {
3199 current = model->index(j - first, 0, parent);
3200 if (isRowHidden(current)) {
3202 last = j - hidden + children;
3204 last = j - hidden + children;
3206 item->hasMoreSiblings = true;
3207 item = &viewItems[last];
3208 item->index = current;
3209 item->parentItem = i;
3210 item->level = level;
3212 item->spanning = q->isFirstColumnSpanned(current.row(), parent);
3213 item->expanded = false;
3215 item->hasMoreSiblings = false;
3216 if (recursiveExpanding || isIndexExpanded(current)) {
3217 if (recursiveExpanding)
3218 expandedIndexes.insert(current);
3219 item->expanded = true;
3220 layout(last, recursiveExpanding, afterIsUninitialized);
3221 item = &viewItems[last];
3222 children += item->total;
3223 item->hasChildren = item->total > 0;
3224 last = j - hidden + children;
3226 item->hasChildren = hasVisibleChildren(current);
3231 // remove hidden items
3233 if (!afterIsUninitialized)
3234 removeViewItems(last + 1, hidden);
3236 viewItems.resize(viewItems.size() - hidden);
3240 return; // nothing changed
3243 viewItems[i].total += count - hidden;
3244 i = viewItems[i].parentItem;
3248 int QTreeViewPrivate::pageUp(int i) const
3250 int index = itemAtCoordinate(coordinateForItem(i) - viewport->height());
3251 while (isItemHiddenOrDisabled(index))
3253 return index == -1 ? 0 : index;
3256 int QTreeViewPrivate::pageDown(int i) const
3258 int index = itemAtCoordinate(coordinateForItem(i) + viewport->height());
3259 while (isItemHiddenOrDisabled(index))
3261 return index == -1 ? viewItems.count() - 1 : index;
3264 int QTreeViewPrivate::indentationForItem(int item) const
3266 if (item < 0 || item >= viewItems.count())
3268 int level = viewItems.at(item).level;
3271 return level * indent;
3274 int QTreeViewPrivate::itemHeight(int item) const
3276 if (uniformRowHeights)
3277 return defaultItemHeight;
3278 if (viewItems.isEmpty())
3280 const QModelIndex &index = viewItems.at(item).index;
3281 if (!index.isValid())
3283 int height = viewItems.at(item).height;
3285 height = q_func()->indexRowSizeHint(index);
3286 viewItems[item].height = height;
3288 return qMax(height, 0);
3294 Returns the viewport y coordinate for \a item.
3296 int QTreeViewPrivate::coordinateForItem(int item) const
3298 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3299 if (uniformRowHeights)
3300 return (item * defaultItemHeight) - vbar->value();
3301 // ### optimize (spans or caching)
3303 for (int i = 0; i < viewItems.count(); ++i) {
3305 return y - vbar->value();
3308 } else { // ScrollPerItem
3309 int topViewItemIndex = vbar->value();
3310 if (uniformRowHeights)
3311 return defaultItemHeight * (item - topViewItemIndex);
3312 if (item >= topViewItemIndex) {
3313 // search in the visible area first and continue down
3314 // ### slow if the item is not visible
3315 int viewItemCoordinate = 0;
3316 int viewItemIndex = topViewItemIndex;
3317 while (viewItemIndex < viewItems.count()) {
3318 if (viewItemIndex == item)
3319 return viewItemCoordinate;
3320 viewItemCoordinate += itemHeight(viewItemIndex);
3323 // below the last item in the view
3325 return viewItemCoordinate;
3327 // search the area above the viewport (used for editor widgets)
3328 int viewItemCoordinate = 0;
3329 for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) {
3330 if (viewItemIndex == item)
3331 return viewItemCoordinate;
3332 viewItemCoordinate -= itemHeight(viewItemIndex - 1);
3334 return viewItemCoordinate;
3342 Returns the index of the view item at the
3343 given viewport \a coordinate.
3347 int QTreeViewPrivate::itemAtCoordinate(int coordinate) const
3349 const int itemCount = viewItems.count();
3352 if (uniformRowHeights && defaultItemHeight <= 0)
3354 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3355 if (uniformRowHeights) {
3356 const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight;
3357 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3360 int viewItemCoordinate = 0;
3361 const int contentsCoordinate = coordinate + vbar->value();
3362 for (int viewItemIndex = 0; viewItemIndex < viewItems.count(); ++viewItemIndex) {
3363 viewItemCoordinate += itemHeight(viewItemIndex);
3364 if (viewItemCoordinate >= contentsCoordinate)
3365 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3367 } else { // ScrollPerItem
3368 int topViewItemIndex = vbar->value();
3369 if (uniformRowHeights) {
3371 coordinate -= defaultItemHeight - 1;
3372 const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight);
3373 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3375 if (coordinate >= 0) {
3376 // the coordinate is in or below the viewport
3377 int viewItemCoordinate = 0;
3378 for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.count(); ++viewItemIndex) {
3379 viewItemCoordinate += itemHeight(viewItemIndex);
3380 if (viewItemCoordinate > coordinate)
3381 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3384 // the coordinate is above the viewport
3385 int viewItemCoordinate = 0;
3386 for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) {
3387 if (viewItemCoordinate <= coordinate)
3388 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3389 viewItemCoordinate -= itemHeight(viewItemIndex);
3396 int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const
3398 if (!_index.isValid() || viewItems.isEmpty())
3401 const int totalCount = viewItems.count();
3402 const QModelIndex index = _index.sibling(_index.row(), 0);
3403 const int row = index.row();
3404 const qint64 internalId = index.internalId();
3406 // We start nearest to the lastViewedItem
3407 int localCount = qMin(lastViewedItem - 1, totalCount - lastViewedItem);
3408 for (int i = 0; i < localCount; ++i) {
3409 const QModelIndex &idx1 = viewItems.at(lastViewedItem + i).index;
3410 if (idx1.row() == row && idx1.internalId() == internalId) {
3411 lastViewedItem = lastViewedItem + i;
3412 return lastViewedItem;
3414 const QModelIndex &idx2 = viewItems.at(lastViewedItem - i - 1).index;
3415 if (idx2.row() == row && idx2.internalId() == internalId) {
3416 lastViewedItem = lastViewedItem - i - 1;
3417 return lastViewedItem;
3421 for (int j = qMax(0, lastViewedItem + localCount); j < totalCount; ++j) {
3422 const QModelIndex &idx = viewItems.at(j).index;
3423 if (idx.row() == row && idx.internalId() == internalId) {
3428 for (int j = qMin(totalCount, lastViewedItem - localCount) - 1; j >= 0; --j) {
3429 const QModelIndex &idx = viewItems.at(j).index;
3430 if (idx.row() == row && idx.internalId() == internalId) {
3440 QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const
3442 if (i < 0 || i >= viewItems.count())
3443 return QModelIndex();
3445 QModelIndex ret = viewItems.at(i).index;
3447 ret = ret.sibling(ret.row(), column);
3451 int QTreeViewPrivate::firstVisibleItem(int *offset) const
3453 const int value = vbar->value();
3454 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3457 return (value < 0 || value >= viewItems.count()) ? -1 : value;
3459 // ScrollMode == ScrollPerPixel
3460 if (uniformRowHeights) {
3461 if (!defaultItemHeight)
3465 *offset = -(value % defaultItemHeight);
3466 return value / defaultItemHeight;
3468 int y = 0; // ### optimize (use spans ?)
3469 for (int i = 0; i < viewItems.count(); ++i) {
3470 y += itemHeight(i); // the height value is cached
3473 *offset = y - value - itemHeight(i);
3480 int QTreeViewPrivate::columnAt(int x) const
3482 return header->logicalIndexAt(x);
3485 void QTreeViewPrivate::updateScrollBars()
3488 QSize viewportSize = viewport->size();
3489 if (!viewportSize.isValid())
3490 viewportSize = QSize(0, 0);
3492 if (viewItems.isEmpty()) {
3496 int itemsInViewport = 0;
3497 if (uniformRowHeights) {
3498 if (defaultItemHeight <= 0)
3499 itemsInViewport = viewItems.count();
3501 itemsInViewport = viewportSize.height() / defaultItemHeight;
3503 const int itemsCount = viewItems.count();
3504 const int viewportHeight = viewportSize.height();
3505 for (int height = 0, item = itemsCount - 1; item >= 0; --item) {
3506 height += itemHeight(item);
3507 if (height > viewportHeight)
3512 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3513 if (!viewItems.isEmpty())
3514 itemsInViewport = qMax(1, itemsInViewport);
3515 vbar->setRange(0, viewItems.count() - itemsInViewport);
3516 vbar->setPageStep(itemsInViewport);
3517 vbar->setSingleStep(1);
3518 } else { // scroll per pixel
3519 int contentsHeight = 0;
3520 if (uniformRowHeights) {
3521 contentsHeight = defaultItemHeight * viewItems.count();
3522 } else { // ### optimize (spans or caching)
3523 for (int i = 0; i < viewItems.count(); ++i)
3524 contentsHeight += itemHeight(i);
3526 vbar->setRange(0, contentsHeight - viewportSize.height());
3527 vbar->setPageStep(viewportSize.height());
3528 vbar->setSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2));
3531 const int columnCount = header->count();
3532 const int viewportWidth = viewportSize.width();
3533 int columnsInViewport = 0;
3534 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
3535 int logical = header->logicalIndex(column);
3536 width += header->sectionSize(logical);
3537 if (width > viewportWidth)
3539 ++columnsInViewport;
3541 if (columnCount > 0)
3542 columnsInViewport = qMax(1, columnsInViewport);
3543 if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) {
3544 hbar->setRange(0, columnCount - columnsInViewport);
3545 hbar->setPageStep(columnsInViewport);
3546 hbar->setSingleStep(1);
3547 } else { // scroll per pixel
3548 const int horizontalLength = header->length();
3549 const QSize maxSize = q->maximumViewportSize();
3550 if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0)
3551 viewportSize = maxSize;
3552 hbar->setPageStep(viewportSize.width());
3553 hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0));
3554 hbar->setSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2));
3558 int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const
3560 executePostedLayout();
3562 int column = header->logicalIndexAt(x);
3564 return -1; // no logical index at x
3566 int viewItemIndex = itemAtCoordinate(pos.y());
3567 QRect returning = itemDecorationRect(modelIndex(viewItemIndex));
3568 if (!returning.contains(pos))
3571 return viewItemIndex;
3574 QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const
3576 Q_Q(const QTreeView);
3577 if (!rootDecoration && index.parent() == root)
3578 return QRect(); // no decoration at root
3580 int viewItemIndex = viewIndex(index);
3581 if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index))
3584 int itemIndentation = indentationForItem(viewItemIndex);
3585 int position = header->sectionViewportPosition(0);
3586 int size = header->sectionSize(0);
3589 if (q->isRightToLeft())
3590 rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex),
3591 indent, itemHeight(viewItemIndex));
3593 rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex),
3594 indent, itemHeight(viewItemIndex));
3598 return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q);
3601 QList<QPair<int, int> > QTreeViewPrivate::columnRanges(const QModelIndex &topIndex,
3602 const QModelIndex &bottomIndex) const
3604 const int topVisual = header->visualIndex(topIndex.column()),
3605 bottomVisual = header->visualIndex(bottomIndex.column());
3607 const int start = qMin(topVisual, bottomVisual);
3608 const int end = qMax(topVisual, bottomVisual);
3610 QList<int> logicalIndexes;
3612 //we iterate over the visual indexes to get the logical indexes
3613 for (int c = start; c <= end; c++) {
3614 const int logical = header->logicalIndex(c);
3615 if (!header->isSectionHidden(logical)) {
3616 logicalIndexes << logical;
3619 //let's sort the list
3620 qSort(logicalIndexes.begin(), logicalIndexes.end());
3622 QList<QPair<int, int> > ret;
3623 QPair<int, int> current;
3624 current.first = -2; // -1 is not enough because -1+1 = 0
3625 current.second = -2;
3626 for(int i = 0; i < logicalIndexes.count(); ++i) {
3627 const int logicalColumn = logicalIndexes.at(i);
3628 if (current.second + 1 != logicalColumn) {
3629 if (current.first != -2) {
3630 //let's save the current one
3633 //let's start a new one
3634 current.first = current.second = logicalColumn;
3640 //let's get the last range
3641 if (current.first != -2) {
3648 void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex,
3649 QItemSelectionModel::SelectionFlags command)
3652 QItemSelection selection;
3653 const int top = viewIndex(topIndex),
3654 bottom = viewIndex(bottomIndex);
3656 const QList< QPair<int, int> > colRanges = columnRanges(topIndex, bottomIndex);
3657 QList< QPair<int, int> >::const_iterator it;
3658 for (it = colRanges.begin(); it != colRanges.end(); ++it) {
3659 const int left = (*it).first,
3660 right = (*it).second;
3662 QModelIndex previous;
3663 QItemSelectionRange currentRange;
3664 QStack<QItemSelectionRange> rangeStack;
3665 for (int i = top; i <= bottom; ++i) {
3666 QModelIndex index = modelIndex(i);
3667 QModelIndex parent = index.parent();
3668 QModelIndex previousParent = previous.parent();
3669 if (previous.isValid() && parent == previousParent) {
3671 if (qAbs(previous.row() - index.row()) > 1) {
3672 //a hole (hidden index inside a range) has been detected
3673 if (currentRange.isValid()) {
3674 selection.append(currentRange);
3676 //let's start a new range
3677 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3679 QModelIndex tl = model->index(currentRange.top(), currentRange.left(),
3680 currentRange.parent());
3681 currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right));
3683 } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) {
3684 // item is child of previous
3685 rangeStack.push(currentRange);
3686 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3688 if (currentRange.isValid())
3689 selection.append(currentRange);
3690 if (rangeStack.isEmpty()) {
3691 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3693 currentRange = rangeStack.pop();
3694 index = currentRange.bottomRight(); //let's resume the range
3695 --i; //we process again the current item
3700 if (currentRange.isValid())
3701 selection.append(currentRange);
3702 for (int i = 0; i < rangeStack.count(); ++i)
3703 selection.append(rangeStack.at(i));
3705 q->selectionModel()->select(selection, command);
3708 QPair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const
3710 Q_Q(const QTreeView);
3711 int start = header->visualIndexAt(rect.left());
3712 int end = header->visualIndexAt(rect.right());
3713 if (q->isRightToLeft()) {
3714 start = (start == -1 ? header->count() - 1 : start);
3715 end = (end == -1 ? 0 : end);
3717 start = (start == -1 ? 0 : start);
3718 end = (end == -1 ? header->count() - 1 : end);
3720 return qMakePair<int,int>(qMin(start, end), qMax(start, end));
3723 bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const
3725 Q_Q(const QTreeView);
3726 if (model->hasChildren(parent)) {
3727 if (hiddenIndexes.isEmpty())
3729 if (q->isIndexHidden(parent))
3731 int rowCount = model->rowCount(parent);
3732 for (int i = 0; i < rowCount; ++i) {
3733 if (!q->isRowHidden(i, parent))
3742 void QTreeViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order)
3744 model->sort(column, order);
3750 void QTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
3752 QAbstractItemView::currentChanged(current, previous);
3754 if (allColumnsShowFocus()) {
3755 if (previous.isValid()) {
3756 QRect previousRect = visualRect(previous);
3757 previousRect.setX(0);
3758 previousRect.setWidth(viewport()->width());
3759 viewport()->update(previousRect);
3761 if (current.isValid()) {
3762 QRect currentRect = visualRect(current);
3763 currentRect.setX(0);
3764 currentRect.setWidth(viewport()->width());
3765 viewport()->update(currentRect);
3768 #ifndef QT_NO_ACCESSIBILITY
3769 if (QAccessible::isActive() && current.isValid()) {
3771 int entry = (visualIndex(current) + (header()?1:0))*current.model()->columnCount()+current.column() + 1;
3772 QAccessible::updateAccessibility(QAccessibleEvent(QAccessible::Focus, this, entry));
3774 int entry = visualIndex(current) + 1;
3777 QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus);
3786 void QTreeView::selectionChanged(const QItemSelection &selected,
3787 const QItemSelection &deselected)
3789 QAbstractItemView::selectionChanged(selected, deselected);
3790 #ifndef QT_NO_ACCESSIBILITY
3791 if (QAccessible::isActive()) {
3792 // ### does not work properly for selection ranges.
3793 QModelIndex sel = selected.indexes().value(0);
3794 if (sel.isValid()) {
3795 int entry = (visualIndex(sel) + (header()?1:0))*sel.model()->columnCount()+sel.column() + 1;
3796 Q_ASSERT(entry > 0);
3797 QAccessible::updateAccessibility(QAccessibleEvent(QAccessible::Selection, this, entry));
3799 QModelIndex desel = deselected.indexes().value(0);
3800 if (desel.isValid()) {
3801 int entry = (visualIndex(desel) + (header()?1:0))*desel.model()->columnCount()+desel.column() + 1;
3802 Q_ASSERT(entry > 0);
3803 QAccessible::updateAccessibility(QAccessibleEvent(QAccessible::SelectionRemove, this, entry));
3809 int QTreeView::visualIndex(const QModelIndex &index) const
3811 Q_D(const QTreeView);
3812 d->executePostedLayout();
3813 return d->viewIndex(index);
3818 #include "moc_qtreeview.cpp"
3820 #endif // QT_NO_TREEVIEW