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