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
qquickcontextmenu.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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 <QtCore/qpointer.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtQml/qqmlcomponent.h>
10#include <QtQml/qqmlinfo.h>
11#include <QtQuick/qquickwindow.h>
12#include <QtQuick/private/qquickitem_p.h>
13#include <QtQuickTemplates2/private/qquickdeferredexecute_p_p.h>
14#include <QtQuickTemplates2/private/qquickmenu_p.h>
15#include <QtQuickTemplates2/private/qquickmenu_p_p.h>
16#include <QtQuickTemplates2/private/qquicktextarea_p.h>
17#include <QtQuickTemplates2/private/qquicktextfield_p.h>
18
20
21Q_STATIC_LOGGING_CATEGORY(lcContextMenu, "qt.quick.controls.contextmenu")
22
23/*!
24 \qmltype ContextMenu
25 \brief The ContextMenu attached type provides a way to open a context menu
26 in a platform-appropriate manner.
27 \inqmlmodule QtQuick.Controls
28 \ingroup qtquickcontrols-menus
29 \since 6.9
30
31 ContextMenu can be attached to any \l {QQuickItem}{item} in order to show a context menu
32 upon a platform-specific event, such as a right click or the context menu
33 key.
34
35 \snippet qtquickcontrols-contextmenu.qml root
36
37 \section1 Sharing context menus
38
39 It's possible to share a \l Menu amongst several attached context menu
40 objects. This allows reusing a single Menu when the items that need
41 context menus have data in common. For example:
42
43 \snippet qtquickcontrols-contextmenu-shared.qml file
44
45 \section1 Performance
46
47 ContextMenu lazily creates its \c Menu only when it's requested. If it
48 wasn't for this optimization, the \c Menu would be created when the
49 containing component is being loaded, which is typically at application
50 startup.
51
52 It is recommended not to give the \c Menu assigned to ContextMenu's \l menu
53 property an id when it's defined where it's assigned. Doing so prevents
54 this optimization. For example:
55
56 \snippet qtquickcontrols-contextmenu-id.qml root
57
58 The example in the \l {Sharing context menus} section works because the
59 \c Menu is defined separately from its assignment.
60
61 \section1 Interaction with other menus
62
63 If a \c Menu is opened via e.g. a \l TapHandler or other means, ContextMenu
64 will not open at the same time. This allows legacy applications that were
65 written before ContextMenu was introduced to continue working as expected.
66*/
67
68/*!
69 \qmlsignal QtQuick.Controls::ContextMenu::requested(point position)
70
71 This signal is emitted when a context menu is requested.
72
73 If it was requested by a right mouse button click, \a position gives the
74 position of the click relative to the parent.
75
76 The example below shows how to programmatically open a context menu:
77
78 \snippet qtquickcontrols-contextmenu-onrequested.qml buttonAndMenu
79
80 If no menu is set, but this signal is connected, the context menu event
81 will be accepted and will not propagate.
82
83 \sa QContextMenuEvent::pos()
84*/
85
86class QQuickContextMenuPrivate : public QObjectPrivate
87{
88public:
89 Q_DECLARE_PUBLIC(QQuickContextMenu)
90
91 static QQuickContextMenuPrivate *get(QQuickContextMenu *attachedObject)
92 {
93 return attachedObject->d_func();
94 }
95
96 void cancelMenu();
97 void executeMenu(bool complete = false);
98
99 bool isRequestedSignalConnected();
100
101 QQuickDeferredPointer<QQuickMenu> menu;
102 bool complete = false;
103};
104
105static const QString menuPropertyName = QStringLiteral("menu");
106
107void QQuickContextMenuPrivate::cancelMenu()
108{
109 Q_Q(QQuickContextMenu);
110 quickCancelDeferred(q, menuPropertyName);
111}
112
113void QQuickContextMenuPrivate::executeMenu(bool complete)
114{
115 Q_Q(QQuickContextMenu);
116 if (menu.wasExecuted())
117 return;
118
119 QQmlEngine *engine = nullptr;
120 auto *parentItem = qobject_cast<QQuickItem *>(q->parent());
121 if (parentItem) {
122 engine = qmlEngine(parentItem);
123 // In most cases, the above line will work, but if it doesn't,
124 // it could be because we're attached to the contentItem of the window.
125 // In that case, we'll be created before the contentItem has a context
126 // and hence an engine. However, the window will have one, so use that.
127 if (!engine)
128 engine = qmlEngine(parentItem->window());
129 }
130
131 if (!menu || complete)
132 quickBeginAttachedDeferred(q, menuPropertyName, menu, engine);
133 if (complete)
134 quickCompleteAttachedDeferred(q, menuPropertyName, menu, engine);
135}
136
137bool QQuickContextMenuPrivate::isRequestedSignalConnected()
138{
139 Q_Q(QQuickContextMenu);
140 IS_SIGNAL_CONNECTED(q, QQuickContextMenu, requested, (QPointF));
141}
142
143QQuickContextMenu::QQuickContextMenu(QObject *parent)
144 : QObject(*(new QQuickContextMenuPrivate), parent)
145{
146 Q_ASSERT(parent);
147 if (parent->isQuickItemType()) {
148 auto *itemPriv = QQuickItemPrivate::get(static_cast<QQuickItem *>(parent));
149 Q_ASSERT(itemPriv);
150 if (QObject *oldMenu = itemPriv->setContextMenu(this))
151 qCWarning(lcContextMenu) << this << "replaced" << oldMenu << "on" << parent;
152 } else {
153 qmlWarning(parent) << "ContextMenu must be attached to an Item";
154 }
155}
156
157QQuickContextMenu *QQuickContextMenu::qmlAttachedProperties(QObject *object)
158{
159 return new QQuickContextMenu(object);
160}
161
162/*!
163 \qmlproperty Menu QtQuick.Controls::ContextMenu::menu
164
165 This property holds the context menu that will be opened. It can be set to
166 any \l Menu object.
167
168 \note The \l Menu assigned to this property cannot be given an id. See
169 \l {Sharing context menus} for more information.
170*/
171QQuickMenu *QQuickContextMenu::menu() const
172{
173 auto *d = const_cast<QQuickContextMenuPrivate *>(d_func());
174 if (!d->menu) {
175 qCDebug(lcContextMenu) << "creating menu via deferred execution"
176 << "- is component complete:" << d->complete;
177 d->executeMenu(d->complete);
178 }
179 return d->menu;
180}
181
182void QQuickContextMenu::setMenu(QQuickMenu *menu)
183{
184 Q_D(QQuickContextMenu);
185 if (!parent()->isQuickItemType())
186 return;
187
188 if (menu == d->menu)
189 return;
190
191 if (!d->menu.isExecuting())
192 d->cancelMenu();
193
194 d->menu = menu;
195
196 if (!d->menu.isExecuting())
197 emit menuChanged();
198}
199
200void QQuickContextMenu::classBegin()
201{
202}
203
204void QQuickContextMenu::componentComplete()
205{
206 Q_D(QQuickContextMenu);
207 d->complete = true;
208}
209
210bool QQuickContextMenu::event(QEvent *event)
211{
212 Q_D(QQuickContextMenu);
213 switch (event->type()) {
214 case QEvent::ContextMenu: {
215 qCDebug(lcContextMenu) << this << "handling" << event << "on behalf of" << parent();
216
217 auto *attacheeItem = qobject_cast<QQuickItem *>(parent());
218 Q_ASSERT(attacheeItem);
219 const auto *contextMenuEvent = static_cast<QContextMenuEvent *>(event);
220 const QPoint posRelativeToParent(attacheeItem->mapFromScene(contextMenuEvent->pos()).toPoint());
221
222 const bool isRequestedSignalConnected = d->isRequestedSignalConnected();
223 if (isRequestedSignalConnected)
224 Q_EMIT requested(posRelativeToParent);
225
226 auto *menu = this->menu();
227 if (!menu) {
228 if (isRequestedSignalConnected) {
229 qCDebug(lcContextMenu) << this << "no menu instance but accepting event anyway"
230 << "since requested signal has connections";
231 event->accept();
232 return true;
233 }
234
235 // No menu set and requested isn't connected; let the event propagate
236 // onwards and do nothing.
237 return QObject::event(event);
238 }
239
240 // On platforms like iOS, we want to use the native edit menu.
241 // TODO: test this on Android and desktop platforms (with popupType: Popup.Native)
242 if (menu && QQuickMenuPrivate::get(menu)->resolvedPopupType() == QQuickPopup::Native
243 && (qobject_cast<const QQuickTextField *>(attacheeItem)
244 || qobject_cast<const QQuickTextArea *>(attacheeItem))) {
245 QQuickMenuPrivate::get(menu)->makeEditMenu();
246 }
247
248 menu->setParentItem(attacheeItem);
249
250 qCDebug(lcContextMenu) << this << "showing" << menu << "at" << posRelativeToParent;
251 menu->popup(posRelativeToParent);
252 event->accept();
253 return true;
254 }
255 default:
256 break;
257 }
258 return QObject::event(event);
259}
260
261QT_END_NAMESPACE
262
263#include "moc_qquickcontextmenu_p.cpp"
static const QString menuPropertyName