Revision control

Copy as Markdown

// Copyright (C) 2021 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 "qquicktreeviewdelegate_p.h"
#include <QtQuickTemplates2/private/qquickitemdelegate_p_p.h>
#include <QtQuick/private/qquicktaphandler_p.h>
#include <QtQuick/private/qquicktreeview_p_p.h>
#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
/*!
\qmltype TreeViewDelegate
\inherits ItemDelegate
\inqmlmodule QtQuick.Controls
\since 6.3
\ingroup qtquickcontrols-delegates
\brief A delegate that can be assigned to a TreeView.
\image qtquickcontrols-treeviewdelegate.png
{Tree view displaying hierarchical data using a tree view delegate}
A TreeViewDelegate is a delegate that can be assigned to the
\l {TableView::delegate} {delegate property} of a \l TreeView.
It renders the tree, as well as the other columns, in the view
using the application style.
\qml
TreeView {
anchors.fill: parent
delegate: TreeViewDelegate {}
// The model needs to be a QAbstractItemModel
// model: yourTreeModel
}
\endqml
TreeViewDelegate inherits \l ItemDelegate, which means that
it's composed of three items: a \l[QML]{Control::}{background},
a \l [QML]{Control::}{contentItem}, and an
\l [QML]{AbstractButton::}{indicator}. TreeViewDelegate takes care
of \l {indentation}{indenting} the contentItem and the indicator according
their location in the tree. The indicator will only be visible if the
delegate item is inside the \l {isTreeNode}{tree column}, and renders
a model item \l {hasChildren}{with children}.
If you change the indicator, it will no longer be indented by default.
Instead you need to indent it yourself by setting the
\l [QML] {Item::x}{x position} of the indicator, taking the \l depth and
\l indentation into account. Below is an example of how to do that:
\qml
TreeViewDelegate {
indicator: Item {
x: leftMargin + (depth * indentation)
}
}
\endqml
The position of the contentItem is controlled with \l [QML]{Control::}{padding}.
This means that you can change the contentItem without dealing with indentation.
But it also means that you should avoid changing padding to something else, as
that will remove the indentation. The space to the left of the indicator is instead
controlled with \l leftMargin. The space between the indicator and the contentItem
is controlled with \l [QML]{Control::}{spacing}. And the space to the right of the
contentItem is controlled with \l rightMargin.
\section2 Interacting with pointers
TreeViewDelegate inherits \l ItemDelegate. This means that it will emit signals
such as \l {AbstractButton::clicked()}{clicked} when the user clicks on the delegate.
If needed, you could connect to that signal to implement application specific
functionality, in addition to the default expand/collapse behavior (and even set \l
{TableView::pointerNavigationEnabled}{pointerNavigationEnabled} to \c false, to
disable the default behavior as well).
But the ItemDelegate API does not give you information about the position of the
click, or which modifiers are being held. If this is needed, a better approach would
be to use pointer handlers, for example:
\qml
TreeView {
id: treeView
delegate: TreeViewDelegate {
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: someContextMenu.open()
}
TapHandler {
acceptedModifiers: Qt.ControlModifier
onTapped: {
if (treeView.isExpanded(row))
treeView.collapseRecursively(row)
else
treeView.expandRecursively(row)
}
}
}
}
\endqml
\note If you want to disable the default behavior that occurs when the
user clicks on the delegate (like changing the current index), you can set
\l {TableView::pointerNavigationEnabled}{pointerNavigationEnabled} to \c false.
\section2 Editing nodes in the tree
TreeViewDelegate has a default \l {TableView::editDelegate}{edit delegate}
assigned. If \l TreeView has \l {TableView::editTriggers}{edit triggers}
set, and the \l {TableView::model}{model} has support for
\l {Editing cells} {editing model items}, then the user can activate any of
the edit triggers to edit the text of the \l {TreeViewDelegate::current}{current}
tree node.
The default edit delegate will try to use the \c {Qt.EditRole} to read and
write data to the \l {TableView::model}{model}.
If \l QAbstractItemModel::data() returns an empty string for this role, then
\c {Qt.DisplayRole} will be used instead.
You can always assign your own custom edit delegate to
\l {TableView::editDelegate}{TableView.editDelegate} if you have needs
outside what the default edit delegate offers.
\sa TreeView
*/
/*!
\qmlproperty TreeView QtQuick.Controls::TreeViewDelegate::treeView
This property points to the \l TreeView that contains the delegate item.
*/
/*!
\qmlproperty bool QtQuick.Controls::TreeViewDelegate::hasChildren
This property is \c true if the model item drawn by the delegate
has children in the model.
*/
/*!
\qmlproperty bool QtQuick.Controls::TreeViewDelegate::isTreeNode
This property is \c true if the delegate item draws a node in the tree.
Only one column in the view will be used to draw the tree, and
therefore, only delegate items in that column will have this
property set to \c true.
A node in the tree is \l{indentation}{indented} according to its \c depth, and show
an \l [QML]{AbstractButton::}{indicator} if hasChildren is \c true. Delegate items
in other columns will have this property set to \c false.
*/
/*!
\qmlproperty bool QtQuick.Controls::TreeViewDelegate::expanded
This property is \c true if the model item drawn by the delegate
is expanded in the view.
*/
/*!
\qmlproperty int QtQuick.Controls::TreeViewDelegate::depth
This property holds the depth of the model item drawn by the delegate.
The depth of a model item is the same as the number of ancestors
it has in the model.
*/
/*!
\qmlproperty bool QtQuick.Controls::TreeViewDelegate::current
This property holds if the delegate represent the
\l {QItemSelectionModel::currentIndex()}{current index}
in the \l {TableView::selectionModel}{selection model}.
*/
/*!
\qmlproperty bool QtQuick.Controls::TreeViewDelegate::selected
This property holds if the delegate represent a
\l {QItemSelectionModel::selection()}{selected index}
in the \l {TableView::selectionModel}{selection model}.
*/
/*!
\qmlproperty bool QtQuick.Controls::TreeViewDelegate::editing
\since 6.5
This property holds if the delegate is being
\l {Editing cells}{edited.}
*/
/*!
\qmlproperty real QtQuick.Controls::TreeViewDelegate::indentation
This property holds the space a child is indented horizontally
relative to its parent.
*/
/*!
\qmlproperty real QtQuick.Controls::TreeViewDelegate::leftMargin
This property holds the space between the left edge of the view
and the left edge of the indicator (in addition to indentation).
If no indicator is visible, the space will be between the left
edge of the view and the left edge of the contentItem.
\sa rightMargin, indentation, {QtQuick.Controls::Control::}{spacing}
*/
/*!
\qmlproperty real QtQuick.Controls::TreeViewDelegate::rightMargin
This property holds the space between the right edge of the view
and the right edge of the contentItem.
\sa leftMargin, indentation, {QtQuick.Controls::Control::}{spacing}
*/
using namespace Qt::Literals::StringLiterals;
class QQuickTreeViewDelegatePrivate : public QQuickItemDelegatePrivate
{
public:
Q_DECLARE_PUBLIC(QQuickTreeViewDelegate)
void updateIndicatorVisibility();
void updateIndicatorPointerHandlers();
void toggleExpanded();
QPalette defaultPalette() const override;
public:
QPointer<QQuickTreeView> m_treeView;
QPointer<QQuickTapHandler> m_tapHandlerOnIndicator;
qreal m_indentation = 18;
qreal m_leftMargin = 0;
qreal m_rightMargin = 0;
bool m_isTreeNode = false;
bool m_expanded = false;
bool m_current = false;
bool m_selected = false;
bool m_editing = false;
bool m_hasChildren = false;
bool m_pressOnTopOfIndicator = false;
int m_depth = 0;
};
void QQuickTreeViewDelegatePrivate::toggleExpanded()
{
Q_Q(QQuickTreeViewDelegate);
auto view = q->treeView();
if (!view)
return;
if (!view->pointerNavigationEnabled())
return;
const int row = qmlContext(q)->contextProperty(u"row"_s).toInt();
view->toggleExpanded(row);
}
void QQuickTreeViewDelegatePrivate::updateIndicatorPointerHandlers()
{
Q_Q(QQuickTreeViewDelegate);
// Remove the tap handler that was installed
// on the previous indicator
delete m_tapHandlerOnIndicator.data();
auto indicator = q->indicator();
if (!indicator)
return;
m_tapHandlerOnIndicator = new QQuickTapHandler(indicator);
m_tapHandlerOnIndicator->setAcceptedModifiers(Qt::NoModifier);
// Work-around to block taps from passing through to TreeView.
m_tapHandlerOnIndicator->setGesturePolicy(QQuickTapHandler::ReleaseWithinBounds);
connect(m_tapHandlerOnIndicator, &QQuickTapHandler::tapped, this, &QQuickTreeViewDelegatePrivate::toggleExpanded);
}
void QQuickTreeViewDelegatePrivate::updateIndicatorVisibility()
{
Q_Q(QQuickTreeViewDelegate);
if (auto indicator = q_func()->indicator()) {
const bool insideDelegateBounds = indicator->x() + indicator->width() < q->width();
indicator->setVisible(m_isTreeNode && m_hasChildren && insideDelegateBounds);
}
}
QQuickTreeViewDelegate::QQuickTreeViewDelegate(QQuickItem *parent)
: QQuickItemDelegate(*(new QQuickTreeViewDelegatePrivate), parent)
{
Q_D(QQuickTreeViewDelegate);
auto tapHandler = new QQuickTapHandler(this);
tapHandler->setAcceptedModifiers(Qt::NoModifier);
QObjectPrivate::connect(this, &QQuickAbstractButton::indicatorChanged, d, &QQuickTreeViewDelegatePrivate::updateIndicatorPointerHandlers);
// Since we override mousePressEvent to avoid QQuickAbstractButton from blocking
// pointer handlers, we inform the button about its pressed state from the tap
// handler instead. This will ensure that we emit button signals like
// pressed, clicked, and doubleClicked.
connect(tapHandler, &QQuickTapHandler::pressedChanged, this, [this, d, tapHandler] {
auto view = treeView();
if (view && !view->pointerNavigationEnabled())
return;
const QQuickHandlerPoint p = tapHandler->point();
if (tapHandler->isPressed())
d->handlePress(p.position(), 0);
else if (tapHandler->tapCount() > 0)
d->handleRelease(p.position(), 0);
else
d->handleUngrab();
if (tapHandler->tapCount() > 1 && !tapHandler->isPressed())
emit doubleClicked();
});
}
void QQuickTreeViewDelegate::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
Q_D(QQuickTreeViewDelegate);
QQuickItemDelegate::geometryChange(newGeometry, oldGeometry);
d->updateIndicatorVisibility();
}
void QQuickTreeViewDelegate::mousePressEvent(QMouseEvent *event)
{
Q_D(QQuickTreeViewDelegate);
const auto view = d->m_treeView;
if (view && view->pointerNavigationEnabled()) {
// Ignore mouse events so that we don't block our own pointer handlers, or
// pointer handlers in e.g TreeView, TableView, or SelectionRectangle. Instead
// we call out to the needed mouse handling functions in QAbstractButton directly
// from our pointer handlers, to ensure that continue to work as a button.
event->ignore();
return;
}
QQuickItemDelegate::mousePressEvent(event);
}
QPalette QQuickTreeViewDelegatePrivate::defaultPalette() const
{
return QQuickTheme::palette(QQuickTheme::ItemView);
}
QFont QQuickTreeViewDelegate::defaultFont() const
{
return QQuickTheme::font(QQuickTheme::ItemView);
}
qreal QQuickTreeViewDelegate::indentation() const
{
return d_func()->m_indentation;
}
void QQuickTreeViewDelegate::setIndentation(qreal indentation)
{
Q_D(QQuickTreeViewDelegate);
if (qFuzzyCompare(d->m_indentation, indentation))
return;
d->m_indentation = indentation;
emit indentationChanged();
}
bool QQuickTreeViewDelegate::isTreeNode() const
{
return d_func()->m_isTreeNode;
}
void QQuickTreeViewDelegate::setIsTreeNode(bool isTreeNode)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_isTreeNode == isTreeNode)
return;
d->m_isTreeNode = isTreeNode;
d->updateIndicatorVisibility();
emit isTreeNodeChanged();
}
bool QQuickTreeViewDelegate::hasChildren() const
{
return d_func()->m_hasChildren;
}
void QQuickTreeViewDelegate::setHasChildren(bool hasChildren)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_hasChildren == hasChildren)
return;
d->m_hasChildren = hasChildren;
d->updateIndicatorVisibility();
emit hasChildrenChanged();
}
bool QQuickTreeViewDelegate::expanded() const
{
return d_func()->m_expanded;
}
void QQuickTreeViewDelegate::setExpanded(bool expanded)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_expanded == expanded)
return;
d->m_expanded = expanded;
emit expandedChanged();
}
bool QQuickTreeViewDelegate::current() const
{
return d_func()->m_current;
}
void QQuickTreeViewDelegate::setCurrent(bool current)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_current == current)
return;
d->m_current = current;
emit currentChanged();
}
bool QQuickTreeViewDelegate::selected() const
{
return d_func()->m_selected;
}
void QQuickTreeViewDelegate::setSelected(bool selected)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_selected == selected)
return;
d->m_selected = selected;
emit selectedChanged();
}
bool QQuickTreeViewDelegate::editing() const
{
return d_func()->m_editing;
}
void QQuickTreeViewDelegate::setEditing(bool editing)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_editing == editing)
return;
d->m_editing = editing;
emit editingChanged();
}
int QQuickTreeViewDelegate::depth() const
{
return d_func()->m_depth;
}
void QQuickTreeViewDelegate::setDepth(int depth)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_depth == depth)
return;
d->m_depth = depth;
emit depthChanged();
}
QQuickTreeView *QQuickTreeViewDelegate::treeView() const
{
return d_func()->m_treeView;
}
void QQuickTreeViewDelegate::setTreeView(QQuickTreeView *treeView)
{
Q_D(QQuickTreeViewDelegate);
if (d->m_treeView == treeView)
return;
d->m_treeView = treeView;
emit treeviewChanged();
}
void QQuickTreeViewDelegate::componentComplete()
{
Q_D(QQuickTreeViewDelegate);
QQuickItemDelegate::componentComplete();
d->updateIndicatorVisibility();
d->updateIndicatorPointerHandlers();
}
qreal QQuickTreeViewDelegate::leftMargin() const
{
return d_func()->m_leftMargin;
}
void QQuickTreeViewDelegate::setLeftMargin(qreal leftMargin)
{
Q_D(QQuickTreeViewDelegate);
if (qFuzzyCompare(d->m_leftMargin, leftMargin))
return;
d->m_leftMargin = leftMargin;
emit leftMarginChanged();
}
qreal QQuickTreeViewDelegate::rightMargin() const
{
return d_func()->m_rightMargin;
}
void QQuickTreeViewDelegate::setRightMargin(qreal rightMargin)
{
Q_D(QQuickTreeViewDelegate);
if (qFuzzyCompare(d->m_rightMargin, rightMargin))
return;
d->m_rightMargin = rightMargin;
emit rightMarginChanged();
}
QT_END_NAMESPACE
#include "moc_qquicktreeviewdelegate_p.cpp"