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

Last change on this file since 104 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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