source: trunk/src/gui/itemviews/qtreeview.cpp@ 842

Last change on this file since 842 was 769, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.3 sources from branches/vendor/nokia/qt.

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