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