Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qtreeview.h"
#include <qheaderview.h>
#include <qabstractitemdelegate.h>
#include <qapplication.h>
#include <qscrollbar.h>
#include <qpainter.h>
#include <qpainterstateguard.h>
#include <qstack.h>
#include <qstyle.h>
#include <qstyleoption.h>
#include <qevent.h>
#include <qpen.h>
#include <qdebug.h>
#include <QMetaMethod>
#include <private/qscrollbar_p.h>
#if QT_CONFIG(accessibility)
#include <qaccessible.h>
#endif
#include <private/qapplication_p.h>
#include <private/qtreeview_p.h>
#include <private/qheaderview_p.h>
#include <algorithm>
#include <QtCore/q26numeric.h>
#include <QtCore/q20utility.h>
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
/*!
\class QTreeView
\brief The QTreeView class provides a default model/view implementation of a tree view.
\ingroup model-view
\ingroup advanced
\inmodule QtWidgets
\image fusion-treeview.png
{Directory widget showing the contents using a tree view}
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 QFileSystemModel and displayed as a tree:
\snippet shareddirmodel/main.cpp 3
\snippet 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.
By default, all columns in a tree view are movable except the first. To
disable movement of these columns, use QHeaderView's
\l {QHeaderView::}{setSectionsMovable()} function. For more information
about rearranging sections, see \l {Moving Header Sections}.
\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 \li Key \li Action
\row \li Up \li 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 \li Down \li 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 \li Left \li Hides the children of the current item (if present)
by collapsing a branch.
\row \li Minus \li Same as Left.
\row \li Right \li Reveals the children of the current item (if present)
by expanding a branch.
\row \li Plus \li Same as Right.
\row \li Asterisk \li Expands the current item and all its children
(if present).
\row \li PageUp \li Moves the cursor up one page.
\row \li PageDown \li Moves the cursor down one page.
\row \li Home \li Moves the cursor to an item in the same column of the first
row of the first top-level item in the model.
\row \li End \li Moves the cursor to an item in the same column of the last
row of the last top-level item in the model.
\row \li F2 \li 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
\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
*/
/*!
\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()
{
Q_D(QTreeView);
d->clearConnections();
}
/*!
\reimp
*/
void QTreeView::setModel(QAbstractItemModel *model)
{
Q_D(QTreeView);
if (model == d->model)
return;
if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
for (QMetaObject::Connection &connection : d->modelConnections)
QObject::disconnect(connection);
}
if (d->selectionModel) { // support row editing
QObject::disconnect(d->selectionmodelConnection);
}
d->viewItems.clear();
d->expandedIndexes.clear();
d->hiddenIndexes.clear();
d->geometryRecursionBlock = true; // do not update geometries due to signals from the headers
d->header->setModel(model);
d->geometryRecursionBlock = false;
QAbstractItemView::setModel(model);
if (d->model) {
// QAbstractItemView connects to a private slot
QObjectPrivate::disconnect(d->model, &QAbstractItemModel::rowsRemoved,
d, &QAbstractItemViewPrivate::rowsRemoved);
// do header layout after the tree
QObjectPrivate::disconnect(d->model, &QAbstractItemModel::layoutChanged,
d->header->d_func(), &QAbstractItemViewPrivate::layoutChanged);
d->modelConnections = {
// QTreeView has a public slot for this
QObject::connect(d->model, &QAbstractItemModel::rowsRemoved,
this, &QTreeView::rowsRemoved),
QObjectPrivate::connect(d->model, &QAbstractItemModel::modelAboutToBeReset,
d, &QTreeViewPrivate::modelAboutToBeReset)
};
}
if (d->sortingEnabled)
d->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
QObject::disconnect(d->selectionmodelConnection);
}
d->header->setSelectionModel(selectionModel);
QAbstractItemView::setSelectionModel(selectionModel);
if (d->selectionModel) {
// support row editing
d->selectionmodelConnection =
connect(d->selectionModel, &QItemSelectionModel::currentRowChanged,
d->model, &QAbstractItemModel::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);
d->header->setFirstSectionMovable(false);
if (!d->header->model()) {
d->header->setModel(d->model);
if (d->selectionModel)
d->header->setSelectionModel(d->selectionModel);
}
d->headerConnections = {
connect(d->header, &QHeaderView::sectionResized,
this, &QTreeView::columnResized),
connect(d->header, &QHeaderView::sectionMoved,
this, &QTreeView::columnMoved),
connect(d->header, &QHeaderView::sectionCountChanged,
this, &QTreeView::columnCountChanged),
connect(d->header, &QHeaderView::sectionHandleDoubleClicked,
this, &QTreeView::resizeColumnToContents),
connect(d->header, &QHeaderView::geometriesChanged,
this, &QTreeView::updateGeometries)
};
setSortingEnabled(d->sortingEnabled);
d->updateGeometry();
}
/*!
\property QTreeView::autoExpandDelay
\brief The delay time before items in a tree are opened during a drag and drop operation.
This property holds the amount of time in milliseconds that the user must wait over
a node before that node will automatically open. 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, the value of this property is style dependent. Thus, when the style
changes, this property updates from it. Calling setIndentation() stops the updates,
calling resetIndentation() will restore default behavior.
*/
int QTreeView::indentation() const
{
Q_D(const QTreeView);
return d->indent;
}
void QTreeView::setIndentation(int i)
{
Q_D(QTreeView);
if (!d->customIndent || (i != d->indent)) {
d->indent = i;
d->customIndent = true;
d->viewport->update();
}
}
void QTreeView::resetIndentation()
{
Q_D(QTreeView);
if (d->customIndent) {
d->updateIndentationFromStyle();
d->customIndent = false;
}
}
/*!
\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 \c 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.
\note If the editor size hint is bigger than the cell size hint, then the
size hint of the editor will be used.
By default, this property is \c 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 \c 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
\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);
}
/*!
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 \c true if the \a column is hidden; otherwise returns \c 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.
If this property is \c 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 \c true if the item in the given \a row of the \a parent is hidden;
otherwise returns \c 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();
}
/*!
Returns \c true if the item in first column in the given \a row
of the \a parent is spanning all the columns; otherwise returns \c false.
\sa setFirstColumnSpanned()
*/
bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const
{
Q_D(const QTreeView);
if (d->spanningIndexes.isEmpty() || !d->model)
return false;
const QModelIndex index = d->model->index(row, 0, parent);
return d->spanningIndexes.contains(index);
}
/*!
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;
const QModelIndex index = d->model->index(row, 0, parent);
if (!index.isValid())
return;
if (span)
d->spanningIndexes.insert(index);
else
d->spanningIndexes.remove(index);
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,
const QList<int> &roles)
{
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, roles);
}
/*!
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);
if (d->header->isSectionHidden(column))
return;
d->header->hideSection(column);
doItemsLayout();
}
/*!
Shows the given \a column in the tree view.
\sa hideColumn(), setColumnHidden()
*/
void QTreeView::showColumn(int column)
{
Q_D(QTreeView);
if (!d->header->isSectionHidden(column))
return;
d->header->showSection(column);
doItemsLayout();
}
/*!
\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 (index.flags() & Qt::ItemNeverHasChildren)
return;
if (d->isIndexExpanded(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 (!d->isIndexExpanded(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 \c 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);
}
/*!
\property QTreeView::sortingEnabled
\brief whether sorting is enabled
If this property is \c 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()->setSectionsClickable(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());
d->sortHeaderConnection =
QObjectPrivate::connect(header(), &QHeaderView::sortIndicatorChanged,
d, &QTreeViewPrivate::sortIndicatorChanged,
Qt::UniqueConnection);
} else {
QObject::disconnect(d->sortHeaderConnection);
}
d->sortingEnabled = enable;
}
bool QTreeView::isSortingEnabled() const
{
Q_D(const QTreeView);
return d->sortingEnabled;
}
/*!
\property QTreeView::animated
\brief whether animations are enabled
If this property is \c true the treeview will animate expansion
and collapsing of branches. If this property is \c false, the treeview
will expand or collapse branches immediately without showing
the animation.
By default, this property is \c false.
*/
void QTreeView::setAnimated(bool animate)
{
Q_D(QTreeView);
d->animationsEnabled = animate;
}
bool QTreeView::isAnimated() const
{
Q_D(const QTreeView);
return d->animationsEnabled;
}
/*!
\property QTreeView::allColumnsShowFocus
\brief whether items should show keyboard focus using all columns
If this property is \c 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
If this property is \c true then the item text is wrapped where
necessary at word-breaks; otherwise it is not wrapped at all.
This property is \c 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;
}
/*!
\since 5.2
This specifies that the tree structure should be placed at logical index \a index.
If \index is set to -1 then the tree will always follow visual index 0.
\sa treePosition(), QHeaderView::swapSections(), QHeaderView::moveSection()
*/
void QTreeView::setTreePosition(int index)
{
Q_D(QTreeView);
d->treePosition = index;
d->viewport->update();
}
/*!
\since 5.2
Return the logical index the tree is set on. If the return value is -1 then the
tree is placed on the visual index 0.
\sa setTreePosition()
*/
int QTreeView::treePosition() const
{
Q_D(const QTreeView);
return d->treePosition;
}
/*!
\reimp
*/
void QTreeView::keyboardSearch(const QString &search)
{
Q_D(QTreeView);
if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root))
return;
// Do a relayout nows, so that we can utilize viewItems
d->executePostedLayout();
if (d->viewItems.isEmpty())
return;
QModelIndex start;
if (currentIndex().isValid())
start = currentIndex();
else
start = d->viewItems.at(0).index;
bool skipRow = false;
bool keyboardTimeWasValid = d->keyboardInputTime.isValid();
qint64 keyboardInputTimeElapsed;
if (keyboardTimeWasValid)
keyboardInputTimeElapsed = d->keyboardInputTime.restart();
else
d->keyboardInputTime.start();
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.size() > 1) {
int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.size() - 1));
sameKey = (c == d->keyboardInput.size());
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 {
const int origCol = start.column();
start = d->viewItems.at(0).index;
if (origCol != start.column())
start = start.sibling(start.row(), origCol);
}
}
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.size(); ++i) {
if ((int)d->viewItems.at(i).level > previousLevel) {
QModelIndex searchFrom = d->viewItems.at(i).index;
if (start.column() > 0)
searchFrom = searchFrom.sibling(searchFrom.row(), start.column());
if (searchFrom.parent() == start.parent())
searchFrom = start;
QModelIndexList match = d->model->match(searchFrom, Qt::DisplayRole, searchString, 1,
keyboardSearchFlags());
if (match.size()) {
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 (start.column() > 0)
index = index.sibling(index.row(), start.column());
if (index.isValid())
setCurrentIndex(index);
}
/*!
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.
\sa itemDecorationRect()
*/
QRect QTreeView::visualRect(const QModelIndex &index) const
{
Q_D(const QTreeView);
return d->visualRect(index, QTreeViewPrivate::SingleSection);
}
/*!
Returns the rectangle occupied by the expand/collapse indicator
of the item at \a index. If the item has no such
indicator, for example because it has no children or because root
decoration is disabled, the returned rectangle is invalid. An invalid
\a index also returns an invalid rectangle.
\since 6.12
\sa visualRect(), QStyle::SE_TreeViewDisclosureItem
*/
QRect QTreeView::itemDecorationRect(const QModelIndex &index) const
{
Q_D(const QTreeView);
return d->itemDecorationRect(index);
}
/*!
\internal
\return the visual rectangle at \param index, according to \param rule.
\list
\li SingleSection
The return value matches the section, which \a index points to.
\li FullRow
Return the rectangle of the entire row, no matter which section
\a index points to.
\li AddRowIndicatorToFirstSection
Like SingleSection. If \index points to the first section, add the
row indicator and its margins.
\endlist
*/
QRect QTreeViewPrivate::visualRect(const QModelIndex &index, RectRule rule) const
{
Q_Q(const QTreeView);
if (!isIndexValid(index))
return QRect();
// Calculate the entire row's rectangle, even if one of the elements is hidden
if (q->isIndexHidden(index) && rule != FullRow)
return QRect();
executePostedLayout();
const int viewIndex = this->viewIndex(index);
if (viewIndex < 0)
return QRect();
const bool spanning = viewItems.at(viewIndex).spanning;
const int column = index.column();
// if we have a spanning item, make the selection stretch from left to right
int x = (spanning ? 0 : q->columnViewportPosition(column));
int width = (spanning ? header->length() : q->columnWidth(column));
const bool addIndentation = isTreePosition(column) && (column > 0 || rule == SingleSection);
if (rule == FullRow) {
x = 0;
width = q->viewport()->width();
} else if (addIndentation) {
// calculate indentation
const int indentation = indentationForItem(viewIndex);
width -= indentation;
if (!q->isRightToLeft())
x += indentation;
}
const int y = coordinateForItem(viewIndex);
const int height = itemHeight(viewIndex);
return QRect(x, y, width, height);
}
/*!
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 != d->root && 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::changeEvent(QEvent *event)
{
Q_D(QTreeView);
if (event->type() == QEvent::StyleChange) {
if (!d->customIndent) {
// QAbstractItemView calls this method in case of a style change,
// so update the indentation here if it wasn't set manually.
d->updateIndentationFromStyle();
}
}
QAbstractItemView::changeEvent(event);
}
/*!
\reimp
*/
void QTreeView::timerEvent(QTimerEvent *event)
{
Q_D(QTreeView);
if (event->id() == d->columnResizeTimer.id()) {
updateGeometries();
d->columnResizeTimer.stop();
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);
expand(index);
}
d->openTimer.stop();
}
QAbstractItemView::timerEvent(event);
}
/*!
\reimp
*/
#if QT_CONFIG(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);
const int oldBranch = d->hoverBranch;
d->hoverBranch = d->itemDecorationAt(he->position().toPoint());
QModelIndex newIndex = indexAt(he->position().toPoint());
if (d->hover != newIndex || d->hoverBranch != oldBranch) {
// Update the whole hovered over row. No need to update the old hovered
// row, that is taken care in superclass hover handling.
viewport()->update(d->visualRect(newIndex, QTreeViewPrivate::FullRow));
}
break; }
default:
break;
}
return QAbstractItemView::viewportEvent(event);
}
/*!
\reimp
*/
void QTreeView::paintEvent(QPaintEvent *event)
{
Q_D(QTreeView);
d->executePostedLayout();
QPainter painter(viewport());
#if QT_CONFIG(animation)
if (d->isAnimating()) {
drawTree(&painter, event->region() - d->animatedOperation.rect());
d->drawAnimatedOperation(&painter);
} else
#endif // animation
{
drawTree(&painter, event->region());
#if QT_CONFIG(draganddrop)
d->paintDropIndicator(&painter);
#endif
}
}
int QTreeViewPrivate::logicalIndexForTree() const
{
int index = treePosition;
if (index < 0)
index = header->logicalIndex(0);
return index;
}
void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItem *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);
option->features.setFlag(QStyleOptionViewItem::Alternate, current & 1);
++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::modelDestroyed()
{
//we need to clear the viewItems because it contains QModelIndexes to
//the model currently being destroyed
viewItems.clear();
QAbstractItemViewPrivate::modelDestroyed();
}
QRect QTreeViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const
{
const auto parentIdx = topLeft.parent();
executePostedLayout();
QRect updateRect;
int left = std::numeric_limits<int>::max();
int right = std::numeric_limits<int>::min();
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
const auto idxCol0 = model->index(row, 0, parentIdx);
if (isRowHidden(idxCol0))
continue;
QRect rowRect;
if (left != std::numeric_limits<int>::max()) {
// we already know left and right boundary of the rect to update
rowRect = visualRect(idxCol0, FullRow);
if (!rowRect.intersects(rect))
continue;
rowRect = QRect(left, rowRect.top(), right, rowRect.bottom());
} else if (!spanningIndexes.isEmpty() && spanningIndexes.contains(idxCol0)) {
// isFirstColumnSpanned re-creates the child index so take a shortcut here
// spans the whole row, therefore ask for FullRow instead for every cell
rowRect = visualRect(idxCol0, FullRow);
if (!rowRect.intersects(rect))
continue;
} else {
for (int col = topLeft.column(); col <= bottomRight.column(); ++col) {
if (header->isSectionHidden(col))
continue;
const QModelIndex idx(model->index(row, col, parentIdx));
const QRect idxRect = visualRect(idx, SingleSection);
if (idxRect.isNull())
continue;
// early exit when complete row is out of viewport
if (idxRect.top() > rect.bottom() || idxRect.bottom() < rect.top())
break;
if (!idxRect.intersects(rect))
continue;
rowRect = rowRect.united(idxRect);
if (rowRect.left() < rect.left() && rowRect.right() > rect.right())
break;
}
if (rowRect.isValid()) {
left = std::min(left, rowRect.left());
right = std::max(right, rowRect.right());
}
}
updateRect = updateRect.united(rowRect);
if (updateRect.contains(rect)) // already full rect covered?
break;
}
return rect.intersected(updateRect);
}
/*!
\class QTreeViewPrivate
\inmodule QtWidgets
\internal
*/
/*!
\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);
Q_Q(const QTreeView);
if (spanningIndexes.isEmpty())
return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r);
QModelIndexList list;
for (const QModelIndex &idx : indexes) {
if (idx.column() > 0 && q->isFirstColumnSpanned(idx.row(), idx.parent()))
continue;
list << idx;
}
return QAbstractItemViewPrivate::draggablePaintPairs(list, r);
}
void QTreeViewPrivate::adjustViewOptionsForIndex(QStyleOptionViewItem *option, const QModelIndex ¤t) const
{
const int row = viewIndex(current); // get the index in viewItems[]
option->state = option->state | (viewItems.at(row).expanded ? QStyle::State_Open : QStyle::State_None)
| (viewItems.at(row).hasChildren ? QStyle::State_Children : QStyle::State_None)
| (viewItems.at(row).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None);
option->showDecorationSelected = (selectionBehavior & QTreeView::SelectRows)
|| option->showDecorationSelected;
QList<int>
logicalIndices; // index = visual index of visible columns only. data = logical index.
QList<QStyleOptionViewItem::ViewItemPosition>
viewItemPosList; // vector of left/middle/end for each logicalIndex, visible columns
// only.
const bool spanning = viewItems.at(row).spanning;
const int left = (spanning ? header->visualIndex(0) : 0);
const int right = (spanning ? header->visualIndex(0) : header->count() - 1 );
calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
const int visualIndex = logicalIndices.indexOf(current.column());
option->viewItemPosition = viewItemPosList.at(visualIndex);
}
/*!
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);
// d->viewItems changes when posted layouts are executed in itemDecorationAt, so don't copy
const QList<QTreeViewItem> &viewItems = d->viewItems;
QStyleOptionViewItem option;
initViewItemOption(&option);
const QStyle::State state = option.state;
d->current = 0;
if (viewItems.size() == 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();
QPoint hoverPos = d->viewport->mapFromGlobal(QCursor::pos());
d->hoverBranch = d->itemDecorationAt(hoverPos);
QList<int> drawn;
bool multipleRects = (region.rectCount() > 1);
for (const QRect &a : region) {
const QRect area = (multipleRects
? QRect(0, a.y(), viewportWidth, a.height())
: 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.size(); ++i) {
const int itemHeight = d->itemHeight(i);
if (y + itemHeight > area.top())
break;
y += itemHeight;
}
// paint the visible rows
for (; i < viewItems.size() && y <= area.bottom(); ++i) {
const QModelIndex &index = viewItems.at(i).index;
const int itemHeight = d->itemHeight(i);
option.rect = d->visualRect(index, QTreeViewPrivate::FullRow);
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 != nullptr; parent = parent->parent()) {
if (parent == widget)
return true;
}
return false;
}
void QTreeViewPrivate::calcLogicalIndices(
QList<int> *logicalIndices, QList<QStyleOptionViewItem::ViewItemPosition> *itemPositions,
int left, int right) const
{
const int columnCount = header->count();
/* '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 QStyleOptionViewItem::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;
}
}
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);
}
}
const auto indicesCount = logicalIndices->size();
itemPositions->resize(indicesCount);
for (qsizetype currentLogicalSection = 0; currentLogicalSection < indicesCount; ++currentLogicalSection) {
// determine the viewItemPosition depending on the position of column 0
int nextLogicalSection = currentLogicalSection + 1 >= indicesCount
? logicalIndexAfterRight
: logicalIndices->at(currentLogicalSection + 1);
int prevLogicalSection = currentLogicalSection - 1 < 0
? logicalIndexBeforeLeft
: logicalIndices->at(currentLogicalSection - 1);
const int headerSection = logicalIndices->at(currentLogicalSection);
QStyleOptionViewItem::ViewItemPosition pos;
if ((nextLogicalSection == -1 && prevLogicalSection == -1) || spanning) {
pos = QStyleOptionViewItem::OnlyOne;
} else if ((nextLogicalSection != 0 && prevLogicalSection == -1) || isTreePosition(headerSection))
pos = QStyleOptionViewItem::Beginning;
else if (nextLogicalSection == 0 || nextLogicalSection == -1)
pos = QStyleOptionViewItem::End;
else
pos = QStyleOptionViewItem::Middle;
(*itemPositions)[currentLogicalSection] = pos;
}
}
/*!
\internal
Get sizeHint width for single index (providing existing hint and style option) and index in viewIndex i.
*/
int QTreeViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option, int i) const
{
Q_Q(const QTreeView);
QWidget *editor = editorForIndex(index).widget.data();
if (editor && persistent.contains(editor)) {
hint = qMax(hint, editor->sizeHint().width());
int min = editor->minimumSize().width();
int max = editor->maximumSize().width();
hint = qBound(min, hint, max);
}
int xhint = q->itemDelegateForIndex(index)->sizeHint(option, index).width();
hint = qMax(hint, xhint + (isTreePosition(index.column()) ? indentationForItem(i) : 0));
return hint;
}
/*!
Draws the row in the tree view that contains the model item \a index,
using the \a painter given. The \a option controls how the item is
displayed.
\sa setAlternatingRowColors()
*/
void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
Q_D(const QTreeView);
QStyleOptionViewItem 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->editorIndexHash.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: if we select entire rows, then we need to draw the
// selection in the first column all the way to the second column, rather
// than just around the item text. We abuse showDecorationSelected to
// indicate this to the style. Below we will reset this value temporarily
// to only respect the styleHint while we are rendering the decoration.
opt.showDecorationSelected = (d->selectionBehavior & SelectRows)
|| option.showDecorationSelected;
int width, height = option.rect.height();
int position;
QModelIndex modelIndex;
const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
&& index.parent() == hover.parent()
&& index.row() == hover.row();
QList<int> logicalIndices;
QList<QStyleOptionViewItem::ViewItemPosition>
viewItemPosList; // vector of left/middle/end for each logicalIndex
d->calcLogicalIndices(&logicalIndices, &viewItemPosList, left, right);
for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.size(); ++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;
opt.viewItemPosition = viewItemPosList.at(currentLogicalSection);
// 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;
}
opt.state.setFlag(QStyle::State_MouseOver,
(hoverRow || modelIndex == hover)
&& (option.showDecorationSelected || d->hoverBranch == -1));
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) {
opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
}
opt.features &= ~QStyleOptionViewItem::IsDecoratedRootColumn;
/* 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_PanelItemViewRow which
does the appropriate fill */
if (d->isTreePosition(headerSection)) {
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)
// We use showDecorationSelected both to store the style hint, and to indicate
// that the entire row has to be selected (see overrides of the value if
// selectionBehavior == SelectRow).
// While we are only painting the background we don't care for the
// selectionBehavior factor, so respect only the style value, and reset later.
const bool oldShowDecorationSelected = opt.showDecorationSelected;
opt.showDecorationSelected = style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected,
&opt, this);
opt.rect = branches;
if (opt.rect.width() > 0) {
// the root item also has a branch decoration
opt.features |= QStyleOptionViewItem::IsDecoratedRootColumn;
// we now want to draw the branch decoration
opt.features |= QStyleOptionViewItem::IsDecorationForRootColumn;
style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this);
opt.features &= ~QStyleOptionViewItem::IsDecorationForRootColumn;
}
// 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;
opt.showDecorationSelected = oldShowDecorationSelected;
if (d->indent != 0)
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;
}
itemDelegateForIndex(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::Window);
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();
QStyleOptionViewItem opt;
initViewItemOption(&opt);
QStyle::State extraFlags = QStyle::State_None;
if (isEnabled())
extraFlags |= QStyle::State_Enabled;
if (hasFocus())
extraFlags |= QStyle::State_Active;
QPainterStateGuard psg(painter, QPainterStateGuard::InitialState::NoSave);
if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel) {
psg.save();
painter->setBrushOrigin(QPoint(0, verticalOffset()));
}
if (d->alternatingColors) {
opt.features.setFlag(QStyleOptionViewItem::Alternate, d->current & 1);
}
// 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);
opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
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;
opt.state.setFlag(QStyle::State_MouseOver, hoverRow || item == d->hoverBranch);
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
current = ancestor;
ancestor = current.parent();
}
}
/*!
\reimp
*/
void QTreeView::mousePressEvent(QMouseEvent *event)
{
Q_D(QTreeView);
bool handled = false;
if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, nullptr, this) == QEvent::MouseButtonPress)
handled = d->expandOrCollapseItemAtPos(event->position().toPoint());
if (!handled && d->itemDecorationAt(event->position().toPoint()) == -1)
QAbstractItemView::mousePressEvent(event);
else
d->pressedIndex = QModelIndex();
}
/*!
\reimp
*/
void QTreeView::mouseReleaseEvent(QMouseEvent *event)
{
Q_D(QTreeView);
if (d->itemDecorationAt(event->position().toPoint()) == -1) {
QAbstractItemView::mouseReleaseEvent(event);
} else {
if (state() == QAbstractItemView::DragSelectingState || state() == QAbstractItemView::DraggingState)
setState(QAbstractItemView::NoState);
if (style()->styleHint(QStyle::SH_ListViewExpand_SelectMouseType, nullptr, this) == QEvent::MouseButtonRelease)
d->expandOrCollapseItemAtPos(event->position().toPoint());
}
}
/*!
\reimp
*/
void QTreeView::mouseDoubleClickEvent(QMouseEvent *event)
{
Q_D(QTreeView);
if (state() != NoState || !d->viewport->rect().contains(event->position().toPoint()))
return;
int i = d->itemDecorationAt(event->position().toPoint());
if (i == -1) {
i = d->itemAtCoordinate(event->position().toPoint().y());
if (i == -1)
return; // user clicked outside the items
const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index;
const QPersistentModelIndex persistent = indexAt(event->position().toPoint());
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, nullptr, this))
emit activated(persistent);
d->releaseFromDoubleClick = true;
d->executePostedLayout(); // we need to make sure viewItems is updated
if (d->itemsExpandable
&& d->expandsOnDoubleClick
&& d->hasVisibleChildren(persistent)) {
if (!((i < d->viewItems.size()) && (d->viewItems.at(i).index == firstColumnIndex))) {
// find the new index of the item
for (i = 0; i < d->viewItems.size(); ++i) {
if (d->viewItems.at(i).index == firstColumnIndex)
break;
}
if (i == d->viewItems.size())
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->position().toPoint()) == -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: {
expandRecursively(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();
const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
}
/*!
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.size())
return QModelIndex();
const QModelIndex firstColumnIndex = d->viewItems.at(i).index;
return firstColumnIndex.sibling(firstColumnIndex.row(), index.column());
}
/*!
\internal
Lays out the items in the tree view.
*/
void QTreeView::doItemsLayout()
{
Q_D(QTreeView);
d->doItemsLayout(QTreeViewPrivate::ExpandItems::RootOnly,
QTreeViewPrivate::InitializeNewItems::Initialize);
}
void QTreeViewPrivate::doItemsLayout(ExpandItems expandItems, InitializeNewItems initializeNewItems)
{
Q_Q(QTreeView);
if (hasRemovedItems) {
//clean the QSet that may contains old (and this invalid) indexes
hasRemovedItems = false;
const auto pred = [](const QPersistentModelIndex &idx) { return !idx.isValid(); };
expandedIndexes.removeIf(pred);
hiddenIndexes.removeIf(pred);
}
viewItems.clear(); // prepare for new layout
QModelIndex parent = root;
if (model->hasChildren(parent))
layout(-1, expandItems, initializeNewItems);
q->QAbstractItemView::doItemsLayout();
header->doItemsLayout();
// reset the accessibility representation of the view once control has
// returned to the event loop. This avoids that we destroy UI tree elements
// in the platform layer as part of a model-reset notification, while those
// elements respond to a query (i.e. of rect, which results in a call to
// doItemsLayout().
QMetaObject::invokeMethod(q, [this]{ updateAccessibility(); }, Qt::QueuedConnection);
}
/*!
\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;
const int cnt = qMin(d->viewItems.size(), verticalScrollBar()->value());
for (int i = 0; i < cnt; ++i)
offset += d->itemHeight(i);
return offset;
}
// 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(d->header->logicalIndex(c)))
++c;
if (i < d->viewItems.size() && c < d->header->count()) {
return d->modelIndex(i, d->header->logicalIndex(c));
}
return QModelIndex();
}
const int 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:
return d->modelIndex(d->below(vi), current.column());
case MovePrevious:
case MoveUp:
return d->modelIndex(d->above(vi), current.column());
case MoveLeft: {
QScrollBar *sb = horizontalScrollBar();
if (vi < d->viewItems.size() && 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, nullptr, 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.size() && !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, nullptr, 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++;
const int newColumn = d->header->logicalIndex(visualColumn);
const QModelIndex next = current.sibling(current.row(), newColumn);
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->modelIndex(d->itemForKeyHome(), current.column());
case MoveEnd:
return d->modelIndex(d->itemForKeyEnd(), 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.constFirst().index;
if (!bottomRight.isValid() && !d->viewItems.isEmpty()) {
const int column = d->header->logicalIndex(d->header->count() - 1);
const QModelIndex index = d->viewItems.constLast().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 (const auto &range : selection) {
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 = d->visualRect(leftIndex, QTreeViewPrivate::SingleSection);
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 = d->visualRect(rightIndex, QTreeViewPrivate::SingleSection);
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.size(); ++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) {
int oldOffset = d->header->offset();
d->header->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
int newOffset = d->header->offset();
dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
}
}
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.size(), 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->editorIndexHash.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;
dy = 0;
if (previousViewIndex < currentViewIndex) { // scrolling down
for (int i = previousViewIndex; i < currentViewIndex; ++i) {
if (i < d->viewItems.size())
dy -= d->itemHeight(i);
}
} else if (previousViewIndex > currentViewIndex) { // scrolling up
for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) {
if (i < d->viewItems.size())
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)
|| (parent == d->root)) {
d->doDelayedItemsLayout();
} else if (parentItem != -1 && parentRowCount == delta) {
// 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();
}
/*!
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->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(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
*/
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));
}
/*!
Sorts the model 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 (column < -1)
return;
d->header->setSortIndicator(column, order);
// If sorting is not enabled or has the same order as before, force to sort now
// else sorting will be trigger through sortIndicatorChanged()
if (!d->sortingEnabled ||
(d->header->sortIndicatorSection() == column && d->header->sortIndicatorOrder() == order))
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 && mode != NoSelection && !d->viewItems.isEmpty()) {
const QModelIndex &idx = d->viewItems.constLast().index;
QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1);
d->select(d->viewItems.constFirst().index, lastItemIndex,
QItemSelectionModel::ClearAndSelect
|QItemSelectionModel::Rows);
}
}
/*!
\reimp
*/
QSize QTreeView::viewportSizeHint() const
{
Q_D(const QTreeView);
d->executePostedLayout(); // Make sure that viewItems are up to date.
if (d->viewItems.size() == 0)
return QAbstractItemView::viewportSizeHint();
// Get rect for last item
const QRect deepestRect = d->visualRect(d->viewItems.last().index,
QTreeViewPrivate::SingleSection);
if (!deepestRect.isValid())
return QAbstractItemView::viewportSizeHint();
QSize result = QSize(d->header->length(), deepestRect.bottom() + 1);
// add size for header
result += QSize(0, d->header->isHidden() ? 0 : d->header->height());
return result;
}
/*!
Expands all expandable items.
\note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
data.
\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->doItemsLayout(QTreeViewPrivate::ExpandItems::AllLevels,
QTreeViewPrivate::InitializeNewItems::Initialize);
updateGeometries();
d->viewport->update();
d->updateAccessibility();
}
/*!
\since 5.13
Expands the item at the given \a index and all its children to the
given \a depth. The \a depth is relative to the given \a index.
A \a depth of -1 will expand all children, a \a depth of 0 will
only expand the given \a index.
\note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
data.
\warning If the model contains a large number of items,
this function will take some time to execute.
\sa expandAll()
*/
void QTreeView::expandRecursively(const QModelIndex &index, int depth)
{
Q_D(QTreeView);
if (depth < -1)
return;
// do layouting only once after expanding is done
d->doDelayedItemsLayout();
expand(index);
if (depth == 0)
return;
QStack<std::pair<QModelIndex, int>> parents;
parents.push({index, 0});
while (!parents.isEmpty()) {
const std::pair<QModelIndex, int> elem = parents.pop();
const QModelIndex &parent = elem.first;
const int curDepth = elem.second;
const int rowCount = d->model->rowCount(parent);
for (int row = 0; row < rowCount; ++row) {
const QModelIndex child = d->model->index(row, 0, parent);
if (!d->isIndexValid(child))
break;
if (depth == -1 || curDepth + 1 < depth)
parents.push({child, curDepth + 1});
if (d->isIndexExpanded(child))
continue;
if (d->storeExpanded(child))
emit expanded(child);
}
}
}
/*!
Collapses all expanded items.
\sa expandAll(), expand(), collapse(), setExpanded()
*/
void QTreeView::collapseAll()
{
Q_D(QTreeView);
QSet<QPersistentModelIndex> old_expandedIndexes;
old_expandedIndexes = d->expandedIndexes;
d->expandedIndexes.clear();
if (!signalsBlocked() && isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed))) {
QSet<QPersistentModelIndex>::const_iterator i = old_expandedIndexes.constBegin();
for (; i != old_expandedIndexes.constEnd(); ++i) {
const QPersistentModelIndex &mi = (*i);
if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
emit collapsed(mi);
}
}
doItemsLayout();
}
/*!
Expands all expandable items to the given \a depth.
\note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more}
data.
\sa expandAll(), collapseAll(), expand(), collapse(), setExpanded()
*/
void QTreeView::expandToDepth(int depth)
{
Q_D(QTreeView);
d->viewItems.clear();
QSet<QPersistentModelIndex> old_expandedIndexes;
old_expandedIndexes = d->expandedIndexes;
d->expandedIndexes.clear();
d->interruptDelayedItemsLayout();
d->layout(-1, QTreeViewPrivate::ExpandItems::RootOnly,
QTreeViewPrivate::InitializeNewItems::Initialize);
for (int i = 0; i < d->viewItems.size(); ++i) {
if (q20::cmp_less_equal(d->viewItems.at(i).level, depth)) {
d->viewItems[i].expanded = true;
d->layout(i, QTreeViewPrivate::ExpandItems::RootOnly,
QTreeViewPrivate::InitializeNewItems::Initialize);
d->storeExpanded(d->viewItems.at(i).index);
}
}
bool someSignalEnabled = isSignalConnected(QMetaMethod::fromSignal(&QTreeView::collapsed));
someSignalEnabled |= isSignalConnected(QMetaMethod::fromSignal(&QTreeView::expanded));
if (!signalsBlocked() && someSignalEnabled) {
// emit signals
QSet<QPersistentModelIndex> collapsedIndexes = old_expandedIndexes - d->expandedIndexes;
QSet<QPersistentModelIndex>::const_iterator i = collapsedIndexes.constBegin();
for (; i != collapsedIndexes.constEnd(); ++i) {
const QPersistentModelIndex &mi = (*i);
if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
emit collapsed(mi);
}
QSet<QPersistentModelIndex> expandedIndexs = d->expandedIndexes - old_expandedIndexes;
i = expandedIndexs.constBegin();
for (; i != expandedIndexs.constEnd(); ++i) {
const QPersistentModelIndex &mi = (*i);
if (mi.isValid() && !(mi.flags() & Qt::ItemNeverHasChildren))
emit expanded(mi);
}
}
updateGeometries();
d->viewport->update();
d->updateAccessibility();
}
/*!
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->columnResizeTimer.isActive())
d->columnResizeTimer.start(0ns, this);
}
/*!
\reimp
*/
void QTreeView::updateGeometries()
{
Q_D(QTreeView);
if (d->header) {
if (d->geometryRecursionBlock)
return;
d->geometryRecursionBlock = true;
int height = 0;
if (!d->header->isHidden()) {
height = qMax(d->header->minimumHeight(), d->header->sizeHint().height());
height = qMin(height, d->header->maximumHeight());
}
setViewportMargins(0, height, 0, 0);
QRect vg = d->viewport->geometry();
QRect geometryRect(vg.left(), vg.top() - height, vg.width(), height);
d->header->setGeometry(geometryRect);
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(), QHeaderView::resizeContentsPrecision()
*/
int QTreeView::sizeHintForColumn(int column) const
{
Q_D(const QTreeView);
d->executePostedLayout();
if (d->viewItems.isEmpty())
return -1;
ensurePolished();
int w = 0;
QStyleOptionViewItem option;
initViewItemOption(&option);
const QList<QTreeViewItem> viewItems = d->viewItems;
const int maximumProcessRows = d->header->resizeContentsPrecision(); // To avoid this to take forever.
int offset = 0;
int start = d->firstVisibleItem(&offset);
int end = d->lastVisibleItem(start, offset);
if (start < 0 || end < 0 || end == viewItems.size() - 1) {
end = viewItems.size() - 1;
if (maximumProcessRows < 0) {
start = 0;
} else if (maximumProcessRows == 0) {
start = qMax(0, end - 1);
int remainingHeight = viewport()->height();
while (start > 0 && remainingHeight > 0) {
remainingHeight -= d->itemHeight(start);
--start;
}
} else {
start = qMax(0, end - maximumProcessRows);
}
}
int rowsProcessed = 0;
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);
w = d->widthHintForIndex(index, w, option, i);
++rowsProcessed;
if (rowsProcessed == maximumProcessRows)
break;
}
--end;
int actualBottom = viewItems.size() - 1;
if (maximumProcessRows == 0)
rowsProcessed = 0; // skip the while loop
while (rowsProcessed != maximumProcessRows && (start > 0 || end < actualBottom)) {
int idx = -1;
if ((rowsProcessed % 2 && start > 0) || end == actualBottom) {
while (start > 0) {
--start;
if (viewItems.at(start).spanning)
continue;
idx = start;
break;
}
} else {
while (end < actualBottom) {
++end;
if (viewItems.at(end).spanning)
continue;
idx = end;
break;
}
}
if (idx < 0)
continue;
QModelIndex index = viewItems.at(idx).index;
index = index.sibling(index.row(), column);
w = d->widthHintForIndex(index, w, option, idx);
++rowsProcessed;
}
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;
QStyleOptionViewItem option;
initViewItemOption(&option);
// ### 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).widget.data();
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 = itemDelegateForIndex(idx)->sizeHint(option, idx).height();
height = qMax(height, hint);
}
}
return height;
}
/*!
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);
updateIndentationFromStyle();
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->setSectionsMovable(true);
header->setStretchLastSection(true);
header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
q->setHeader(header);
#if QT_CONFIG(animation)
animationsEnabled = q->style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, q) > 0;
animationConnection =
QObjectPrivate::connect(&animatedOperation, &QVariantAnimation::finished,
this, &QTreeViewPrivate::endAnimatedOperation);
#endif // animation
}
void QTreeViewPrivate::clearConnections()
{
for (QMetaObject::Connection &connection : modelConnections)
QObject::disconnect(connection);
for (QMetaObject::Connection &connection : headerConnections)
QObject::disconnect(connection);
QObject::disconnect(selectionmodelConnection);
QObject::disconnect(sortHeaderConnection);
#if QT_CONFIG(animation)
QObject::disconnect(animationConnection);
#endif
}
void QTreeViewPrivate::expand(int item, bool emitSignal)
{
Q_Q(QTreeView);
if (item == -1 || viewItems.at(item).expanded)
return;
const QModelIndex index = viewItems.at(item).index;
if (index.flags() & Qt::ItemNeverHasChildren)
return;
#if QT_CONFIG(animation)
if (emitSignal && animationsEnabled)
prepareAnimatedOperation(item, QVariantAnimation::Forward);
#endif // animation
//if already animating, stateBeforeAnimation is set to the correct value
if (state != QAbstractItemView::AnimatingState)
stateBeforeAnimation = state;
q->setState(QAbstractItemView::ExpandingState);
storeExpanded(index);
viewItems[item].expanded = true;
layout(item, ExpandItems::RootOnly, InitializeNewItems::Initialize);
q->setState(stateBeforeAnimation);
if (model->canFetchMore(index))
model->fetchMore(index);
if (emitSignal) {
emit q->expanded(index);
#if QT_CONFIG(animation)
if (animationsEnabled)
beginAnimatedOperation();
#endif // animation
}
updateAccessibility();
}
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.size(); 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.size(); i++)
if (items[i].parentItem >= pos)
items[i].parentItem -= count;
}
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
#if QT_CONFIG(animation)
if (emitSignal && animationsEnabled)
prepareAnimatedOperation(item, QVariantAnimation::Backward);
#endif // animation
//if already animating, stateBeforeAnimation is set to the correct value
if (state != QAbstractItemView::AnimatingState)
stateBeforeAnimation = 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(stateBeforeAnimation);
if (emitSignal) {
emit q->collapsed(modelIndex);
#if QT_CONFIG(animation)
if (animationsEnabled)
beginAnimatedOperation();
#endif // animation
}
}
#if QT_CONFIG(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() * q->devicePixelRatio());
pixmap.setDevicePixelRatio(q->devicePixelRatio());
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.setLayoutDirection(q->layoutDirection());
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
QStyleOptionViewItem option;
q->initViewItemOption(&option);
for (QEditorIndexHash::const_iterator it = editorIndexHash.constBegin(); it != editorIndexHash.constEnd(); ++it) {
QWidget *editor = it.key();
const QModelIndex &index = it.value();
option.rect = visualRect(index, SingleSection);
if (option.rect.isValid()) {
if (QAbstractItemDelegate *delegate = q->itemDelegateForIndex(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::endAnimatedOperation()
{
Q_Q(QTreeView);
q->setState(stateBeforeAnimation);
q->updateGeometries();
viewport->update();
}
#endif // animation
void QTreeViewPrivate::modelAboutToBeReset()
{
viewItems.clear();
}
void QTreeViewPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
if (start <= 0 && 0 <= end)
viewItems.clear();
QAbstractItemViewPrivate::columnsAboutToBeRemoved(parent, start, end);
}
void QTreeViewPrivate::columnsRemoved(const QModelIndex &parent, int start, int end)
{
if (start <= 0 && 0 <= end)
doDelayedItemsLayout();
QAbstractItemViewPrivate::columnsRemoved(parent, start, end);
}
void QTreeViewPrivate::updateAccessibility()
{
#if QT_CONFIG(accessibility)
Q_Q(QTreeView);
if (pendingAccessibilityUpdate) {
pendingAccessibilityUpdate = false;
if (QAccessible::isActive()) {
QAccessibleTableModelChangeEvent event(q, QAccessibleTableModelChangeEvent::ModelReset);
QAccessible::updateAccessibility(&event);
}
}
#endif
}
/** \internal
creates and initialize the viewItem structure of the children of the element \li
set \a recursiveExpanding if the function has to expand all the children (called from expandAll)
\a initializeNewItems 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, ExpandItems recursiveExpanding, InitializeNewItems initializeNewItems)
{
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;
}
#if QT_CONFIG(accessibility)
// QAccessibleTree's rowCount implementation uses viewItems.size(), so
// we need to invalidate any cached accessibility data structures if
// that value changes during the run of this function.
const auto resetModelIfNeeded = qScopeGuard([oldViewItemsSize = viewItems.size(), this]{
pendingAccessibilityUpdate |= oldViewItemsSize != viewItems.size();
});
#endif
int count = 0;
if (model->hasChildren(parent)) {
if (model->canFetchMore(parent)) {
// fetchMore first, otherwise we might not yet have any data for sizeHintForRow
model->fetchMore(parent);
// guestimate the number of items in the viewport, and fetch as many as might fit
const int itemHeight = defaultItemHeight <= 0 ? q->sizeHintForRow(0) : defaultItemHeight;
const int viewCount = itemHeight ? viewport->height() / itemHeight : 0;
int lastCount = -1;
while ((count = model->rowCount(parent)) < viewCount &&
count != lastCount && model->canFetchMore(parent)) {
model->fetchMore(parent);
lastCount = count;
}
} else {
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);
initializeNewItems = InitializeNewItems::DontInitialize;
} else if (q20::cmp_not_equal(viewItems[i].total, count)) {
if (initializeNewItems == InitializeNewItems::Initialize)
insertViewItems(i + 1, count, QTreeViewItem()); // expand
else if (count > 0)
viewItems.resize(viewItems.size() + 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 = nullptr;
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 == ExpandItems::AllLevels
&& !(current.flags() & Qt::ItemNeverHasChildren))
|| isIndexExpanded(current)) {
if (recursiveExpanding == ExpandItems::AllLevels && storeExpanded(current)
&& !q->signalsBlocked()) {
emit q->expanded(current);
}
item->expanded = true;
layout(last, recursiveExpanding, initializeNewItems);
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 (initializeNewItems == InitializeNewItems::Initialize)
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--;
if (index == -1)
index = 0;
while (isItemHiddenOrDisabled(index))
index++;
return index >= viewItems.size() ? 0 : index;
}
int QTreeViewPrivate::pageDown(int i) const
{
int index = itemAtCoordinate(coordinateForItem(i) + viewport->height());
while (isItemHiddenOrDisabled(index))
index++;
if (index == -1 || index >= viewItems.size())
index = viewItems.size() - 1;
while (isItemHiddenOrDisabled(index))
index--;
return index == -1 ? viewItems.size() - 1 : index;
}
int QTreeViewPrivate::itemForKeyHome() const
{
int index = 0;
while (isItemHiddenOrDisabled(index))
index++;
return index >= viewItems.size() ? 0 : index;
}
int QTreeViewPrivate::itemForKeyEnd() const
{
int index = viewItems.size() - 1;
while (isItemHiddenOrDisabled(index))
index--;
return index == -1 ? viewItems.size() - 1 : index;
}
int QTreeViewPrivate::indentationForItem(int item) const
{
if (item < 0 || item >= viewItems.size())
return 0;
int level = viewItems.at(item).level;
if (rootDecoration)
++level;
return level * indent;
}
int QTreeViewPrivate::itemHeight(int item) const
{
Q_ASSERT(item < viewItems.size());
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 (maybe do like QHeaderView by letting items have startposition)
int y = 0;
for (int i = 0; i < viewItems.size(); ++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.size()) {
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.size();
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.size(); ++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.size(); ++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.size();
const QModelIndex index = _index.sibling(_index.row(), 0);
const int row = index.row();
const quintptr 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.size())
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.size()) ? -1 : value;
}
// ScrollMode == ScrollPerPixel
if (uniformRowHeights) {
if (!defaultItemHeight)
return -1;
if (offset)
*offset = -(value % defaultItemHeight);
return value / defaultItemHeight;
}
int y = 0; // ### (maybe do like QHeaderView by letting items have startposition)
for (int i = 0; i < viewItems.size(); ++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::lastVisibleItem(int firstVisual, int offset) const
{
if (firstVisual < 0 || offset < 0) {
firstVisual = firstVisibleItem(&offset);
if (firstVisual < 0)
return -1;
}
int y = - offset;
int value = viewport->height();
for (int i = firstVisual; i < viewItems.size(); ++i) {
y += itemHeight(i); // the height value is cached
if (y > value)
return i;
}
return viewItems.size() - 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);
executePostedLayout();
if (viewItems.isEmpty()) {
q->doItemsLayout();
}
int itemsInViewport = 0;
if (uniformRowHeights) {
if (defaultItemHeight <= 0)
itemsInViewport = viewItems.size();
else
itemsInViewport = viewportSize.height() / defaultItemHeight;
} else {
const int itemsCount = viewItems.size();
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.size() - itemsInViewport);
vbar->setPageStep(itemsInViewport);
vbar->setSingleStep(1);
} else { // scroll per pixel
int contentsHeight = 0;
if (uniformRowHeights) {
contentsHeight = defaultItemHeight * viewItems.size();
} else { // ### (maybe do like QHeaderView by letting items have startposition)
for (int i = 0; i < viewItems.size(); ++i)
contentsHeight += itemHeight(i);
}
vbar->setRange(0, contentsHeight - viewportSize.height());
vbar->setPageStep(viewportSize.height());
vbar->d_func()->itemviewChangeSingleStep(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->d_func()->itemviewChangeSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2));
}
}
int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const
{
Q_Q(const QTreeView);
executePostedLayout();
bool spanned = false;
if (!spanningIndexes.isEmpty()) {
const QModelIndex index = q->indexAt(pos);
if (index.isValid())
spanned = q->isFirstColumnSpanned(index.row(), index.parent());
}
const int column = spanned ? 0 : header->logicalIndexAt(pos.x());
if (!isTreePosition(column))
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(logicalIndexForTree());
int size = header->sectionSize(logicalIndexForTree());
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<std::pair<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
std::sort(logicalIndexes.begin(), logicalIndexes.end());
QList<std::pair<int, int>> ret;
std::pair<int, int> current;
current.first = -2; // -1 is not enough because -1+1 = 0
current.second = -2;
for(int i = 0; i < logicalIndexes.size(); ++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<std::pair<int, int>> colRanges = columnRanges(topIndex, bottomIndex);
QList<std::pair<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.size(); ++i)
selection.append(rangeStack.at(i));
}
q->selectionModel()->select(selection, command);
}
std::pair<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 std::pair(qMin(start, end), qMax(start, end));
}
bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const
{
Q_Q(const QTreeView);
if (parent.flags() & Qt::ItemNeverHasChildren)
return false;
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::sortIndicatorChanged(int column, Qt::SortOrder order)
{
model->sort(column, order);
}
#if QT_CONFIG(accessibility)
int QTreeViewPrivate::accessibleChildIndex(const QModelIndex &index) const
{
Q_Q(const QTreeView);
Q_ASSERT(index.isValid());
int rowForIndex = q->visualIndex(index);
if (rowForIndex < 0)
return -1; // should happen rarely, if ever. Possibly when select all is performed
// Note that this will include the header, even if its hidden.
if (q->header())
++rowForIndex;
const int columnCount = index.model()->columnCount();
const qulonglong childIndex = qulonglong(rowForIndex) * columnCount + index.column();
const int result = q26::saturating_cast<int>(childIndex);
if (!q20::cmp_equal(result, childIndex)) {
qWarning("QTreeView: model is too large to be made accessible (%d < %llu)",
result, childIndex);
return -1;
}
return result;
}
#endif
void QTreeViewPrivate::updateIndentationFromStyle()
{
Q_Q(const QTreeView);
indent = q->style()->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, q);
}
/*!
\reimp
*/
void QTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
{
Q_D(QTreeView);
QAbstractItemView::currentChanged(current, previous);
if (allColumnsShowFocus()) {
if (previous.isValid())
viewport()->update(d->visualRect(previous, QTreeViewPrivate::FullRow));
if (current.isValid())
viewport()->update(d->visualRect(current, QTreeViewPrivate::FullRow));
}
#if QT_CONFIG(accessibility)
if (QAccessible::isActive() && current.isValid() && hasFocus()) {
Q_D(QTreeView);
const int entry = d->accessibleChildIndex(current);
if (entry >= 0) {
QAccessibleEvent event(this, QAccessible::Focus);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
}
}
#endif
}
/*!
\reimp
*/
void QTreeView::selectionChanged(const QItemSelection &selected,
const QItemSelection &deselected)
{
QAbstractItemView::selectionChanged(selected, deselected);
#if QT_CONFIG(accessibility)
if (QAccessible::isActive()) {
Q_D(QTreeView);
// ### does not work properly for selection ranges.
QModelIndex sel = selected.indexes().value(0);
if (sel.isValid()) {
int entry = d->accessibleChildIndex(sel);
if (entry >= 0) {
QAccessibleEvent event(this, QAccessible::SelectionAdd);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
}
}
QModelIndex desel = deselected.indexes().value(0);
if (desel.isValid()) {
int entry = d->accessibleChildIndex(desel);
if (entry >= 0) {
QAccessibleEvent event(this, QAccessible::SelectionRemove);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
}
}
}
#endif
}
int QTreeView::visualIndex(const QModelIndex &index) const
{
Q_D(const QTreeView);
d->executePostedLayout();
return d->viewIndex(index);
}
/*!
\internal
*/
void QTreeView::verticalScrollbarValueChanged(int value)
{
Q_D(QTreeView);
if (!d->viewItems.isEmpty() && value == verticalScrollBar()->maximum()) {
QModelIndex ret = d->viewItems.last().index;
// Root index will be handled by base class implementation
while (ret.isValid()) {
if (isExpanded(ret) && d->model->canFetchMore(ret)) {
d->model->fetchMore(ret);
break;
}
ret = ret.parent();
}
}
QAbstractItemView::verticalScrollbarValueChanged(value);
}
QT_END_NAMESPACE
#include "moc_qtreeview.cpp"