1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquicktreeviewdelegate_p.h"
5
6#include <QtQuickTemplates2/private/qquickitemdelegate_p_p.h>
7#include <QtQuick/private/qquicktaphandler_p.h>
8#include <QtQuick/private/qquicktreeview_p_p.h>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype TreeViewDelegate
14 \inherits ItemDelegate
15 \inqmlmodule QtQuick.Controls
16 \since 6.3
17 \ingroup qtquickcontrols-delegates
18 \brief A delegate that can be assigned to a TreeView.
19
20 \image qtquickcontrols-treeviewdelegate.png
21
22 A TreeViewDelegate is a delegate that can be assigned to the
23 \l {TableView::delegate} {delegate property} of a \l TreeView.
24 It renders the tree, as well as the other columns, in the view
25 using the application style.
26
27 \code
28 TreeView {
29 anchors.fill: parent
30 delegate: TreeViewDelegate {}
31 // The model needs to be a QAbstractItemModel
32 // model: yourTreeModel
33 }
34 \endcode
35
36 TreeViewDelegate inherits \l ItemDelegate, which means that
37 it's composed of three items: a \l[QML]{Control::}{background},
38 a \l [QML]{Control::}{contentItem}, and an
39 \l [QML]{AbstractButton::}{indicator}. TreeViewDelegate takes care
40 of \l {indentation}{indenting} the contentItem and the indicator according
41 their location in the tree. The indicator will only be visible if the
42 delegate item is inside the \l {isTreeNode}{tree column}, and renders
43 a model item \l {hasChildren}{with children}.
44
45 If you change the indicator, it will no longer be indented by default.
46 Instead you need to indent it yourself by setting the
47 \l [QML] {Item::x}{x position} of the indicator, taking the \l depth and
48 \l indentation into account. Below is an example of how to do that:
49
50 \code
51 TreeViewDelegate {
52 indicator: Item {
53 x: leftMargin + (depth * indentation)
54 }
55 }
56 \endcode
57
58 The position of the contentItem is controlled with \l [QML]{Control::}{padding}.
59 This means that you can change the contentItem without dealing with indentation.
60 But it also means that you should avoid changing padding to something else, as
61 that will remove the indentation. The space to the left of the indicator is instead
62 controlled with \l leftMargin. The space between the indicator and the contentItem
63 is controlled with \l [QML]{Control::}{spacing}. And the space to the right of the
64 contentItem is controlled with \l rightMargin.
65
66 \section2 Interacting with pointers
67 TreeViewDelegate inherits \l ItemDelegate. This means that it will emit signals
68 such as \l {AbstractButton::clicked()}{clicked} when the user clicks on the delegate.
69 If needed, you could connect to that signal to implement application specific
70 functionality, in addition to the default expand/collapse behavior (and even set \l
71 {TableView::pointerNavigationEnabled}{pointerNavigationEnabled} to \c false, to
72 disable the default behavior as well).
73
74 But the ItemDelegate API does not give you information about the position of the
75 click, or which modifiers are being held. If this is needed, a better approach would
76 be to use pointer handlers, for example:
77
78 \code
79 TreeView {
80 id: treeView
81 delegate: TreeViewDelegate {
82 TapHandler {
83 acceptedButtons: Qt.RightButton
84 onTapped: someContextMenu.open()
85 }
86
87 TapHandler {
88 acceptedModifiers: Qt.ControlModifier
89 onTapped: {
90 if (treeView.isExpanded(row))
91 treeView.collapseRecursively(row)
92 else
93 treeView.expandRecursively(row)
94 }
95 }
96 }
97 }
98 \endcode
99
100 \note If you want to disable the default behavior that occurs when the
101 user clicks on the delegate (like changing the current index), you can set
102 \l {TableView::pointerNavigationEnabled}{pointerNavigationEnabled} to \c false.
103
104 \section2 Editing nodes in the tree
105 TreeViewDelegate has a default \l {TableView::editDelegate}{edit delegate}
106 assigned. If \l TreeView has \l {TableView::editTriggers}{edit triggers}
107 set, and the \l {TableView::model}{model} has support for
108 \l {Editing cells} {editing model items}, then the user can activate any of
109 the edit triggers to edit the text of the \l {TreeViewDelegate::current}{current}
110 tree node.
111
112 The default edit delegate will try to use the \c {Qt.EditRole} to read and
113 write data to the \l {TableView::model}{model}.
114 If \l QAbstractItemModel::data() returns an empty string for this role, then
115 \c {Qt.DisplayRole} will be used instead.
116
117 You can always assign your own custom edit delegate to
118 \l {TableView::editDelegate}{TableView.editDelegate} if you have needs
119 outside what the default edit delegate offers.
120
121 \sa TreeView
122*/
123
124/*!
125 \qmlproperty TreeView QtQuick.Controls::TreeViewDelegate::treeView
126
127 This property points to the \l TreeView that contains the delegate item.
128*/
129
130/*!
131 \qmlproperty bool QtQuick.Controls::TreeViewDelegate::hasChildren
132
133 This property is \c true if the model item drawn by the delegate
134 has children in the model.
135*/
136
137/*!
138 \qmlproperty bool QtQuick.Controls::TreeViewDelegate::isTreeNode
139
140 This property is \c true if the delegate item draws a node in the tree.
141 Only one column in the view will be used to draw the tree, and
142 therefore, only delegate items in that column will have this
143 property set to \c true.
144
145 A node in the tree is \l{indentation}{indented} according to its \c depth, and show
146 an \l [QML]{AbstractButton::}{indicator} if hasChildren is \c true. Delegate items
147 in other columns will have this property set to \c false.
148*/
149
150/*!
151 \qmlproperty bool QtQuick.Controls::TreeViewDelegate::expanded
152
153 This property is \c true if the model item drawn by the delegate
154 is expanded in the view.
155*/
156
157/*!
158 \qmlproperty int QtQuick.Controls::TreeViewDelegate::depth
159
160 This property holds the depth of the model item drawn by the delegate.
161 The depth of a model item is the same as the number of ancestors
162 it has in the model.
163*/
164
165/*!
166 \qmlproperty bool QtQuick.Controls::TreeViewDelegate::current
167
168 This property holds if the delegate represent the
169 \l {QItemSelectionModel::currentIndex()}{current index}
170 in the \l {TableView::selectionModel}{selection model}.
171*/
172
173/*!
174 \qmlproperty bool QtQuick.Controls::TreeViewDelegate::selected
175
176 This property holds if the delegate represent a
177 \l {QItemSelectionModel::selection()}{selected index}
178 in the \l {TableView::selectionModel}{selection model}.
179*/
180
181/*!
182 \qmlproperty bool QtQuick.Controls::TreeViewDelegate::editing
183 \since 6.5
184
185 This property holds if the delegate is being
186 \l {Editing cells}{edited.}
187*/
188
189/*!
190 \qmlproperty real QtQuick.Controls::TreeViewDelegate::indentation
191
192 This property holds the space a child is indented horizontally
193 relative to its parent.
194*/
195
196/*!
197 \qmlproperty real QtQuick.Controls::TreeViewDelegate::leftMargin
198
199 This property holds the space between the left edge of the view
200 and the left edge of the indicator (in addition to indentation).
201 If no indicator is visible, the space will be between the left
202 edge of the view and the left edge of the contentItem.
203
204 \sa rightMargin, indentation, {QQuickControl::}{spacing}
205*/
206
207/*!
208 \qmlproperty real QtQuick.Controls::TreeViewDelegate::rightMargin
209
210 This property holds the space between the right edge of the view
211 and the right edge of the contentItem.
212
213 \sa leftMargin, indentation, {QQuickControl::}{spacing}
214*/
215
216using namespace Qt::Literals::StringLiterals;
217
218class QQuickTreeViewDelegatePrivate : public QQuickItemDelegatePrivate
219{
220public:
221 Q_DECLARE_PUBLIC(QQuickTreeViewDelegate)
222
223 void updateIndicatorVisibility();
224 void updateIndicatorPointerHandlers();
225 void toggleExpanded();
226 QPalette defaultPalette() const override;
227
228public:
229 QPointer<QQuickTreeView> m_treeView;
230 QPointer<QQuickTapHandler> m_tapHandlerOnIndicator;
231 qreal m_indentation = 18;
232 qreal m_leftMargin = 0;
233 qreal m_rightMargin = 0;
234 bool m_isTreeNode = false;
235 bool m_expanded = false;
236 bool m_current = false;
237 bool m_selected = false;
238 bool m_editing = false;
239 bool m_hasChildren = false;
240 bool m_pressOnTopOfIndicator = false;
241 int m_depth = 0;
242};
243
244void QQuickTreeViewDelegatePrivate::toggleExpanded()
245{
246 Q_Q(QQuickTreeViewDelegate);
247
248 auto view = q->treeView();
249 if (!view)
250 return;
251 if (!view->pointerNavigationEnabled())
252 return;
253
254 const int row = qmlContext(q)->contextProperty(u"row"_s).toInt();
255 view->toggleExpanded(row);
256}
257
258void QQuickTreeViewDelegatePrivate::updateIndicatorPointerHandlers()
259{
260 Q_Q(QQuickTreeViewDelegate);
261
262 // Remove the tap handler that was installed
263 // on the previous indicator
264 delete m_tapHandlerOnIndicator.data();
265
266 auto indicator = q->indicator();
267 if (!indicator)
268 return;
269
270 m_tapHandlerOnIndicator = new QQuickTapHandler(indicator);
271 m_tapHandlerOnIndicator->setAcceptedModifiers(Qt::NoModifier);
272 // Work-around to block taps from passing through to TreeView.
273 m_tapHandlerOnIndicator->setGesturePolicy(QQuickTapHandler::ReleaseWithinBounds);
274 connect(sender: m_tapHandlerOnIndicator, signal: &QQuickTapHandler::tapped, receiverPrivate: this, slot: &QQuickTreeViewDelegatePrivate::toggleExpanded);
275}
276
277void QQuickTreeViewDelegatePrivate::updateIndicatorVisibility()
278{
279 Q_Q(QQuickTreeViewDelegate);
280
281 if (auto indicator = q_func()->indicator()) {
282 const bool insideDelegateBounds = indicator->x() + indicator->width() < q->width();
283 indicator->setVisible(m_isTreeNode && m_hasChildren && insideDelegateBounds);
284 }
285}
286
287QQuickTreeViewDelegate::QQuickTreeViewDelegate(QQuickItem *parent)
288 : QQuickItemDelegate(*(new QQuickTreeViewDelegatePrivate), parent)
289{
290 Q_D(QQuickTreeViewDelegate);
291
292 auto tapHandler = new QQuickTapHandler(this);
293 tapHandler->setAcceptedModifiers(Qt::NoModifier);
294 QObjectPrivate::connect(sender: this, signal: &QQuickAbstractButton::indicatorChanged, receiverPrivate: d, slot: &QQuickTreeViewDelegatePrivate::updateIndicatorPointerHandlers);
295
296 // Since we override mousePressEvent to avoid QQuickAbstractButton from blocking
297 // pointer handlers, we inform the button about its pressed state from the tap
298 // handler instead. This will ensure that we emit button signals like
299 // pressed, clicked, and doubleClicked.
300 connect(sender: tapHandler, signal: &QQuickTapHandler::pressedChanged, slot: [this, d, tapHandler] {
301 auto view = treeView();
302 if (view && !view->pointerNavigationEnabled())
303 return;
304
305 const QQuickHandlerPoint p = tapHandler->point();
306 if (tapHandler->isPressed())
307 d->handlePress(point: p.position(), timestamp: 0);
308 else if (tapHandler->tapCount() > 0)
309 d->handleRelease(point: p.position(), timestamp: 0);
310 else
311 d->handleUngrab();
312
313 if (tapHandler->tapCount() > 1 && !tapHandler->isPressed())
314 emit doubleClicked();
315 });
316}
317
318void QQuickTreeViewDelegate::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
319{
320 Q_D(QQuickTreeViewDelegate);
321
322 QQuickItemDelegate::geometryChange(newGeometry, oldGeometry);
323 d->updateIndicatorVisibility();
324}
325
326void QQuickTreeViewDelegate::mousePressEvent(QMouseEvent *event)
327{
328 Q_D(QQuickTreeViewDelegate);
329
330 const auto view = d->m_treeView;
331 if (view && view->pointerNavigationEnabled()) {
332 // Ignore mouse events so that we don't block our own pointer handlers, or
333 // pointer handlers in e.g TreeView, TableView, or SelectionRectangle. Instead
334 // we call out to the needed mouse handling functions in QAbstractButton directly
335 // from our pointer handlers, to ensure that continue to work as a button.
336 event->ignore();
337 return;
338 }
339
340 QQuickItemDelegate::mousePressEvent(event);
341}
342
343QPalette QQuickTreeViewDelegatePrivate::defaultPalette() const
344{
345 return QQuickTheme::palette(scope: QQuickTheme::ItemView);
346}
347
348QFont QQuickTreeViewDelegate::defaultFont() const
349{
350 return QQuickTheme::font(scope: QQuickTheme::ItemView);
351}
352
353qreal QQuickTreeViewDelegate::indentation() const
354{
355 return d_func()->m_indentation;
356}
357
358void QQuickTreeViewDelegate::setIndentation(qreal indentation)
359{
360 Q_D(QQuickTreeViewDelegate);
361 if (qFuzzyCompare(p1: d->m_indentation, p2: indentation))
362 return;
363
364 d->m_indentation = indentation;
365 emit indentationChanged();
366}
367
368bool QQuickTreeViewDelegate::isTreeNode() const
369{
370 return d_func()->m_isTreeNode;
371}
372
373void QQuickTreeViewDelegate::setIsTreeNode(bool isTreeNode)
374{
375 Q_D(QQuickTreeViewDelegate);
376 if (d->m_isTreeNode == isTreeNode)
377 return;
378
379 d->m_isTreeNode = isTreeNode;
380 d->updateIndicatorVisibility();
381 emit isTreeNodeChanged();
382}
383
384bool QQuickTreeViewDelegate::hasChildren() const
385{
386 return d_func()->m_hasChildren;
387}
388
389void QQuickTreeViewDelegate::setHasChildren(bool hasChildren)
390{
391 Q_D(QQuickTreeViewDelegate);
392 if (d->m_hasChildren == hasChildren)
393 return;
394
395 d->m_hasChildren = hasChildren;
396 d->updateIndicatorVisibility();
397 emit hasChildrenChanged();
398}
399
400bool QQuickTreeViewDelegate::expanded() const
401{
402 return d_func()->m_expanded;
403}
404
405void QQuickTreeViewDelegate::setExpanded(bool expanded)
406{
407 Q_D(QQuickTreeViewDelegate);
408 if (d->m_expanded == expanded)
409 return;
410
411 d->m_expanded = expanded;
412 emit expandedChanged();
413}
414
415bool QQuickTreeViewDelegate::current() const
416{
417 return d_func()->m_current;
418}
419
420void QQuickTreeViewDelegate::setCurrent(bool current)
421{
422 Q_D(QQuickTreeViewDelegate);
423 if (d->m_current == current)
424 return;
425
426 d->m_current = current;
427 emit currentChanged();
428}
429
430bool QQuickTreeViewDelegate::selected() const
431{
432 return d_func()->m_selected;
433}
434
435void QQuickTreeViewDelegate::setSelected(bool selected)
436{
437 Q_D(QQuickTreeViewDelegate);
438 if (d->m_selected == selected)
439 return;
440
441 d->m_selected = selected;
442 emit selectedChanged();
443}
444
445bool QQuickTreeViewDelegate::editing() const
446{
447 return d_func()->m_editing;
448}
449
450void QQuickTreeViewDelegate::setEditing(bool editing)
451{
452 Q_D(QQuickTreeViewDelegate);
453 if (d->m_editing == editing)
454 return;
455
456 d->m_editing = editing;
457 emit editingChanged();
458}
459
460int QQuickTreeViewDelegate::depth() const
461{
462 return d_func()->m_depth;
463}
464
465void QQuickTreeViewDelegate::setDepth(int depth)
466{
467 Q_D(QQuickTreeViewDelegate);
468 if (d->m_depth == depth)
469 return;
470
471 d->m_depth = depth;
472 emit depthChanged();
473}
474
475QQuickTreeView *QQuickTreeViewDelegate::treeView() const
476{
477 return d_func()->m_treeView;
478}
479
480void QQuickTreeViewDelegate::setTreeView(QQuickTreeView *treeView)
481{
482 Q_D(QQuickTreeViewDelegate);
483 if (d->m_treeView == treeView)
484 return;
485
486 d->m_treeView = treeView;
487 emit treeviewChanged();
488}
489
490void QQuickTreeViewDelegate::componentComplete()
491{
492 Q_D(QQuickTreeViewDelegate);
493 QQuickItemDelegate::componentComplete();
494 d->updateIndicatorVisibility();
495 d->updateIndicatorPointerHandlers();
496}
497
498qreal QQuickTreeViewDelegate::leftMargin() const
499{
500 return d_func()->m_leftMargin;
501}
502
503void QQuickTreeViewDelegate::setLeftMargin(qreal leftMargin)
504{
505 Q_D(QQuickTreeViewDelegate);
506 if (qFuzzyCompare(p1: d->m_leftMargin, p2: leftMargin))
507 return;
508
509 d->m_leftMargin = leftMargin;
510 emit leftMarginChanged();
511}
512
513qreal QQuickTreeViewDelegate::rightMargin() const
514{
515 return d_func()->m_rightMargin;
516}
517
518void QQuickTreeViewDelegate::setRightMargin(qreal rightMargin)
519{
520 Q_D(QQuickTreeViewDelegate);
521 if (qFuzzyCompare(p1: d->m_rightMargin, p2: rightMargin))
522 return;
523
524 d->m_rightMargin = rightMargin;
525 emit rightMarginChanged();
526}
527
528QT_END_NAMESPACE
529
530#include "moc_qquicktreeviewdelegate_p.cpp"
531

source code of qtdeclarative/src/quicktemplates/qquicktreeviewdelegate.cpp