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

Last change on this file since 1147 was 846, checked in by Dmitry A. Kuminov, 14 years ago

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

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