/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qtreeview.h" | |
#ifndef QT_NO_TREEVIEW | |
#include <qheaderview.h> | |
#include <qitemdelegate.h> | |
#include <qapplication.h> | |
#include <qscrollbar.h> | |
#include <qpainter.h> | |
#include <qstack.h> | |
#include <qstyle.h> | |
#include <qstyleoption.h> | |
#include <qevent.h> | |
#include <qpen.h> | |
#include <qdebug.h> | |
#ifndef QT_NO_ACCESSIBILITY | |
#include <qaccessible.h> | |
#endif | |
#include <private/qtreeview_p.h> | |
QT_BEGIN_NAMESPACE | |
/*! | |
\class QTreeView | |
\brief The QTreeView class provides a default model/view implementation of a tree view. | |
\ingroup model-view | |
\ingroup advanced | |
A QTreeView implements a tree representation of items from a | |
model. This class is used to provide standard hierarchical lists that | |
were previously provided by the \c QListView class, but using the more | |
flexible approach provided by Qt's model/view architecture. | |
The QTreeView class is one of the \l{Model/View Classes} and is part of | |
Qt's \l{Model/View Programming}{model/view framework}. | |
QTreeView implements the interfaces defined by the | |
QAbstractItemView class to allow it to display data provided by | |
models derived from the QAbstractItemModel class. | |
It is simple to construct a tree view displaying data from a | |
model. In the following example, the contents of a directory are | |
supplied by a QDirModel and displayed as a tree: | |
\snippet doc/src/snippets/shareddirmodel/main.cpp 3 | |
\snippet doc/src/snippets/shareddirmodel/main.cpp 6 | |
The model/view architecture ensures that the contents of the tree view | |
are updated as the model changes. | |
Items that have children can be in an expanded (children are | |
visible) or collapsed (children are hidden) state. When this state | |
changes a collapsed() or expanded() signal is emitted with the | |
model index of the relevant item. | |
The amount of indentation used to indicate levels of hierarchy is | |
controlled by the \l indentation property. | |
Headers in tree views are constructed using the QHeaderView class and can | |
be hidden using \c{header()->hide()}. Note that each header is configured | |
with its \l{QHeaderView::}{stretchLastSection} property set to true, | |
ensuring that the view does not waste any of the space assigned to it for | |
its header. If this value is set to true, this property will override the | |
resize mode set on the last section in the header. | |
\section1 Key Bindings | |
QTreeView supports a set of key bindings that enable the user to | |
navigate in the view and interact with the contents of items: | |
\table | |
\header \o Key \o Action | |
\row \o Up \o Moves the cursor to the item in the same column on | |
the previous row. If the parent of the current item has no more rows to | |
navigate to, the cursor moves to the relevant item in the last row | |
of the sibling that precedes the parent. | |
\row \o Down \o Moves the cursor to the item in the same column on | |
the next row. If the parent of the current item has no more rows to | |
navigate to, the cursor moves to the relevant item in the first row | |
of the sibling that follows the parent. | |
\row \o Left \o Hides the children of the current item (if present) | |
by collapsing a branch. | |
\row \o Minus \o Same as LeftArrow. | |
\row \o Right \o Reveals the children of the current item (if present) | |
by expanding a branch. | |
\row \o Plus \o Same as RightArrow. | |
\row \o Asterisk \o Expands all children of the current item (if present). | |
\row \o PageUp \o Moves the cursor up one page. | |
\row \o PageDown \o Moves the cursor down one page. | |
\row \o Home \o Moves the cursor to an item in the same column of the first | |
row of the first top-level item in the model. | |
\row \o End \o Moves the cursor to an item in the same column of the last | |
row of the last top-level item in the model. | |
\row \o F2 \o In editable models, this opens the current item for editing. | |
The Escape key can be used to cancel the editing process and revert | |
any changes to the data displayed. | |
\endtable | |
\omit | |
Describe the expanding/collapsing concept if not covered elsewhere. | |
\endomit | |
\table 100% | |
\row \o \inlineimage windowsxp-treeview.png Screenshot of a Windows XP style tree view | |
\o \inlineimage macintosh-treeview.png Screenshot of a Macintosh style tree view | |
\o \inlineimage plastique-treeview.png Screenshot of a Plastique style tree view | |
\row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} tree view. | |
\o A \l{Macintosh Style Widget Gallery}{Macintosh style} tree view. | |
\o A \l{Plastique Style Widget Gallery}{Plastique style} tree view. | |
\endtable | |
\section1 Improving Performance | |
It is possible to give the view hints about the data it is handling in order | |
to improve its performance when displaying large numbers of items. One approach | |
that can be taken for views that are intended to display items with equal heights | |
is to set the \l uniformRowHeights property to true. | |
\sa QListView, QTreeWidget, {View Classes}, QAbstractItemModel, QAbstractItemView, | |
{Dir View Example} | |
*/ | |
/*! | |
\fn void QTreeView::expanded(const QModelIndex &index) | |
This signal is emitted when the item specified by \a index is expanded. | |
*/ | |
/*! | |
\fn void QTreeView::collapsed(const QModelIndex &index) | |
This signal is emitted when the item specified by \a index is collapsed. | |
*/ | |
/*! | |
Constructs a tree view with a \a parent to represent a model's | |
data. Use setModel() to set the model. | |
\sa QAbstractItemModel | |
*/ | |
QTreeView::QTreeView(QWidget *parent) | |
: QAbstractItemView(*new QTreeViewPrivate, parent) | |
{ | |
Q_D(QTreeView); | |
d->initialize(); | |
} | |
/*! | |
\internal | |
*/ | |
QTreeView::QTreeView(QTreeViewPrivate &dd, QWidget *parent) | |
: QAbstractItemView(dd, parent) | |
{ | |
Q_D(QTreeView); | |
d->initialize(); | |
} | |
/*! | |
Destroys the tree view. | |
*/ | |
QTreeView::~QTreeView() | |
{ | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::setModel(QAbstractItemModel *model) | |
{ | |
Q_D(QTreeView); | |
if (model == d->model) | |
return; | |
if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { | |
disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), | |
this, SLOT(rowsRemoved(QModelIndex,int,int))); | |
disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset())); | |
} | |
if (d->selectionModel) { // support row editing | |
disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), | |
d->model, SLOT(submit())); | |
disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), | |
this, SLOT(rowsRemoved(QModelIndex,int,int))); | |
disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset())); | |
} | |
d->viewItems.clear(); | |
d->expandedIndexes.clear(); | |
d->hiddenIndexes.clear(); | |
d->header->setModel(model); | |
QAbstractItemView::setModel(model); | |
// QAbstractItemView connects to a private slot | |
disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), | |
this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); | |
// do header layout after the tree | |
disconnect(d->model, SIGNAL(layoutChanged()), | |
d->header, SLOT(_q_layoutChanged())); | |
// QTreeView has a public slot for this | |
connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), | |
this, SLOT(rowsRemoved(QModelIndex,int,int))); | |
connect(d->model, SIGNAL(modelAboutToBeReset()), SLOT(_q_modelAboutToBeReset())); | |
if (d->sortingEnabled) | |
d->_q_sortIndicatorChanged(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::setRootIndex(const QModelIndex &index) | |
{ | |
Q_D(QTreeView); | |
d->header->setRootIndex(index); | |
QAbstractItemView::setRootIndex(index); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::setSelectionModel(QItemSelectionModel *selectionModel) | |
{ | |
Q_D(QTreeView); | |
Q_ASSERT(selectionModel); | |
if (d->selectionModel) { | |
// support row editing | |
disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), | |
d->model, SLOT(submit())); | |
} | |
d->header->setSelectionModel(selectionModel); | |
QAbstractItemView::setSelectionModel(selectionModel); | |
if (d->selectionModel) { | |
// support row editing | |
connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), | |
d->model, SLOT(submit())); | |
} | |
} | |
/*! | |
Returns the header for the tree view. | |
\sa QAbstractItemModel::headerData() | |
*/ | |
QHeaderView *QTreeView::header() const | |
{ | |
Q_D(const QTreeView); | |
return d->header; | |
} | |
/*! | |
Sets the header for the tree view, to the given \a header. | |
The view takes ownership over the given \a header and deletes it | |
when a new header is set. | |
\sa QAbstractItemModel::headerData() | |
*/ | |
void QTreeView::setHeader(QHeaderView *header) | |
{ | |
Q_D(QTreeView); | |
if (header == d->header || !header) | |
return; | |
if (d->header && d->header->parent() == this) | |
delete d->header; | |
d->header = header; | |
d->header->setParent(this); | |
if (!d->header->model()) { | |
d->header->setModel(d->model); | |
if (d->selectionModel) | |
d->header->setSelectionModel(d->selectionModel); | |
} | |
connect(d->header, SIGNAL(sectionResized(int,int,int)), | |
this, SLOT(columnResized(int,int,int))); | |
connect(d->header, SIGNAL(sectionMoved(int,int,int)), | |
this, SLOT(columnMoved())); | |
connect(d->header, SIGNAL(sectionCountChanged(int,int)), | |
this, SLOT(columnCountChanged(int,int))); | |
connect(d->header, SIGNAL(sectionHandleDoubleClicked(int)), | |
this, SLOT(resizeColumnToContents(int))); | |
connect(d->header, SIGNAL(geometriesChanged()), | |
this, SLOT(updateGeometries())); | |
setSortingEnabled(d->sortingEnabled); | |
} | |
/*! | |
\property QTreeView::autoExpandDelay | |
\brief The delay time before items in a tree are opened during a drag and drop operation. | |
\since 4.3 | |
This property holds the amount of time in milliseconds that the user must wait over | |
a node before that node will automatically open or close. If the time is | |
set to less then 0 then it will not be activated. | |
By default, this property has a value of -1, meaning that auto-expansion is disabled. | |
*/ | |
int QTreeView::autoExpandDelay() const | |
{ | |
Q_D(const QTreeView); | |
return d->autoExpandDelay; | |
} | |
void QTreeView::setAutoExpandDelay(int delay) | |
{ | |
Q_D(QTreeView); | |
d->autoExpandDelay = delay; | |
} | |
/*! | |
\property QTreeView::indentation | |
\brief indentation of the items in the tree view. | |
This property holds the indentation measured in pixels of the items for each | |
level in the tree view. For top-level items, the indentation specifies the | |
horizontal distance from the viewport edge to the items in the first column; | |
for child items, it specifies their indentation from their parent items. | |
By default, this property has a value of 20. | |
*/ | |
int QTreeView::indentation() const | |
{ | |
Q_D(const QTreeView); | |
return d->indent; | |
} | |
void QTreeView::setIndentation(int i) | |
{ | |
Q_D(QTreeView); | |
if (i != d->indent) { | |
d->indent = i; | |
d->viewport->update(); | |
} | |
} | |
/*! | |
\property QTreeView::rootIsDecorated | |
\brief whether to show controls for expanding and collapsing top-level items | |
Items with children are typically shown with controls to expand and collapse | |
them, allowing their children to be shown or hidden. If this property is | |
false, these controls are not shown for top-level items. This can be used to | |
make a single level tree structure appear like a simple list of items. | |
By default, this property is true. | |
*/ | |
bool QTreeView::rootIsDecorated() const | |
{ | |
Q_D(const QTreeView); | |
return d->rootDecoration; | |
} | |
void QTreeView::setRootIsDecorated(bool show) | |
{ | |
Q_D(QTreeView); | |
if (show != d->rootDecoration) { | |
d->rootDecoration = show; | |
d->viewport->update(); | |
} | |
} | |
/*! | |
\property QTreeView::uniformRowHeights | |
\brief whether all items in the treeview have the same height | |
This property should only be set to true if it is guaranteed that all items | |
in the view has the same height. This enables the view to do some | |
optimizations. | |
The height is obtained from the first item in the view. It is updated | |
when the data changes on that item. | |
By default, this property is false. | |
*/ | |
bool QTreeView::uniformRowHeights() const | |
{ | |
Q_D(const QTreeView); | |
return d->uniformRowHeights; | |
} | |
void QTreeView::setUniformRowHeights(bool uniform) | |
{ | |
Q_D(QTreeView); | |
d->uniformRowHeights = uniform; | |
} | |
/*! | |
\property QTreeView::itemsExpandable | |
\brief whether the items are expandable by the user. | |
This property holds whether the user can expand and collapse items | |
interactively. | |
By default, this property is true. | |
*/ | |
bool QTreeView::itemsExpandable() const | |
{ | |
Q_D(const QTreeView); | |
return d->itemsExpandable; | |
} | |
void QTreeView::setItemsExpandable(bool enable) | |
{ | |
Q_D(QTreeView); | |
d->itemsExpandable = enable; | |
} | |
/*! | |
\property QTreeView::expandsOnDoubleClick | |
\since 4.4 | |
\brief whether the items can be expanded by double-clicking. | |
This property holds whether the user can expand and collapse items | |
by double-clicking. The default value is true. | |
\sa itemsExpandable | |
*/ | |
bool QTreeView::expandsOnDoubleClick() const | |
{ | |
Q_D(const QTreeView); | |
return d->expandsOnDoubleClick; | |
} | |
void QTreeView::setExpandsOnDoubleClick(bool enable) | |
{ | |
Q_D(QTreeView); | |
d->expandsOnDoubleClick = enable; | |
} | |
/*! | |
Returns the horizontal position of the \a column in the viewport. | |
*/ | |
int QTreeView::columnViewportPosition(int column) const | |
{ | |
Q_D(const QTreeView); | |
return d->header->sectionViewportPosition(column); | |
} | |
/*! | |
Returns the width of the \a column. | |
\sa resizeColumnToContents(), setColumnWidth() | |
*/ | |
int QTreeView::columnWidth(int column) const | |
{ | |
Q_D(const QTreeView); | |
return d->header->sectionSize(column); | |
} | |
/*! | |
\since 4.2 | |
Sets the width of the given \a column to the \a width specified. | |
\sa columnWidth(), resizeColumnToContents() | |
*/ | |
void QTreeView::setColumnWidth(int column, int width) | |
{ | |
Q_D(QTreeView); | |
d->header->resizeSection(column, width); | |
} | |
/*! | |
Returns the column in the tree view whose header covers the \a x | |
coordinate given. | |
*/ | |
int QTreeView::columnAt(int x) const | |
{ | |
Q_D(const QTreeView); | |
return d->header->logicalIndexAt(x); | |
} | |
/*! | |
Returns true if the \a column is hidden; otherwise returns false. | |
\sa hideColumn(), isRowHidden() | |
*/ | |
bool QTreeView::isColumnHidden(int column) const | |
{ | |
Q_D(const QTreeView); | |
return d->header->isSectionHidden(column); | |
} | |
/*! | |
If \a hide is true the \a column is hidden, otherwise the \a column is shown. | |
\sa hideColumn(), setRowHidden() | |
*/ | |
void QTreeView::setColumnHidden(int column, bool hide) | |
{ | |
Q_D(QTreeView); | |
if (column < 0 || column >= d->header->count()) | |
return; | |
d->header->setSectionHidden(column, hide); | |
} | |
/*! | |
\property QTreeView::headerHidden | |
\brief whether the header is shown or not. | |
\since 4.4 | |
If this property is true, the header is not shown otherwise it is. | |
The default value is false. | |
\sa header() | |
*/ | |
bool QTreeView::isHeaderHidden() const | |
{ | |
Q_D(const QTreeView); | |
return d->header->isHidden(); | |
} | |
void QTreeView::setHeaderHidden(bool hide) | |
{ | |
Q_D(QTreeView); | |
d->header->setHidden(hide); | |
} | |
/*! | |
Returns true if the item in the given \a row of the \a parent is hidden; | |
otherwise returns false. | |
\sa setRowHidden(), isColumnHidden() | |
*/ | |
bool QTreeView::isRowHidden(int row, const QModelIndex &parent) const | |
{ | |
Q_D(const QTreeView); | |
if (!d->model) | |
return false; | |
return d->isRowHidden(d->model->index(row, 0, parent)); | |
} | |
/*! | |
If \a hide is true the \a row with the given \a parent is hidden, otherwise the \a row is shown. | |
\sa isRowHidden(), setColumnHidden() | |
*/ | |
void QTreeView::setRowHidden(int row, const QModelIndex &parent, bool hide) | |
{ | |
Q_D(QTreeView); | |
if (!d->model) | |
return; | |
QModelIndex index = d->model->index(row, 0, parent); | |
if (!index.isValid()) | |
return; | |
if (hide) { | |
d->hiddenIndexes.insert(index); | |
} else if(d->isPersistent(index)) { //if the index is not persistent, it cannot be in the set | |
d->hiddenIndexes.remove(index); | |
} | |
d->doDelayedItemsLayout(); | |
} | |
/*! | |
\since 4.3 | |
Returns true if the item in first column in the given \a row | |
of the \a parent is spanning all the columns; otherwise returns false. | |
\sa setFirstColumnSpanned() | |
*/ | |
bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const | |
{ | |
Q_D(const QTreeView); | |
if (d->spanningIndexes.isEmpty() || !d->model) | |
return false; | |
QModelIndex index = d->model->index(row, 0, parent); | |
for (int i = 0; i < d->spanningIndexes.count(); ++i) | |
if (d->spanningIndexes.at(i) == index) | |
return true; | |
return false; | |
} | |
/*! | |
\since 4.3 | |
If \a span is true the item in the first column in the \a row | |
with the given \a parent is set to span all columns, otherwise all items | |
on the \a row are shown. | |
\sa isFirstColumnSpanned() | |
*/ | |
void QTreeView::setFirstColumnSpanned(int row, const QModelIndex &parent, bool span) | |
{ | |
Q_D(QTreeView); | |
if (!d->model) | |
return; | |
QModelIndex index = d->model->index(row, 0, parent); | |
if (!index.isValid()) | |
return; | |
if (span) { | |
QPersistentModelIndex persistent(index); | |
if (!d->spanningIndexes.contains(persistent)) | |
d->spanningIndexes.append(persistent); | |
} else { | |
QPersistentModelIndex persistent(index); | |
int i = d->spanningIndexes.indexOf(persistent); | |
if (i >= 0) | |
d->spanningIndexes.remove(i); | |
} | |
d->executePostedLayout(); | |
int i = d->viewIndex(index); | |
if (i >= 0) | |
d->viewItems[i].spanning = span; | |
d->viewport->update(); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) | |
{ | |
Q_D(QTreeView); | |
// if we are going to do a complete relayout anyway, there is no need to update | |
if (d->delayedPendingLayout) | |
return; | |
// refresh the height cache here; we don't really lose anything by getting the size hint, | |
// since QAbstractItemView::dataChanged() will get the visualRect for the items anyway | |
bool sizeChanged = false; | |
int topViewIndex = d->viewIndex(topLeft); | |
if (topViewIndex == 0) { | |
int newDefaultItemHeight = indexRowSizeHint(topLeft); | |
sizeChanged = d->defaultItemHeight != newDefaultItemHeight; | |
d->defaultItemHeight = newDefaultItemHeight; | |
} | |
if (topViewIndex != -1) { | |
if (topLeft.row() == bottomRight.row()) { | |
int oldHeight = d->itemHeight(topViewIndex); | |
d->invalidateHeightCache(topViewIndex); | |
sizeChanged |= (oldHeight != d->itemHeight(topViewIndex)); | |
if (topLeft.column() == 0) | |
d->viewItems[topViewIndex].hasChildren = d->hasVisibleChildren(topLeft); | |
} else { | |
int bottomViewIndex = d->viewIndex(bottomRight); | |
for (int i = topViewIndex; i <= bottomViewIndex; ++i) { | |
int oldHeight = d->itemHeight(i); | |
d->invalidateHeightCache(i); | |
sizeChanged |= (oldHeight != d->itemHeight(i)); | |
if (topLeft.column() == 0) | |
d->viewItems[i].hasChildren = d->hasVisibleChildren(d->viewItems.at(i).index); | |
} | |
} | |
} | |
if (sizeChanged) { | |
d->updateScrollBars(); | |
d->viewport->update(); | |
} | |
QAbstractItemView::dataChanged(topLeft, bottomRight); | |
} | |
/*! | |
Hides the \a column given. | |
\note This function should only be called after the model has been | |
initialized, as the view needs to know the number of columns in order to | |
hide \a column. | |
\sa showColumn(), setColumnHidden() | |
*/ | |
void QTreeView::hideColumn(int column) | |
{ | |
Q_D(QTreeView); | |
d->header->hideSection(column); | |
} | |
/*! | |
Shows the given \a column in the tree view. | |
\sa hideColumn(), setColumnHidden() | |
*/ | |
void QTreeView::showColumn(int column) | |
{ | |
Q_D(QTreeView); | |
d->header->showSection(column); | |
} | |
/*! | |
\fn void QTreeView::expand(const QModelIndex &index) | |
Expands the model item specified by the \a index. | |
\sa expanded() | |
*/ | |
void QTreeView::expand(const QModelIndex &index) | |
{ | |
Q_D(QTreeView); | |
if (!d->isIndexValid(index)) | |
return; | |
if (d->delayedPendingLayout) { | |
//A complete relayout is going to be performed, just store the expanded index, no need to layout. | |
if (d->storeExpanded(index)) | |
emit expanded(index); | |
return; | |
} | |
int i = d->viewIndex(index); | |
if (i != -1) { // is visible | |
d->expand(i, true); | |
if (!d->isAnimating()) { | |
updateGeometries(); | |
d->viewport->update(); | |
} | |
} else if (d->storeExpanded(index)) { | |
emit expanded(index); | |
} | |
} | |
/*! | |
\fn void QTreeView::collapse(const QModelIndex &index) | |
Collapses the model item specified by the \a index. | |
\sa collapsed() | |
*/ | |
void QTreeView::collapse(const QModelIndex &index) | |
{ | |
Q_D(QTreeView); | |
if (!d->isIndexValid(index)) | |
return; | |
//if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll | |
d->delayedAutoScroll.stop(); | |
if (d->delayedPendingLayout) { | |
//A complete relayout is going to be performed, just un-store the expanded index, no need to layout. | |
if (d->isPersistent(index) && d->expandedIndexes.remove(index)) | |
emit collapsed(index); | |
return; | |
} | |
int i = d->viewIndex(index); | |
if (i != -1) { // is visible | |
d->collapse(i, true); | |
if (!d->isAnimating()) { | |
updateGeometries(); | |
viewport()->update(); | |
} | |
} else { | |
if (d->isPersistent(index) && d->expandedIndexes.remove(index)) | |
emit collapsed(index); | |
} | |
} | |
/*! | |
\fn bool QTreeView::isExpanded(const QModelIndex &index) const | |
Returns true if the model item \a index is expanded; otherwise returns | |
false. | |
\sa expand(), expanded(), setExpanded() | |
*/ | |
bool QTreeView::isExpanded(const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
return d->isIndexExpanded(index); | |
} | |
/*! | |
Sets the item referred to by \a index to either collapse or expanded, | |
depending on the value of \a expanded. | |
\sa expanded(), expand(), isExpanded() | |
*/ | |
void QTreeView::setExpanded(const QModelIndex &index, bool expanded) | |
{ | |
if (expanded) | |
this->expand(index); | |
else | |
this->collapse(index); | |
} | |
/*! | |
\since 4.2 | |
\property QTreeView::sortingEnabled | |
\brief whether sorting is enabled | |
If this property is true, sorting is enabled for the tree; if the property | |
is false, sorting is not enabled. The default value is false. | |
\note In order to avoid performance issues, it is recommended that | |
sorting is enabled \e after inserting the items into the tree. | |
Alternatively, you could also insert the items into a list before inserting | |
the items into the tree. | |
\sa sortByColumn() | |
*/ | |
void QTreeView::setSortingEnabled(bool enable) | |
{ | |
Q_D(QTreeView); | |
header()->setSortIndicatorShown(enable); | |
header()->setClickable(enable); | |
if (enable) { | |
//sortByColumn has to be called before we connect or set the sortingEnabled flag | |
// because otherwise it will not call sort on the model. | |
sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); | |
connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), | |
this, SLOT(_q_sortIndicatorChanged(int,Qt::SortOrder)), Qt::UniqueConnection); | |
} else { | |
disconnect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), | |
this, SLOT(_q_sortIndicatorChanged(int,Qt::SortOrder))); | |
} | |
d->sortingEnabled = enable; | |
} | |
bool QTreeView::isSortingEnabled() const | |
{ | |
Q_D(const QTreeView); | |
return d->sortingEnabled; | |
} | |
/*! | |
\since 4.2 | |
\property QTreeView::animated | |
\brief whether animations are enabled | |
If this property is true the treeview will animate expandsion | |
and collasping of branches. If this property is false, the treeview | |
will expand or collapse branches immediately without showing | |
the animation. | |
By default, this property is false. | |
*/ | |
void QTreeView::setAnimated(bool animate) | |
{ | |
Q_D(QTreeView); | |
d->animationsEnabled = animate; | |
} | |
bool QTreeView::isAnimated() const | |
{ | |
Q_D(const QTreeView); | |
return d->animationsEnabled; | |
} | |
/*! | |
\since 4.2 | |
\property QTreeView::allColumnsShowFocus | |
\brief whether items should show keyboard focus using all columns | |
If this property is true all columns will show focus, otherwise only | |
one column will show focus. | |
The default is false. | |
*/ | |
void QTreeView::setAllColumnsShowFocus(bool enable) | |
{ | |
Q_D(QTreeView); | |
if (d->allColumnsShowFocus == enable) | |
return; | |
d->allColumnsShowFocus = enable; | |
d->viewport->update(); | |
} | |
bool QTreeView::allColumnsShowFocus() const | |
{ | |
Q_D(const QTreeView); | |
return d->allColumnsShowFocus; | |
} | |
/*! | |
\property QTreeView::wordWrap | |
\brief the item text word-wrapping policy | |
\since 4.3 | |
If this property is true then the item text is wrapped where | |
necessary at word-breaks; otherwise it is not wrapped at all. | |
This property is false by default. | |
Note that even if wrapping is enabled, the cell will not be | |
expanded to fit all text. Ellipsis will be inserted according to | |
the current \l{QAbstractItemView::}{textElideMode}. | |
*/ | |
void QTreeView::setWordWrap(bool on) | |
{ | |
Q_D(QTreeView); | |
if (d->wrapItemText == on) | |
return; | |
d->wrapItemText = on; | |
d->doDelayedItemsLayout(); | |
} | |
bool QTreeView::wordWrap() const | |
{ | |
Q_D(const QTreeView); | |
return d->wrapItemText; | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::keyboardSearch(const QString &search) | |
{ | |
Q_D(QTreeView); | |
if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root)) | |
return; | |
QModelIndex start; | |
if (currentIndex().isValid()) | |
start = currentIndex(); | |
else | |
start = d->model->index(0, 0, d->root); | |
bool skipRow = false; | |
bool keyboardTimeWasValid = d->keyboardInputTime.isValid(); | |
qint64 keyboardInputTimeElapsed = d->keyboardInputTime.restart(); | |
if (search.isEmpty() || !keyboardTimeWasValid | |
|| keyboardInputTimeElapsed > QApplication::keyboardInputInterval()) { | |
d->keyboardInput = search; | |
skipRow = currentIndex().isValid(); //if it is not valid we should really start at QModelIndex(0,0) | |
} else { | |
d->keyboardInput += search; | |
} | |
// special case for searches with same key like 'aaaaa' | |
bool sameKey = false; | |
if (d->keyboardInput.length() > 1) { | |
int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.length() - 1)); | |
sameKey = (c == d->keyboardInput.length()); | |
if (sameKey) | |
skipRow = true; | |
} | |
// skip if we are searching for the same key or a new search started | |
if (skipRow) { | |
if (indexBelow(start).isValid()) | |
start = indexBelow(start); | |
else | |
start = d->model->index(0, start.column(), d->root); | |
} | |
d->executePostedLayout(); | |
int startIndex = d->viewIndex(start); | |
if (startIndex <= -1) | |
return; | |
int previousLevel = -1; | |
int bestAbove = -1; | |
int bestBelow = -1; | |
QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput; | |
for (int i = 0; i < d->viewItems.count(); ++i) { | |
if ((int)d->viewItems.at(i).level > previousLevel) { | |
QModelIndex searchFrom = d->viewItems.at(i).index; | |
if (searchFrom.parent() == start.parent()) | |
searchFrom = start; | |
QModelIndexList match = d->model->match(searchFrom, Qt::DisplayRole, searchString); | |
if (match.count()) { | |
int hitIndex = d->viewIndex(match.at(0)); | |
if (hitIndex >= 0 && hitIndex < startIndex) | |
bestAbove = bestAbove == -1 ? hitIndex : qMin(hitIndex, bestAbove); | |
else if (hitIndex >= startIndex) | |
bestBelow = bestBelow == -1 ? hitIndex : qMin(hitIndex, bestBelow); | |
} | |
} | |
previousLevel = d->viewItems.at(i).level; | |
} | |
QModelIndex index; | |
if (bestBelow > -1) | |
index = d->viewItems.at(bestBelow).index; | |
else if (bestAbove > -1) | |
index = d->viewItems.at(bestAbove).index; | |
if (index.isValid()) { | |
QItemSelectionModel::SelectionFlags flags = (d->selectionMode == SingleSelection | |
? QItemSelectionModel::SelectionFlags( | |
QItemSelectionModel::ClearAndSelect | |
|d->selectionBehaviorFlags()) | |
: QItemSelectionModel::SelectionFlags( | |
QItemSelectionModel::NoUpdate)); | |
selectionModel()->setCurrentIndex(index, flags); | |
} | |
} | |
/*! | |
Returns the rectangle on the viewport occupied by the item at \a index. | |
If the index is not visible or explicitly hidden, the returned rectangle is invalid. | |
*/ | |
QRect QTreeView::visualRect(const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
if (!d->isIndexValid(index) || isIndexHidden(index)) | |
return QRect(); | |
d->executePostedLayout(); | |
int vi = d->viewIndex(index); | |
if (vi < 0) | |
return QRect(); | |
bool spanning = d->viewItems.at(vi).spanning; | |
// if we have a spanning item, make the selection stretch from left to right | |
int x = (spanning ? 0 : columnViewportPosition(index.column())); | |
int w = (spanning ? d->header->length() : columnWidth(index.column())); | |
// handle indentation | |
if (index.column() == 0) { | |
int i = d->indentationForItem(vi); | |
w -= i; | |
if (!isRightToLeft()) | |
x += i; | |
} | |
int y = d->coordinateForItem(vi); | |
int h = d->itemHeight(vi); | |
return QRect(x, y, w, h); | |
} | |
/*! | |
Scroll the contents of the tree view until the given model item | |
\a index is visible. The \a hint parameter specifies more | |
precisely where the item should be located after the | |
operation. | |
If any of the parents of the model item are collapsed, they will | |
be expanded to ensure that the model item is visible. | |
*/ | |
void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint) | |
{ | |
Q_D(QTreeView); | |
if (!d->isIndexValid(index)) | |
return; | |
d->executePostedLayout(); | |
d->updateScrollBars(); | |
// Expand all parents if the parent(s) of the node are not expanded. | |
QModelIndex parent = index.parent(); | |
while (parent.isValid() && state() == NoState && d->itemsExpandable) { | |
if (!isExpanded(parent)) | |
expand(parent); | |
parent = d->model->parent(parent); | |
} | |
int item = d->viewIndex(index); | |
if (item < 0) | |
return; | |
QRect area = d->viewport->rect(); | |
// vertical | |
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { | |
int top = verticalScrollBar()->value(); | |
int bottom = top + verticalScrollBar()->pageStep(); | |
if (hint == EnsureVisible && item >= top && item < bottom) { | |
// nothing to do | |
} else if (hint == PositionAtTop || (hint == EnsureVisible && item < top)) { | |
verticalScrollBar()->setValue(item); | |
} else { // PositionAtBottom or PositionAtCenter | |
const int currentItemHeight = d->itemHeight(item); | |
int y = (hint == PositionAtCenter | |
//we center on the current item with a preference to the top item (ie. -1) | |
? area.height() / 2 + currentItemHeight - 1 | |
//otherwise we simply take the whole space | |
: area.height()); | |
if (y > currentItemHeight) { | |
while (item >= 0) { | |
y -= d->itemHeight(item); | |
if (y < 0) { //there is no more space left | |
item++; | |
break; | |
} | |
item--; | |
} | |
} | |
verticalScrollBar()->setValue(item); | |
} | |
} else { // ScrollPerPixel | |
QRect rect(columnViewportPosition(index.column()), | |
d->coordinateForItem(item), // ### slow for items outside the view | |
columnWidth(index.column()), | |
d->itemHeight(item)); | |
if (rect.isEmpty()) { | |
// nothing to do | |
} else if (hint == EnsureVisible && area.contains(rect)) { | |
d->viewport->update(rect); | |
// nothing to do | |
} else { | |
bool above = (hint == EnsureVisible | |
&& (rect.top() < area.top() | |
|| area.height() < rect.height())); | |
bool below = (hint == EnsureVisible | |
&& rect.bottom() > area.bottom() | |
&& rect.height() < area.height()); | |
int verticalValue = verticalScrollBar()->value(); | |
if (hint == PositionAtTop || above) | |
verticalValue += rect.top(); | |
else if (hint == PositionAtBottom || below) | |
verticalValue += rect.bottom() - area.height(); | |
else if (hint == PositionAtCenter) | |
verticalValue += rect.top() - ((area.height() - rect.height()) / 2); | |
verticalScrollBar()->setValue(verticalValue); | |
} | |
} | |
// horizontal | |
int viewportWidth = d->viewport->width(); | |
int horizontalOffset = d->header->offset(); | |
int horizontalPosition = d->header->sectionPosition(index.column()); | |
int cellWidth = d->header->sectionSize(index.column()); | |
if (hint == PositionAtCenter) { | |
horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2)); | |
} else { | |
if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth) | |
horizontalScrollBar()->setValue(horizontalPosition); | |
else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth) | |
horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth); | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::timerEvent(QTimerEvent *event) | |
{ | |
Q_D(QTreeView); | |
if (event->timerId() == d->columnResizeTimerID) { | |
updateGeometries(); | |
killTimer(d->columnResizeTimerID); | |
d->columnResizeTimerID = 0; | |
QRect rect; | |
int viewportHeight = d->viewport->height(); | |
int viewportWidth = d->viewport->width(); | |
for (int i = d->columnsToUpdate.size() - 1; i >= 0; --i) { | |
int column = d->columnsToUpdate.at(i); | |
int x = columnViewportPosition(column); | |
if (isRightToLeft()) | |
rect |= QRect(0, 0, x + columnWidth(column), viewportHeight); | |
else | |
rect |= QRect(x, 0, viewportWidth - x, viewportHeight); | |
} | |
d->viewport->update(rect.normalized()); | |
d->columnsToUpdate.clear(); | |
} else if (event->timerId() == d->openTimer.timerId()) { | |
QPoint pos = d->viewport->mapFromGlobal(QCursor::pos()); | |
if (state() == QAbstractItemView::DraggingState | |
&& d->viewport->rect().contains(pos)) { | |
QModelIndex index = indexAt(pos); | |
setExpanded(index, !isExpanded(index)); | |
} | |
d->openTimer.stop(); | |
} | |
QAbstractItemView::timerEvent(event); | |
} | |
/*! | |
\reimp | |
*/ | |
#ifndef QT_NO_DRAGANDDROP | |
void QTreeView::dragMoveEvent(QDragMoveEvent *event) | |
{ | |
Q_D(QTreeView); | |
if (d->autoExpandDelay >= 0) | |
d->openTimer.start(d->autoExpandDelay, this); | |
QAbstractItemView::dragMoveEvent(event); | |
} | |
#endif | |
/*! | |
\reimp | |
*/ | |
bool QTreeView::viewportEvent(QEvent *event) | |
{ | |
Q_D(QTreeView); | |
switch (event->type()) { | |
case QEvent::HoverEnter: | |
case QEvent::HoverLeave: | |
case QEvent::HoverMove: { | |
QHoverEvent *he = static_cast<QHoverEvent*>(event); | |
int oldBranch = d->hoverBranch; | |
d->hoverBranch = d->itemDecorationAt(he->pos()); | |
if (oldBranch != d->hoverBranch) { | |
//we need to paint the whole items (including the decoration) so that when the user | |
//moves the mouse over those elements they are updated | |
if (oldBranch >= 0) { | |
int y = d->coordinateForItem(oldBranch); | |
int h = d->itemHeight(oldBranch); | |
viewport()->update(QRect(0, y, viewport()->width(), h)); | |
} | |
if (d->hoverBranch >= 0) { | |
int y = d->coordinateForItem(d->hoverBranch); | |
int h = d->itemHeight(d->hoverBranch); | |
viewport()->update(QRect(0, y, viewport()->width(), h)); | |
} | |
} | |
break; } | |
default: | |
break; | |
} | |
return QAbstractItemView::viewportEvent(event); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::paintEvent(QPaintEvent *event) | |
{ | |
Q_D(QTreeView); | |
d->executePostedLayout(); | |
QPainter painter(viewport()); | |
#ifndef QT_NO_ANIMATION | |
if (d->isAnimating()) { | |
drawTree(&painter, event->region() - d->animatedOperation.rect()); | |
d->drawAnimatedOperation(&painter); | |
} else | |
#endif //QT_NO_ANIMATION | |
{ | |
drawTree(&painter, event->region()); | |
#ifndef QT_NO_DRAGANDDROP | |
d->paintDropIndicator(&painter); | |
#endif | |
} | |
} | |
void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const | |
{ | |
Q_Q(const QTreeView); | |
if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q)) | |
return; | |
int rowHeight = defaultItemHeight; | |
if (rowHeight <= 0) { | |
rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height(); | |
if (rowHeight <= 0) | |
return; | |
} | |
while (y <= bottom) { | |
option->rect.setRect(0, y, viewport->width(), rowHeight); | |
if (current & 1) { | |
option->features |= QStyleOptionViewItemV2::Alternate; | |
} else { | |
option->features &= ~QStyleOptionViewItemV2::Alternate; | |
} | |
++current; | |
q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q); | |
y += rowHeight; | |
} | |
} | |
bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos) | |
{ | |
Q_Q(QTreeView); | |
// we want to handle mousePress in EditingState (persistent editors) | |
if ((state != QAbstractItemView::NoState | |
&& state != QAbstractItemView::EditingState) | |
|| !viewport->rect().contains(pos)) | |
return true; | |
int i = itemDecorationAt(pos); | |
if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) { | |
if (viewItems.at(i).expanded) | |
collapse(i, true); | |
else | |
expand(i, true); | |
if (!isAnimating()) { | |
q->updateGeometries(); | |
viewport->update(); | |
} | |
return true; | |
} | |
return false; | |
} | |
void QTreeViewPrivate::_q_modelDestroyed() | |
{ | |
//we need to clear that list because it contais QModelIndex to | |
//the model currently being destroyed | |
viewItems.clear(); | |
QAbstractItemViewPrivate::_q_modelDestroyed(); | |
} | |
/*! | |
\reimp | |
We have a QTreeView way of knowing what elements are on the viewport | |
*/ | |
QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const | |
{ | |
Q_ASSERT(r); | |
return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r); | |
Q_Q(const QTreeView); | |
QRect &rect = *r; | |
const QRect viewportRect = viewport->rect(); | |
int itemOffset = 0; | |
int row = firstVisibleItem(&itemOffset); | |
QPair<int, int> startEnd = startAndEndColumns(viewportRect); | |
QVector<int> columns; | |
for (int i = startEnd.first; i <= startEnd.second; ++i) { | |
int logical = header->logicalIndex(i); | |
if (!header->isSectionHidden(logical)) | |
columns += logical; | |
} | |
QSet<QModelIndex> visibleIndexes; | |
for (; itemOffset < viewportRect.bottom() && row < viewItems.count(); ++row) { | |
const QModelIndex &index = viewItems.at(row).index; | |
for (int colIndex = 0; colIndex < columns.count(); ++colIndex) | |
visibleIndexes += index.sibling(index.row(), columns.at(colIndex)); | |
itemOffset += itemHeight(row); | |
} | |
//now that we have the visible indexes, we can try to find those which are selected | |
QItemViewPaintPairs ret; | |
for (int i = 0; i < indexes.count(); ++i) { | |
const QModelIndex &index = indexes.at(i); | |
if (visibleIndexes.contains(index)) { | |
const QRect current = q->visualRect(index); | |
ret += qMakePair(current, index); | |
rect |= current; | |
} | |
} | |
rect &= viewportRect; | |
return ret; | |
} | |
/*! | |
\since 4.2 | |
Draws the part of the tree intersecting the given \a region using the specified | |
\a painter. | |
\sa paintEvent() | |
*/ | |
void QTreeView::drawTree(QPainter *painter, const QRegion ®ion) const | |
{ | |
Q_D(const QTreeView); | |
const QVector<QTreeViewItem> viewItems = d->viewItems; | |
QStyleOptionViewItemV4 option = d->viewOptionsV4(); | |
const QStyle::State state = option.state; | |
d->current = 0; | |
if (viewItems.count() == 0 || d->header->count() == 0 || !d->itemDelegate) { | |
d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); | |
return; | |
} | |
int firstVisibleItemOffset = 0; | |
const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset); | |
if (firstVisibleItem < 0) { | |
d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); | |
return; | |
} | |
const int viewportWidth = d->viewport->width(); | |
QVector<QRect> rects = region.rects(); | |
QVector<int> drawn; | |
bool multipleRects = (rects.size() > 1); | |
for (int a = 0; a < rects.size(); ++a) { | |
const QRect area = (multipleRects | |
? QRect(0, rects.at(a).y(), viewportWidth, rects.at(a).height()) | |
: rects.at(a)); | |
d->leftAndRight = d->startAndEndColumns(area); | |
int i = firstVisibleItem; // the first item at the top of the viewport | |
int y = firstVisibleItemOffset; // we may only see part of the first item | |
// start at the top of the viewport and iterate down to the update area | |
for (; i < viewItems.count(); ++i) { | |
const int itemHeight = d->itemHeight(i); | |
if (y + itemHeight > area.top()) | |
break; | |
y += itemHeight; | |
} | |
// paint the visible rows | |
for (; i < viewItems.count() && y <= area.bottom(); ++i) { | |
const int itemHeight = d->itemHeight(i); | |
option.rect.setRect(0, y, viewportWidth, itemHeight); | |
option.state = state | (viewItems.at(i).expanded ? QStyle::State_Open : QStyle::State_None) | |
| (viewItems.at(i).hasChildren ? QStyle::State_Children : QStyle::State_None) | |
| (viewItems.at(i).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None); | |
d->current = i; | |
d->spanning = viewItems.at(i).spanning; | |
if (!multipleRects || !drawn.contains(i)) { | |
drawRow(painter, option, viewItems.at(i).index); | |
if (multipleRects) // even if the rect only intersects the item, | |
drawn.append(i); // the entire item will be painted | |
} | |
y += itemHeight; | |
} | |
if (y <= area.bottom()) { | |
d->current = i; | |
d->paintAlternatingRowColors(painter, &option, y, area.bottom()); | |
} | |
} | |
} | |
/// ### move to QObject :) | |
static inline bool ancestorOf(QObject *widget, QObject *other) | |
{ | |
for (QObject *parent = other; parent != 0; parent = parent->parent()) { | |
if (parent == widget) | |
return true; | |
} | |
return false; | |
} | |
/*! | |
Draws the row in the tree view that contains the model item \a index, | |
using the \a painter given. The \a option control how the item is | |
displayed. | |
\sa setAlternatingRowColors() | |
*/ | |
void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, | |
const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
QStyleOptionViewItemV4 opt = option; | |
const QPoint offset = d->scrollDelayOffset; | |
const int y = option.rect.y() + offset.y(); | |
const QModelIndex parent = index.parent(); | |
const QHeaderView *header = d->header; | |
const QModelIndex current = currentIndex(); | |
const QModelIndex hover = d->hover; | |
const bool reverse = isRightToLeft(); | |
const QStyle::State state = opt.state; | |
const bool spanning = d->spanning; | |
const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first); | |
const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second); | |
const bool alternate = d->alternatingColors; | |
const bool enabled = (state & QStyle::State_Enabled) != 0; | |
const bool allColumnsShowFocus = d->allColumnsShowFocus; | |
// when the row contains an index widget which has focus, | |
// we want to paint the entire row as active | |
bool indexWidgetHasFocus = false; | |
if ((current.row() == index.row()) && !d->editors.isEmpty()) { | |
const int r = index.row(); | |
QWidget *fw = QApplication::focusWidget(); | |
for (int c = 0; c < header->count(); ++c) { | |
QModelIndex idx = d->model->index(r, c, parent); | |
if (QWidget *editor = indexWidget(idx)) { | |
if (ancestorOf(editor, fw)) { | |
indexWidgetHasFocus = true; | |
break; | |
} | |
} | |
} | |
} | |
const bool widgetHasFocus = hasFocus(); | |
bool currentRowHasFocus = false; | |
if (allColumnsShowFocus && widgetHasFocus && current.isValid()) { | |
// check if the focus index is before or after the visible columns | |
const int r = index.row(); | |
for (int c = 0; c < left && !currentRowHasFocus; ++c) { | |
QModelIndex idx = d->model->index(r, c, parent); | |
currentRowHasFocus = (idx == current); | |
} | |
QModelIndex parent = d->model->parent(index); | |
for (int c = right; c < header->count() && !currentRowHasFocus; ++c) { | |
currentRowHasFocus = (d->model->index(r, c, parent) == current); | |
} | |
} | |
// ### special case: treeviews with multiple columns draw | |
// the selections differently than with only one column | |
opt.showDecorationSelected = (d->selectionBehavior & SelectRows) | |
|| option.showDecorationSelected; | |
int width, height = option.rect.height(); | |
int position; | |
QModelIndex modelIndex; | |
int columnCount = header->count(); | |
const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows | |
&& index.parent() == hover.parent() | |
&& index.row() == hover.row(); | |
/* 'left' and 'right' are the left-most and right-most visible visual indices. | |
Compute the first visible logical indices before and after the left and right. | |
We will use these values to determine the QStyleOptionViewItemV4::viewItemPosition. */ | |
int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1; | |
for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) { | |
int logicalIndex = header->logicalIndex(visualIndex); | |
if (!header->isSectionHidden(logicalIndex)) { | |
logicalIndexBeforeLeft = logicalIndex; | |
break; | |
} | |
} | |
QVector<int> logicalIndices; // vector of currently visibly logical indices | |
for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) { | |
int logicalIndex = header->logicalIndex(visualIndex); | |
if (!header->isSectionHidden(logicalIndex)) { | |
if (visualIndex > right) { | |
logicalIndexAfterRight = logicalIndex; | |
break; | |
} | |
logicalIndices.append(logicalIndex); | |
} | |
} | |
for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection) { | |
int headerSection = logicalIndices.at(currentLogicalSection); | |
position = columnViewportPosition(headerSection) + offset.x(); | |
width = header->sectionSize(headerSection); | |
if (spanning) { | |
int lastSection = header->logicalIndex(header->count() - 1); | |
if (!reverse) { | |
width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position; | |
} else { | |
width += position - columnViewportPosition(lastSection); | |
position = columnViewportPosition(lastSection); | |
} | |
} | |
modelIndex = d->model->index(index.row(), headerSection, parent); | |
if (!modelIndex.isValid()) | |
continue; | |
opt.state = state; | |
// determine the viewItemPosition depending on the position of column 0 | |
int nextLogicalSection = currentLogicalSection + 1 >= logicalIndices.count() | |
? logicalIndexAfterRight | |
: logicalIndices.at(currentLogicalSection + 1); | |
int prevLogicalSection = currentLogicalSection - 1 < 0 | |
? logicalIndexBeforeLeft | |
: logicalIndices.at(currentLogicalSection - 1); | |
if (columnCount == 1 || (nextLogicalSection == 0 && prevLogicalSection == -1) | |
|| (headerSection == 0 && nextLogicalSection == -1) || spanning) | |
opt.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; | |
else if (headerSection == 0 || (nextLogicalSection != 0 && prevLogicalSection == -1)) | |
opt.viewItemPosition = QStyleOptionViewItemV4::Beginning; | |
else if (nextLogicalSection == 0 || nextLogicalSection == -1) | |
opt.viewItemPosition = QStyleOptionViewItemV4::End; | |
else | |
opt.viewItemPosition = QStyleOptionViewItemV4::Middle; | |
// fake activeness when row editor has focus | |
if (indexWidgetHasFocus) | |
opt.state |= QStyle::State_Active; | |
if (d->selectionModel->isSelected(modelIndex)) | |
opt.state |= QStyle::State_Selected; | |
if (widgetHasFocus && (current == modelIndex)) { | |
if (allColumnsShowFocus) | |
currentRowHasFocus = true; | |
else | |
opt.state |= QStyle::State_HasFocus; | |
} | |
if ((hoverRow || modelIndex == hover) | |
&& (option.showDecorationSelected || (d->hoverBranch == -1))) | |
opt.state |= QStyle::State_MouseOver; | |
else | |
opt.state &= ~QStyle::State_MouseOver; | |
if (enabled) { | |
QPalette::ColorGroup cg; | |
if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) { | |
opt.state &= ~QStyle::State_Enabled; | |
cg = QPalette::Disabled; | |
} else if (opt.state & QStyle::State_Active) { | |
cg = QPalette::Active; | |
} else { | |
cg = QPalette::Inactive; | |
} | |
opt.palette.setCurrentColorGroup(cg); | |
} | |
if (alternate) { | |
if (d->current & 1) { | |
opt.features |= QStyleOptionViewItemV2::Alternate; | |
} else { | |
opt.features &= ~QStyleOptionViewItemV2::Alternate; | |
} | |
} | |
/* Prior to Qt 4.3, the background of the branch (in selected state and | |
alternate row color was provided by the view. For backward compatibility, | |
this is now delegated to the style using PE_PanelViewItemRow which | |
does the appropriate fill */ | |
if (headerSection == 0) { | |
const int i = d->indentationForItem(d->current); | |
QRect branches(reverse ? position + width - i : position, y, i, height); | |
const bool setClipRect = branches.width() > width; | |
if (setClipRect) { | |
painter->save(); | |
painter->setClipRect(QRect(position, y, width, height)); | |
} | |
// draw background for the branch (selection + alternate row) | |
opt.rect = branches; | |
style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); | |
// draw background of the item (only alternate row). rest of the background | |
// is provided by the delegate | |
QStyle::State oldState = opt.state; | |
opt.state &= ~QStyle::State_Selected; | |
opt.rect.setRect(reverse ? position : i + position, y, width - i, height); | |
style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); | |
opt.state = oldState; | |
drawBranches(painter, branches, index); | |
if (setClipRect) | |
painter->restore(); | |
} else { | |
QStyle::State oldState = opt.state; | |
opt.state &= ~QStyle::State_Selected; | |
opt.rect.setRect(position, y, width, height); | |
style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); | |
opt.state = oldState; | |
} | |
if (const QWidget *widget = d->editorForIndex(modelIndex).editor) { | |
painter->save(); | |
painter->setClipRect(widget->geometry()); | |
d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex); | |
painter->restore(); | |
} else { | |
d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex); | |
} | |
} | |
if (currentRowHasFocus) { | |
QStyleOptionFocusRect o; | |
o.QStyleOption::operator=(option); | |
o.state |= QStyle::State_KeyboardFocusChange; | |
QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) | |
? QPalette::Normal : QPalette::Disabled; | |
o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index) | |
? QPalette::Highlight : QPalette::Background); | |
int x = 0; | |
if (!option.showDecorationSelected) | |
x = header->sectionPosition(0) + d->indentationForItem(d->current); | |
QRect focusRect(x - header->offset(), y, header->length() - x, height); | |
o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect); | |
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); | |
// if we show focus on all columns and the first section is moved, | |
// we have to split the focus rect into two rects | |
if (allColumnsShowFocus && !option.showDecorationSelected | |
&& header->sectionsMoved() && (header->visualIndex(0) != 0)) { | |
QRect sectionRect(0, y, header->sectionPosition(0), height); | |
o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect); | |
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); | |
} | |
} | |
} | |
/*! | |
Draws the branches in the tree view on the same row as the model item | |
\a index, using the \a painter given. The branches are drawn in the | |
rectangle specified by \a rect. | |
*/ | |
void QTreeView::drawBranches(QPainter *painter, const QRect &rect, | |
const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
const bool reverse = isRightToLeft(); | |
const int indent = d->indent; | |
const int outer = d->rootDecoration ? 0 : 1; | |
const int item = d->current; | |
const QTreeViewItem &viewItem = d->viewItems.at(item); | |
int level = viewItem.level; | |
QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height()); | |
QModelIndex parent = index.parent(); | |
QModelIndex current = parent; | |
QModelIndex ancestor = current.parent(); | |
QStyleOptionViewItemV2 opt = viewOptions(); | |
QStyle::State extraFlags = QStyle::State_None; | |
if (isEnabled()) | |
extraFlags |= QStyle::State_Enabled; | |
if (window()->isActiveWindow()) | |
extraFlags |= QStyle::State_Active; | |
QPoint oldBO = painter->brushOrigin(); | |
if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel) | |
painter->setBrushOrigin(QPoint(0, verticalOffset())); | |
if (d->alternatingColors) { | |
if (d->current & 1) { | |
opt.features |= QStyleOptionViewItemV2::Alternate; | |
} else { | |
opt.features &= ~QStyleOptionViewItemV2::Alternate; | |
} | |
} | |
// When hovering over a row, pass State_Hover for painting the branch | |
// indicators if it has the decoration (aka branch) selected. | |
bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows | |
&& opt.showDecorationSelected | |
&& index.parent() == d->hover.parent() | |
&& index.row() == d->hover.row(); | |
if (d->selectionModel->isSelected(index)) | |
extraFlags |= QStyle::State_Selected; | |
if (level >= outer) { | |
// start with the innermost branch | |
primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent); | |
opt.rect = primitive; | |
const bool expanded = viewItem.expanded; | |
const bool children = viewItem.hasChildren; | |
bool moreSiblings = viewItem.hasMoreSiblings; | |
opt.state = QStyle::State_Item | extraFlags | |
| (moreSiblings ? QStyle::State_Sibling : QStyle::State_None) | |
| (children ? QStyle::State_Children : QStyle::State_None) | |
| (expanded ? QStyle::State_Open : QStyle::State_None); | |
if (hoverRow || item == d->hoverBranch) | |
opt.state |= QStyle::State_MouseOver; | |
else | |
opt.state &= ~QStyle::State_MouseOver; | |
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); | |
} | |
// then go out level by level | |
for (--level; level >= outer; --level) { // we have already drawn the innermost branch | |
primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent); | |
opt.rect = primitive; | |
opt.state = extraFlags; | |
bool moreSiblings = false; | |
if (d->hiddenIndexes.isEmpty()) { | |
moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row()); | |
} else { | |
int successor = item + viewItem.total + 1; | |
while (successor < d->viewItems.size() | |
&& d->viewItems.at(successor).level >= uint(level)) { | |
const QTreeViewItem &successorItem = d->viewItems.at(successor); | |
if (successorItem.level == uint(level)) { | |
moreSiblings = true; | |
break; | |
} | |
successor += successorItem.total + 1; | |
} | |
} | |
if (moreSiblings) | |
opt.state |= QStyle::State_Sibling; | |
if (hoverRow || item == d->hoverBranch) | |
opt.state |= QStyle::State_MouseOver; | |
else | |
opt.state &= ~QStyle::State_MouseOver; | |
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); | |
current = ancestor; | |
ancestor = current.parent(); | |
} | |
painter->setBrushOrigin(oldBO); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::mousePressEvent(QMouseEvent *event) | |
{ | |
Q_D(QTreeView); | |
bool handled = false; | |
if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonPress) | |
handled = d->expandOrCollapseItemAtPos(event->pos()); | |
if (!handled && d->itemDecorationAt(event->pos()) == -1) | |
QAbstractItemView::mousePressEvent(event); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::mouseReleaseEvent(QMouseEvent *event) | |
{ | |
Q_D(QTreeView); | |
if (d->itemDecorationAt(event->pos()) == -1) { | |
QAbstractItemView::mouseReleaseEvent(event); | |
} else { | |
if (state() == QAbstractItemView::DragSelectingState) | |
setState(QAbstractItemView::NoState); | |
if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonRelease) | |
d->expandOrCollapseItemAtPos(event->pos()); | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::mouseDoubleClickEvent(QMouseEvent *event) | |
{ | |
Q_D(QTreeView); | |
if (state() != NoState || !d->viewport->rect().contains(event->pos())) | |
return; | |
int i = d->itemDecorationAt(event->pos()); | |
if (i == -1) { | |
i = d->itemAtCoordinate(event->y()); | |
if (i == -1) | |
return; // user clicked outside the items | |
const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index; | |
const QPersistentModelIndex persistent = indexAt(event->pos()); | |
if (d->pressedIndex != persistent) { | |
mousePressEvent(event); | |
return; | |
} | |
// signal handlers may change the model | |
emit doubleClicked(persistent); | |
if (!persistent.isValid()) | |
return; | |
if (edit(persistent, DoubleClicked, event) || state() != NoState) | |
return; // the double click triggered editing | |
if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this)) | |
emit activated(persistent); | |
d->executePostedLayout(); // we need to make sure viewItems is updated | |
if (d->itemsExpandable | |
&& d->expandsOnDoubleClick | |
&& d->hasVisibleChildren(persistent)) { | |
if (!((i < d->viewItems.count()) && (d->viewItems.at(i).index == firstColumnIndex))) { | |
// find the new index of the item | |
for (i = 0; i < d->viewItems.count(); ++i) { | |
if (d->viewItems.at(i).index == firstColumnIndex) | |
break; | |
} | |
if (i == d->viewItems.count()) | |
return; | |
} | |
if (d->viewItems.at(i).expanded) | |
d->collapse(i, true); | |
else | |
d->expand(i, true); | |
updateGeometries(); | |
viewport()->update(); | |
} | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::mouseMoveEvent(QMouseEvent *event) | |
{ | |
Q_D(QTreeView); | |
if (d->itemDecorationAt(event->pos()) == -1) // ### what about expanding/collapsing state ? | |
QAbstractItemView::mouseMoveEvent(event); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::keyPressEvent(QKeyEvent *event) | |
{ | |
Q_D(QTreeView); | |
QModelIndex current = currentIndex(); | |
//this is the management of the expansion | |
if (d->isIndexValid(current) && d->model && d->itemsExpandable) { | |
switch (event->key()) { | |
case Qt::Key_Asterisk: { | |
QStack<QModelIndex> parents; | |
parents.push(current); | |
while (!parents.isEmpty()) { | |
QModelIndex parent = parents.pop(); | |
for (int row = 0; row < d->model->rowCount(parent); ++row) { | |
QModelIndex child = d->model->index(row, 0, parent); | |
if (!d->isIndexValid(child)) | |
break; | |
parents.push(child); | |
expand(child); | |
} | |
} | |
expand(current); | |
break; } | |
case Qt::Key_Plus: | |
expand(current); | |
break; | |
case Qt::Key_Minus: | |
collapse(current); | |
break; | |
} | |
} | |
QAbstractItemView::keyPressEvent(event); | |
} | |
/*! | |
\reimp | |
*/ | |
QModelIndex QTreeView::indexAt(const QPoint &point) const | |
{ | |
Q_D(const QTreeView); | |
d->executePostedLayout(); | |
int visualIndex = d->itemAtCoordinate(point.y()); | |
QModelIndex idx = d->modelIndex(visualIndex); | |
if (!idx.isValid()) | |
return QModelIndex(); | |
if (d->viewItems.at(visualIndex).spanning) | |
return idx; | |
int column = d->columnAt(point.x()); | |
if (column == idx.column()) | |
return idx; | |
if (column < 0) | |
return QModelIndex(); | |
return idx.sibling(idx.row(), column); | |
} | |
/*! | |
Returns the model index of the item above \a index. | |
*/ | |
QModelIndex QTreeView::indexAbove(const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
if (!d->isIndexValid(index)) | |
return QModelIndex(); | |
d->executePostedLayout(); | |
int i = d->viewIndex(index); | |
if (--i < 0) | |
return QModelIndex(); | |
return d->viewItems.at(i).index; | |
} | |
/*! | |
Returns the model index of the item below \a index. | |
*/ | |
QModelIndex QTreeView::indexBelow(const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
if (!d->isIndexValid(index)) | |
return QModelIndex(); | |
d->executePostedLayout(); | |
int i = d->viewIndex(index); | |
if (++i >= d->viewItems.count()) | |
return QModelIndex(); | |
return d->viewItems.at(i).index; | |
} | |
/*! | |
\internal | |
Lays out the items in the tree view. | |
*/ | |
void QTreeView::doItemsLayout() | |
{ | |
Q_D(QTreeView); | |
if (d->hasRemovedItems) { | |
//clean the QSet that may contains old (and this invalid) indexes | |
d->hasRemovedItems = false; | |
QSet<QPersistentModelIndex>::iterator it = d->expandedIndexes.begin(); | |
while (it != d->expandedIndexes.constEnd()) { | |
if (!it->isValid()) | |
it = d->expandedIndexes.erase(it); | |
else | |
++it; | |
} | |
it = d->hiddenIndexes.begin(); | |
while (it != d->hiddenIndexes.constEnd()) { | |
if (!it->isValid()) | |
it = d->hiddenIndexes.erase(it); | |
else | |
++it; | |
} | |
} | |
d->viewItems.clear(); // prepare for new layout | |
QModelIndex parent = d->root; | |
if (d->model->hasChildren(parent)) { | |
d->layout(-1); | |
} | |
QAbstractItemView::doItemsLayout(); | |
d->header->doItemsLayout(); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::reset() | |
{ | |
Q_D(QTreeView); | |
d->expandedIndexes.clear(); | |
d->hiddenIndexes.clear(); | |
d->spanningIndexes.clear(); | |
d->viewItems.clear(); | |
QAbstractItemView::reset(); | |
} | |
/*! | |
Returns the horizontal offset of the items in the treeview. | |
Note that the tree view uses the horizontal header section | |
positions to determine the positions of columns in the view. | |
\sa verticalOffset() | |
*/ | |
int QTreeView::horizontalOffset() const | |
{ | |
Q_D(const QTreeView); | |
return d->header->offset(); | |
} | |
/*! | |
Returns the vertical offset of the items in the tree view. | |
\sa horizontalOffset() | |
*/ | |
int QTreeView::verticalOffset() const | |
{ | |
Q_D(const QTreeView); | |
if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) { | |
if (d->uniformRowHeights) | |
return verticalScrollBar()->value() * d->defaultItemHeight; | |
// If we are scrolling per item and have non-uniform row heights, | |
// finding the vertical offset in pixels is going to be relatively slow. | |
// ### find a faster way to do this | |
d->executePostedLayout(); | |
int offset = 0; | |
for (int i = 0; i < d->viewItems.count(); ++i) { | |
if (i == verticalScrollBar()->value()) | |
return offset; | |
offset += d->itemHeight(i); | |
} | |
return 0; | |
} | |
// scroll per pixel | |
return verticalScrollBar()->value(); | |
} | |
/*! | |
Move the cursor in the way described by \a cursorAction, using the | |
information provided by the button \a modifiers. | |
*/ | |
QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) | |
{ | |
Q_D(QTreeView); | |
Q_UNUSED(modifiers); | |
d->executePostedLayout(); | |
QModelIndex current = currentIndex(); | |
if (!current.isValid()) { | |
int i = d->below(-1); | |
int c = 0; | |
while (c < d->header->count() && d->header->isSectionHidden(c)) | |
++c; | |
if (i < d->viewItems.count() && c < d->header->count()) { | |
return d->modelIndex(i, c); | |
} | |
return QModelIndex(); | |
} | |
int vi = -1; | |
#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) | |
// Selection behavior is slightly different on the Mac. | |
if (d->selectionMode == QAbstractItemView::ExtendedSelection | |
&& d->selectionModel | |
&& d->selectionModel->hasSelection()) { | |
const bool moveUpDown = (cursorAction == MoveUp || cursorAction == MoveDown); | |
const bool moveNextPrev = (cursorAction == MoveNext || cursorAction == MovePrevious); | |
const bool contiguousSelection = moveUpDown && (modifiers & Qt::ShiftModifier); | |
// Use the outermost index in the selection as the current index | |
if (!contiguousSelection && (moveUpDown || moveNextPrev)) { | |
// Find outermost index. | |
const bool useTopIndex = (cursorAction == MoveUp || cursorAction == MovePrevious); | |
int index = useTopIndex ? INT_MAX : INT_MIN; | |
const QItemSelection selection = d->selectionModel->selection(); | |
for (int i = 0; i < selection.count(); ++i) { | |
const QItemSelectionRange &range = selection.at(i); | |
int candidate = d->viewIndex(useTopIndex ? range.topLeft() : range.bottomRight()); | |
if (candidate >= 0) | |
index = useTopIndex ? qMin(index, candidate) : qMax(index, candidate); | |
} | |
if (index >= 0 && index < INT_MAX) | |
vi = index; | |
} | |
} | |
#endif | |
if (vi < 0) | |
vi = qMax(0, d->viewIndex(current)); | |
if (isRightToLeft()) { | |
if (cursorAction == MoveRight) | |
cursorAction = MoveLeft; | |
else if (cursorAction == MoveLeft) | |
cursorAction = MoveRight; | |
} | |
switch (cursorAction) { | |
case MoveNext: | |
case MoveDown: | |
#ifdef QT_KEYPAD_NAVIGATION | |
if (vi == d->viewItems.count()-1 && QApplication::keypadNavigationEnabled()) | |
return d->model->index(0, current.column(), d->root); | |
#endif | |
return d->modelIndex(d->below(vi), current.column()); | |
case MovePrevious: | |
case MoveUp: | |
#ifdef QT_KEYPAD_NAVIGATION | |
if (vi == 0 && QApplication::keypadNavigationEnabled()) | |
return d->modelIndex(d->viewItems.count() - 1, current.column()); | |
#endif | |
return d->modelIndex(d->above(vi), current.column()); | |
case MoveLeft: { | |
QScrollBar *sb = horizontalScrollBar(); | |
if (vi < d->viewItems.count() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) { | |
d->collapse(vi, true); | |
d->moveCursorUpdatedView = true; | |
} else { | |
bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); | |
if (descend) { | |
QModelIndex par = current.parent(); | |
if (par.isValid() && par != rootIndex()) | |
return par; | |
else | |
descend = false; | |
} | |
if (!descend) { | |
if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { | |
int visualColumn = d->header->visualIndex(current.column()) - 1; | |
while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn))) | |
visualColumn--; | |
int newColumn = d->header->logicalIndex(visualColumn); | |
QModelIndex next = current.sibling(current.row(), newColumn); | |
if (next.isValid()) | |
return next; | |
} | |
int oldValue = sb->value(); | |
sb->setValue(sb->value() - sb->singleStep()); | |
if (oldValue != sb->value()) | |
d->moveCursorUpdatedView = true; | |
} | |
} | |
updateGeometries(); | |
viewport()->update(); | |
break; | |
} | |
case MoveRight: | |
if (vi < d->viewItems.count() && !d->viewItems.at(vi).expanded && d->itemsExpandable | |
&& d->hasVisibleChildren(d->viewItems.at(vi).index)) { | |
d->expand(vi, true); | |
d->moveCursorUpdatedView = true; | |
} else { | |
bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); | |
if (descend) { | |
QModelIndex idx = d->modelIndex(d->below(vi)); | |
if (idx.parent() == current) | |
return idx; | |
else | |
descend = false; | |
} | |
if (!descend) { | |
if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { | |
int visualColumn = d->header->visualIndex(current.column()) + 1; | |
while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn))) | |
visualColumn++; | |
QModelIndex next = current.sibling(current.row(), visualColumn); | |
if (next.isValid()) | |
return next; | |
} | |
//last restort: we change the scrollbar value | |
QScrollBar *sb = horizontalScrollBar(); | |
int oldValue = sb->value(); | |
sb->setValue(sb->value() + sb->singleStep()); | |
if (oldValue != sb->value()) | |
d->moveCursorUpdatedView = true; | |
} | |
} | |
updateGeometries(); | |
viewport()->update(); | |
break; | |
case MovePageUp: | |
return d->modelIndex(d->pageUp(vi), current.column()); | |
case MovePageDown: | |
return d->modelIndex(d->pageDown(vi), current.column()); | |
case MoveHome: | |
return d->model->index(0, current.column(), d->root); | |
case MoveEnd: | |
return d->modelIndex(d->viewItems.count() - 1, current.column()); | |
} | |
return current; | |
} | |
/*! | |
Applies the selection \a command to the items in or touched by the | |
rectangle, \a rect. | |
\sa selectionCommand() | |
*/ | |
void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) | |
{ | |
Q_D(QTreeView); | |
if (!selectionModel() || rect.isNull()) | |
return; | |
d->executePostedLayout(); | |
QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right()) | |
: qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())); | |
QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) : | |
qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())); | |
QModelIndex topLeft = indexAt(tl); | |
QModelIndex bottomRight = indexAt(br); | |
if (!topLeft.isValid() && !bottomRight.isValid()) { | |
if (command & QItemSelectionModel::Clear) | |
selectionModel()->clear(); | |
return; | |
} | |
if (!topLeft.isValid() && !d->viewItems.isEmpty()) | |
topLeft = d->viewItems.first().index; | |
if (!bottomRight.isValid() && !d->viewItems.isEmpty()) { | |
const int column = d->header->logicalIndex(d->header->count() - 1); | |
const QModelIndex index = d->viewItems.last().index; | |
bottomRight = index.sibling(index.row(), column); | |
} | |
if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight)) | |
return; | |
d->select(topLeft, bottomRight, command); | |
} | |
/*! | |
Returns the rectangle from the viewport of the items in the given | |
\a selection. | |
Since 4.7, the returned region only contains rectangles intersecting | |
(or included in) the viewport. | |
*/ | |
QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const | |
{ | |
Q_D(const QTreeView); | |
if (selection.isEmpty()) | |
return QRegion(); | |
QRegion selectionRegion; | |
const QRect &viewportRect = d->viewport->rect(); | |
for (int i = 0; i < selection.count(); ++i) { | |
QItemSelectionRange range = selection.at(i); | |
if (!range.isValid()) | |
continue; | |
QModelIndex parent = range.parent(); | |
QModelIndex leftIndex = range.topLeft(); | |
int columnCount = d->model->columnCount(parent); | |
while (leftIndex.isValid() && isIndexHidden(leftIndex)) { | |
if (leftIndex.column() + 1 < columnCount) | |
leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent); | |
else | |
leftIndex = QModelIndex(); | |
} | |
if (!leftIndex.isValid()) | |
continue; | |
const QRect leftRect = visualRect(leftIndex); | |
int top = leftRect.top(); | |
QModelIndex rightIndex = range.bottomRight(); | |
while (rightIndex.isValid() && isIndexHidden(rightIndex)) { | |
if (rightIndex.column() - 1 >= 0) | |
rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent); | |
else | |
rightIndex = QModelIndex(); | |
} | |
if (!rightIndex.isValid()) | |
continue; | |
const QRect rightRect = visualRect(rightIndex); | |
int bottom = rightRect.bottom(); | |
if (top > bottom) | |
qSwap<int>(top, bottom); | |
int height = bottom - top + 1; | |
if (d->header->sectionsMoved()) { | |
for (int c = range.left(); c <= range.right(); ++c) { | |
const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height); | |
if (viewportRect.intersects(rangeRect)) | |
selectionRegion += rangeRect; | |
} | |
} else { | |
QRect combined = leftRect|rightRect; | |
combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left())); | |
if (viewportRect.intersects(combined)) | |
selectionRegion += combined; | |
} | |
} | |
return selectionRegion; | |
} | |
/*! | |
\reimp | |
*/ | |
QModelIndexList QTreeView::selectedIndexes() const | |
{ | |
QModelIndexList viewSelected; | |
QModelIndexList modelSelected; | |
if (selectionModel()) | |
modelSelected = selectionModel()->selectedIndexes(); | |
for (int i = 0; i < modelSelected.count(); ++i) { | |
// check that neither the parents nor the index is hidden before we add | |
QModelIndex index = modelSelected.at(i); | |
while (index.isValid() && !isIndexHidden(index)) | |
index = index.parent(); | |
if (index.isValid()) | |
continue; | |
viewSelected.append(modelSelected.at(i)); | |
} | |
return viewSelected; | |
} | |
/*! | |
Scrolls the contents of the tree view by (\a dx, \a dy). | |
*/ | |
void QTreeView::scrollContentsBy(int dx, int dy) | |
{ | |
Q_D(QTreeView); | |
d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling | |
dx = isRightToLeft() ? -dx : dx; | |
if (dx) { | |
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { | |
int oldOffset = d->header->offset(); | |
if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) | |
d->header->setOffsetToLastSection(); | |
else | |
d->header->setOffsetToSectionPosition(horizontalScrollBar()->value()); | |
int newOffset = d->header->offset(); | |
dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset; | |
} else { | |
d->header->setOffset(horizontalScrollBar()->value()); | |
} | |
} | |
const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight; | |
if (d->viewItems.isEmpty() || itemHeight == 0) | |
return; | |
// guestimate the number of items in the viewport | |
int viewCount = d->viewport->height() / itemHeight; | |
int maxDeltaY = qMin(d->viewItems.count(), viewCount); | |
// no need to do a lot of work if we are going to redraw the whole thing anyway | |
if (qAbs(dy) > qAbs(maxDeltaY) && d->editors.isEmpty()) { | |
verticalScrollBar()->update(); | |
d->viewport->update(); | |
return; | |
} | |
if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) { | |
int currentScrollbarValue = verticalScrollBar()->value(); | |
int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy) | |
int currentViewIndex = currentScrollbarValue; // the first visible item | |
int previousViewIndex = previousScrollbarValue; | |
const QVector<QTreeViewItem> viewItems = d->viewItems; | |
dy = 0; | |
if (previousViewIndex < currentViewIndex) { // scrolling down | |
for (int i = previousViewIndex; i < currentViewIndex; ++i) { | |
if (i < d->viewItems.count()) | |
dy -= d->itemHeight(i); | |
} | |
} else if (previousViewIndex > currentViewIndex) { // scrolling up | |
for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) { | |
if (i < d->viewItems.count()) | |
dy += d->itemHeight(i); | |
} | |
} | |
} | |
d->scrollContentsBy(dx, dy); | |
} | |
/*! | |
This slot is called whenever a column has been moved. | |
*/ | |
void QTreeView::columnMoved() | |
{ | |
Q_D(QTreeView); | |
updateEditorGeometries(); | |
d->viewport->update(); | |
} | |
/*! | |
\internal | |
*/ | |
void QTreeView::reexpand() | |
{ | |
// do nothing | |
} | |
/*! | |
Informs the view that the rows from the \a start row to the \a end row | |
inclusive have been inserted into the \a parent model item. | |
*/ | |
void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end) | |
{ | |
Q_D(QTreeView); | |
// if we are going to do a complete relayout anyway, there is no need to update | |
if (d->delayedPendingLayout) { | |
QAbstractItemView::rowsInserted(parent, start, end); | |
return; | |
} | |
//don't add a hierarchy on a column != 0 | |
if (parent.column() != 0 && parent.isValid()) { | |
QAbstractItemView::rowsInserted(parent, start, end); | |
return; | |
} | |
const int parentRowCount = d->model->rowCount(parent); | |
const int delta = end - start + 1; | |
if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) { | |
QAbstractItemView::rowsInserted(parent, start, end); | |
return; | |
} | |
const int parentItem = d->viewIndex(parent); | |
if (((parentItem != -1) && d->viewItems.at(parentItem).expanded && updatesEnabled()) | |
|| (parent == d->root)) { | |
d->doDelayedItemsLayout(); | |
} else if ((parentItem != -1) && d->viewItems.at(parentItem).expanded) { | |
d->doDelayedItemsLayout(); | |
} else if (parentItem != -1 && (d->model->rowCount(parent) == end - start + 1)) { | |
// the parent just went from 0 children to more. update to re-paint the decoration | |
d->viewItems[parentItem].hasChildren = true; | |
viewport()->update(); | |
} | |
QAbstractItemView::rowsInserted(parent, start, end); | |
} | |
/*! | |
Informs the view that the rows from the \a start row to the \a end row | |
inclusive are about to removed from the given \a parent model item. | |
*/ | |
void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) | |
{ | |
Q_D(QTreeView); | |
QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); | |
d->viewItems.clear(); | |
} | |
/*! | |
\since 4.1 | |
Informs the view that the rows from the \a start row to the \a end row | |
inclusive have been removed from the given \a parent model item. | |
*/ | |
void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end) | |
{ | |
Q_D(QTreeView); | |
d->viewItems.clear(); | |
d->doDelayedItemsLayout(); | |
d->hasRemovedItems = true; | |
d->_q_rowsRemoved(parent, start, end); | |
} | |
/*! | |
Informs the tree view that the number of columns in the tree view has | |
changed from \a oldCount to \a newCount. | |
*/ | |
void QTreeView::columnCountChanged(int oldCount, int newCount) | |
{ | |
Q_D(QTreeView); | |
if (oldCount == 0 && newCount > 0) { | |
//if the first column has just been added we need to relayout. | |
d->doDelayedItemsLayout(); | |
} | |
if (isVisible()) | |
updateGeometries(); | |
viewport()->update(); | |
} | |
/*! | |
Resizes the \a column given to the size of its contents. | |
\sa columnWidth(), setColumnWidth() | |
*/ | |
void QTreeView::resizeColumnToContents(int column) | |
{ | |
Q_D(QTreeView); | |
d->executePostedLayout(); | |
if (column < 0 || column >= d->header->count()) | |
return; | |
int contents = sizeHintForColumn(column); | |
int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column); | |
d->header->resizeSection(column, qMax(contents, header)); | |
} | |
/*! | |
\obsolete | |
\overload | |
Sorts the model by the values in the given \a column. | |
*/ | |
void QTreeView::sortByColumn(int column) | |
{ | |
Q_D(QTreeView); | |
sortByColumn(column, d->header->sortIndicatorOrder()); | |
} | |
/*! | |
\since 4.2 | |
Sets the model up for sorting by the values in the given \a column and \a order. | |
\a column may be -1, in which case no sort indicator will be shown | |
and the model will return to its natural, unsorted order. Note that not | |
all models support this and may even crash in this case. | |
\sa sortingEnabled | |
*/ | |
void QTreeView::sortByColumn(int column, Qt::SortOrder order) | |
{ | |
Q_D(QTreeView); | |
//If sorting is enabled will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts | |
d->header->setSortIndicator(column, order); | |
//If sorting is not enabled, force to sort now. | |
if (!d->sortingEnabled) | |
d->model->sort(column, order); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::selectAll() | |
{ | |
Q_D(QTreeView); | |
if (!selectionModel()) | |
return; | |
SelectionMode mode = d->selectionMode; | |
d->executePostedLayout(); //make sure we lay out the items | |
if (mode != SingleSelection && !d->viewItems.isEmpty()) { | |
const QModelIndex &idx = d->viewItems.last().index; | |
QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1); | |
d->select(d->viewItems.first().index, lastItemIndex, | |
QItemSelectionModel::ClearAndSelect | |
|QItemSelectionModel::Rows); | |
} | |
} | |
/*! | |
\since 4.2 | |
Expands all expandable items. | |
Warning: if the model contains a large number of items, | |
this function will take some time to execute. | |
\sa collapseAll() expand() collapse() setExpanded() | |
*/ | |
void QTreeView::expandAll() | |
{ | |
Q_D(QTreeView); | |
d->viewItems.clear(); | |
d->interruptDelayedItemsLayout(); | |
d->layout(-1, true); | |
updateGeometries(); | |
d->viewport->update(); | |
} | |
/*! | |
\since 4.2 | |
Collapses all expanded items. | |
\sa expandAll() expand() collapse() setExpanded() | |
*/ | |
void QTreeView::collapseAll() | |
{ | |
Q_D(QTreeView); | |
d->expandedIndexes.clear(); | |
doItemsLayout(); | |
} | |
/*! | |
\since 4.3 | |
Expands all expandable items to the given \a depth. | |
\sa expandAll() collapseAll() expand() collapse() setExpanded() | |
*/ | |
void QTreeView::expandToDepth(int depth) | |
{ | |
Q_D(QTreeView); | |
d->viewItems.clear(); | |
d->expandedIndexes.clear(); | |
d->interruptDelayedItemsLayout(); | |
d->layout(-1); | |
for (int i = 0; i < d->viewItems.count(); ++i) { | |
if (d->viewItems.at(i).level <= (uint)depth) { | |
d->viewItems[i].expanded = true; | |
d->layout(i); | |
d->storeExpanded(d->viewItems.at(i).index); | |
} | |
} | |
updateGeometries(); | |
d->viewport->update(); | |
} | |
/*! | |
This function is called whenever \a{column}'s size is changed in | |
the header. \a oldSize and \a newSize give the previous size and | |
the new size in pixels. | |
\sa setColumnWidth() | |
*/ | |
void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */) | |
{ | |
Q_D(QTreeView); | |
d->columnsToUpdate.append(column); | |
if (d->columnResizeTimerID == 0) | |
d->columnResizeTimerID = startTimer(0); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::updateGeometries() | |
{ | |
Q_D(QTreeView); | |
if (d->header) { | |
if (d->geometryRecursionBlock) | |
return; | |
d->geometryRecursionBlock = true; | |
QSize hint = d->header->isHidden() ? QSize(0, 0) : d->header->sizeHint(); | |
setViewportMargins(0, hint.height(), 0, 0); | |
QRect vg = d->viewport->geometry(); | |
QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height()); | |
d->header->setGeometry(geometryRect); | |
//d->header->setOffset(horizontalScrollBar()->value()); // ### bug ??? | |
QMetaObject::invokeMethod(d->header, "updateGeometries"); | |
d->updateScrollBars(); | |
d->geometryRecursionBlock = false; | |
} | |
QAbstractItemView::updateGeometries(); | |
} | |
/*! | |
Returns the size hint for the \a column's width or -1 if there is no | |
model. | |
If you need to set the width of a given column to a fixed value, call | |
QHeaderView::resizeSection() on the view's header. | |
If you reimplement this function in a subclass, note that the value you | |
return is only used when resizeColumnToContents() is called. In that case, | |
if a larger column width is required by either the view's header or | |
the item delegate, that width will be used instead. | |
\sa QWidget::sizeHint, header() | |
*/ | |
int QTreeView::sizeHintForColumn(int column) const | |
{ | |
Q_D(const QTreeView); | |
d->executePostedLayout(); | |
if (d->viewItems.isEmpty()) | |
return -1; | |
ensurePolished(); | |
int w = 0; | |
QStyleOptionViewItemV4 option = d->viewOptionsV4(); | |
const QVector<QTreeViewItem> viewItems = d->viewItems; | |
int start = 0; | |
int end = viewItems.count(); | |
if(end > 1000) { //if we have too many item this function would be too slow. | |
//we get a good approximation by only iterate over 1000 items. | |
start = qMax(0, d->firstVisibleItem() - 100); | |
end = qMin(end, start + 900); | |
} | |
for (int i = start; i < end; ++i) { | |
if (viewItems.at(i).spanning) | |
continue; // we have no good size hint | |
QModelIndex index = viewItems.at(i).index; | |
index = index.sibling(index.row(), column); | |
QWidget *editor = d->editorForIndex(index).editor; | |
if (editor && d->persistent.contains(editor)) { | |
w = qMax(w, editor->sizeHint().width()); | |
int min = editor->minimumSize().width(); | |
int max = editor->maximumSize().width(); | |
w = qBound(min, w, max); | |
} | |
int hint = d->delegateForIndex(index)->sizeHint(option, index).width(); | |
w = qMax(w, hint + (column == 0 ? d->indentationForItem(i) : 0)); | |
} | |
return w; | |
} | |
/*! | |
Returns the size hint for the row indicated by \a index. | |
\sa sizeHintForColumn(), uniformRowHeights() | |
*/ | |
int QTreeView::indexRowSizeHint(const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
if (!d->isIndexValid(index) || !d->itemDelegate) | |
return 0; | |
int start = -1; | |
int end = -1; | |
int indexRow = index.row(); | |
int count = d->header->count(); | |
bool emptyHeader = (count == 0); | |
QModelIndex parent = index.parent(); | |
if (count && isVisible()) { | |
// If the sections have moved, we end up checking too many or too few | |
start = d->header->visualIndexAt(0); | |
} else { | |
// If the header has not been laid out yet, we use the model directly | |
count = d->model->columnCount(parent); | |
} | |
if (isRightToLeft()) { | |
start = (start == -1 ? count - 1 : start); | |
end = 0; | |
} else { | |
start = (start == -1 ? 0 : start); | |
end = count - 1; | |
} | |
if (end < start) | |
qSwap(end, start); | |
int height = -1; | |
QStyleOptionViewItemV4 option = d->viewOptionsV4(); | |
// ### If we want word wrapping in the items, | |
// ### we need to go through all the columns | |
// ### and set the width of the column | |
// Hack to speed up the function | |
option.rect.setWidth(-1); | |
for (int column = start; column <= end; ++column) { | |
int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column); | |
if (d->header->isSectionHidden(logicalColumn)) | |
continue; | |
QModelIndex idx = d->model->index(indexRow, logicalColumn, parent); | |
if (idx.isValid()) { | |
QWidget *editor = d->editorForIndex(idx).editor; | |
if (editor && d->persistent.contains(editor)) { | |
height = qMax(height, editor->sizeHint().height()); | |
int min = editor->minimumSize().height(); | |
int max = editor->maximumSize().height(); | |
height = qBound(min, height, max); | |
} | |
int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height(); | |
height = qMax(height, hint); | |
} | |
} | |
return height; | |
} | |
/*! | |
\since 4.3 | |
Returns the height of the row indicated by the given \a index. | |
\sa indexRowSizeHint() | |
*/ | |
int QTreeView::rowHeight(const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
d->executePostedLayout(); | |
int i = d->viewIndex(index); | |
if (i == -1) | |
return 0; | |
return d->itemHeight(i); | |
} | |
/*! | |
\internal | |
*/ | |
void QTreeView::horizontalScrollbarAction(int action) | |
{ | |
QAbstractItemView::horizontalScrollbarAction(action); | |
} | |
/*! | |
\reimp | |
*/ | |
bool QTreeView::isIndexHidden(const QModelIndex &index) const | |
{ | |
return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent())); | |
} | |
/* | |
private implementation | |
*/ | |
void QTreeViewPrivate::initialize() | |
{ | |
Q_Q(QTreeView); | |
updateStyledFrameWidths(); | |
q->setSelectionBehavior(QAbstractItemView::SelectRows); | |
q->setSelectionMode(QAbstractItemView::SingleSelection); | |
q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); | |
q->setAttribute(Qt::WA_MacShowFocusRect); | |
QHeaderView *header = new QHeaderView(Qt::Horizontal, q); | |
header->setMovable(true); | |
header->setStretchLastSection(true); | |
header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter); | |
q->setHeader(header); | |
#ifndef QT_NO_ANIMATION | |
QObject::connect(&animatedOperation, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation())); | |
#endif //QT_NO_ANIMATION | |
} | |
void QTreeViewPrivate::expand(int item, bool emitSignal) | |
{ | |
Q_Q(QTreeView); | |
if (item == -1 || viewItems.at(item).expanded) | |
return; | |
#ifndef QT_NO_ANIMATION | |
if (emitSignal && animationsEnabled) | |
prepareAnimatedOperation(item, QVariantAnimation::Forward); | |
#endif //QT_NO_ANIMATION | |
QAbstractItemView::State oldState = state; | |
q->setState(QAbstractItemView::ExpandingState); | |
const QModelIndex index = viewItems.at(item).index; | |
storeExpanded(index); | |
viewItems[item].expanded = true; | |
layout(item); | |
q->setState(oldState); | |
if (model->canFetchMore(index)) | |
model->fetchMore(index); | |
if (emitSignal) { | |
emit q->expanded(index); | |
#ifndef QT_NO_ANIMATION | |
if (animationsEnabled) | |
beginAnimatedOperation(); | |
#endif //QT_NO_ANIMATION | |
} | |
} | |
void QTreeViewPrivate::insertViewItems(int pos, int count, const QTreeViewItem &viewItem) | |
{ | |
viewItems.insert(pos, count, viewItem); | |
QTreeViewItem *items = viewItems.data(); | |
for (int i = pos + count; i < viewItems.count(); i++) | |
if (items[i].parentItem >= pos) | |
items[i].parentItem += count; | |
} | |
void QTreeViewPrivate::removeViewItems(int pos, int count) | |
{ | |
viewItems.remove(pos, count); | |
QTreeViewItem *items = viewItems.data(); | |
for (int i = pos; i < viewItems.count(); i++) | |
if (items[i].parentItem >= pos) | |
items[i].parentItem -= count; | |
} | |
#if 0 | |
bool QTreeViewPrivate::checkViewItems() const | |
{ | |
for (int i = 0; i < viewItems.count(); ++i) { | |
const QTreeViewItem &vi = viewItems.at(i); | |
if (vi.parentItem == -1) { | |
Q_ASSERT(!vi.index.parent().isValid() || vi.index.parent() == root); | |
} else { | |
Q_ASSERT(vi.index.parent() == viewItems.at(vi.parentItem).index); | |
} | |
} | |
return true; | |
} | |
#endif | |
void QTreeViewPrivate::collapse(int item, bool emitSignal) | |
{ | |
Q_Q(QTreeView); | |
if (item == -1 || expandedIndexes.isEmpty()) | |
return; | |
//if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll | |
delayedAutoScroll.stop(); | |
int total = viewItems.at(item).total; | |
const QModelIndex &modelIndex = viewItems.at(item).index; | |
if (!isPersistent(modelIndex)) | |
return; // if the index is not persistent, no chances it is expanded | |
QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex); | |
if (it == expandedIndexes.end() || viewItems.at(item).expanded == false) | |
return; // nothing to do | |
#ifndef QT_NO_ANIMATION | |
if (emitSignal && animationsEnabled) | |
prepareAnimatedOperation(item, QVariantAnimation::Backward); | |
#endif //QT_NO_ANIMATION | |
QAbstractItemView::State oldState = state; | |
q->setState(QAbstractItemView::CollapsingState); | |
expandedIndexes.erase(it); | |
viewItems[item].expanded = false; | |
int index = item; | |
while (index > -1) { | |
viewItems[index].total -= total; | |
index = viewItems[index].parentItem; | |
} | |
removeViewItems(item + 1, total); // collapse | |
q->setState(oldState); | |
if (emitSignal) { | |
emit q->collapsed(modelIndex); | |
#ifndef QT_NO_ANIMATION | |
if (animationsEnabled) | |
beginAnimatedOperation(); | |
#endif //QT_NO_ANIMATION | |
} | |
} | |
#ifndef QT_NO_ANIMATION | |
void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction) | |
{ | |
animatedOperation.item = item; | |
animatedOperation.viewport = viewport; | |
animatedOperation.setDirection(direction); | |
int top = coordinateForItem(item) + itemHeight(item); | |
QRect rect = viewport->rect(); | |
rect.setTop(top); | |
if (direction == QVariantAnimation::Backward) { | |
const int limit = rect.height() * 2; | |
int h = 0; | |
int c = item + viewItems.at(item).total + 1; | |
for (int i = item + 1; i < c && h < limit; ++i) | |
h += itemHeight(i); | |
rect.setHeight(h); | |
animatedOperation.setEndValue(top + h); | |
} | |
animatedOperation.setStartValue(top); | |
animatedOperation.before = renderTreeToPixmapForAnimation(rect); | |
} | |
void QTreeViewPrivate::beginAnimatedOperation() | |
{ | |
Q_Q(QTreeView); | |
QRect rect = viewport->rect(); | |
rect.setTop(animatedOperation.top()); | |
if (animatedOperation.direction() == QVariantAnimation::Forward) { | |
const int limit = rect.height() * 2; | |
int h = 0; | |
int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1; | |
for (int i = animatedOperation.item + 1; i < c && h < limit; ++i) | |
h += itemHeight(i); | |
rect.setHeight(h); | |
animatedOperation.setEndValue(animatedOperation.top() + h); | |
} | |
if (!rect.isEmpty()) { | |
animatedOperation.after = renderTreeToPixmapForAnimation(rect); | |
q->setState(QAbstractItemView::AnimatingState); | |
animatedOperation.start(); //let's start the animation | |
} | |
} | |
void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const | |
{ | |
const int start = animatedOperation.startValue().toInt(), | |
end = animatedOperation.endValue().toInt(), | |
current = animatedOperation.currentValue().toInt(); | |
bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward; | |
const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after; | |
painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height()); | |
const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before; | |
painter->drawPixmap(0, current, bottom); | |
} | |
QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const | |
{ | |
Q_Q(const QTreeView); | |
QPixmap pixmap(rect.size()); | |
if (rect.size().isEmpty()) | |
return pixmap; | |
pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels. | |
QPainter painter(&pixmap); | |
painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base()); | |
painter.translate(0, -rect.top()); | |
q->drawTree(&painter, QRegion(rect)); | |
painter.end(); | |
//and now let's render the editors the editors | |
QStyleOptionViewItemV4 option = viewOptionsV4(); | |
for (QList<QEditorInfo>::const_iterator it = editors.constBegin(); it != editors.constEnd(); ++it) { | |
QWidget *editor = it->editor; | |
QModelIndex index = it->index; | |
option.rect = q->visualRect(index); | |
if (option.rect.isValid()) { | |
if (QAbstractItemDelegate *delegate = delegateForIndex(index)) | |
delegate->updateEditorGeometry(editor, option, index); | |
const QPoint pos = editor->pos(); | |
if (rect.contains(pos)) { | |
editor->render(&pixmap, pos - rect.topLeft()); | |
//the animation uses pixmap to display the treeview's content | |
//the editor is rendered on this pixmap and thus can (should) be hidden | |
editor->hide(); | |
} | |
} | |
} | |
return pixmap; | |
} | |
void QTreeViewPrivate::_q_endAnimatedOperation() | |
{ | |
Q_Q(QTreeView); | |
q->setState(QAbstractItemView::NoState); | |
q->updateGeometries(); | |
viewport->update(); | |
} | |
#endif //QT_NO_ANIMATION | |
void QTreeViewPrivate::_q_modelAboutToBeReset() | |
{ | |
viewItems.clear(); | |
} | |
void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) | |
{ | |
if (start <= 0 && 0 <= end) | |
viewItems.clear(); | |
QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(parent, start, end); | |
} | |
void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end) | |
{ | |
if (start <= 0 && 0 <= end) | |
doDelayedItemsLayout(); | |
QAbstractItemViewPrivate::_q_columnsRemoved(parent, start, end); | |
} | |
/** \internal | |
creates and initialize the viewItem structure of the children of the element \i | |
set \a recursiveExpanding if the function has to expand all the children (called from expandAll) | |
\a afterIsUninitialized is when we recurse from layout(-1), it means all the items after 'i' are | |
not yet initialized and need not to be moved | |
*/ | |
void QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninitialized) | |
{ | |
Q_Q(QTreeView); | |
QModelIndex current; | |
QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i); | |
if (i>=0 && !parent.isValid()) { | |
//modelIndex() should never return something invalid for the real items. | |
//This can happen if columncount has been set to 0. | |
//To avoid infinite loop we stop here. | |
return; | |
} | |
int count = 0; | |
if (model->hasChildren(parent)) { | |
if (model->canFetchMore(parent)) | |
model->fetchMore(parent); | |
count = model->rowCount(parent); | |
} | |
bool expanding = true; | |
if (i == -1) { | |
if (uniformRowHeights) { | |
QModelIndex index = model->index(0, 0, parent); | |
defaultItemHeight = q->indexRowSizeHint(index); | |
} | |
viewItems.resize(count); | |
afterIsUninitialized = true; | |
} else if (viewItems[i].total != (uint)count) { | |
if (!afterIsUninitialized) | |
insertViewItems(i + 1, count, QTreeViewItem()); // expand | |
else if (count > 0) | |
viewItems.resize(viewItems.count() + count); | |
} else { | |
expanding = false; | |
} | |
int first = i + 1; | |
int level = (i >= 0 ? viewItems.at(i).level + 1 : 0); | |
int hidden = 0; | |
int last = 0; | |
int children = 0; | |
QTreeViewItem *item = 0; | |
for (int j = first; j < first + count; ++j) { | |
current = model->index(j - first, 0, parent); | |
if (isRowHidden(current)) { | |
++hidden; | |
last = j - hidden + children; | |
} else { | |
last = j - hidden + children; | |
if (item) | |
item->hasMoreSiblings = true; | |
item = &viewItems[last]; | |
item->index = current; | |
item->parentItem = i; | |
item->level = level; | |
item->height = 0; | |
item->spanning = q->isFirstColumnSpanned(current.row(), parent); | |
item->expanded = false; | |
item->total = 0; | |
item->hasMoreSiblings = false; | |
if (recursiveExpanding || isIndexExpanded(current)) { | |
if (recursiveExpanding) | |
expandedIndexes.insert(current); | |
item->expanded = true; | |
layout(last, recursiveExpanding, afterIsUninitialized); | |
item = &viewItems[last]; | |
children += item->total; | |
item->hasChildren = item->total > 0; | |
last = j - hidden + children; | |
} else { | |
item->hasChildren = hasVisibleChildren(current); | |
} | |
} | |
} | |
// remove hidden items | |
if (hidden > 0) { | |
if (!afterIsUninitialized) | |
removeViewItems(last + 1, hidden); | |
else | |
viewItems.resize(viewItems.size() - hidden); | |
} | |
if (!expanding) | |
return; // nothing changed | |
while (i > -1) { | |
viewItems[i].total += count - hidden; | |
i = viewItems[i].parentItem; | |
} | |
} | |
int QTreeViewPrivate::pageUp(int i) const | |
{ | |
int index = itemAtCoordinate(coordinateForItem(i) - viewport->height()); | |
while (isItemHiddenOrDisabled(index)) | |
index--; | |
return index == -1 ? 0 : index; | |
} | |
int QTreeViewPrivate::pageDown(int i) const | |
{ | |
int index = itemAtCoordinate(coordinateForItem(i) + viewport->height()); | |
while (isItemHiddenOrDisabled(index)) | |
index++; | |
return index == -1 ? viewItems.count() - 1 : index; | |
} | |
int QTreeViewPrivate::indentationForItem(int item) const | |
{ | |
if (item < 0 || item >= viewItems.count()) | |
return 0; | |
int level = viewItems.at(item).level; | |
if (rootDecoration) | |
++level; | |
return level * indent; | |
} | |
int QTreeViewPrivate::itemHeight(int item) const | |
{ | |
if (uniformRowHeights) | |
return defaultItemHeight; | |
if (viewItems.isEmpty()) | |
return 0; | |
const QModelIndex &index = viewItems.at(item).index; | |
if (!index.isValid()) | |
return 0; | |
int height = viewItems.at(item).height; | |
if (height <= 0) { | |
height = q_func()->indexRowSizeHint(index); | |
viewItems[item].height = height; | |
} | |
return qMax(height, 0); | |
} | |
/*! | |
\internal | |
Returns the viewport y coordinate for \a item. | |
*/ | |
int QTreeViewPrivate::coordinateForItem(int item) const | |
{ | |
if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { | |
if (uniformRowHeights) | |
return (item * defaultItemHeight) - vbar->value(); | |
// ### optimize (spans or caching) | |
int y = 0; | |
for (int i = 0; i < viewItems.count(); ++i) { | |
if (i == item) | |
return y - vbar->value(); | |
y += itemHeight(i); | |
} | |
} else { // ScrollPerItem | |
int topViewItemIndex = vbar->value(); | |
if (uniformRowHeights) | |
return defaultItemHeight * (item - topViewItemIndex); | |
if (item >= topViewItemIndex) { | |
// search in the visible area first and continue down | |
// ### slow if the item is not visible | |
int viewItemCoordinate = 0; | |
int viewItemIndex = topViewItemIndex; | |
while (viewItemIndex < viewItems.count()) { | |
if (viewItemIndex == item) | |
return viewItemCoordinate; | |
viewItemCoordinate += itemHeight(viewItemIndex); | |
++viewItemIndex; | |
} | |
// below the last item in the view | |
Q_ASSERT(false); | |
return viewItemCoordinate; | |
} else { | |
// search the area above the viewport (used for editor widgets) | |
int viewItemCoordinate = 0; | |
for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) { | |
if (viewItemIndex == item) | |
return viewItemCoordinate; | |
viewItemCoordinate -= itemHeight(viewItemIndex - 1); | |
} | |
return viewItemCoordinate; | |
} | |
} | |
return 0; | |
} | |
/*! | |
\internal | |
Returns the index of the view item at the | |
given viewport \a coordinate. | |
\sa modelIndex() | |
*/ | |
int QTreeViewPrivate::itemAtCoordinate(int coordinate) const | |
{ | |
const int itemCount = viewItems.count(); | |
if (itemCount == 0) | |
return -1; | |
if (uniformRowHeights && defaultItemHeight <= 0) | |
return -1; | |
if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { | |
if (uniformRowHeights) { | |
const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight; | |
return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); | |
} | |
// ### optimize | |
int viewItemCoordinate = 0; | |
const int contentsCoordinate = coordinate + vbar->value(); | |
for (int viewItemIndex = 0; viewItemIndex < viewItems.count(); ++viewItemIndex) { | |
viewItemCoordinate += itemHeight(viewItemIndex); | |
if (viewItemCoordinate >= contentsCoordinate) | |
return (viewItemIndex >= itemCount ? -1 : viewItemIndex); | |
} | |
} else { // ScrollPerItem | |
int topViewItemIndex = vbar->value(); | |
if (uniformRowHeights) { | |
if (coordinate < 0) | |
coordinate -= defaultItemHeight - 1; | |
const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight); | |
return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); | |
} | |
if (coordinate >= 0) { | |
// the coordinate is in or below the viewport | |
int viewItemCoordinate = 0; | |
for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.count(); ++viewItemIndex) { | |
viewItemCoordinate += itemHeight(viewItemIndex); | |
if (viewItemCoordinate > coordinate) | |
return (viewItemIndex >= itemCount ? -1 : viewItemIndex); | |
} | |
} else { | |
// the coordinate is above the viewport | |
int viewItemCoordinate = 0; | |
for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) { | |
if (viewItemCoordinate <= coordinate) | |
return (viewItemIndex >= itemCount ? -1 : viewItemIndex); | |
viewItemCoordinate -= itemHeight(viewItemIndex); | |
} | |
} | |
} | |
return -1; | |
} | |
int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const | |
{ | |
if (!_index.isValid() || viewItems.isEmpty()) | |
return -1; | |
const int totalCount = viewItems.count(); | |
const QModelIndex index = _index.sibling(_index.row(), 0); | |
const int row = index.row(); | |
const qint64 internalId = index.internalId(); | |
// We start nearest to the lastViewedItem | |
int localCount = qMin(lastViewedItem - 1, totalCount - lastViewedItem); | |
for (int i = 0; i < localCount; ++i) { | |
const QModelIndex &idx1 = viewItems.at(lastViewedItem + i).index; | |
if (idx1.row() == row && idx1.internalId() == internalId) { | |
lastViewedItem = lastViewedItem + i; | |
return lastViewedItem; | |
} | |
const QModelIndex &idx2 = viewItems.at(lastViewedItem - i - 1).index; | |
if (idx2.row() == row && idx2.internalId() == internalId) { | |
lastViewedItem = lastViewedItem - i - 1; | |
return lastViewedItem; | |
} | |
} | |
for (int j = qMax(0, lastViewedItem + localCount); j < totalCount; ++j) { | |
const QModelIndex &idx = viewItems.at(j).index; | |
if (idx.row() == row && idx.internalId() == internalId) { | |
lastViewedItem = j; | |
return j; | |
} | |
} | |
for (int j = qMin(totalCount, lastViewedItem - localCount) - 1; j >= 0; --j) { | |
const QModelIndex &idx = viewItems.at(j).index; | |
if (idx.row() == row && idx.internalId() == internalId) { | |
lastViewedItem = j; | |
return j; | |
} | |
} | |
// nothing found | |
return -1; | |
} | |
QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const | |
{ | |
if (i < 0 || i >= viewItems.count()) | |
return QModelIndex(); | |
QModelIndex ret = viewItems.at(i).index; | |
if (column) | |
ret = ret.sibling(ret.row(), column); | |
return ret; | |
} | |
int QTreeViewPrivate::firstVisibleItem(int *offset) const | |
{ | |
const int value = vbar->value(); | |
if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { | |
if (offset) | |
*offset = 0; | |
return (value < 0 || value >= viewItems.count()) ? -1 : value; | |
} | |
// ScrollMode == ScrollPerPixel | |
if (uniformRowHeights) { | |
if (!defaultItemHeight) | |
return -1; | |
if (offset) | |
*offset = -(value % defaultItemHeight); | |
return value / defaultItemHeight; | |
} | |
int y = 0; // ### optimize (use spans ?) | |
for (int i = 0; i < viewItems.count(); ++i) { | |
y += itemHeight(i); // the height value is cached | |
if (y > value) { | |
if (offset) | |
*offset = y - value - itemHeight(i); | |
return i; | |
} | |
} | |
return -1; | |
} | |
int QTreeViewPrivate::columnAt(int x) const | |
{ | |
return header->logicalIndexAt(x); | |
} | |
void QTreeViewPrivate::updateScrollBars() | |
{ | |
Q_Q(QTreeView); | |
QSize viewportSize = viewport->size(); | |
if (!viewportSize.isValid()) | |
viewportSize = QSize(0, 0); | |
if (viewItems.isEmpty()) { | |
q->doItemsLayout(); | |
} | |
int itemsInViewport = 0; | |
if (uniformRowHeights) { | |
if (defaultItemHeight <= 0) | |
itemsInViewport = viewItems.count(); | |
else | |
itemsInViewport = viewportSize.height() / defaultItemHeight; | |
} else { | |
const int itemsCount = viewItems.count(); | |
const int viewportHeight = viewportSize.height(); | |
for (int height = 0, item = itemsCount - 1; item >= 0; --item) { | |
height += itemHeight(item); | |
if (height > viewportHeight) | |
break; | |
++itemsInViewport; | |
} | |
} | |
if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { | |
if (!viewItems.isEmpty()) | |
itemsInViewport = qMax(1, itemsInViewport); | |
vbar->setRange(0, viewItems.count() - itemsInViewport); | |
vbar->setPageStep(itemsInViewport); | |
vbar->setSingleStep(1); | |
} else { // scroll per pixel | |
int contentsHeight = 0; | |
if (uniformRowHeights) { | |
contentsHeight = defaultItemHeight * viewItems.count(); | |
} else { // ### optimize (spans or caching) | |
for (int i = 0; i < viewItems.count(); ++i) | |
contentsHeight += itemHeight(i); | |
} | |
vbar->setRange(0, contentsHeight - viewportSize.height()); | |
vbar->setPageStep(viewportSize.height()); | |
vbar->setSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2)); | |
} | |
const int columnCount = header->count(); | |
const int viewportWidth = viewportSize.width(); | |
int columnsInViewport = 0; | |
for (int width = 0, column = columnCount - 1; column >= 0; --column) { | |
int logical = header->logicalIndex(column); | |
width += header->sectionSize(logical); | |
if (width > viewportWidth) | |
break; | |
++columnsInViewport; | |
} | |
if (columnCount > 0) | |
columnsInViewport = qMax(1, columnsInViewport); | |
if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) { | |
hbar->setRange(0, columnCount - columnsInViewport); | |
hbar->setPageStep(columnsInViewport); | |
hbar->setSingleStep(1); | |
} else { // scroll per pixel | |
const int horizontalLength = header->length(); | |
const QSize maxSize = q->maximumViewportSize(); | |
if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0) | |
viewportSize = maxSize; | |
hbar->setPageStep(viewportSize.width()); | |
hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0)); | |
hbar->setSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2)); | |
} | |
} | |
int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const | |
{ | |
executePostedLayout(); | |
int x = pos.x(); | |
int column = header->logicalIndexAt(x); | |
if (column != 0) | |
return -1; // no logical index at x | |
int viewItemIndex = itemAtCoordinate(pos.y()); | |
QRect returning = itemDecorationRect(modelIndex(viewItemIndex)); | |
if (!returning.contains(pos)) | |
return -1; | |
return viewItemIndex; | |
} | |
QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const | |
{ | |
Q_Q(const QTreeView); | |
if (!rootDecoration && index.parent() == root) | |
return QRect(); // no decoration at root | |
int viewItemIndex = viewIndex(index); | |
if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index)) | |
return QRect(); | |
int itemIndentation = indentationForItem(viewItemIndex); | |
int position = header->sectionViewportPosition(0); | |
int size = header->sectionSize(0); | |
QRect rect; | |
if (q->isRightToLeft()) | |
rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex), | |
indent, itemHeight(viewItemIndex)); | |
else | |
rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex), | |
indent, itemHeight(viewItemIndex)); | |
QStyleOption opt; | |
opt.initFrom(q); | |
opt.rect = rect; | |
return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q); | |
} | |
QList<QPair<int, int> > QTreeViewPrivate::columnRanges(const QModelIndex &topIndex, | |
const QModelIndex &bottomIndex) const | |
{ | |
const int topVisual = header->visualIndex(topIndex.column()), | |
bottomVisual = header->visualIndex(bottomIndex.column()); | |
const int start = qMin(topVisual, bottomVisual); | |
const int end = qMax(topVisual, bottomVisual); | |
QList<int> logicalIndexes; | |
//we iterate over the visual indexes to get the logical indexes | |
for (int c = start; c <= end; c++) { | |
const int logical = header->logicalIndex(c); | |
if (!header->isSectionHidden(logical)) { | |
logicalIndexes << logical; | |
} | |
} | |
//let's sort the list | |
qSort(logicalIndexes.begin(), logicalIndexes.end()); | |
QList<QPair<int, int> > ret; | |
QPair<int, int> current; | |
current.first = -2; // -1 is not enough because -1+1 = 0 | |
current.second = -2; | |
for(int i = 0; i < logicalIndexes.count(); ++i) { | |
const int logicalColumn = logicalIndexes.at(i); | |
if (current.second + 1 != logicalColumn) { | |
if (current.first != -2) { | |
//let's save the current one | |
ret += current; | |
} | |
//let's start a new one | |
current.first = current.second = logicalColumn; | |
} else { | |
current.second++; | |
} | |
} | |
//let's get the last range | |
if (current.first != -2) { | |
ret += current; | |
} | |
return ret; | |
} | |
void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex, | |
QItemSelectionModel::SelectionFlags command) | |
{ | |
Q_Q(QTreeView); | |
QItemSelection selection; | |
const int top = viewIndex(topIndex), | |
bottom = viewIndex(bottomIndex); | |
const QList< QPair<int, int> > colRanges = columnRanges(topIndex, bottomIndex); | |
QList< QPair<int, int> >::const_iterator it; | |
for (it = colRanges.begin(); it != colRanges.end(); ++it) { | |
const int left = (*it).first, | |
right = (*it).second; | |
QModelIndex previous; | |
QItemSelectionRange currentRange; | |
QStack<QItemSelectionRange> rangeStack; | |
for (int i = top; i <= bottom; ++i) { | |
QModelIndex index = modelIndex(i); | |
QModelIndex parent = index.parent(); | |
QModelIndex previousParent = previous.parent(); | |
if (previous.isValid() && parent == previousParent) { | |
// same parent | |
if (qAbs(previous.row() - index.row()) > 1) { | |
//a hole (hidden index inside a range) has been detected | |
if (currentRange.isValid()) { | |
selection.append(currentRange); | |
} | |
//let's start a new range | |
currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); | |
} else { | |
QModelIndex tl = model->index(currentRange.top(), currentRange.left(), | |
currentRange.parent()); | |
currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right)); | |
} | |
} else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) { | |
// item is child of previous | |
rangeStack.push(currentRange); | |
currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); | |
} else { | |
if (currentRange.isValid()) | |
selection.append(currentRange); | |
if (rangeStack.isEmpty()) { | |
currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); | |
} else { | |
currentRange = rangeStack.pop(); | |
index = currentRange.bottomRight(); //let's resume the range | |
--i; //we process again the current item | |
} | |
} | |
previous = index; | |
} | |
if (currentRange.isValid()) | |
selection.append(currentRange); | |
for (int i = 0; i < rangeStack.count(); ++i) | |
selection.append(rangeStack.at(i)); | |
} | |
q->selectionModel()->select(selection, command); | |
} | |
QPair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const | |
{ | |
Q_Q(const QTreeView); | |
int start = header->visualIndexAt(rect.left()); | |
int end = header->visualIndexAt(rect.right()); | |
if (q->isRightToLeft()) { | |
start = (start == -1 ? header->count() - 1 : start); | |
end = (end == -1 ? 0 : end); | |
} else { | |
start = (start == -1 ? 0 : start); | |
end = (end == -1 ? header->count() - 1 : end); | |
} | |
return qMakePair<int,int>(qMin(start, end), qMax(start, end)); | |
} | |
bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const | |
{ | |
Q_Q(const QTreeView); | |
if (model->hasChildren(parent)) { | |
if (hiddenIndexes.isEmpty()) | |
return true; | |
if (q->isIndexHidden(parent)) | |
return false; | |
int rowCount = model->rowCount(parent); | |
for (int i = 0; i < rowCount; ++i) { | |
if (!q->isRowHidden(i, parent)) | |
return true; | |
} | |
if (rowCount == 0) | |
return true; | |
} | |
return false; | |
} | |
void QTreeViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order) | |
{ | |
model->sort(column, order); | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) | |
{ | |
#ifndef QT_NO_ACCESSIBILITY | |
if (QAccessible::isActive()) { | |
int entry = visualIndex(current) + 1; | |
if (header()) | |
++entry; | |
QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); | |
} | |
#endif | |
QAbstractItemView::currentChanged(current, previous); | |
if (allColumnsShowFocus()) { | |
if (previous.isValid()) { | |
QRect previousRect = visualRect(previous); | |
previousRect.setX(0); | |
previousRect.setWidth(viewport()->width()); | |
viewport()->update(previousRect); | |
} | |
if (current.isValid()) { | |
QRect currentRect = visualRect(current); | |
currentRect.setX(0); | |
currentRect.setWidth(viewport()->width()); | |
viewport()->update(currentRect); | |
} | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
void QTreeView::selectionChanged(const QItemSelection &selected, | |
const QItemSelection &deselected) | |
{ | |
#ifndef QT_NO_ACCESSIBILITY | |
if (QAccessible::isActive()) { | |
// ### does not work properly for selection ranges. | |
QModelIndex sel = selected.indexes().value(0); | |
if (sel.isValid()) { | |
int entry = visualIndex(sel) + 1; | |
if (header()) | |
++entry; | |
QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); | |
} | |
QModelIndex desel = deselected.indexes().value(0); | |
if (desel.isValid()) { | |
int entry = visualIndex(desel) + 1; | |
if (header()) | |
++entry; | |
QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); | |
} | |
} | |
#endif | |
QAbstractItemView::selectionChanged(selected, deselected); | |
} | |
int QTreeView::visualIndex(const QModelIndex &index) const | |
{ | |
Q_D(const QTreeView); | |
d->executePostedLayout(); | |
return d->viewIndex(index); | |
} | |
QT_END_NAMESPACE | |
#include "moc_qtreeview.cpp" | |
#endif // QT_NO_TREEVIEW |