aboutsummaryrefslogtreecommitdiffstats
path: root/src/quicktemplates/qquicktreeviewdelegate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quicktemplates/qquicktreeviewdelegate.cpp')
-rw-r--r--src/quicktemplates/qquicktreeviewdelegate.cpp532
1 files changed, 532 insertions, 0 deletions
diff --git a/src/quicktemplates/qquicktreeviewdelegate.cpp b/src/quicktemplates/qquicktreeviewdelegate.cpp
new file mode 100644
index 0000000000..32d51975be
--- /dev/null
+++ b/src/quicktemplates/qquicktreeviewdelegate.cpp
@@ -0,0 +1,532 @@
+// 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
+
+#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
+
+ 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.
+
+ \code
+ TreeView {
+ anchors.fill: parent
+ delegate: TreeViewDelegate {}
+ // The model needs to be a QAbstractItemModel
+ // model: yourTreeModel
+ }
+ \endcode
+
+ 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:
+
+ \code
+ TreeViewDelegate {
+ indicator: Item {
+ x: leftMargin + (depth * indentation)
+ }
+ }
+ \endcode
+
+ 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:
+
+ \code
+ 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)
+ }
+ }
+ }
+ }
+ \endcode
+
+ \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, {QQuickControl::}{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, {QQuickControl::}{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, 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"