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

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

trunk: Merged in qt 4.6.2 sources.

File size: 128.4 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 insertedItems[i].index = d->model->index(i + start, 0, parent);
2478 insertedItems[i].level = childLevel;
2479 insertedItems[i].hasChildren = d->hasVisibleChildren(insertedItems[i].index);
2480 insertedItems[i].hasMoreSiblings = !((i == delta - 1) && (parentRowCount == end +1));
2481 }
2482 if (d->viewItems.isEmpty())
2483 d->defaultItemHeight = indexRowSizeHint(insertedItems[0].index);
2484
2485 int insertPos;
2486 if (lastChildItem < firstChildItem) { // no children
2487 insertPos = firstChildItem;
2488 } else {
2489 // do a binary search to figure out where to insert
2490 QVector<QTreeViewItem>::iterator it;
2491 it = qLowerBound(d->viewItems.begin() + firstChildItem,
2492 d->viewItems.begin() + lastChildItem + 1,
2493 insertedItems.at(0), treeViewItemLessThan);
2494 insertPos = it - d->viewItems.begin();
2495
2496 // update stale model indexes of siblings
2497 for (int item = insertPos; item <= lastChildItem; ) {
2498 Q_ASSERT(d->viewItems.at(item).level == childLevel);
2499 const QModelIndex modelIndex = d->viewItems.at(item).index;
2500 //Q_ASSERT(modelIndex.parent() == parent);
2501 d->viewItems[item].index = d->model->index(
2502 modelIndex.row() + delta, modelIndex.column(), parent);
2503
2504 if (!d->viewItems[item].index.isValid()) {
2505 // Something really bad is happening, a bad model is
2506 // often the cause. We can't optimize in this case :(
2507 qWarning() << "QTreeView::rowsInserted internal representation of the model has been corrupted, resetting.";
2508 doItemsLayout();
2509 return;
2510 }
2511
2512 item += d->viewItems.at(item).total + 1;
2513 }
2514 }
2515
2516 d->viewItems.insert(insertPos, delta, insertedItems.at(0));
2517 if (delta > 1) {
2518 qCopy(insertedItems.begin() + 1, insertedItems.end(),
2519 d->viewItems.begin() + insertPos + 1);
2520 }
2521
2522 if (parentItem != -1)
2523 d->viewItems[parentItem].hasChildren = true;
2524 d->updateChildCount(parentItem, delta);
2525
2526 updateGeometries();
2527 viewport()->update();
2528 } else if ((parentItem != -1) && d->viewItems.at(parentItem).expanded) {
2529 d->doDelayedItemsLayout();
2530 } else if (parentItem != -1 && (d->model->rowCount(parent) == end - start + 1)) {
2531 // the parent just went from 0 children to more. update to re-paint the decoration
2532 d->viewItems[parentItem].hasChildren = true;
2533 viewport()->update();
2534 }
2535 QAbstractItemView::rowsInserted(parent, start, end);
2536}
2537
2538/*!
2539 Informs the view that the rows from the \a start row to the \a end row
2540 inclusive are about to removed from the given \a parent model item.
2541*/
2542void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
2543{
2544 Q_D(QTreeView);
2545 d->rowsRemoved(parent, start, end, false);
2546 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
2547}
2548
2549/*!
2550 \since 4.1
2551
2552 Informs the view that the rows from the \a start row to the \a end row
2553 inclusive have been removed from the given \a parent model item.
2554*/
2555void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end)
2556{
2557 Q_D(QTreeView);
2558 d->rowsRemoved(parent, start, end, true);
2559}
2560
2561/*!
2562 Informs the tree view that the number of columns in the tree view has
2563 changed from \a oldCount to \a newCount.
2564*/
2565void QTreeView::columnCountChanged(int oldCount, int newCount)
2566{
2567 Q_D(QTreeView);
2568 if (oldCount == 0 && newCount > 0) {
2569 //if the first column has just been added we need to relayout.
2570 d->doDelayedItemsLayout();
2571 }
2572
2573 if (isVisible())
2574 updateGeometries();
2575 viewport()->update();
2576}
2577
2578/*!
2579 Resizes the \a column given to the size of its contents.
2580
2581 \sa columnWidth(), setColumnWidth()
2582*/
2583void QTreeView::resizeColumnToContents(int column)
2584{
2585 Q_D(QTreeView);
2586 d->executePostedLayout();
2587 if (column < 0 || column >= d->header->count())
2588 return;
2589 int contents = sizeHintForColumn(column);
2590 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column);
2591 d->header->resizeSection(column, qMax(contents, header));
2592}
2593
2594/*!
2595 \obsolete
2596 \overload
2597
2598 Sorts the model by the values in the given \a column.
2599*/
2600void QTreeView::sortByColumn(int column)
2601{
2602 Q_D(QTreeView);
2603 sortByColumn(column, d->header->sortIndicatorOrder());
2604}
2605
2606/*!
2607 \since 4.2
2608
2609 Sets the model up for sorting by the values in the given \a column and \a order.
2610
2611 \a column may be -1, in which case no sort indicator will be shown
2612 and the model will return to its natural, unsorted order. Note that not
2613 all models support this and may even crash in this case.
2614
2615 \sa sortingEnabled
2616*/
2617void QTreeView::sortByColumn(int column, Qt::SortOrder order)
2618{
2619 Q_D(QTreeView);
2620
2621 //If sorting is enabled will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts
2622 d->header->setSortIndicator(column, order);
2623 //If sorting is not enabled, force to sort now.
2624 if (!d->sortingEnabled)
2625 d->model->sort(column, order);
2626}
2627
2628/*!
2629 \reimp
2630*/
2631void QTreeView::selectAll()
2632{
2633 Q_D(QTreeView);
2634 if (!selectionModel())
2635 return;
2636 SelectionMode mode = d->selectionMode;
2637 d->executePostedLayout(); //make sure we lay out the items
2638 if (mode != SingleSelection && !d->viewItems.isEmpty()) {
2639 const QModelIndex &idx = d->viewItems.last().index;
2640 QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1);
2641 d->select(d->viewItems.first().index, lastItemIndex,
2642 QItemSelectionModel::ClearAndSelect
2643 |QItemSelectionModel::Rows);
2644 }
2645}
2646
2647/*!
2648 \since 4.2
2649 Expands all expandable items.
2650
2651 Warning: if the model contains a large number of items,
2652 this function will take some time to execute.
2653
2654 \sa collapseAll() expand() collapse() setExpanded()
2655*/
2656void QTreeView::expandAll()
2657{
2658 Q_D(QTreeView);
2659 d->viewItems.clear();
2660 d->expandedIndexes.clear();
2661 d->interruptDelayedItemsLayout();
2662 d->layout(-1);
2663 for (int i = 0; i < d->viewItems.count(); ++i) {
2664 if (d->viewItems[i].expanded)
2665 continue;
2666 d->viewItems[i].expanded = true;
2667 d->layout(i);
2668 QModelIndex idx = d->viewItems.at(i).index;
2669 d->expandedIndexes.insert(idx);
2670 }
2671 updateGeometries();
2672 d->viewport->update();
2673}
2674
2675/*!
2676 \since 4.2
2677
2678 Collapses all expanded items.
2679
2680 \sa expandAll() expand() collapse() setExpanded()
2681*/
2682void QTreeView::collapseAll()
2683{
2684 Q_D(QTreeView);
2685 d->expandedIndexes.clear();
2686 doItemsLayout();
2687}
2688
2689/*!
2690 \since 4.3
2691 Expands all expandable items to the given \a depth.
2692
2693 \sa expandAll() collapseAll() expand() collapse() setExpanded()
2694*/
2695void QTreeView::expandToDepth(int depth)
2696{
2697 Q_D(QTreeView);
2698 d->viewItems.clear();
2699 d->expandedIndexes.clear();
2700 d->interruptDelayedItemsLayout();
2701 d->layout(-1);
2702 for (int i = 0; i < d->viewItems.count(); ++i) {
2703 if (d->viewItems.at(i).level <= (uint)depth) {
2704 d->viewItems[i].expanded = true;
2705 d->layout(i);
2706 d->storeExpanded(d->viewItems.at(i).index);
2707 }
2708 }
2709 updateGeometries();
2710 d->viewport->update();
2711}
2712
2713/*!
2714 This function is called whenever \a{column}'s size is changed in
2715 the header. \a oldSize and \a newSize give the previous size and
2716 the new size in pixels.
2717
2718 \sa setColumnWidth()
2719*/
2720void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */)
2721{
2722 Q_D(QTreeView);
2723 d->columnsToUpdate.append(column);
2724 if (d->columnResizeTimerID == 0)
2725 d->columnResizeTimerID = startTimer(0);
2726}
2727
2728/*!
2729 \reimp
2730*/
2731void QTreeView::updateGeometries()
2732{
2733 Q_D(QTreeView);
2734 if (d->header) {
2735 if (d->geometryRecursionBlock)
2736 return;
2737 d->geometryRecursionBlock = true;
2738 QSize hint = d->header->isHidden() ? QSize(0, 0) : d->header->sizeHint();
2739 setViewportMargins(0, hint.height(), 0, 0);
2740 QRect vg = d->viewport->geometry();
2741 QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height());
2742 d->header->setGeometry(geometryRect);
2743 //d->header->setOffset(horizontalScrollBar()->value()); // ### bug ???
2744 QMetaObject::invokeMethod(d->header, "updateGeometries");
2745 d->updateScrollBars();
2746 d->geometryRecursionBlock = false;
2747 }
2748 QAbstractItemView::updateGeometries();
2749}
2750
2751/*!
2752 Returns the size hint for the \a column's width or -1 if there is no
2753 model.
2754
2755 If you need to set the width of a given column to a fixed value, call
2756 QHeaderView::resizeSection() on the view's header.
2757
2758 If you reimplement this function in a subclass, note that the value you
2759 return is only used when resizeColumnToContents() is called. In that case,
2760 if a larger column width is required by either the view's header or
2761 the item delegate, that width will be used instead.
2762
2763 \sa QWidget::sizeHint, header()
2764*/
2765int QTreeView::sizeHintForColumn(int column) const
2766{
2767 Q_D(const QTreeView);
2768 d->executePostedLayout();
2769 if (d->viewItems.isEmpty())
2770 return -1;
2771 ensurePolished();
2772 int w = 0;
2773 QStyleOptionViewItemV4 option = d->viewOptionsV4();
2774 const QVector<QTreeViewItem> viewItems = d->viewItems;
2775
2776 int start = 0;
2777 int end = viewItems.count();
2778 if(end > 1000) { //if we have too many item this function would be too slow.
2779 //we get a good approximation by only iterate over 1000 items.
2780 start = qMax(0, d->firstVisibleItem() - 100);
2781 end = qMin(end, start + 900);
2782 }
2783
2784 for (int i = start; i < end; ++i) {
2785 if (viewItems.at(i).spanning)
2786 continue; // we have no good size hint
2787 QModelIndex index = viewItems.at(i).index;
2788 index = index.sibling(index.row(), column);
2789 QWidget *editor = d->editorForIndex(index).editor;
2790 if (editor && d->persistent.contains(editor)) {
2791 w = qMax(w, editor->sizeHint().width());
2792 int min = editor->minimumSize().width();
2793 int max = editor->maximumSize().width();
2794 w = qBound(min, w, max);
2795 }
2796 int hint = d->delegateForIndex(index)->sizeHint(option, index).width();
2797 w = qMax(w, hint + (column == 0 ? d->indentationForItem(i) : 0));
2798 }
2799 return w;
2800}
2801
2802/*!
2803 Returns the size hint for the row indicated by \a index.
2804
2805 \sa sizeHintForColumn(), uniformRowHeights()
2806*/
2807int QTreeView::indexRowSizeHint(const QModelIndex &index) const
2808{
2809 Q_D(const QTreeView);
2810 if (!d->isIndexValid(index) || !d->itemDelegate)
2811 return 0;
2812
2813 int start = -1;
2814 int end = -1;
2815 int count = d->header->count();
2816 bool emptyHeader = (count == 0);
2817 QModelIndex parent = index.parent();
2818
2819 if (count && isVisible()) {
2820 // If the sections have moved, we end up checking too many or too few
2821 start = d->header->visualIndexAt(0);
2822 } else {
2823 // If the header has not been laid out yet, we use the model directly
2824 count = d->model->columnCount(parent);
2825 }
2826
2827 if (isRightToLeft()) {
2828 start = (start == -1 ? count - 1 : start);
2829 end = 0;
2830 } else {
2831 start = (start == -1 ? 0 : start);
2832 end = count - 1;
2833 }
2834
2835 if (end < start)
2836 qSwap(end, start);
2837
2838 int height = -1;
2839 QStyleOptionViewItemV4 option = d->viewOptionsV4();
2840 // ### If we want word wrapping in the items,
2841 // ### we need to go through all the columns
2842 // ### and set the width of the column
2843
2844 // Hack to speed up the function
2845 option.rect.setWidth(-1);
2846
2847 for (int column = start; column <= end; ++column) {
2848 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column);
2849 if (d->header->isSectionHidden(logicalColumn))
2850 continue;
2851 QModelIndex idx = d->model->index(index.row(), logicalColumn, parent);
2852 if (idx.isValid()) {
2853 QWidget *editor = d->editorForIndex(idx).editor;
2854 if (editor && d->persistent.contains(editor)) {
2855 height = qMax(height, editor->sizeHint().height());
2856 int min = editor->minimumSize().height();
2857 int max = editor->maximumSize().height();
2858 height = qBound(min, height, max);
2859 }
2860 int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height();
2861 height = qMax(height, hint);
2862 }
2863 }
2864
2865 return height;
2866}
2867
2868/*!
2869 \since 4.3
2870 Returns the height of the row indicated by the given \a index.
2871 \sa indexRowSizeHint()
2872*/
2873int QTreeView::rowHeight(const QModelIndex &index) const
2874{
2875 Q_D(const QTreeView);
2876 d->executePostedLayout();
2877 int i = d->viewIndex(index);
2878 if (i == -1)
2879 return 0;
2880 return d->itemHeight(i);
2881}
2882
2883/*!
2884 \internal
2885*/
2886void QTreeView::horizontalScrollbarAction(int action)
2887{
2888 QAbstractItemView::horizontalScrollbarAction(action);
2889}
2890
2891/*!
2892 \reimp
2893*/
2894bool QTreeView::isIndexHidden(const QModelIndex &index) const
2895{
2896 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent()));
2897}
2898
2899/*
2900 private implementation
2901*/
2902void QTreeViewPrivate::initialize()
2903{
2904 Q_Q(QTreeView);
2905 updateStyledFrameWidths();
2906 q->setSelectionBehavior(QAbstractItemView::SelectRows);
2907 q->setSelectionMode(QAbstractItemView::SingleSelection);
2908 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
2909 q->setAttribute(Qt::WA_MacShowFocusRect);
2910
2911 QHeaderView *header = new QHeaderView(Qt::Horizontal, q);
2912 header->setMovable(true);
2913 header->setStretchLastSection(true);
2914 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
2915 q->setHeader(header);
2916#ifndef QT_NO_ANIMATION
2917 QObject::connect(&animatedOperation, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation()));
2918#endif //QT_NO_ANIMATION
2919}
2920
2921void QTreeViewPrivate::expand(int item, bool emitSignal)
2922{
2923 Q_Q(QTreeView);
2924
2925 if (item == -1 || viewItems.at(item).expanded)
2926 return;
2927
2928#ifndef QT_NO_ANIMATION
2929 if (emitSignal && animationsEnabled)
2930 prepareAnimatedOperation(item, QVariantAnimation::Forward);
2931#endif //QT_NO_ANIMATION
2932 QAbstractItemView::State oldState = state;
2933 q->setState(QAbstractItemView::ExpandingState);
2934 const QModelIndex index = viewItems.at(item).index;
2935 storeExpanded(index);
2936 viewItems[item].expanded = true;
2937 layout(item);
2938 q->setState(oldState);
2939
2940 if (model->canFetchMore(index))
2941 model->fetchMore(index);
2942 if (emitSignal) {
2943 emit q->expanded(index);
2944#ifndef QT_NO_ANIMATION
2945 if (animationsEnabled)
2946 beginAnimatedOperation();
2947#endif //QT_NO_ANIMATION
2948 }
2949}
2950
2951void QTreeViewPrivate::collapse(int item, bool emitSignal)
2952{
2953 Q_Q(QTreeView);
2954
2955 if (item == -1 || expandedIndexes.isEmpty())
2956 return;
2957
2958 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
2959 delayedAutoScroll.stop();
2960
2961 int total = viewItems.at(item).total;
2962 const QModelIndex &modelIndex = viewItems.at(item).index;
2963 if (!isPersistent(modelIndex))
2964 return; // if the index is not persistent, no chances it is expanded
2965 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex);
2966 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false)
2967 return; // nothing to do
2968
2969#ifndef QT_NO_ANIMATION
2970 if (emitSignal && animationsEnabled)
2971 prepareAnimatedOperation(item, QVariantAnimation::Backward);
2972#endif //QT_NO_ANIMATION
2973
2974 QAbstractItemView::State oldState = state;
2975 q->setState(QAbstractItemView::CollapsingState);
2976 expandedIndexes.erase(it);
2977 viewItems[item].expanded = false;
2978 int index = item;
2979 QModelIndex parent = modelIndex;
2980 while (parent.isValid() && parent != root) {
2981 Q_ASSERT(index > -1);
2982 viewItems[index].total -= total;
2983 parent = parent.parent();
2984 index = viewIndex(parent);
2985 }
2986 viewItems.remove(item + 1, total); // collapse
2987 q->setState(oldState);
2988
2989 if (emitSignal) {
2990 emit q->collapsed(modelIndex);
2991#ifndef QT_NO_ANIMATION
2992 if (animationsEnabled)
2993 beginAnimatedOperation();
2994#endif //QT_NO_ANIMATION
2995 }
2996}
2997
2998#ifndef QT_NO_ANIMATION
2999void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction)
3000{
3001 animatedOperation.item = item;
3002 animatedOperation.viewport = viewport;
3003 animatedOperation.setDirection(direction);
3004
3005 int top = coordinateForItem(item) + itemHeight(item);
3006 QRect rect = viewport->rect();
3007 rect.setTop(top);
3008 if (direction == QVariantAnimation::Backward) {
3009 const int limit = rect.height() * 2;
3010 int h = 0;
3011 int c = item + viewItems.at(item).total + 1;
3012 for (int i = item + 1; i < c && h < limit; ++i)
3013 h += itemHeight(i);
3014 rect.setHeight(h);
3015 animatedOperation.setEndValue(top + h);
3016 }
3017 animatedOperation.setStartValue(top);
3018 animatedOperation.before = renderTreeToPixmapForAnimation(rect);
3019}
3020
3021void QTreeViewPrivate::beginAnimatedOperation()
3022{
3023 Q_Q(QTreeView);
3024
3025 QRect rect = viewport->rect();
3026 rect.setTop(animatedOperation.top());
3027 if (animatedOperation.direction() == QVariantAnimation::Forward) {
3028 const int limit = rect.height() * 2;
3029 int h = 0;
3030 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1;
3031 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i)
3032 h += itemHeight(i);
3033 rect.setHeight(h);
3034 animatedOperation.setEndValue(animatedOperation.top() + h);
3035 }
3036
3037 if (!rect.isEmpty()) {
3038 animatedOperation.after = renderTreeToPixmapForAnimation(rect);
3039
3040 q->setState(QAbstractItemView::AnimatingState);
3041 animatedOperation.start(); //let's start the animation
3042 }
3043}
3044
3045void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const
3046{
3047 const int start = animatedOperation.startValue().toInt(),
3048 end = animatedOperation.endValue().toInt(),
3049 current = animatedOperation.currentValue().toInt();
3050 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward;
3051 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after;
3052 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height());
3053 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before;
3054 painter->drawPixmap(0, current, bottom);
3055}
3056
3057QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const
3058{
3059 Q_Q(const QTreeView);
3060 QPixmap pixmap(rect.size());
3061 if (rect.size().isEmpty())
3062 return pixmap;
3063 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels.
3064 QPainter painter(&pixmap);
3065 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base());
3066 painter.translate(0, -rect.top());
3067 q->drawTree(&painter, QRegion(rect));
3068 painter.end();
3069
3070 //and now let's render the editors the editors
3071 QStyleOptionViewItemV4 option = viewOptionsV4();
3072 for (QList<QEditorInfo>::const_iterator it = editors.constBegin(); it != editors.constEnd(); ++it) {
3073 QWidget *editor = it->editor;
3074 QModelIndex index = it->index;
3075 option.rect = q->visualRect(index);
3076 if (option.rect.isValid()) {
3077
3078 if (QAbstractItemDelegate *delegate = delegateForIndex(index))
3079 delegate->updateEditorGeometry(editor, option, index);
3080
3081 const QPoint pos = editor->pos();
3082 if (rect.contains(pos)) {
3083 editor->render(&pixmap, pos - rect.topLeft());
3084 //the animation uses pixmap to display the treeview's content
3085 //the editor is rendered on this pixmap and thus can (should) be hidden
3086 editor->hide();
3087 }
3088 }
3089 }
3090
3091
3092 return pixmap;
3093}
3094
3095void QTreeViewPrivate::_q_endAnimatedOperation()
3096{
3097 Q_Q(QTreeView);
3098 q->setState(QAbstractItemView::NoState);
3099 q->updateGeometries();
3100 viewport->update();
3101}
3102#endif //QT_NO_ANIMATION
3103
3104void QTreeViewPrivate::_q_modelAboutToBeReset()
3105{
3106 viewItems.clear();
3107}
3108
3109void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
3110{
3111 if (start <= 0 && 0 <= end)
3112 viewItems.clear();
3113 QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(parent, start, end);
3114}
3115
3116void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end)
3117{
3118 if (start <= 0 && 0 <= end)
3119 doDelayedItemsLayout();
3120 QAbstractItemViewPrivate::_q_columnsRemoved(parent, start, end);
3121}
3122
3123void QTreeViewPrivate::layout(int i)
3124{
3125 Q_Q(QTreeView);
3126 QModelIndex current;
3127 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i);
3128
3129 if (i>=0 && !parent.isValid()) {
3130 //modelIndex() should never return something invalid for the real items.
3131 //This can happen if columncount has been set to 0.
3132 //To avoid infinite loop we stop here.
3133 return;
3134 }
3135
3136 int count = 0;
3137 if (model->hasChildren(parent)) {
3138 if (model->canFetchMore(parent))
3139 model->fetchMore(parent);
3140 count = model->rowCount(parent);
3141 }
3142
3143 bool expanding = true;
3144 if (i == -1) {
3145 if (uniformRowHeights) {
3146 QModelIndex index = model->index(0, 0, parent);
3147 defaultItemHeight = q->indexRowSizeHint(index);
3148 }
3149 viewItems.resize(count);
3150 } else if (viewItems[i].total != (uint)count) {
3151 viewItems.insert(i + 1, count, QTreeViewItem()); // expand
3152 } else {
3153 expanding = false;
3154 }
3155
3156 int first = i + 1;
3157 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0);
3158 int hidden = 0;
3159 int last = 0;
3160 int children = 0;
3161 QTreeViewItem *item = 0;
3162 for (int j = first; j < first + count; ++j) {
3163 current = model->index(j - first, 0, parent);
3164 if (isRowHidden(current)) {
3165 ++hidden;
3166 last = j - hidden + children;
3167 } else {
3168 last = j - hidden + children;
3169 if (item)
3170 item->hasMoreSiblings = true;
3171 item = &viewItems[last];
3172 item->index = current;
3173 item->level = level;
3174 item->height = 0;
3175 item->spanning = q->isFirstColumnSpanned(current.row(), parent);
3176 item->expanded = false;
3177 item->total = 0;
3178 item->hasMoreSiblings = false;
3179 if (isIndexExpanded(current)) {
3180 item->expanded = true;
3181 layout(last);
3182 item = &viewItems[last];
3183 children += item->total;
3184 item->hasChildren = item->total > 0;
3185 last = j - hidden + children;
3186 } else {
3187 item->hasChildren = hasVisibleChildren(current);
3188 }
3189 }
3190 }
3191
3192 // remove hidden items
3193 if (hidden > 0)
3194 viewItems.remove(last + 1, hidden); // collapse
3195
3196 if (!expanding)
3197 return; // nothing changed
3198
3199 while (parent != root) {
3200 Q_ASSERT(i > -1);
3201 viewItems[i].total += count - hidden;
3202 parent = parent.parent();
3203 i = viewIndex(parent);
3204 }
3205}
3206
3207int QTreeViewPrivate::pageUp(int i) const
3208{
3209 int index = itemAtCoordinate(coordinateForItem(i) - viewport->height());
3210 return index == -1 ? 0 : index;
3211}
3212
3213int QTreeViewPrivate::pageDown(int i) const
3214{
3215 int index = itemAtCoordinate(coordinateForItem(i) + viewport->height());
3216 return index == -1 ? viewItems.count() - 1 : index;
3217}
3218
3219int QTreeViewPrivate::indentationForItem(int item) const
3220{
3221 if (item < 0 || item >= viewItems.count())
3222 return 0;
3223 int level = viewItems.at(item).level;
3224 if (rootDecoration)
3225 ++level;
3226 return level * indent;
3227}
3228
3229int QTreeViewPrivate::itemHeight(int item) const
3230{
3231 if (uniformRowHeights)
3232 return defaultItemHeight;
3233 if (viewItems.isEmpty())
3234 return 0;
3235 const QModelIndex &index = viewItems.at(item).index;
3236 int height = viewItems.at(item).height;
3237 if (height <= 0 && index.isValid()) {
3238 height = q_func()->indexRowSizeHint(index);
3239 viewItems[item].height = height;
3240 }
3241 if (!index.isValid() || height < 0)
3242 return 0;
3243 return height;
3244}
3245
3246
3247/*!
3248 \internal
3249 Returns the viewport y coordinate for \a item.
3250*/
3251int QTreeViewPrivate::coordinateForItem(int item) const
3252{
3253 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3254 if (uniformRowHeights)
3255 return (item * defaultItemHeight) - vbar->value();
3256 // ### optimize (spans or caching)
3257 int y = 0;
3258 for (int i = 0; i < viewItems.count(); ++i) {
3259 if (i == item)
3260 return y - vbar->value();
3261 y += itemHeight(i);
3262 }
3263 } else { // ScrollPerItem
3264 int topViewItemIndex = vbar->value();
3265 if (uniformRowHeights)
3266 return defaultItemHeight * (item - topViewItemIndex);
3267 if (item >= topViewItemIndex) {
3268 // search in the visible area first and continue down
3269 // ### slow if the item is not visible
3270 int viewItemCoordinate = 0;
3271 int viewItemIndex = topViewItemIndex;
3272 while (viewItemIndex < viewItems.count()) {
3273 if (viewItemIndex == item)
3274 return viewItemCoordinate;
3275 viewItemCoordinate += itemHeight(viewItemIndex);
3276 ++viewItemIndex;
3277 }
3278 // below the last item in the view
3279 Q_ASSERT(false);
3280 return viewItemCoordinate;
3281 } else {
3282 // search the area above the viewport (used for editor widgets)
3283 int viewItemCoordinate = 0;
3284 for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) {
3285 if (viewItemIndex == item)
3286 return viewItemCoordinate;
3287 viewItemCoordinate -= itemHeight(viewItemIndex - 1);
3288 }
3289 return viewItemCoordinate;
3290 }
3291 }
3292 return 0;
3293}
3294
3295/*!
3296 \internal
3297 Returns the index of the view item at the
3298 given viewport \a coordinate.
3299
3300 \sa modelIndex()
3301*/
3302int QTreeViewPrivate::itemAtCoordinate(int coordinate) const
3303{
3304 const int itemCount = viewItems.count();
3305 if (itemCount == 0)
3306 return -1;
3307 if (uniformRowHeights && defaultItemHeight <= 0)
3308 return -1;
3309 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3310 if (uniformRowHeights) {
3311 const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight;
3312 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3313 }
3314 // ### optimize
3315 int viewItemCoordinate = 0;
3316 const int contentsCoordinate = coordinate + vbar->value();
3317 for (int viewItemIndex = 0; viewItemIndex < viewItems.count(); ++viewItemIndex) {
3318 viewItemCoordinate += itemHeight(viewItemIndex);
3319 if (viewItemCoordinate >= contentsCoordinate)
3320 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3321 }
3322 } else { // ScrollPerItem
3323 int topViewItemIndex = vbar->value();
3324 if (uniformRowHeights) {
3325 if (coordinate < 0)
3326 coordinate -= defaultItemHeight - 1;
3327 const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight);
3328 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3329 }
3330 if (coordinate >= 0) {
3331 // the coordinate is in or below the viewport
3332 int viewItemCoordinate = 0;
3333 for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.count(); ++viewItemIndex) {
3334 viewItemCoordinate += itemHeight(viewItemIndex);
3335 if (viewItemCoordinate > coordinate)
3336 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3337 }
3338 } else {
3339 // the coordinate is above the viewport
3340 int viewItemCoordinate = 0;
3341 for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) {
3342 if (viewItemCoordinate <= coordinate)
3343 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3344 viewItemCoordinate -= itemHeight(viewItemIndex);
3345 }
3346 }
3347 }
3348 return -1;
3349}
3350
3351int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const
3352{
3353 if (!_index.isValid() || viewItems.isEmpty())
3354 return -1;
3355
3356 const int totalCount = viewItems.count();
3357 const QModelIndex index = _index.sibling(_index.row(), 0);
3358
3359
3360 // A quick check near the last item to see if we are just incrementing
3361 const int start = lastViewedItem > 2 ? lastViewedItem - 2 : 0;
3362 const int end = lastViewedItem < totalCount - 2 ? lastViewedItem + 2 : totalCount;
3363 int row = index.row();
3364 for (int i = start; i < end; ++i) {
3365 const QModelIndex &idx = viewItems.at(i).index;
3366 if (idx.row() == row) {
3367 if (idx.internalId() == index.internalId()) {
3368 lastViewedItem = i;
3369 return i;
3370 }
3371 }
3372 }
3373
3374 // NOTE: this function is slow if the item is outside the visible area
3375 // search in visible items first and below
3376 int t = firstVisibleItem();
3377 t = t > 100 ? t - 100 : 0; // start 100 items above the visible area
3378
3379 for (int i = t; i < totalCount; ++i) {
3380 const QModelIndex &idx = viewItems.at(i).index;
3381 if (idx.row() == row) {
3382 if (idx.internalId() == index.internalId()) {
3383 lastViewedItem = i;
3384 return i;
3385 }
3386 }
3387 }
3388 // search from top to first visible
3389 for (int j = 0; j < t; ++j) {
3390 const QModelIndex &idx = viewItems.at(j).index;
3391 if (idx.row() == row) {
3392 if (idx.internalId() == index.internalId()) {
3393 lastViewedItem = j;
3394 return j;
3395 }
3396 }
3397 }
3398 // nothing found
3399 return -1;
3400}
3401
3402QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const
3403{
3404 if (i < 0 || i >= viewItems.count())
3405 return QModelIndex();
3406
3407 QModelIndex ret = viewItems.at(i).index;
3408 if (column)
3409 ret = ret.sibling(ret.row(), column);
3410 return ret;
3411}
3412
3413int QTreeViewPrivate::firstVisibleItem(int *offset) const
3414{
3415 const int value = vbar->value();
3416 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3417 if (offset)
3418 *offset = 0;
3419 return (value < 0 || value >= viewItems.count()) ? -1 : value;
3420 }
3421 // ScrollMode == ScrollPerPixel
3422 if (uniformRowHeights) {
3423 if (!defaultItemHeight)
3424 return -1;
3425
3426 if (offset)
3427 *offset = -(value % defaultItemHeight);
3428 return value / defaultItemHeight;
3429 }
3430 int y = 0; // ### optimize (use spans ?)
3431 for (int i = 0; i < viewItems.count(); ++i) {
3432 y += itemHeight(i); // the height value is cached
3433 if (y > value) {
3434 if (offset)
3435 *offset = y - value - itemHeight(i);
3436 return i;
3437 }
3438 }
3439 return -1;
3440}
3441
3442int QTreeViewPrivate::columnAt(int x) const
3443{
3444 return header->logicalIndexAt(x);
3445}
3446
3447void QTreeViewPrivate::relayout(const QModelIndex &parent)
3448{
3449 Q_Q(QTreeView);
3450 // do a local relayout of the items
3451 if (parent.isValid()) {
3452 int parentViewIndex = viewIndex(parent);
3453 if (parentViewIndex > -1 && viewItems.at(parentViewIndex).expanded) {
3454 collapse(parentViewIndex, false); // remove the current layout
3455 expand(parentViewIndex, false); // do the relayout
3456 q->updateGeometries();
3457 viewport->update();
3458 }
3459 } else {
3460 viewItems.clear();
3461 q->doItemsLayout();
3462 }
3463}
3464
3465
3466void QTreeViewPrivate::updateScrollBars()
3467{
3468 Q_Q(QTreeView);
3469 QSize viewportSize = viewport->size();
3470 if (!viewportSize.isValid())
3471 viewportSize = QSize(0, 0);
3472
3473 int itemsInViewport = 0;
3474 if (uniformRowHeights) {
3475 if (defaultItemHeight <= 0)
3476 itemsInViewport = viewItems.count();
3477 else
3478 itemsInViewport = viewportSize.height() / defaultItemHeight;
3479 } else {
3480 const int itemsCount = viewItems.count();
3481 const int viewportHeight = viewportSize.height();
3482 for (int height = 0, item = itemsCount - 1; item >= 0; --item) {
3483 height += itemHeight(item);
3484 if (height > viewportHeight)
3485 break;
3486 ++itemsInViewport;
3487 }
3488 }
3489 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3490 if (!viewItems.isEmpty())
3491 itemsInViewport = qMax(1, itemsInViewport);
3492 vbar->setRange(0, viewItems.count() - itemsInViewport);
3493 vbar->setPageStep(itemsInViewport);
3494 vbar->setSingleStep(1);
3495 } else { // scroll per pixel
3496 int contentsHeight = 0;
3497 if (uniformRowHeights) {
3498 contentsHeight = defaultItemHeight * viewItems.count();
3499 } else { // ### optimize (spans or caching)
3500 for (int i = 0; i < viewItems.count(); ++i)
3501 contentsHeight += itemHeight(i);
3502 }
3503 vbar->setRange(0, contentsHeight - viewportSize.height());
3504 vbar->setPageStep(viewportSize.height());
3505 vbar->setSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2));
3506 }
3507
3508 const int columnCount = header->count();
3509 const int viewportWidth = viewportSize.width();
3510 int columnsInViewport = 0;
3511 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
3512 int logical = header->logicalIndex(column);
3513 width += header->sectionSize(logical);
3514 if (width > viewportWidth)
3515 break;
3516 ++columnsInViewport;
3517 }
3518 if (columnCount > 0)
3519 columnsInViewport = qMax(1, columnsInViewport);
3520 if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) {
3521 hbar->setRange(0, columnCount - columnsInViewport);
3522 hbar->setPageStep(columnsInViewport);
3523 hbar->setSingleStep(1);
3524 } else { // scroll per pixel
3525 const int horizontalLength = header->length();
3526 const QSize maxSize = q->maximumViewportSize();
3527 if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0)
3528 viewportSize = maxSize;
3529 hbar->setPageStep(viewportSize.width());
3530 hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0));
3531 hbar->setSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2));
3532 }
3533}
3534
3535int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const
3536{
3537 executePostedLayout();
3538 int x = pos.x();
3539 int column = header->logicalIndexAt(x);
3540 if (column != 0)
3541 return -1; // no logical index at x
3542
3543 int viewItemIndex = itemAtCoordinate(pos.y());
3544 QRect returning = itemDecorationRect(modelIndex(viewItemIndex));
3545 if (!returning.contains(pos))
3546 return -1;
3547
3548 return viewItemIndex;
3549}
3550
3551QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const
3552{
3553 Q_Q(const QTreeView);
3554 if (!rootDecoration && index.parent() == root)
3555 return QRect(); // no decoration at root
3556
3557 int viewItemIndex = viewIndex(index);
3558 if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index))
3559 return QRect();
3560
3561 int itemIndentation = indentationForItem(viewItemIndex);
3562 int position = header->sectionViewportPosition(0);
3563 int size = header->sectionSize(0);
3564
3565 QRect rect;
3566 if (q->isRightToLeft())
3567 rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex),
3568 indent, itemHeight(viewItemIndex));
3569 else
3570 rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex),
3571 indent, itemHeight(viewItemIndex));
3572 QStyleOption opt;
3573 opt.initFrom(q);
3574 opt.rect = rect;
3575 return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q);
3576}
3577
3578QList<QPair<int, int> > QTreeViewPrivate::columnRanges(const QModelIndex &topIndex,
3579 const QModelIndex &bottomIndex) const
3580{
3581 const int topVisual = header->visualIndex(topIndex.column()),
3582 bottomVisual = header->visualIndex(bottomIndex.column());
3583
3584 const int start = qMin(topVisual, bottomVisual);
3585 const int end = qMax(topVisual, bottomVisual);
3586
3587 QList<int> logicalIndexes;
3588
3589 //we iterate over the visual indexes to get the logical indexes
3590 for (int c = start; c <= end; c++) {
3591 const int logical = header->logicalIndex(c);
3592 if (!header->isSectionHidden(logical)) {
3593 logicalIndexes << logical;
3594 }
3595 }
3596 //let's sort the list
3597 qSort(logicalIndexes.begin(), logicalIndexes.end());
3598
3599 QList<QPair<int, int> > ret;
3600 QPair<int, int> current;
3601 current.first = -2; // -1 is not enough because -1+1 = 0
3602 current.second = -2;
3603 for(int i = 0; i < logicalIndexes.count(); ++i) {
3604 const int logicalColumn = logicalIndexes.at(i);
3605 if (current.second + 1 != logicalColumn) {
3606 if (current.first != -2) {
3607 //let's save the current one
3608 ret += current;
3609 }
3610 //let's start a new one
3611 current.first = current.second = logicalColumn;
3612 } else {
3613 current.second++;
3614 }
3615 }
3616
3617 //let's get the last range
3618 if (current.first != -2) {
3619 ret += current;
3620 }
3621
3622 return ret;
3623}
3624
3625void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex,
3626 QItemSelectionModel::SelectionFlags command)
3627{
3628 Q_Q(QTreeView);
3629 QItemSelection selection;
3630 const int top = viewIndex(topIndex),
3631 bottom = viewIndex(bottomIndex);
3632
3633 const QList< QPair<int, int> > colRanges = columnRanges(topIndex, bottomIndex);
3634 QList< QPair<int, int> >::const_iterator it;
3635 for (it = colRanges.begin(); it != colRanges.end(); ++it) {
3636 const int left = (*it).first,
3637 right = (*it).second;
3638
3639 QModelIndex previous;
3640 QItemSelectionRange currentRange;
3641 QStack<QItemSelectionRange> rangeStack;
3642 for (int i = top; i <= bottom; ++i) {
3643 QModelIndex index = modelIndex(i);
3644 QModelIndex parent = index.parent();
3645 QModelIndex previousParent = previous.parent();
3646 if (previous.isValid() && parent == previousParent) {
3647 // same parent
3648 if (qAbs(previous.row() - index.row()) > 1) {
3649 //a hole (hidden index inside a range) has been detected
3650 if (currentRange.isValid()) {
3651 selection.append(currentRange);
3652 }
3653 //let's start a new range
3654 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3655 } else {
3656 QModelIndex tl = model->index(currentRange.top(), currentRange.left(),
3657 currentRange.parent());
3658 currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right));
3659 }
3660 } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) {
3661 // item is child of previous
3662 rangeStack.push(currentRange);
3663 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3664 } else {
3665 if (currentRange.isValid())
3666 selection.append(currentRange);
3667 if (rangeStack.isEmpty()) {
3668 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3669 } else {
3670 currentRange = rangeStack.pop();
3671 index = currentRange.bottomRight(); //let's resume the range
3672 --i; //we process again the current item
3673 }
3674 }
3675 previous = index;
3676 }
3677 if (currentRange.isValid())
3678 selection.append(currentRange);
3679 for (int i = 0; i < rangeStack.count(); ++i)
3680 selection.append(rangeStack.at(i));
3681 }
3682 q->selectionModel()->select(selection, command);
3683}
3684
3685QPair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const
3686{
3687 Q_Q(const QTreeView);
3688 int start = header->visualIndexAt(rect.left());
3689 int end = header->visualIndexAt(rect.right());
3690 if (q->isRightToLeft()) {
3691 start = (start == -1 ? header->count() - 1 : start);
3692 end = (end == -1 ? 0 : end);
3693 } else {
3694 start = (start == -1 ? 0 : start);
3695 end = (end == -1 ? header->count() - 1 : end);
3696 }
3697 return qMakePair<int,int>(qMin(start, end), qMax(start, end));
3698}
3699
3700bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const
3701{
3702 Q_Q(const QTreeView);
3703 if (model->hasChildren(parent)) {
3704 if (hiddenIndexes.isEmpty())
3705 return true;
3706 if (q->isIndexHidden(parent))
3707 return false;
3708 int rowCount = model->rowCount(parent);
3709 for (int i = 0; i < rowCount; ++i) {
3710 if (!q->isRowHidden(i, parent))
3711 return true;
3712 }
3713 if (rowCount == 0)
3714 return true;
3715 }
3716 return false;
3717}
3718
3719void QTreeViewPrivate::rowsRemoved(const QModelIndex &parent,
3720 int start, int end, bool after)
3721{
3722 Q_Q(QTreeView);
3723 // if we are going to do a complete relayout anyway, there is no need to update
3724 if (delayedPendingLayout) {
3725 _q_rowsRemoved(parent, start, end);
3726 return;
3727 }
3728
3729 const int parentItem = viewIndex(parent);
3730 if ((parentItem != -1) || (parent == root)) {
3731
3732 const uint childLevel = (parentItem == -1)
3733 ? uint(0) : viewItems.at(parentItem).level + 1;
3734 Q_UNUSED(childLevel); // unused in release mode, used in assert below
3735
3736 const int firstChildItem = parentItem + 1;
3737 int lastChildItem = firstChildItem + ((parentItem == -1)
3738 ? viewItems.count()
3739 : viewItems.at(parentItem).total) - 1;
3740
3741 const int delta = end - start + 1;
3742
3743 int previousSibiling = -1;
3744 int removedCount = 0;
3745 for (int item = firstChildItem; item <= lastChildItem; ) {
3746 Q_ASSERT(viewItems.at(item).level == childLevel);
3747 const QModelIndex modelIndex = viewItems.at(item).index;
3748 //Q_ASSERT(modelIndex.parent() == parent);
3749 const int count = viewItems.at(item).total + 1;
3750 if (modelIndex.row() < start) {
3751 previousSibiling = item;
3752 // not affected by the removal
3753 item += count;
3754 } else if (modelIndex.row() <= end) {
3755 // removed
3756 viewItems.remove(item, count);
3757 removedCount += count;
3758 lastChildItem -= count;
3759 } else {
3760 if (after) {
3761 // moved; update the model index
3762 viewItems[item].index = model->index(
3763 modelIndex.row() - delta, modelIndex.column(), parent);
3764 }
3765 item += count;
3766 }
3767 }
3768
3769 if (previousSibiling != -1 && after && model->rowCount(parent) == start)
3770 viewItems[previousSibiling].hasMoreSiblings = false;
3771
3772
3773 updateChildCount(parentItem, -removedCount);
3774 if (parentItem != -1 && viewItems.at(parentItem).total == 0)
3775 viewItems[parentItem].hasChildren = false; //every children have been removed;
3776 if (after) {
3777 q->updateGeometries();
3778 viewport->update();
3779 } else {
3780 //we have removed items: we should at least update the scroll bar values.
3781 // They are used to determine the item geometry.
3782 updateScrollBars();
3783 }
3784 } else {
3785 // If an ancestor of root is removed then relayout
3786 QModelIndex idx = root;
3787 while (idx.isValid()) {
3788 idx = idx.parent();
3789 if (idx == parent) {
3790 doDelayedItemsLayout();
3791 break;
3792 }
3793 }
3794 }
3795 _q_rowsRemoved(parent, start, end);
3796
3797 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.begin();
3798 while (it != expandedIndexes.constEnd()) {
3799 if (!it->isValid())
3800 it = expandedIndexes.erase(it);
3801 else
3802 ++it;
3803 }
3804 it = hiddenIndexes.begin();
3805 while (it != hiddenIndexes.constEnd()) {
3806 if (!it->isValid())
3807 it = hiddenIndexes.erase(it);
3808 else
3809 ++it;
3810 }
3811}
3812
3813void QTreeViewPrivate::updateChildCount(const int parentItem, const int delta)
3814{
3815 if ((parentItem != -1) && delta) {
3816 int level = viewItems.at(parentItem).level;
3817 int item = parentItem;
3818 do {
3819 Q_ASSERT(item >= 0);
3820 for ( ; int(viewItems.at(item).level) != level; --item) ;
3821 viewItems[item].total += delta;
3822 --level;
3823 } while (level >= 0);
3824 }
3825}
3826
3827
3828void QTreeViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order)
3829{
3830 model->sort(column, order);
3831}
3832
3833/*!
3834 \reimp
3835 */
3836void QTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3837{
3838#ifndef QT_NO_ACCESSIBILITY
3839 if (QAccessible::isActive()) {
3840 int entry = visualIndex(current) + 1;
3841 if (header())
3842 ++entry;
3843 QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus);
3844 }
3845#endif
3846 QAbstractItemView::currentChanged(current, previous);
3847
3848 if (allColumnsShowFocus()) {
3849 if (previous.isValid()) {
3850 QRect previousRect = visualRect(previous);
3851 previousRect.setX(0);
3852 previousRect.setWidth(viewport()->width());
3853 viewport()->update(previousRect);
3854 }
3855 if (current.isValid()) {
3856 QRect currentRect = visualRect(current);
3857 currentRect.setX(0);
3858 currentRect.setWidth(viewport()->width());
3859 viewport()->update(currentRect);
3860 }
3861 }
3862}
3863
3864/*!
3865 \reimp
3866 */
3867void QTreeView::selectionChanged(const QItemSelection &selected,
3868 const QItemSelection &deselected)
3869{
3870#ifndef QT_NO_ACCESSIBILITY
3871 if (QAccessible::isActive()) {
3872 // ### does not work properly for selection ranges.
3873 QModelIndex sel = selected.indexes().value(0);
3874 if (sel.isValid()) {
3875 int entry = visualIndex(sel) + 1;
3876 if (header())
3877 ++entry;
3878 QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection);
3879 }
3880 QModelIndex desel = deselected.indexes().value(0);
3881 if (desel.isValid()) {
3882 int entry = visualIndex(desel) + 1;
3883 if (header())
3884 ++entry;
3885 QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove);
3886 }
3887 }
3888#endif
3889 QAbstractItemView::selectionChanged(selected, deselected);
3890}
3891
3892int QTreeView::visualIndex(const QModelIndex &index) const
3893{
3894 Q_D(const QTreeView);
3895 d->executePostedLayout();
3896 return d->viewIndex(index);
3897}
3898
3899QT_END_NAMESPACE
3900
3901#include "moc_qtreeview.cpp"
3902
3903#endif // QT_NO_TREEVIEW
Note: See TracBrowser for help on using the repository browser.