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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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