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

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

trunk: Merged in qt 4.6.1 sources.

File size: 128.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
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 [email protected].
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 table 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 if (selectionBehavior() == QAbstractItemView::SelectRows) {
1245 QModelIndex newHoverIndex = indexAt(he->pos());
1246 if (d->hover != newHoverIndex) {
1247 QRect oldHoverRect = visualRect(d->hover);
1248 QRect newHoverRect = visualRect(newHoverIndex);
1249 viewport()->update(QRect(0, newHoverRect.y(), viewport()->width(), newHoverRect.height()));
1250 viewport()->update(QRect(0, oldHoverRect.y(), viewport()->width(), oldHoverRect.height()));
1251 }
1252 }
1253 break; }
1254 default:
1255 break;
1256 }
1257 return QAbstractItemView::viewportEvent(event);
1258}
1259
1260/*!
1261 \reimp
1262*/
1263void QTreeView::paintEvent(QPaintEvent *event)
1264{
1265 Q_D(QTreeView);
1266 d->executePostedLayout();
1267 QPainter painter(viewport());
1268#ifndef QT_NO_ANIMATION
1269 if (d->isAnimating()) {
1270 drawTree(&painter, event->region() - d->animatedOperation.rect());
1271 d->drawAnimatedOperation(&painter);
1272 } else
1273#endif //QT_NO_ANIMATION
1274 {
1275 drawTree(&painter, event->region());
1276#ifndef QT_NO_DRAGANDDROP
1277 d->paintDropIndicator(&painter);
1278#endif
1279 }
1280}
1281
1282void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const
1283{
1284 Q_Q(const QTreeView);
1285 if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q))
1286 return;
1287 int rowHeight = defaultItemHeight;
1288 if (rowHeight <= 0) {
1289 rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height();
1290 if (rowHeight <= 0)
1291 return;
1292 }
1293 while (y <= bottom) {
1294 option->rect.setRect(0, y, viewport->width(), rowHeight);
1295 if (current & 1) {
1296 option->features |= QStyleOptionViewItemV2::Alternate;
1297 } else {
1298 option->features &= ~QStyleOptionViewItemV2::Alternate;
1299 }
1300 ++current;
1301 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q);
1302 y += rowHeight;
1303 }
1304}
1305
1306bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos)
1307{
1308 Q_Q(QTreeView);
1309 // we want to handle mousePress in EditingState (persistent editors)
1310 if ((state != QAbstractItemView::NoState
1311 && state != QAbstractItemView::EditingState)
1312 || !viewport->rect().contains(pos))
1313 return true;
1314
1315 int i = itemDecorationAt(pos);
1316 if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) {
1317 if (viewItems.at(i).expanded)
1318 collapse(i, true);
1319 else
1320 expand(i, true);
1321 if (!isAnimating()) {
1322 q->updateGeometries();
1323 viewport->update();
1324 }
1325 return true;
1326 }
1327 return false;
1328}
1329
1330void QTreeViewPrivate::_q_modelDestroyed()
1331{
1332 //we need to clear that list because it contais QModelIndex to
1333 //the model currently being destroyed
1334 viewItems.clear();
1335 QAbstractItemViewPrivate::_q_modelDestroyed();
1336}
1337
1338/*!
1339 \reimp
1340
1341 We have a QTreeView way of knowing what elements are on the viewport
1342*/
1343QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
1344{
1345 Q_ASSERT(r);
1346 return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r);
1347 Q_Q(const QTreeView);
1348 QRect &rect = *r;
1349 const QRect viewportRect = viewport->rect();
1350 int itemOffset = 0;
1351 int row = firstVisibleItem(&itemOffset);
1352 QPair<int, int> startEnd = startAndEndColumns(viewportRect);
1353 QVector<int> columns;
1354 for (int i = startEnd.first; i <= startEnd.second; ++i) {
1355 int logical = header->logicalIndex(i);
1356 if (!header->isSectionHidden(logical))
1357 columns += logical;
1358 }
1359 QSet<QModelIndex> visibleIndexes;
1360 for (; itemOffset < viewportRect.bottom() && row < viewItems.count(); ++row) {
1361 const QModelIndex &index = viewItems.at(row).index;
1362 for (int colIndex = 0; colIndex < columns.count(); ++colIndex)
1363 visibleIndexes += index.sibling(index.row(), columns.at(colIndex));
1364 itemOffset += itemHeight(row);
1365 }
1366
1367 //now that we have the visible indexes, we can try to find those which are selected
1368 QItemViewPaintPairs ret;
1369 for (int i = 0; i < indexes.count(); ++i) {
1370 const QModelIndex &index = indexes.at(i);
1371 if (visibleIndexes.contains(index)) {
1372 const QRect current = q->visualRect(index);
1373 ret += qMakePair(current, index);
1374 rect |= current;
1375 }
1376 }
1377 rect &= viewportRect;
1378 return ret;
1379}
1380
1381
1382/*!
1383 \since 4.2
1384 Draws the part of the tree intersecting the given \a region using the specified
1385 \a painter.
1386
1387 \sa paintEvent()
1388*/
1389void QTreeView::drawTree(QPainter *painter, const QRegion &region) const
1390{
1391 Q_D(const QTreeView);
1392 const QVector<QTreeViewItem> viewItems = d->viewItems;
1393
1394 QStyleOptionViewItemV4 option = d->viewOptionsV4();
1395 const QStyle::State state = option.state;
1396 d->current = 0;
1397
1398 if (viewItems.count() == 0 || d->header->count() == 0 || !d->itemDelegate) {
1399 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1400 return;
1401 }
1402
1403 int firstVisibleItemOffset = 0;
1404 const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset);
1405 if (firstVisibleItem < 0) {
1406 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1);
1407 return;
1408 }
1409
1410 const int viewportWidth = d->viewport->width();
1411
1412 QVector<QRect> rects = region.rects();
1413 QVector<int> drawn;
1414 bool multipleRects = (rects.size() > 1);
1415 for (int a = 0; a < rects.size(); ++a) {
1416 const QRect area = (multipleRects
1417 ? QRect(0, rects.at(a).y(), viewportWidth, rects.at(a).height())
1418 : rects.at(a));
1419 d->leftAndRight = d->startAndEndColumns(area);
1420
1421 int i = firstVisibleItem; // the first item at the top of the viewport
1422 int y = firstVisibleItemOffset; // we may only see part of the first item
1423
1424 // start at the top of the viewport and iterate down to the update area
1425 for (; i < viewItems.count(); ++i) {
1426 const int itemHeight = d->itemHeight(i);
1427 if (y + itemHeight > area.top())
1428 break;
1429 y += itemHeight;
1430 }
1431
1432 // paint the visible rows
1433 for (; i < viewItems.count() && y <= area.bottom(); ++i) {
1434 const int itemHeight = d->itemHeight(i);
1435 option.rect.setRect(0, y, viewportWidth, itemHeight);
1436 option.state = state | (viewItems.at(i).expanded ? QStyle::State_Open : QStyle::State_None)
1437 | (viewItems.at(i).hasChildren ? QStyle::State_Children : QStyle::State_None)
1438 | (viewItems.at(i).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
1439 d->current = i;
1440 d->spanning = viewItems.at(i).spanning;
1441 if (!multipleRects || !drawn.contains(i)) {
1442 drawRow(painter, option, viewItems.at(i).index);
1443 if (multipleRects) // even if the rect only intersects the item,
1444 drawn.append(i); // the entire item will be painted
1445 }
1446 y += itemHeight;
1447 }
1448
1449 if (y <= area.bottom()) {
1450 d->current = i;
1451 d->paintAlternatingRowColors(painter, &option, y, area.bottom());
1452 }
1453 }
1454}
1455
1456/// ### move to QObject :)
1457static inline bool ancestorOf(QObject *widget, QObject *other)
1458{
1459 for (QObject *parent = other; parent != 0; parent = parent->parent()) {
1460 if (parent == widget)
1461 return true;
1462 }
1463 return false;
1464}
1465
1466/*!
1467 Draws the row in the tree view that contains the model item \a index,
1468 using the \a painter given. The \a option control how the item is
1469 displayed.
1470
1471 \sa setAlternatingRowColors()
1472*/
1473void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
1474 const QModelIndex &index) const
1475{
1476 Q_D(const QTreeView);
1477 QStyleOptionViewItemV4 opt = option;
1478 const QPoint offset = d->scrollDelayOffset;
1479 const int y = option.rect.y() + offset.y();
1480 const QModelIndex parent = index.parent();
1481 const QHeaderView *header = d->header;
1482 const QModelIndex current = currentIndex();
1483 const QModelIndex hover = d->hover;
1484 const bool reverse = isRightToLeft();
1485 const QStyle::State state = opt.state;
1486 const bool spanning = d->spanning;
1487 const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first);
1488 const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second);
1489 const bool alternate = d->alternatingColors;
1490 const bool enabled = (state & QStyle::State_Enabled) != 0;
1491 const bool allColumnsShowFocus = d->allColumnsShowFocus;
1492
1493
1494 // when the row contains an index widget which has focus,
1495 // we want to paint the entire row as active
1496 bool indexWidgetHasFocus = false;
1497 if ((current.row() == index.row()) && !d->editors.isEmpty()) {
1498 const int r = index.row();
1499 QWidget *fw = QApplication::focusWidget();
1500 for (int c = 0; c < header->count(); ++c) {
1501 QModelIndex idx = d->model->index(r, c, parent);
1502 if (QWidget *editor = indexWidget(idx)) {
1503 if (ancestorOf(editor, fw)) {
1504 indexWidgetHasFocus = true;
1505 break;
1506 }
1507 }
1508 }
1509 }
1510
1511 const bool widgetHasFocus = hasFocus();
1512 bool currentRowHasFocus = false;
1513 if (allColumnsShowFocus && widgetHasFocus && current.isValid()) {
1514 // check if the focus index is before or after the visible columns
1515 const int r = index.row();
1516 for (int c = 0; c < left && !currentRowHasFocus; ++c) {
1517 QModelIndex idx = d->model->index(r, c, parent);
1518 currentRowHasFocus = (idx == current);
1519 }
1520 QModelIndex parent = d->model->parent(index);
1521 for (int c = right; c < header->count() && !currentRowHasFocus; ++c) {
1522 currentRowHasFocus = (d->model->index(r, c, parent) == current);
1523 }
1524 }
1525
1526 // ### special case: treeviews with multiple columns draw
1527 // the selections differently than with only one column
1528 opt.showDecorationSelected = (d->selectionBehavior & SelectRows)
1529 || option.showDecorationSelected;
1530
1531 int width, height = option.rect.height();
1532 int position;
1533 QModelIndex modelIndex;
1534 int columnCount = header->count();
1535 const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1536 && index.parent() == hover.parent()
1537 && index.row() == hover.row();
1538
1539 /* 'left' and 'right' are the left-most and right-most visible visual indices.
1540 Compute the first visible logical indices before and after the left and right.
1541 We will use these values to determine the QStyleOptionViewItemV4::viewItemPosition. */
1542 int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1;
1543 for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) {
1544 int logicalIndex = header->logicalIndex(visualIndex);
1545 if (!header->isSectionHidden(logicalIndex)) {
1546 logicalIndexBeforeLeft = logicalIndex;
1547 break;
1548 }
1549 }
1550 QVector<int> logicalIndices; // vector of currently visibly logical indices
1551 for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) {
1552 int logicalIndex = header->logicalIndex(visualIndex);
1553 if (!header->isSectionHidden(logicalIndex)) {
1554 if (visualIndex > right) {
1555 logicalIndexAfterRight = logicalIndex;
1556 break;
1557 }
1558 logicalIndices.append(logicalIndex);
1559 }
1560 }
1561
1562 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection) {
1563 int headerSection = logicalIndices.at(currentLogicalSection);
1564 position = columnViewportPosition(headerSection) + offset.x();
1565 width = header->sectionSize(headerSection);
1566
1567 if (spanning) {
1568 int lastSection = header->logicalIndex(header->count() - 1);
1569 if (!reverse) {
1570 width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position;
1571 } else {
1572 width += position - columnViewportPosition(lastSection);
1573 position = columnViewportPosition(lastSection);
1574 }
1575 }
1576
1577 modelIndex = d->model->index(index.row(), headerSection, parent);
1578 if (!modelIndex.isValid())
1579 continue;
1580 opt.state = state;
1581
1582 // determine the viewItemPosition depending on the position of column 0
1583 int nextLogicalSection = currentLogicalSection + 1 >= logicalIndices.count()
1584 ? logicalIndexAfterRight
1585 : logicalIndices.at(currentLogicalSection + 1);
1586 int prevLogicalSection = currentLogicalSection - 1 < 0
1587 ? logicalIndexBeforeLeft
1588 : logicalIndices.at(currentLogicalSection - 1);
1589 if (columnCount == 1 || (nextLogicalSection == 0 && prevLogicalSection == -1)
1590 || (headerSection == 0 && nextLogicalSection == -1) || spanning)
1591 opt.viewItemPosition = QStyleOptionViewItemV4::OnlyOne;
1592 else if (headerSection == 0 || (nextLogicalSection != 0 && prevLogicalSection == -1))
1593 opt.viewItemPosition = QStyleOptionViewItemV4::Beginning;
1594 else if (nextLogicalSection == 0 || nextLogicalSection == -1)
1595 opt.viewItemPosition = QStyleOptionViewItemV4::End;
1596 else
1597 opt.viewItemPosition = QStyleOptionViewItemV4::Middle;
1598
1599 // fake activeness when row editor has focus
1600 if (indexWidgetHasFocus)
1601 opt.state |= QStyle::State_Active;
1602
1603 if (d->selectionModel->isSelected(modelIndex))
1604 opt.state |= QStyle::State_Selected;
1605 if (widgetHasFocus && (current == modelIndex)) {
1606 if (allColumnsShowFocus)
1607 currentRowHasFocus = true;
1608 else
1609 opt.state |= QStyle::State_HasFocus;
1610 }
1611 if ((hoverRow || modelIndex == hover)
1612 && (option.showDecorationSelected || (d->hoverBranch == -1)))
1613 opt.state |= QStyle::State_MouseOver;
1614 else
1615 opt.state &= ~QStyle::State_MouseOver;
1616
1617 if (enabled) {
1618 QPalette::ColorGroup cg;
1619 if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) {
1620 opt.state &= ~QStyle::State_Enabled;
1621 cg = QPalette::Disabled;
1622 } else if (opt.state & QStyle::State_Active) {
1623 cg = QPalette::Active;
1624 } else {
1625 cg = QPalette::Inactive;
1626 }
1627 opt.palette.setCurrentColorGroup(cg);
1628 }
1629
1630 if (alternate) {
1631 if (d->current & 1) {
1632 opt.features |= QStyleOptionViewItemV2::Alternate;
1633 } else {
1634 opt.features &= ~QStyleOptionViewItemV2::Alternate;
1635 }
1636 }
1637
1638 /* Prior to Qt 4.3, the background of the branch (in selected state and
1639 alternate row color was provided by the view. For backward compatibility,
1640 this is now delegated to the style using PE_PanelViewItemRow which
1641 does the appropriate fill */
1642 if (headerSection == 0) {
1643 const int i = d->indentationForItem(d->current);
1644 QRect branches(reverse ? position + width - i : position, y, i, height);
1645 const bool setClipRect = branches.width() > width;
1646 if (setClipRect) {
1647 painter->save();
1648 painter->setClipRect(QRect(position, y, width, height));
1649 }
1650 // draw background for the branch (selection + alternate row)
1651 opt.rect = branches;
1652 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1653
1654 // draw background of the item (only alternate row). rest of the background
1655 // is provided by the delegate
1656 QStyle::State oldState = opt.state;
1657 opt.state &= ~QStyle::State_Selected;
1658 opt.rect.setRect(reverse ? position : i + position, y, width - i, height);
1659 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1660 opt.state = oldState;
1661
1662 drawBranches(painter, branches, index);
1663 if (setClipRect)
1664 painter->restore();
1665 } else {
1666 QStyle::State oldState = opt.state;
1667 opt.state &= ~QStyle::State_Selected;
1668 opt.rect.setRect(position, y, width, height);
1669 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
1670 opt.state = oldState;
1671 }
1672
1673 if (const QWidget *widget = d->editorForIndex(modelIndex).editor) {
1674 painter->save();
1675 painter->setClipRect(widget->geometry());
1676 d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex);
1677 painter->restore();
1678 } else {
1679 d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex);
1680 }
1681 }
1682
1683 if (currentRowHasFocus) {
1684 QStyleOptionFocusRect o;
1685 o.QStyleOption::operator=(option);
1686 o.state |= QStyle::State_KeyboardFocusChange;
1687 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
1688 ? QPalette::Normal : QPalette::Disabled;
1689 o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index)
1690 ? QPalette::Highlight : QPalette::Background);
1691 int x = 0;
1692 if (!option.showDecorationSelected)
1693 x = header->sectionPosition(0) + d->indentationForItem(d->current);
1694 QRect focusRect(x - header->offset(), y, header->length() - x, height);
1695 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect);
1696 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1697 // if we show focus on all columns and the first section is moved,
1698 // we have to split the focus rect into two rects
1699 if (allColumnsShowFocus && !option.showDecorationSelected
1700 && header->sectionsMoved() && (header->visualIndex(0) != 0)) {
1701 QRect sectionRect(0, y, header->sectionPosition(0), height);
1702 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect);
1703 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
1704 }
1705 }
1706}
1707
1708/*!
1709 Draws the branches in the tree view on the same row as the model item
1710 \a index, using the \a painter given. The branches are drawn in the
1711 rectangle specified by \a rect.
1712*/
1713void QTreeView::drawBranches(QPainter *painter, const QRect &rect,
1714 const QModelIndex &index) const
1715{
1716 Q_D(const QTreeView);
1717 const bool reverse = isRightToLeft();
1718 const int indent = d->indent;
1719 const int outer = d->rootDecoration ? 0 : 1;
1720 const int item = d->current;
1721 const QTreeViewItem &viewItem = d->viewItems.at(item);
1722 int level = viewItem.level;
1723 QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height());
1724
1725 QModelIndex parent = index.parent();
1726 QModelIndex current = parent;
1727 QModelIndex ancestor = current.parent();
1728
1729 QStyleOptionViewItemV2 opt = viewOptions();
1730 QStyle::State extraFlags = QStyle::State_None;
1731 if (isEnabled())
1732 extraFlags |= QStyle::State_Enabled;
1733 if (window()->isActiveWindow())
1734 extraFlags |= QStyle::State_Active;
1735 QPoint oldBO = painter->brushOrigin();
1736 if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel)
1737 painter->setBrushOrigin(QPoint(0, verticalOffset()));
1738
1739 if (d->alternatingColors) {
1740 if (d->current & 1) {
1741 opt.features |= QStyleOptionViewItemV2::Alternate;
1742 } else {
1743 opt.features &= ~QStyleOptionViewItemV2::Alternate;
1744 }
1745 }
1746
1747 // When hovering over a row, pass State_Hover for painting the branch
1748 // indicators if it has the decoration (aka branch) selected.
1749 bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
1750 && opt.showDecorationSelected
1751 && index.parent() == d->hover.parent()
1752 && index.row() == d->hover.row();
1753
1754 if (d->selectionModel->isSelected(index))
1755 extraFlags |= QStyle::State_Selected;
1756
1757 if (level >= outer) {
1758 // start with the innermost branch
1759 primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent);
1760 opt.rect = primitive;
1761
1762 const bool expanded = viewItem.expanded;
1763 const bool children = viewItem.hasChildren;
1764 bool moreSiblings = viewItem.hasMoreSiblings;
1765
1766 opt.state = QStyle::State_Item | extraFlags
1767 | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None)
1768 | (children ? QStyle::State_Children : QStyle::State_None)
1769 | (expanded ? QStyle::State_Open : QStyle::State_None);
1770 if (hoverRow || item == d->hoverBranch)
1771 opt.state |= QStyle::State_MouseOver;
1772 else
1773 opt.state &= ~QStyle::State_MouseOver;
1774 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1775 }
1776 // then go out level by level
1777 for (--level; level >= outer; --level) { // we have already drawn the innermost branch
1778 primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent);
1779 opt.rect = primitive;
1780 opt.state = extraFlags;
1781 bool moreSiblings = false;
1782 if (d->hiddenIndexes.isEmpty()) {
1783 moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row());
1784 } else {
1785 int successor = item + viewItem.total + 1;
1786 while (successor < d->viewItems.size()
1787 && d->viewItems.at(successor).level >= uint(level)) {
1788 const QTreeViewItem &successorItem = d->viewItems.at(successor);
1789 if (successorItem.level == uint(level)) {
1790 moreSiblings = true;
1791 break;
1792 }
1793 successor += successorItem.total + 1;
1794 }
1795 }
1796 if (moreSiblings)
1797 opt.state |= QStyle::State_Sibling;
1798 if (hoverRow || item == d->hoverBranch)
1799 opt.state |= QStyle::State_MouseOver;
1800 else
1801 opt.state &= ~QStyle::State_MouseOver;
1802 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
1803 current = ancestor;
1804 ancestor = current.parent();
1805 }
1806 painter->setBrushOrigin(oldBO);
1807}
1808
1809/*!
1810 \reimp
1811*/
1812void QTreeView::mousePressEvent(QMouseEvent *event)
1813{
1814 Q_D(QTreeView);
1815 bool handled = false;
1816 if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonPress)
1817 handled = d->expandOrCollapseItemAtPos(event->pos());
1818 if (!handled && d->itemDecorationAt(event->pos()) == -1)
1819 QAbstractItemView::mousePressEvent(event);
1820}
1821
1822/*!
1823 \reimp
1824*/
1825void QTreeView::mouseReleaseEvent(QMouseEvent *event)
1826{
1827 Q_D(QTreeView);
1828 if (d->itemDecorationAt(event->pos()) == -1) {
1829 QAbstractItemView::mouseReleaseEvent(event);
1830 } else {
1831 if (state() == QAbstractItemView::DragSelectingState)
1832 setState(QAbstractItemView::NoState);
1833 if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonRelease)
1834 d->expandOrCollapseItemAtPos(event->pos());
1835 }
1836}
1837
1838/*!
1839 \reimp
1840*/
1841void QTreeView::mouseDoubleClickEvent(QMouseEvent *event)
1842{
1843 Q_D(QTreeView);
1844 if (state() != NoState || !d->viewport->rect().contains(event->pos()))
1845 return;
1846
1847 int i = d->itemDecorationAt(event->pos());
1848 if (i == -1) {
1849 i = d->itemAtCoordinate(event->y());
1850 if (i == -1)
1851 return; // user clicked outside the items
1852
1853 const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index;
1854 const QPersistentModelIndex persistent = indexAt(event->pos());
1855
1856 if (d->pressedIndex != persistent) {
1857 mousePressEvent(event);
1858 return;
1859 }
1860
1861 // signal handlers may change the model
1862 emit doubleClicked(persistent);
1863
1864 if (!persistent.isValid())
1865 return;
1866
1867 if (edit(persistent, DoubleClicked, event) || state() != NoState)
1868 return; // the double click triggered editing
1869
1870 if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this))
1871 emit activated(persistent);
1872
1873 d->executePostedLayout(); // we need to make sure viewItems is updated
1874 if (d->itemsExpandable
1875 && d->expandsOnDoubleClick
1876 && d->hasVisibleChildren(persistent)) {
1877 if (!((i < d->viewItems.count()) && (d->viewItems.at(i).index == firstColumnIndex))) {
1878 // find the new index of the item
1879 for (i = 0; i < d->viewItems.count(); ++i) {
1880 if (d->viewItems.at(i).index == firstColumnIndex)
1881 break;
1882 }
1883 if (i == d->viewItems.count())
1884 return;
1885 }
1886 if (d->viewItems.at(i).expanded)
1887 d->collapse(i, true);
1888 else
1889 d->expand(i, true);
1890 updateGeometries();
1891 viewport()->update();
1892 }
1893 }
1894}
1895
1896/*!
1897 \reimp
1898*/
1899void QTreeView::mouseMoveEvent(QMouseEvent *event)
1900{
1901 Q_D(QTreeView);
1902 if (d->itemDecorationAt(event->pos()) == -1) // ### what about expanding/collapsing state ?
1903 QAbstractItemView::mouseMoveEvent(event);
1904}
1905
1906/*!
1907 \reimp
1908*/
1909void QTreeView::keyPressEvent(QKeyEvent *event)
1910{
1911 Q_D(QTreeView);
1912 QModelIndex current = currentIndex();
1913 //this is the management of the expansion
1914 if (d->isIndexValid(current) && d->model && d->itemsExpandable) {
1915 switch (event->key()) {
1916 case Qt::Key_Asterisk: {
1917 QStack<QModelIndex> parents;
1918 parents.push(current);
1919 while (!parents.isEmpty()) {
1920 QModelIndex parent = parents.pop();
1921 for (int row = 0; row < d->model->rowCount(parent); ++row) {
1922 QModelIndex child = d->model->index(row, 0, parent);
1923 if (!d->isIndexValid(child))
1924 break;
1925 parents.push(child);
1926 expand(child);
1927 }
1928 }
1929 expand(current);
1930 break; }
1931 case Qt::Key_Plus:
1932 expand(current);
1933 break;
1934 case Qt::Key_Minus:
1935 collapse(current);
1936 break;
1937 }
1938 }
1939
1940 QAbstractItemView::keyPressEvent(event);
1941}
1942
1943/*!
1944 \reimp
1945*/
1946QModelIndex QTreeView::indexAt(const QPoint &point) const
1947{
1948 Q_D(const QTreeView);
1949 d->executePostedLayout();
1950
1951 int visualIndex = d->itemAtCoordinate(point.y());
1952 QModelIndex idx = d->modelIndex(visualIndex);
1953 if (!idx.isValid())
1954 return QModelIndex();
1955
1956 if (d->viewItems.at(visualIndex).spanning)
1957 return idx;
1958
1959 int column = d->columnAt(point.x());
1960 if (column == idx.column())
1961 return idx;
1962 if (column < 0)
1963 return QModelIndex();
1964 return idx.sibling(idx.row(), column);
1965}
1966
1967/*!
1968 Returns the model index of the item above \a index.
1969*/
1970QModelIndex QTreeView::indexAbove(const QModelIndex &index) const
1971{
1972 Q_D(const QTreeView);
1973 if (!d->isIndexValid(index))
1974 return QModelIndex();
1975 d->executePostedLayout();
1976 int i = d->viewIndex(index);
1977 if (--i < 0)
1978 return QModelIndex();
1979 return d->viewItems.at(i).index;
1980}
1981
1982/*!
1983 Returns the model index of the item below \a index.
1984*/
1985QModelIndex QTreeView::indexBelow(const QModelIndex &index) const
1986{
1987 Q_D(const QTreeView);
1988 if (!d->isIndexValid(index))
1989 return QModelIndex();
1990 d->executePostedLayout();
1991 int i = d->viewIndex(index);
1992 if (++i >= d->viewItems.count())
1993 return QModelIndex();
1994 return d->viewItems.at(i).index;
1995}
1996
1997/*!
1998 \internal
1999
2000 Lays out the items in the tree view.
2001*/
2002void QTreeView::doItemsLayout()
2003{
2004 Q_D(QTreeView);
2005 d->viewItems.clear(); // prepare for new layout
2006 QModelIndex parent = d->root;
2007 if (d->model->hasChildren(parent)) {
2008 d->layout(-1);
2009 }
2010 QAbstractItemView::doItemsLayout();
2011 d->header->doItemsLayout();
2012}
2013
2014/*!
2015 \reimp
2016*/
2017void QTreeView::reset()
2018{
2019 Q_D(QTreeView);
2020 d->expandedIndexes.clear();
2021 d->hiddenIndexes.clear();
2022 d->spanningIndexes.clear();
2023 d->viewItems.clear();
2024 QAbstractItemView::reset();
2025}
2026
2027/*!
2028 Returns the horizontal offset of the items in the treeview.
2029
2030 Note that the tree view uses the horizontal header section
2031 positions to determine the positions of columns in the view.
2032
2033 \sa verticalOffset()
2034*/
2035int QTreeView::horizontalOffset() const
2036{
2037 Q_D(const QTreeView);
2038 return d->header->offset();
2039}
2040
2041/*!
2042 Returns the vertical offset of the items in the tree view.
2043
2044 \sa horizontalOffset()
2045*/
2046int QTreeView::verticalOffset() const
2047{
2048 Q_D(const QTreeView);
2049 if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) {
2050 if (d->uniformRowHeights)
2051 return verticalScrollBar()->value() * d->defaultItemHeight;
2052 // If we are scrolling per item and have non-uniform row heights,
2053 // finding the vertical offset in pixels is going to be relatively slow.
2054 // ### find a faster way to do this
2055 d->executePostedLayout();
2056 int offset = 0;
2057 for (int i = 0; i < d->viewItems.count(); ++i) {
2058 if (i == verticalScrollBar()->value())
2059 return offset;
2060 offset += d->itemHeight(i);
2061 }
2062 return 0;
2063 }
2064 // scroll per pixel
2065 return verticalScrollBar()->value();
2066}
2067
2068/*!
2069 Move the cursor in the way described by \a cursorAction, using the
2070 information provided by the button \a modifiers.
2071*/
2072QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
2073{
2074 Q_D(QTreeView);
2075 Q_UNUSED(modifiers);
2076
2077 d->executePostedLayout();
2078
2079 QModelIndex current = currentIndex();
2080 if (!current.isValid()) {
2081 int i = d->below(-1);
2082 int c = 0;
2083 while (c < d->header->count() && d->header->isSectionHidden(c))
2084 ++c;
2085 if (i < d->viewItems.count() && c < d->header->count()) {
2086 return d->modelIndex(i, c);
2087 }
2088 return QModelIndex();
2089 }
2090 int vi = -1;
2091#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC)
2092 // Selection behavior is slightly different on the Mac.
2093 if (d->selectionMode == QAbstractItemView::ExtendedSelection
2094 && d->selectionModel
2095 && d->selectionModel->hasSelection()) {
2096
2097 const bool moveUpDown = (cursorAction == MoveUp || cursorAction == MoveDown);
2098 const bool moveNextPrev = (cursorAction == MoveNext || cursorAction == MovePrevious);
2099 const bool contiguousSelection = moveUpDown && (modifiers & Qt::ShiftModifier);
2100
2101 // Use the outermost index in the selection as the current index
2102 if (!contiguousSelection && (moveUpDown || moveNextPrev)) {
2103
2104 // Find outermost index.
2105 const bool useTopIndex = (cursorAction == MoveUp || cursorAction == MovePrevious);
2106 int index = useTopIndex ? INT_MAX : INT_MIN;
2107 const QItemSelection selection = d->selectionModel->selection();
2108 for (int i = 0; i < selection.count(); ++i) {
2109 const QItemSelectionRange &range = selection.at(i);
2110 int candidate = d->viewIndex(useTopIndex ? range.topLeft() : range.bottomRight());
2111 if (candidate >= 0)
2112 index = useTopIndex ? qMin(index, candidate) : qMax(index, candidate);
2113 }
2114
2115 if (index >= 0 && index < INT_MAX)
2116 vi = index;
2117 }
2118 }
2119#endif
2120 if (vi < 0)
2121 vi = qMax(0, d->viewIndex(current));
2122
2123 if (isRightToLeft()) {
2124 if (cursorAction == MoveRight)
2125 cursorAction = MoveLeft;
2126 else if (cursorAction == MoveLeft)
2127 cursorAction = MoveRight;
2128 }
2129 switch (cursorAction) {
2130 case MoveNext:
2131 case MoveDown:
2132#ifdef QT_KEYPAD_NAVIGATION
2133 if (vi == d->viewItems.count()-1 && QApplication::keypadNavigationEnabled())
2134 return d->model->index(0, current.column(), d->root);
2135#endif
2136 return d->modelIndex(d->below(vi), current.column());
2137 case MovePrevious:
2138 case MoveUp:
2139#ifdef QT_KEYPAD_NAVIGATION
2140 if (vi == 0 && QApplication::keypadNavigationEnabled())
2141 return d->modelIndex(d->viewItems.count() - 1, current.column());
2142#endif
2143 return d->modelIndex(d->above(vi), current.column());
2144 case MoveLeft: {
2145 QScrollBar *sb = horizontalScrollBar();
2146 if (vi < d->viewItems.count() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum())
2147 d->collapse(vi, true);
2148 else {
2149 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this);
2150 if (descend) {
2151 QModelIndex par = current.parent();
2152 if (par.isValid() && par != rootIndex())
2153 return par;
2154 else
2155 descend = false;
2156 }
2157 if (!descend) {
2158 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2159 int visualColumn = d->header->visualIndex(current.column()) - 1;
2160 while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn)))
2161 visualColumn--;
2162 int newColumn = d->header->logicalIndex(visualColumn);
2163 QModelIndex next = current.sibling(current.row(), newColumn);
2164 if (next.isValid())
2165 return next;
2166 }
2167
2168 sb->setValue(sb->value() - sb->singleStep());
2169 }
2170
2171 }
2172 updateGeometries();
2173 viewport()->update();
2174 break;
2175 }
2176 case MoveRight:
2177 if (vi < d->viewItems.count() && !d->viewItems.at(vi).expanded && d->itemsExpandable
2178 && d->hasVisibleChildren(d->viewItems.at(vi).index)) {
2179 d->expand(vi, true);
2180 } else {
2181 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this);
2182 if (descend) {
2183 QModelIndex idx = d->modelIndex(d->below(vi));
2184 if (idx.parent() == current)
2185 return idx;
2186 else
2187 descend = false;
2188 }
2189 if (!descend) {
2190 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) {
2191 int visualColumn = d->header->visualIndex(current.column()) + 1;
2192 while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn)))
2193 visualColumn++;
2194
2195 QModelIndex next = current.sibling(current.row(), visualColumn);
2196 if (next.isValid())
2197 return next;
2198 }
2199
2200 //last restort: we change the scrollbar value
2201 QScrollBar *sb = horizontalScrollBar();
2202 sb->setValue(sb->value() + sb->singleStep());
2203 }
2204 }
2205 updateGeometries();
2206 viewport()->update();
2207 break;
2208 case MovePageUp:
2209 return d->modelIndex(d->pageUp(vi), current.column());
2210 case MovePageDown:
2211 return d->modelIndex(d->pageDown(vi), current.column());
2212 case MoveHome:
2213 return d->model->index(0, current.column(), d->root);
2214 case MoveEnd:
2215 return d->modelIndex(d->viewItems.count() - 1, current.column());
2216 }
2217 return current;
2218}
2219
2220/*!
2221 Applies the selection \a command to the items in or touched by the
2222 rectangle, \a rect.
2223
2224 \sa selectionCommand()
2225*/
2226void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2227{
2228 Q_D(QTreeView);
2229 if (!selectionModel() || rect.isNull())
2230 return;
2231
2232 d->executePostedLayout();
2233 QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right())
2234 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()));
2235 QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2236 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()));
2237 QModelIndex topLeft = indexAt(tl);
2238 QModelIndex bottomRight = indexAt(br);
2239 if (!topLeft.isValid() && !bottomRight.isValid()) {
2240 if (command & QItemSelectionModel::Clear)
2241 selectionModel()->clear();
2242 return;
2243 }
2244 if (!topLeft.isValid() && !d->viewItems.isEmpty())
2245 topLeft = d->viewItems.first().index;
2246 if (!bottomRight.isValid() && !d->viewItems.isEmpty()) {
2247 const int column = d->header->logicalIndex(d->header->count() - 1);
2248 const QModelIndex index = d->viewItems.last().index;
2249 bottomRight = index.sibling(index.row(), column);
2250 }
2251
2252 if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight))
2253 return;
2254
2255 d->select(topLeft, bottomRight, command);
2256}
2257
2258/*!
2259 Returns the rectangle from the viewport of the items in the given
2260 \a selection.
2261*/
2262QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const
2263{
2264 Q_D(const QTreeView);
2265 if (selection.isEmpty())
2266 return QRegion();
2267
2268 QRegion selectionRegion;
2269 for (int i = 0; i < selection.count(); ++i) {
2270 QItemSelectionRange range = selection.at(i);
2271 if (!range.isValid())
2272 continue;
2273 QModelIndex parent = range.parent();
2274 QModelIndex leftIndex = range.topLeft();
2275 int columnCount = d->model->columnCount(parent);
2276 while (leftIndex.isValid() && isIndexHidden(leftIndex)) {
2277 if (leftIndex.column() + 1 < columnCount)
2278 leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent);
2279 else
2280 leftIndex = QModelIndex();
2281 }
2282 if (!leftIndex.isValid())
2283 continue;
2284 const QRect leftRect = visualRect(leftIndex);
2285 int top = leftRect.top();
2286 QModelIndex rightIndex = range.bottomRight();
2287 while (rightIndex.isValid() && isIndexHidden(rightIndex)) {
2288 if (rightIndex.column() - 1 >= 0)
2289 rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent);
2290 else
2291 rightIndex = QModelIndex();
2292 }
2293 if (!rightIndex.isValid())
2294 continue;
2295 const QRect rightRect = visualRect(rightIndex);
2296 int bottom = rightRect.bottom();
2297 if (top > bottom)
2298 qSwap<int>(top, bottom);
2299 int height = bottom - top + 1;
2300 if (d->header->sectionsMoved()) {
2301 for (int c = range.left(); c <= range.right(); ++c)
2302 selectionRegion += QRegion(QRect(columnViewportPosition(c), top,
2303 columnWidth(c), height));
2304 } else {
2305 QRect combined = leftRect|rightRect;
2306 combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left()));
2307 selectionRegion += combined;
2308 }
2309 }
2310 return selectionRegion;
2311}
2312
2313/*!
2314 \reimp
2315*/
2316QModelIndexList QTreeView::selectedIndexes() const
2317{
2318 QModelIndexList viewSelected;
2319 QModelIndexList modelSelected;
2320 if (selectionModel())
2321 modelSelected = selectionModel()->selectedIndexes();
2322 for (int i = 0; i < modelSelected.count(); ++i) {
2323 // check that neither the parents nor the index is hidden before we add
2324 QModelIndex index = modelSelected.at(i);
2325 while (index.isValid() && !isIndexHidden(index))
2326 index = index.parent();
2327 if (index.isValid())
2328 continue;
2329 viewSelected.append(modelSelected.at(i));
2330 }
2331 return viewSelected;
2332}
2333
2334/*!
2335 Scrolls the contents of the tree view by (\a dx, \a dy).
2336*/
2337void QTreeView::scrollContentsBy(int dx, int dy)
2338{
2339 Q_D(QTreeView);
2340
2341 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
2342
2343 dx = isRightToLeft() ? -dx : dx;
2344 if (dx) {
2345 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2346 int oldOffset = d->header->offset();
2347 if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum())
2348 d->header->setOffsetToLastSection();
2349 else
2350 d->header->setOffsetToSectionPosition(horizontalScrollBar()->value());
2351 int newOffset = d->header->offset();
2352 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
2353 } else {
2354 d->header->setOffset(horizontalScrollBar()->value());
2355 }
2356 }
2357
2358 const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight;
2359 if (d->viewItems.isEmpty() || itemHeight == 0)
2360 return;
2361
2362 // guestimate the number of items in the viewport
2363 int viewCount = d->viewport->height() / itemHeight;
2364 int maxDeltaY = qMin(d->viewItems.count(), viewCount);
2365 // no need to do a lot of work if we are going to redraw the whole thing anyway
2366 if (qAbs(dy) > qAbs(maxDeltaY) && d->editors.isEmpty()) {
2367 verticalScrollBar()->update();
2368 d->viewport->update();
2369 return;
2370 }
2371
2372 if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2373 int currentScrollbarValue = verticalScrollBar()->value();
2374 int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy)
2375 int currentViewIndex = currentScrollbarValue; // the first visible item
2376 int previousViewIndex = previousScrollbarValue;
2377 const QVector<QTreeViewItem> viewItems = d->viewItems;
2378 dy = 0;
2379 if (previousViewIndex < currentViewIndex) { // scrolling down
2380 for (int i = previousViewIndex; i < currentViewIndex; ++i) {
2381 if (i < d->viewItems.count())
2382 dy -= d->itemHeight(i);
2383 }
2384 } else if (previousViewIndex > currentViewIndex) { // scrolling up
2385 for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) {
2386 if (i < d->viewItems.count())
2387 dy += d->itemHeight(i);
2388 }
2389 }
2390 }
2391
2392 d->scrollContentsBy(dx, dy);
2393}
2394
2395/*!
2396 This slot is called whenever a column has been moved.
2397*/
2398void QTreeView::columnMoved()
2399{
2400 Q_D(QTreeView);
2401 updateEditorGeometries();
2402 d->viewport->update();
2403}
2404
2405/*!
2406 \internal
2407*/
2408void QTreeView::reexpand()
2409{
2410 // do nothing
2411}
2412
2413/*!
2414 \internal
2415*/
2416static bool treeViewItemLessThan(const QTreeViewItem &left,
2417 const QTreeViewItem &right)
2418{
2419 if (left.level != right.level) {
2420 Q_ASSERT(left.level > right.level);
2421 QModelIndex leftParent = left.index.parent();
2422 QModelIndex rightParent = right.index.parent();
2423 // computer parent, don't get
2424 while (leftParent.isValid() && leftParent.parent() != rightParent)
2425 leftParent = leftParent.parent();
2426 return (leftParent.row() < right.index.row());
2427 }
2428 return (left.index.row() < right.index.row());
2429}
2430
2431/*!
2432 Informs the view that the rows from the \a start row to the \a end row
2433 inclusive have been inserted into the \a parent model item.
2434*/
2435void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end)
2436{
2437 Q_D(QTreeView);
2438 // if we are going to do a complete relayout anyway, there is no need to update
2439 if (d->delayedPendingLayout) {
2440 QAbstractItemView::rowsInserted(parent, start, end);
2441 return;
2442 }
2443
2444 //don't add a hierarchy on a column != 0
2445 if (parent.column() != 0 && parent.isValid()) {
2446 QAbstractItemView::rowsInserted(parent, start, end);
2447 return;
2448 }
2449
2450 const int parentRowCount = d->model->rowCount(parent);
2451 const int delta = end - start + 1;
2452 if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) {
2453 QAbstractItemView::rowsInserted(parent, start, end);
2454 return;
2455 }
2456
2457 const int parentItem = d->viewIndex(parent);
2458 if (((parentItem != -1) && d->viewItems.at(parentItem).expanded && updatesEnabled())
2459 || (parent == d->root)) {
2460 const uint childLevel = (parentItem == -1)
2461 ? uint(0) : d->viewItems.at(parentItem).level + 1;
2462 const int firstChildItem = parentItem + 1;
2463 const int lastChildItem = firstChildItem + ((parentItem == -1)
2464 ? d->viewItems.count()
2465 : d->viewItems.at(parentItem).total) - 1;
2466
2467 if (parentRowCount == end + 1 && start > 0) {
2468 //need to Update hasMoreSiblings
2469 int previousRow = start - 1;
2470 QModelIndex previousSibilingModelIndex = d->model->index(previousRow, 0, parent);
2471 bool isHidden = d->isRowHidden(previousSibilingModelIndex);
2472 while (isHidden && previousRow > 0) {
2473 previousRow--;
2474 previousSibilingModelIndex = d->model->index(previousRow, 0, parent);
2475 isHidden = d->isRowHidden(previousSibilingModelIndex);
2476 }
2477 if (!isHidden) {
2478 const int previousSibilling = d->viewIndex(previousSibilingModelIndex);
2479 if(previousSibilling != -1)
2480 d->viewItems[previousSibilling].hasMoreSiblings = true;
2481 }
2482 }
2483
2484 QVector<QTreeViewItem> insertedItems(delta);
2485 for (int i = 0; i < delta; ++i) {
2486 insertedItems[i].index = d->model->index(i + start, 0, parent);
2487 insertedItems[i].level = childLevel;
2488 insertedItems[i].hasChildren = d->hasVisibleChildren(insertedItems[i].index);
2489 insertedItems[i].hasMoreSiblings = !((i == delta - 1) && (parentRowCount == end +1));
2490 }
2491 if (d->viewItems.isEmpty())
2492 d->defaultItemHeight = indexRowSizeHint(insertedItems[0].index);
2493
2494 int insertPos;
2495 if (lastChildItem < firstChildItem) { // no children
2496 insertPos = firstChildItem;
2497 } else {
2498 // do a binary search to figure out where to insert
2499 QVector<QTreeViewItem>::iterator it;
2500 it = qLowerBound(d->viewItems.begin() + firstChildItem,
2501 d->viewItems.begin() + lastChildItem + 1,
2502 insertedItems.at(0), treeViewItemLessThan);
2503 insertPos = it - d->viewItems.begin();
2504
2505 // update stale model indexes of siblings
2506 for (int item = insertPos; item <= lastChildItem; ) {
2507 Q_ASSERT(d->viewItems.at(item).level == childLevel);
2508 const QModelIndex modelIndex = d->viewItems.at(item).index;
2509 //Q_ASSERT(modelIndex.parent() == parent);
2510 d->viewItems[item].index = d->model->index(
2511 modelIndex.row() + delta, modelIndex.column(), parent);
2512
2513 if (!d->viewItems[item].index.isValid()) {
2514 // Something really bad is happening, a bad model is
2515 // often the cause. We can't optimize in this case :(
2516 qWarning() << "QTreeView::rowsInserted internal representation of the model has been corrupted, resetting.";
2517 doItemsLayout();
2518 return;
2519 }
2520
2521 item += d->viewItems.at(item).total + 1;
2522 }
2523 }
2524
2525 d->viewItems.insert(insertPos, delta, insertedItems.at(0));
2526 if (delta > 1) {
2527 qCopy(insertedItems.begin() + 1, insertedItems.end(),
2528 d->viewItems.begin() + insertPos + 1);
2529 }
2530
2531 if (parentItem != -1)
2532 d->viewItems[parentItem].hasChildren = true;
2533 d->updateChildCount(parentItem, delta);
2534
2535 updateGeometries();
2536 viewport()->update();
2537 } else if ((parentItem != -1) && d->viewItems.at(parentItem).expanded) {
2538 d->doDelayedItemsLayout();
2539 } else if (parentItem != -1 && (d->model->rowCount(parent) == end - start + 1)) {
2540 // the parent just went from 0 children to more. update to re-paint the decoration
2541 d->viewItems[parentItem].hasChildren = true;
2542 viewport()->update();
2543 }
2544 QAbstractItemView::rowsInserted(parent, start, end);
2545}
2546
2547/*!
2548 Informs the view that the rows from the \a start row to the \a end row
2549 inclusive are about to removed from the given \a parent model item.
2550*/
2551void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
2552{
2553 Q_D(QTreeView);
2554 d->rowsRemoved(parent, start, end, false);
2555 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
2556}
2557
2558/*!
2559 \since 4.1
2560
2561 Informs the view that the rows from the \a start row to the \a end row
2562 inclusive have been removed from the given \a parent model item.
2563*/
2564void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end)
2565{
2566 Q_D(QTreeView);
2567 d->rowsRemoved(parent, start, end, true);
2568}
2569
2570/*!
2571 Informs the tree view that the number of columns in the tree view has
2572 changed from \a oldCount to \a newCount.
2573*/
2574void QTreeView::columnCountChanged(int oldCount, int newCount)
2575{
2576 Q_D(QTreeView);
2577 if (oldCount == 0 && newCount > 0) {
2578 //if the first column has just been added we need to relayout.
2579 d->doDelayedItemsLayout();
2580 }
2581
2582 if (isVisible())
2583 updateGeometries();
2584 viewport()->update();
2585}
2586
2587/*!
2588 Resizes the \a column given to the size of its contents.
2589
2590 \sa columnWidth(), setColumnWidth()
2591*/
2592void QTreeView::resizeColumnToContents(int column)
2593{
2594 Q_D(QTreeView);
2595 d->executePostedLayout();
2596 if (column < 0 || column >= d->header->count())
2597 return;
2598 int contents = sizeHintForColumn(column);
2599 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column);
2600 d->header->resizeSection(column, qMax(contents, header));
2601}
2602
2603/*!
2604 \obsolete
2605 \overload
2606
2607 Sorts the model by the values in the given \a column.
2608*/
2609void QTreeView::sortByColumn(int column)
2610{
2611 Q_D(QTreeView);
2612 sortByColumn(column, d->header->sortIndicatorOrder());
2613}
2614
2615/*!
2616 \since 4.2
2617
2618 Sets the model up for sorting by the values in the given \a column and \a order.
2619
2620 \a column may be -1, in which case no sort indicator will be shown
2621 and the model will return to its natural, unsorted order. Note that not
2622 all models support this and may even crash in this case.
2623
2624 \sa sortingEnabled
2625*/
2626void QTreeView::sortByColumn(int column, Qt::SortOrder order)
2627{
2628 Q_D(QTreeView);
2629
2630 //If sorting is enabled will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts
2631 d->header->setSortIndicator(column, order);
2632 //If sorting is not enabled, force to sort now.
2633 if (!d->sortingEnabled)
2634 d->model->sort(column, order);
2635}
2636
2637/*!
2638 \reimp
2639*/
2640void QTreeView::selectAll()
2641{
2642 Q_D(QTreeView);
2643 if (!selectionModel())
2644 return;
2645 SelectionMode mode = d->selectionMode;
2646 d->executePostedLayout(); //make sure we lay out the items
2647 if (mode != SingleSelection && !d->viewItems.isEmpty())
2648 d->select(d->viewItems.first().index, d->viewItems.last().index,
2649 QItemSelectionModel::ClearAndSelect
2650 |QItemSelectionModel::Rows);
2651}
2652
2653/*!
2654 \since 4.2
2655 Expands all expandable items.
2656
2657 Warning: if the model contains a large number of items,
2658 this function will take some time to execute.
2659
2660 \sa collapseAll() expand() collapse() setExpanded()
2661*/
2662void QTreeView::expandAll()
2663{
2664 Q_D(QTreeView);
2665 d->viewItems.clear();
2666 d->expandedIndexes.clear();
2667 d->interruptDelayedItemsLayout();
2668 d->layout(-1);
2669 for (int i = 0; i < d->viewItems.count(); ++i) {
2670 if (d->viewItems[i].expanded)
2671 continue;
2672 d->viewItems[i].expanded = true;
2673 d->layout(i);
2674 QModelIndex idx = d->viewItems.at(i).index;
2675 d->expandedIndexes.insert(idx);
2676 }
2677 updateGeometries();
2678 d->viewport->update();
2679}
2680
2681/*!
2682 \since 4.2
2683
2684 Collapses all expanded items.
2685
2686 \sa expandAll() expand() collapse() setExpanded()
2687*/
2688void QTreeView::collapseAll()
2689{
2690 Q_D(QTreeView);
2691 d->expandedIndexes.clear();
2692 doItemsLayout();
2693}
2694
2695/*!
2696 \since 4.3
2697 Expands all expandable items to the given \a depth.
2698
2699 \sa expandAll() collapseAll() expand() collapse() setExpanded()
2700*/
2701void QTreeView::expandToDepth(int depth)
2702{
2703 Q_D(QTreeView);
2704 d->viewItems.clear();
2705 d->expandedIndexes.clear();
2706 d->interruptDelayedItemsLayout();
2707 d->layout(-1);
2708 for (int i = 0; i < d->viewItems.count(); ++i) {
2709 if (d->viewItems.at(i).level <= (uint)depth) {
2710 d->viewItems[i].expanded = true;
2711 d->layout(i);
2712 d->storeExpanded(d->viewItems.at(i).index);
2713 }
2714 }
2715 updateGeometries();
2716 d->viewport->update();
2717}
2718
2719/*!
2720 This function is called whenever \a{column}'s size is changed in
2721 the header. \a oldSize and \a newSize give the previous size and
2722 the new size in pixels.
2723
2724 \sa setColumnWidth()
2725*/
2726void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */)
2727{
2728 Q_D(QTreeView);
2729 d->columnsToUpdate.append(column);
2730 if (d->columnResizeTimerID == 0)
2731 d->columnResizeTimerID = startTimer(0);
2732}
2733
2734/*!
2735 \reimp
2736*/
2737void QTreeView::updateGeometries()
2738{
2739 Q_D(QTreeView);
2740 if (d->header) {
2741 if (d->geometryRecursionBlock)
2742 return;
2743 d->geometryRecursionBlock = true;
2744 QSize hint = d->header->isHidden() ? QSize(0, 0) : d->header->sizeHint();
2745 setViewportMargins(0, hint.height(), 0, 0);
2746 QRect vg = d->viewport->geometry();
2747 QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height());
2748 d->header->setGeometry(geometryRect);
2749 //d->header->setOffset(horizontalScrollBar()->value()); // ### bug ???
2750 QMetaObject::invokeMethod(d->header, "updateGeometries");
2751 d->updateScrollBars();
2752 d->geometryRecursionBlock = false;
2753 }
2754 QAbstractItemView::updateGeometries();
2755}
2756
2757/*!
2758 Returns the size hint for the \a column's width or -1 if there is no
2759 model.
2760
2761 If you need to set the width of a given column to a fixed value, call
2762 QHeaderView::resizeSection() on the view's header.
2763
2764 If you reimplement this function in a subclass, note that the value you
2765 return is only used when resizeColumnToContents() is called. In that case,
2766 if a larger column width is required by either the view's header or
2767 the item delegate, that width will be used instead.
2768
2769 \sa QWidget::sizeHint, header()
2770*/
2771int QTreeView::sizeHintForColumn(int column) const
2772{
2773 Q_D(const QTreeView);
2774 d->executePostedLayout();
2775 if (d->viewItems.isEmpty())
2776 return -1;
2777 ensurePolished();
2778 int w = 0;
2779 QStyleOptionViewItemV4 option = d->viewOptionsV4();
2780 const QVector<QTreeViewItem> viewItems = d->viewItems;
2781
2782 int start = 0;
2783 int end = viewItems.count();
2784 if(end > 1000) { //if we have too many item this function would be too slow.
2785 //we get a good approximation by only iterate over 1000 items.
2786 start = qMax(0, d->firstVisibleItem() - 100);
2787 end = qMin(end, start + 900);
2788 }
2789
2790 for (int i = start; i < end; ++i) {
2791 if (viewItems.at(i).spanning)
2792 continue; // we have no good size hint
2793 QModelIndex index = viewItems.at(i).index;
2794 index = index.sibling(index.row(), column);
2795 QWidget *editor = d->editorForIndex(index).editor;
2796 if (editor && d->persistent.contains(editor)) {
2797 w = qMax(w, editor->sizeHint().width());
2798 int min = editor->minimumSize().width();
2799 int max = editor->maximumSize().width();
2800 w = qBound(min, w, max);
2801 }
2802 int hint = d->delegateForIndex(index)->sizeHint(option, index).width();
2803 w = qMax(w, hint + (column == 0 ? d->indentationForItem(i) : 0));
2804 }
2805 return w;
2806}
2807
2808/*!
2809 Returns the size hint for the row indicated by \a index.
2810
2811 \sa sizeHintForColumn(), uniformRowHeights()
2812*/
2813int QTreeView::indexRowSizeHint(const QModelIndex &index) const
2814{
2815 Q_D(const QTreeView);
2816 if (!d->isIndexValid(index) || !d->itemDelegate)
2817 return 0;
2818
2819 int start = -1;
2820 int end = -1;
2821 int count = d->header->count();
2822 bool emptyHeader = (count == 0);
2823 QModelIndex parent = index.parent();
2824
2825 if (count && isVisible()) {
2826 // If the sections have moved, we end up checking too many or too few
2827 start = d->header->visualIndexAt(0);
2828 } else {
2829 // If the header has not been laid out yet, we use the model directly
2830 count = d->model->columnCount(parent);
2831 }
2832
2833 if (isRightToLeft()) {
2834 start = (start == -1 ? count - 1 : start);
2835 end = 0;
2836 } else {
2837 start = (start == -1 ? 0 : start);
2838 end = count - 1;
2839 }
2840
2841 if (end < start)
2842 qSwap(end, start);
2843
2844 int height = -1;
2845 QStyleOptionViewItemV4 option = d->viewOptionsV4();
2846 // ### If we want word wrapping in the items,
2847 // ### we need to go through all the columns
2848 // ### and set the width of the column
2849
2850 // Hack to speed up the function
2851 option.rect.setWidth(-1);
2852
2853 for (int column = start; column <= end; ++column) {
2854 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column);
2855 if (d->header->isSectionHidden(logicalColumn))
2856 continue;
2857 QModelIndex idx = d->model->index(index.row(), logicalColumn, parent);
2858 if (idx.isValid()) {
2859 QWidget *editor = d->editorForIndex(idx).editor;
2860 if (editor && d->persistent.contains(editor)) {
2861 height = qMax(height, editor->sizeHint().height());
2862 int min = editor->minimumSize().height();
2863 int max = editor->maximumSize().height();
2864 height = qBound(min, height, max);
2865 }
2866 int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height();
2867 height = qMax(height, hint);
2868 }
2869 }
2870
2871 return height;
2872}
2873
2874/*!
2875 \since 4.3
2876 Returns the height of the row indicated by the given \a index.
2877 \sa indexRowSizeHint()
2878*/
2879int QTreeView::rowHeight(const QModelIndex &index) const
2880{
2881 Q_D(const QTreeView);
2882 d->executePostedLayout();
2883 int i = d->viewIndex(index);
2884 if (i == -1)
2885 return 0;
2886 return d->itemHeight(i);
2887}
2888
2889/*!
2890 \internal
2891*/
2892void QTreeView::horizontalScrollbarAction(int action)
2893{
2894 QAbstractItemView::horizontalScrollbarAction(action);
2895}
2896
2897/*!
2898 \reimp
2899*/
2900bool QTreeView::isIndexHidden(const QModelIndex &index) const
2901{
2902 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent()));
2903}
2904
2905/*
2906 private implementation
2907*/
2908void QTreeViewPrivate::initialize()
2909{
2910 Q_Q(QTreeView);
2911 updateStyledFrameWidths();
2912 q->setSelectionBehavior(QAbstractItemView::SelectRows);
2913 q->setSelectionMode(QAbstractItemView::SingleSelection);
2914 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
2915 q->setAttribute(Qt::WA_MacShowFocusRect);
2916
2917 QHeaderView *header = new QHeaderView(Qt::Horizontal, q);
2918 header->setMovable(true);
2919 header->setStretchLastSection(true);
2920 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
2921 q->setHeader(header);
2922#ifndef QT_NO_ANIMATION
2923 QObject::connect(&animatedOperation, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation()));
2924#endif //QT_NO_ANIMATION
2925}
2926
2927void QTreeViewPrivate::expand(int item, bool emitSignal)
2928{
2929 Q_Q(QTreeView);
2930
2931 if (item == -1 || viewItems.at(item).expanded)
2932 return;
2933
2934#ifndef QT_NO_ANIMATION
2935 if (emitSignal && animationsEnabled)
2936 prepareAnimatedOperation(item, QVariantAnimation::Forward);
2937#endif //QT_NO_ANIMATION
2938 QAbstractItemView::State oldState = state;
2939 q->setState(QAbstractItemView::ExpandingState);
2940 const QModelIndex index = viewItems.at(item).index;
2941 storeExpanded(index);
2942 viewItems[item].expanded = true;
2943 layout(item);
2944 q->setState(oldState);
2945
2946 if (model->canFetchMore(index))
2947 model->fetchMore(index);
2948 if (emitSignal) {
2949 emit q->expanded(index);
2950#ifndef QT_NO_ANIMATION
2951 if (animationsEnabled)
2952 beginAnimatedOperation();
2953#endif //QT_NO_ANIMATION
2954 }
2955}
2956
2957void QTreeViewPrivate::collapse(int item, bool emitSignal)
2958{
2959 Q_Q(QTreeView);
2960
2961 if (item == -1 || expandedIndexes.isEmpty())
2962 return;
2963
2964 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll
2965 delayedAutoScroll.stop();
2966
2967 int total = viewItems.at(item).total;
2968 const QModelIndex &modelIndex = viewItems.at(item).index;
2969 if (!isPersistent(modelIndex))
2970 return; // if the index is not persistent, no chances it is expanded
2971 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex);
2972 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false)
2973 return; // nothing to do
2974
2975#ifndef QT_NO_ANIMATION
2976 if (emitSignal && animationsEnabled)
2977 prepareAnimatedOperation(item, QVariantAnimation::Backward);
2978#endif //QT_NO_ANIMATION
2979
2980 QAbstractItemView::State oldState = state;
2981 q->setState(QAbstractItemView::CollapsingState);
2982 expandedIndexes.erase(it);
2983 viewItems[item].expanded = false;
2984 int index = item;
2985 QModelIndex parent = modelIndex;
2986 while (parent.isValid() && parent != root) {
2987 Q_ASSERT(index > -1);
2988 viewItems[index].total -= total;
2989 parent = parent.parent();
2990 index = viewIndex(parent);
2991 }
2992 viewItems.remove(item + 1, total); // collapse
2993 q->setState(oldState);
2994
2995 if (emitSignal) {
2996 emit q->collapsed(modelIndex);
2997#ifndef QT_NO_ANIMATION
2998 if (animationsEnabled)
2999 beginAnimatedOperation();
3000#endif //QT_NO_ANIMATION
3001 }
3002}
3003
3004#ifndef QT_NO_ANIMATION
3005void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction)
3006{
3007 animatedOperation.item = item;
3008 animatedOperation.viewport = viewport;
3009 animatedOperation.setDirection(direction);
3010
3011 int top = coordinateForItem(item) + itemHeight(item);
3012 QRect rect = viewport->rect();
3013 rect.setTop(top);
3014 if (direction == QVariantAnimation::Backward) {
3015 const int limit = rect.height() * 2;
3016 int h = 0;
3017 int c = item + viewItems.at(item).total + 1;
3018 for (int i = item + 1; i < c && h < limit; ++i)
3019 h += itemHeight(i);
3020 rect.setHeight(h);
3021 animatedOperation.setEndValue(top + h);
3022 }
3023 animatedOperation.setStartValue(top);
3024 animatedOperation.before = renderTreeToPixmapForAnimation(rect);
3025}
3026
3027void QTreeViewPrivate::beginAnimatedOperation()
3028{
3029 Q_Q(QTreeView);
3030
3031 QRect rect = viewport->rect();
3032 rect.setTop(animatedOperation.top());
3033 if (animatedOperation.direction() == QVariantAnimation::Forward) {
3034 const int limit = rect.height() * 2;
3035 int h = 0;
3036 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1;
3037 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i)
3038 h += itemHeight(i);
3039 rect.setHeight(h);
3040 animatedOperation.setEndValue(animatedOperation.top() + h);
3041 }
3042
3043 if (!rect.isEmpty()) {
3044 animatedOperation.after = renderTreeToPixmapForAnimation(rect);
3045
3046 q->setState(QAbstractItemView::AnimatingState);
3047 animatedOperation.start(); //let's start the animation
3048 }
3049}
3050
3051void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const
3052{
3053 const int start = animatedOperation.startValue().toInt(),
3054 end = animatedOperation.endValue().toInt(),
3055 current = animatedOperation.currentValue().toInt();
3056 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward;
3057 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after;
3058 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height());
3059 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before;
3060 painter->drawPixmap(0, current, bottom);
3061}
3062
3063QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const
3064{
3065 Q_Q(const QTreeView);
3066 QPixmap pixmap(rect.size());
3067 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels.
3068 QPainter painter(&pixmap);
3069 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base());
3070 painter.translate(0, -rect.top());
3071 q->drawTree(&painter, QRegion(rect));
3072 painter.end();
3073
3074 //and now let's render the editors the editors
3075 QStyleOptionViewItemV4 option = viewOptionsV4();
3076 for (QList<QEditorInfo>::const_iterator it = editors.constBegin(); it != editors.constEnd(); ++it) {
3077 QWidget *editor = it->editor;
3078 QModelIndex index = it->index;
3079 option.rect = q->visualRect(index);
3080 if (option.rect.isValid()) {
3081
3082 if (QAbstractItemDelegate *delegate = delegateForIndex(index))
3083 delegate->updateEditorGeometry(editor, option, index);
3084
3085 const QPoint pos = editor->pos();
3086 if (rect.contains(pos)) {
3087 editor->render(&pixmap, pos - rect.topLeft());
3088 //the animation uses pixmap to display the treeview's content
3089 //the editor is rendered on this pixmap and thus can (should) be hidden
3090 editor->hide();
3091 }
3092 }
3093 }
3094
3095
3096 return pixmap;
3097}
3098
3099void QTreeViewPrivate::_q_endAnimatedOperation()
3100{
3101 Q_Q(QTreeView);
3102 q->setState(QAbstractItemView::NoState);
3103 q->updateGeometries();
3104 viewport->update();
3105}
3106#endif //QT_NO_ANIMATION
3107
3108void QTreeViewPrivate::_q_modelAboutToBeReset()
3109{
3110 viewItems.clear();
3111}
3112
3113void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
3114{
3115 if (start <= 0 && 0 <= end)
3116 viewItems.clear();
3117 QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(parent, start, end);
3118}
3119
3120void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end)
3121{
3122 if (start <= 0 && 0 <= end)
3123 doDelayedItemsLayout();
3124 QAbstractItemViewPrivate::_q_columnsRemoved(parent, start, end);
3125}
3126
3127void QTreeViewPrivate::layout(int i)
3128{
3129 Q_Q(QTreeView);
3130 QModelIndex current;
3131 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i);
3132
3133 if (i>=0 && !parent.isValid()) {
3134 //modelIndex() should never return something invalid for the real items.
3135 //This can happen if columncount has been set to 0.
3136 //To avoid infinite loop we stop here.
3137 return;
3138 }
3139
3140 int count = 0;
3141 if (model->hasChildren(parent)) {
3142 if (model->canFetchMore(parent))
3143 model->fetchMore(parent);
3144 count = model->rowCount(parent);
3145 }
3146
3147 bool expanding = true;
3148 if (i == -1) {
3149 if (uniformRowHeights) {
3150 QModelIndex index = model->index(0, 0, parent);
3151 defaultItemHeight = q->indexRowSizeHint(index);
3152 }
3153 viewItems.resize(count);
3154 } else if (viewItems[i].total != (uint)count) {
3155 viewItems.insert(i + 1, count, QTreeViewItem()); // expand
3156 } else {
3157 expanding = false;
3158 }
3159
3160 int first = i + 1;
3161 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0);
3162 int hidden = 0;
3163 int last = 0;
3164 int children = 0;
3165 QTreeViewItem *item = 0;
3166 for (int j = first; j < first + count; ++j) {
3167 current = model->index(j - first, 0, parent);
3168 if (isRowHidden(current)) {
3169 ++hidden;
3170 last = j - hidden + children;
3171 } else {
3172 last = j - hidden + children;
3173 if (item)
3174 item->hasMoreSiblings = true;
3175 item = &viewItems[last];
3176 item->index = current;
3177 item->level = level;
3178 item->height = 0;
3179 item->spanning = q->isFirstColumnSpanned(current.row(), parent);
3180 item->expanded = false;
3181 item->total = 0;
3182 item->hasMoreSiblings = false;
3183 if (isIndexExpanded(current)) {
3184 item->expanded = true;
3185 layout(last);
3186 item = &viewItems[last];
3187 children += item->total;
3188 item->hasChildren = item->total > 0;
3189 last = j - hidden + children;
3190 } else {
3191 item->hasChildren = hasVisibleChildren(current);
3192 }
3193 }
3194 }
3195
3196 // remove hidden items
3197 if (hidden > 0)
3198 viewItems.remove(last + 1, hidden); // collapse
3199
3200 if (!expanding)
3201 return; // nothing changed
3202
3203 while (parent != root) {
3204 Q_ASSERT(i > -1);
3205 viewItems[i].total += count - hidden;
3206 parent = parent.parent();
3207 i = viewIndex(parent);
3208 }
3209}
3210
3211int QTreeViewPrivate::pageUp(int i) const
3212{
3213 int index = itemAtCoordinate(coordinateForItem(i) - viewport->height());
3214 return index == -1 ? 0 : index;
3215}
3216
3217int QTreeViewPrivate::pageDown(int i) const
3218{
3219 int index = itemAtCoordinate(coordinateForItem(i) + viewport->height());
3220 return index == -1 ? viewItems.count() - 1 : index;
3221}
3222
3223int QTreeViewPrivate::indentationForItem(int item) const
3224{
3225 if (item < 0 || item >= viewItems.count())
3226 return 0;
3227 int level = viewItems.at(item).level;
3228 if (rootDecoration)
3229 ++level;
3230 return level * indent;
3231}
3232
3233int QTreeViewPrivate::itemHeight(int item) const
3234{
3235 if (uniformRowHeights)
3236 return defaultItemHeight;
3237 if (viewItems.isEmpty())
3238 return 0;
3239 const QModelIndex &index = viewItems.at(item).index;
3240 int height = viewItems.at(item).height;
3241 if (height <= 0 && index.isValid()) {
3242 height = q_func()->indexRowSizeHint(index);
3243 viewItems[item].height = height;
3244 }
3245 if (!index.isValid() || height < 0)
3246 return 0;
3247 return height;
3248}
3249
3250
3251/*!
3252 \internal
3253 Returns the viewport y coordinate for \a item.
3254*/
3255int QTreeViewPrivate::coordinateForItem(int item) const
3256{
3257 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3258 if (uniformRowHeights)
3259 return (item * defaultItemHeight) - vbar->value();
3260 // ### optimize (spans or caching)
3261 int y = 0;
3262 for (int i = 0; i < viewItems.count(); ++i) {
3263 if (i == item)
3264 return y - vbar->value();
3265 y += itemHeight(i);
3266 }
3267 } else { // ScrollPerItem
3268 int topViewItemIndex = vbar->value();
3269 if (uniformRowHeights)
3270 return defaultItemHeight * (item - topViewItemIndex);
3271 if (item >= topViewItemIndex) {
3272 // search in the visible area first and continue down
3273 // ### slow if the item is not visible
3274 int viewItemCoordinate = 0;
3275 int viewItemIndex = topViewItemIndex;
3276 while (viewItemIndex < viewItems.count()) {
3277 if (viewItemIndex == item)
3278 return viewItemCoordinate;
3279 viewItemCoordinate += itemHeight(viewItemIndex);
3280 ++viewItemIndex;
3281 }
3282 // below the last item in the view
3283 Q_ASSERT(false);
3284 return viewItemCoordinate;
3285 } else {
3286 // search the area above the viewport (used for editor widgets)
3287 int viewItemCoordinate = 0;
3288 for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) {
3289 if (viewItemIndex == item)
3290 return viewItemCoordinate;
3291 viewItemCoordinate -= itemHeight(viewItemIndex - 1);
3292 }
3293 return viewItemCoordinate;
3294 }
3295 }
3296 return 0;
3297}
3298
3299/*!
3300 \internal
3301 Returns the index of the view item at the
3302 given viewport \a coordinate.
3303
3304 \sa modelIndex()
3305*/
3306int QTreeViewPrivate::itemAtCoordinate(int coordinate) const
3307{
3308 const int itemCount = viewItems.count();
3309 if (itemCount == 0)
3310 return -1;
3311 if (uniformRowHeights && defaultItemHeight <= 0)
3312 return -1;
3313 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) {
3314 if (uniformRowHeights) {
3315 const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight;
3316 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3317 }
3318 // ### optimize
3319 int viewItemCoordinate = 0;
3320 const int contentsCoordinate = coordinate + vbar->value();
3321 for (int viewItemIndex = 0; viewItemIndex < viewItems.count(); ++viewItemIndex) {
3322 viewItemCoordinate += itemHeight(viewItemIndex);
3323 if (viewItemCoordinate >= contentsCoordinate)
3324 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3325 }
3326 } else { // ScrollPerItem
3327 int topViewItemIndex = vbar->value();
3328 if (uniformRowHeights) {
3329 if (coordinate < 0)
3330 coordinate -= defaultItemHeight - 1;
3331 const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight);
3332 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex);
3333 }
3334 if (coordinate >= 0) {
3335 // the coordinate is in or below the viewport
3336 int viewItemCoordinate = 0;
3337 for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.count(); ++viewItemIndex) {
3338 viewItemCoordinate += itemHeight(viewItemIndex);
3339 if (viewItemCoordinate > coordinate)
3340 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3341 }
3342 } else {
3343 // the coordinate is above the viewport
3344 int viewItemCoordinate = 0;
3345 for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) {
3346 if (viewItemCoordinate <= coordinate)
3347 return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
3348 viewItemCoordinate -= itemHeight(viewItemIndex);
3349 }
3350 }
3351 }
3352 return -1;
3353}
3354
3355int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const
3356{
3357 if (!_index.isValid() || viewItems.isEmpty())
3358 return -1;
3359
3360 const int totalCount = viewItems.count();
3361 const QModelIndex index = _index.sibling(_index.row(), 0);
3362
3363
3364 // A quick check near the last item to see if we are just incrementing
3365 const int start = lastViewedItem > 2 ? lastViewedItem - 2 : 0;
3366 const int end = lastViewedItem < totalCount - 2 ? lastViewedItem + 2 : totalCount;
3367 int row = index.row();
3368 for (int i = start; i < end; ++i) {
3369 const QModelIndex &idx = viewItems.at(i).index;
3370 if (idx.row() == row) {
3371 if (idx.internalId() == index.internalId()) {
3372 lastViewedItem = i;
3373 return i;
3374 }
3375 }
3376 }
3377
3378 // NOTE: this function is slow if the item is outside the visible area
3379 // search in visible items first and below
3380 int t = firstVisibleItem();
3381 t = t > 100 ? t - 100 : 0; // start 100 items above the visible area
3382
3383 for (int i = t; i < totalCount; ++i) {
3384 const QModelIndex &idx = viewItems.at(i).index;
3385 if (idx.row() == row) {
3386 if (idx.internalId() == index.internalId()) {
3387 lastViewedItem = i;
3388 return i;
3389 }
3390 }
3391 }
3392 // search from top to first visible
3393 for (int j = 0; j < t; ++j) {
3394 const QModelIndex &idx = viewItems.at(j).index;
3395 if (idx.row() == row) {
3396 if (idx.internalId() == index.internalId()) {
3397 lastViewedItem = j;
3398 return j;
3399 }
3400 }
3401 }
3402 // nothing found
3403 return -1;
3404}
3405
3406QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const
3407{
3408 if (i < 0 || i >= viewItems.count())
3409 return QModelIndex();
3410
3411 QModelIndex ret = viewItems.at(i).index;
3412 if (column)
3413 ret = ret.sibling(ret.row(), column);
3414 return ret;
3415}
3416
3417int QTreeViewPrivate::firstVisibleItem(int *offset) const
3418{
3419 const int value = vbar->value();
3420 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3421 if (offset)
3422 *offset = 0;
3423 return (value < 0 || value >= viewItems.count()) ? -1 : value;
3424 }
3425 // ScrollMode == ScrollPerPixel
3426 if (uniformRowHeights) {
3427 if (!defaultItemHeight)
3428 return -1;
3429
3430 if (offset)
3431 *offset = -(value % defaultItemHeight);
3432 return value / defaultItemHeight;
3433 }
3434 int y = 0; // ### optimize (use spans ?)
3435 for (int i = 0; i < viewItems.count(); ++i) {
3436 y += itemHeight(i); // the height value is cached
3437 if (y > value) {
3438 if (offset)
3439 *offset = y - value - itemHeight(i);
3440 return i;
3441 }
3442 }
3443 return -1;
3444}
3445
3446int QTreeViewPrivate::columnAt(int x) const
3447{
3448 return header->logicalIndexAt(x);
3449}
3450
3451void QTreeViewPrivate::relayout(const QModelIndex &parent)
3452{
3453 Q_Q(QTreeView);
3454 // do a local relayout of the items
3455 if (parent.isValid()) {
3456 int parentViewIndex = viewIndex(parent);
3457 if (parentViewIndex > -1 && viewItems.at(parentViewIndex).expanded) {
3458 collapse(parentViewIndex, false); // remove the current layout
3459 expand(parentViewIndex, false); // do the relayout
3460 q->updateGeometries();
3461 viewport->update();
3462 }
3463 } else {
3464 viewItems.clear();
3465 q->doItemsLayout();
3466 }
3467}
3468
3469
3470void QTreeViewPrivate::updateScrollBars()
3471{
3472 Q_Q(QTreeView);
3473 QSize viewportSize = viewport->size();
3474 if (!viewportSize.isValid())
3475 viewportSize = QSize(0, 0);
3476
3477 int itemsInViewport = 0;
3478 if (uniformRowHeights) {
3479 if (defaultItemHeight <= 0)
3480 itemsInViewport = viewItems.count();
3481 else
3482 itemsInViewport = viewportSize.height() / defaultItemHeight;
3483 } else {
3484 const int itemsCount = viewItems.count();
3485 const int viewportHeight = viewportSize.height();
3486 for (int height = 0, item = itemsCount - 1; item >= 0; --item) {
3487 height += itemHeight(item);
3488 if (height > viewportHeight)
3489 break;
3490 ++itemsInViewport;
3491 }
3492 }
3493 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) {
3494 if (!viewItems.isEmpty())
3495 itemsInViewport = qMax(1, itemsInViewport);
3496 vbar->setRange(0, viewItems.count() - itemsInViewport);
3497 vbar->setPageStep(itemsInViewport);
3498 vbar->setSingleStep(1);
3499 } else { // scroll per pixel
3500 int contentsHeight = 0;
3501 if (uniformRowHeights) {
3502 contentsHeight = defaultItemHeight * viewItems.count();
3503 } else { // ### optimize (spans or caching)
3504 for (int i = 0; i < viewItems.count(); ++i)
3505 contentsHeight += itemHeight(i);
3506 }
3507 vbar->setRange(0, contentsHeight - viewportSize.height());
3508 vbar->setPageStep(viewportSize.height());
3509 vbar->setSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2));
3510 }
3511
3512 const int columnCount = header->count();
3513 const int viewportWidth = viewportSize.width();
3514 int columnsInViewport = 0;
3515 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
3516 int logical = header->logicalIndex(column);
3517 width += header->sectionSize(logical);
3518 if (width > viewportWidth)
3519 break;
3520 ++columnsInViewport;
3521 }
3522 if (columnCount > 0)
3523 columnsInViewport = qMax(1, columnsInViewport);
3524 if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) {
3525 hbar->setRange(0, columnCount - columnsInViewport);
3526 hbar->setPageStep(columnsInViewport);
3527 hbar->setSingleStep(1);
3528 } else { // scroll per pixel
3529 const int horizontalLength = header->length();
3530 const QSize maxSize = q->maximumViewportSize();
3531 if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0)
3532 viewportSize = maxSize;
3533 hbar->setPageStep(viewportSize.width());
3534 hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0));
3535 hbar->setSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2));
3536 }
3537}
3538
3539int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const
3540{
3541 executePostedLayout();
3542 int x = pos.x();
3543 int column = header->logicalIndexAt(x);
3544 if (column != 0)
3545 return -1; // no logical index at x
3546
3547 int viewItemIndex = itemAtCoordinate(pos.y());
3548 QRect returning = itemDecorationRect(modelIndex(viewItemIndex));
3549 if (!returning.contains(pos))
3550 return -1;
3551
3552 return viewItemIndex;
3553}
3554
3555QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const
3556{
3557 Q_Q(const QTreeView);
3558 if (!rootDecoration && index.parent() == root)
3559 return QRect(); // no decoration at root
3560
3561 int viewItemIndex = viewIndex(index);
3562 if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index))
3563 return QRect();
3564
3565 int itemIndentation = indentationForItem(viewItemIndex);
3566 int position = header->sectionViewportPosition(0);
3567 int size = header->sectionSize(0);
3568
3569 QRect rect;
3570 if (q->isRightToLeft())
3571 rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex),
3572 indent, itemHeight(viewItemIndex));
3573 else
3574 rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex),
3575 indent, itemHeight(viewItemIndex));
3576 QStyleOption opt;
3577 opt.initFrom(q);
3578 opt.rect = rect;
3579 return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q);
3580}
3581
3582QList<QPair<int, int> > QTreeViewPrivate::columnRanges(const QModelIndex &topIndex,
3583 const QModelIndex &bottomIndex) const
3584{
3585 const int topVisual = header->visualIndex(topIndex.column()),
3586 bottomVisual = header->visualIndex(bottomIndex.column());
3587
3588 const int start = qMin(topVisual, bottomVisual);
3589 const int end = qMax(topVisual, bottomVisual);
3590
3591 QList<int> logicalIndexes;
3592
3593 //we iterate over the visual indexes to get the logical indexes
3594 for (int c = start; c <= end; c++) {
3595 const int logical = header->logicalIndex(c);
3596 if (!header->isSectionHidden(logical)) {
3597 logicalIndexes << logical;
3598 }
3599 }
3600 //let's sort the list
3601 qSort(logicalIndexes.begin(), logicalIndexes.end());
3602
3603 QList<QPair<int, int> > ret;
3604 QPair<int, int> current;
3605 current.first = -2; // -1 is not enough because -1+1 = 0
3606 current.second = -2;
3607 for(int i = 0; i < logicalIndexes.count(); ++i) {
3608 const int logicalColumn = logicalIndexes.at(i);
3609 if (current.second + 1 != logicalColumn) {
3610 if (current.first != -2) {
3611 //let's save the current one
3612 ret += current;
3613 }
3614 //let's start a new one
3615 current.first = current.second = logicalColumn;
3616 } else {
3617 current.second++;
3618 }
3619 }
3620
3621 //let's get the last range
3622 if (current.first != -2) {
3623 ret += current;
3624 }
3625
3626 return ret;
3627}
3628
3629void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex,
3630 QItemSelectionModel::SelectionFlags command)
3631{
3632 Q_Q(QTreeView);
3633 QItemSelection selection;
3634 const int top = viewIndex(topIndex),
3635 bottom = viewIndex(bottomIndex);
3636
3637 const QList< QPair<int, int> > colRanges = columnRanges(topIndex, bottomIndex);
3638 QList< QPair<int, int> >::const_iterator it;
3639 for (it = colRanges.begin(); it != colRanges.end(); ++it) {
3640 const int left = (*it).first,
3641 right = (*it).second;
3642
3643 QModelIndex previous;
3644 QItemSelectionRange currentRange;
3645 QStack<QItemSelectionRange> rangeStack;
3646 for (int i = top; i <= bottom; ++i) {
3647 QModelIndex index = modelIndex(i);
3648 QModelIndex parent = index.parent();
3649 QModelIndex previousParent = previous.parent();
3650 if (previous.isValid() && parent == previousParent) {
3651 // same parent
3652 if (qAbs(previous.row() - index.row()) > 1) {
3653 //a hole (hidden index inside a range) has been detected
3654 if (currentRange.isValid()) {
3655 selection.append(currentRange);
3656 }
3657 //let's start a new range
3658 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3659 } else {
3660 QModelIndex tl = model->index(currentRange.top(), currentRange.left(),
3661 currentRange.parent());
3662 currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right));
3663 }
3664 } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) {
3665 // item is child of previous
3666 rangeStack.push(currentRange);
3667 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3668 } else {
3669 if (currentRange.isValid())
3670 selection.append(currentRange);
3671 if (rangeStack.isEmpty()) {
3672 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right));
3673 } else {
3674 currentRange = rangeStack.pop();
3675 index = currentRange.bottomRight(); //let's resume the range
3676 --i; //we process again the current item
3677 }
3678 }
3679 previous = index;
3680 }
3681 if (currentRange.isValid())
3682 selection.append(currentRange);
3683 for (int i = 0; i < rangeStack.count(); ++i)
3684 selection.append(rangeStack.at(i));
3685 }
3686 q->selectionModel()->select(selection, command);
3687}
3688
3689QPair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const
3690{
3691 Q_Q(const QTreeView);
3692 int start = header->visualIndexAt(rect.left());
3693 int end = header->visualIndexAt(rect.right());
3694 if (q->isRightToLeft()) {
3695 start = (start == -1 ? header->count() - 1 : start);
3696 end = (end == -1 ? 0 : end);
3697 } else {
3698 start = (start == -1 ? 0 : start);
3699 end = (end == -1 ? header->count() - 1 : end);
3700 }
3701 return qMakePair<int,int>(qMin(start, end), qMax(start, end));
3702}
3703
3704bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const
3705{
3706 Q_Q(const QTreeView);
3707 if (model->hasChildren(parent)) {
3708 if (hiddenIndexes.isEmpty())
3709 return true;
3710 if (q->isIndexHidden(parent))
3711 return false;
3712 int rowCount = model->rowCount(parent);
3713 for (int i = 0; i < rowCount; ++i) {
3714 if (!q->isRowHidden(i, parent))
3715 return true;
3716 }
3717 if (rowCount == 0)
3718 return true;
3719 }
3720 return false;
3721}
3722
3723void QTreeViewPrivate::rowsRemoved(const QModelIndex &parent,
3724 int start, int end, bool after)
3725{
3726 Q_Q(QTreeView);
3727 // if we are going to do a complete relayout anyway, there is no need to update
3728 if (delayedPendingLayout) {
3729 _q_rowsRemoved(parent, start, end);
3730 return;
3731 }
3732
3733 const int parentItem = viewIndex(parent);
3734 if ((parentItem != -1) || (parent == root)) {
3735
3736 const uint childLevel = (parentItem == -1)
3737 ? uint(0) : viewItems.at(parentItem).level + 1;
3738 Q_UNUSED(childLevel); // unused in release mode, used in assert below
3739
3740 const int firstChildItem = parentItem + 1;
3741 int lastChildItem = firstChildItem + ((parentItem == -1)
3742 ? viewItems.count()
3743 : viewItems.at(parentItem).total) - 1;
3744
3745 const int delta = end - start + 1;
3746
3747 int previousSibiling = -1;
3748 int removedCount = 0;
3749 for (int item = firstChildItem; item <= lastChildItem; ) {
3750 Q_ASSERT(viewItems.at(item).level == childLevel);
3751 const QModelIndex modelIndex = viewItems.at(item).index;
3752 //Q_ASSERT(modelIndex.parent() == parent);
3753 const int count = viewItems.at(item).total + 1;
3754 if (modelIndex.row() < start) {
3755 previousSibiling = item;
3756 // not affected by the removal
3757 item += count;
3758 } else if (modelIndex.row() <= end) {
3759 // removed
3760 viewItems.remove(item, count);
3761 removedCount += count;
3762 lastChildItem -= count;
3763 } else {
3764 if (after) {
3765 // moved; update the model index
3766 viewItems[item].index = model->index(
3767 modelIndex.row() - delta, modelIndex.column(), parent);
3768 }
3769 item += count;
3770 }
3771 }
3772
3773 if (previousSibiling != -1 && after && model->rowCount(parent) == start)
3774 viewItems[previousSibiling].hasMoreSiblings = false;
3775
3776
3777 updateChildCount(parentItem, -removedCount);
3778 if (parentItem != -1 && viewItems.at(parentItem).total == 0)
3779 viewItems[parentItem].hasChildren = false; //every children have been removed;
3780 if (after) {
3781 q->updateGeometries();
3782 viewport->update();
3783 } else {
3784 //we have removed items: we should at least update the scroll bar values.
3785 // They are used to determine the item geometry.
3786 updateScrollBars();
3787 }
3788 } else {
3789 // If an ancestor of root is removed then relayout
3790 QModelIndex idx = root;
3791 while (idx.isValid()) {
3792 idx = idx.parent();
3793 if (idx == parent) {
3794 doDelayedItemsLayout();
3795 break;
3796 }
3797 }
3798 }
3799 _q_rowsRemoved(parent, start, end);
3800
3801 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.begin();
3802 while (it != expandedIndexes.constEnd()) {
3803 if (!it->isValid())
3804 it = expandedIndexes.erase(it);
3805 else
3806 ++it;
3807 }
3808 it = hiddenIndexes.begin();
3809 while (it != hiddenIndexes.constEnd()) {
3810 if (!it->isValid())
3811 it = hiddenIndexes.erase(it);
3812 else
3813 ++it;
3814 }
3815}
3816
3817void QTreeViewPrivate::updateChildCount(const int parentItem, const int delta)
3818{
3819 if ((parentItem != -1) && delta) {
3820 int level = viewItems.at(parentItem).level;
3821 int item = parentItem;
3822 do {
3823 Q_ASSERT(item >= 0);
3824 for ( ; int(viewItems.at(item).level) != level; --item) ;
3825 viewItems[item].total += delta;
3826 --level;
3827 } while (level >= 0);
3828 }
3829}
3830
3831
3832void QTreeViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order)
3833{
3834 model->sort(column, order);
3835}
3836
3837/*!
3838 \reimp
3839 */
3840void QTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3841{
3842#ifndef QT_NO_ACCESSIBILITY
3843 if (QAccessible::isActive()) {
3844 int entry = visualIndex(current) + 1;
3845 if (header())
3846 ++entry;
3847 QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus);
3848 }
3849#endif
3850 QAbstractItemView::currentChanged(current, previous);
3851
3852 if (allColumnsShowFocus()) {
3853 if (previous.isValid()) {
3854 QRect previousRect = visualRect(previous);
3855 previousRect.setX(0);
3856 previousRect.setWidth(viewport()->width());
3857 viewport()->update(previousRect);
3858 }
3859 if (current.isValid()) {
3860 QRect currentRect = visualRect(current);
3861 currentRect.setX(0);
3862 currentRect.setWidth(viewport()->width());
3863 viewport()->update(currentRect);
3864 }
3865 }
3866}
3867
3868/*!
3869 \reimp
3870 */
3871void QTreeView::selectionChanged(const QItemSelection &selected,
3872 const QItemSelection &deselected)
3873{
3874#ifndef QT_NO_ACCESSIBILITY
3875 if (QAccessible::isActive()) {
3876 // ### does not work properly for selection ranges.
3877 QModelIndex sel = selected.indexes().value(0);
3878 if (sel.isValid()) {
3879 int entry = visualIndex(sel) + 1;
3880 if (header())
3881 ++entry;
3882 QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection);
3883 }
3884 QModelIndex desel = deselected.indexes().value(0);
3885 if (desel.isValid()) {
3886 int entry = visualIndex(desel) + 1;
3887 if (header())
3888 ++entry;
3889 QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove);
3890 }
3891 }
3892#endif
3893 QAbstractItemView::selectionChanged(selected, deselected);
3894}
3895
3896int QTreeView::visualIndex(const QModelIndex &index) const
3897{
3898 Q_D(const QTreeView);
3899 d->executePostedLayout();
3900 return d->viewIndex(index);
3901}
3902
3903QT_END_NAMESPACE
3904
3905#include "moc_qtreeview.cpp"
3906
3907#endif // QT_NO_TREEVIEW
Note: See TracBrowser for help on using the repository browser.