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 \section1 Native menu support
68
69 ContextMenu is backed by a native menu on iOS.
70
71 \note if you assign your own \l menu, you must set
72 \l {Popup::popupType}{popupType} to \c {Popup.Native} to ensure native menu
73 support.
74*/
75
76/*!
77 \qmlsignal QtQuick.Controls::ContextMenu::requested(point position)
78
79 This signal is emitted when a context menu is requested.
80
81 If it was requested by a right mouse button click, \a position gives the
82 position of the click relative to the parent.
83
84 The example below shows how to programmatically open a context menu:
85
86 \snippet qtquickcontrols-contextmenu-onrequested.qml buttonAndMenu
87
88 If no menu is set, but this signal is connected, the context menu event
89 will be accepted and will not propagate.
90
91 \sa QContextMenuEvent::pos()
92*/
93
94class QQuickContextMenuPrivate : public QObjectPrivate
95{
96public:
97 Q_DECLARE_PUBLIC(QQuickContextMenu)
98
99 static QQuickContextMenuPrivate *get(QQuickContextMenu *attachedObject)
100 {
101 return attachedObject->d_func();
102 }
103
104 void cancelMenu();
105 void executeMenu(bool complete = false);
106
107 bool isRequestedSignalConnected();
108
109 QQuickDeferredPointer<QQuickMenu> menu;
110 bool complete = false;
111};
112
113static const QString menuPropertyName = QStringLiteral("menu");
114
115void QQuickContextMenuPrivate::cancelMenu()
116{
117 Q_Q(QQuickContextMenu);
118 quickCancelDeferred(q, menuPropertyName);
119}
120
121void QQuickContextMenuPrivate::executeMenu(bool complete)
122{
123 Q_Q(QQuickContextMenu);
124 if (menu.wasExecuted())
125 return;
126
127 QQmlEngine *engine = nullptr;
128 auto *parentItem = qobject_cast<QQuickItem *>(q->parent());
129 if (parentItem) {
130 engine = qmlEngine(parentItem);
131 // In most cases, the above line will work, but if it doesn't,
132 // it could be because we're attached to the contentItem of the window.
133 // In that case, we'll be created before the contentItem has a context
134 // and hence an engine. However, the window will have one, so use that.
135 if (!engine)
136 engine = qmlEngine(parentItem->window());
137 }
138
139 if (!menu || complete)
140 quickBeginAttachedDeferred(q, menuPropertyName, menu, engine);
141 if (complete)
142 quickCompleteAttachedDeferred(q, menuPropertyName, menu, engine);
143}
144
145bool QQuickContextMenuPrivate::isRequestedSignalConnected()
146{
147 Q_Q(QQuickContextMenu);
148 IS_SIGNAL_CONNECTED(q, QQuickContextMenu, requested, (QPointF));
149}
150
151QQuickContextMenu::QQuickContextMenu(QObject *parent)
152 : QObject(*(new QQuickContextMenuPrivate), parent)
153{
154 Q_ASSERT(parent);
155 if (parent->isQuickItemType()) {
156 auto *itemPriv = QQuickItemPrivate::get(static_cast<QQuickItem *>(parent));
157 Q_ASSERT(itemPriv);
158 if (QObject *oldMenu = itemPriv->setContextMenu(this))
159 qCWarning(lcContextMenu) << this << "replaced" << oldMenu << "on" << parent;
160 } else {
161 qmlWarning(parent) << "ContextMenu must be attached to an Item";
162 }
163}
164
165QQuickContextMenu *QQuickContextMenu::qmlAttachedProperties(QObject *object)
166{
167 return new QQuickContextMenu(object);
168}
169
170/*!
171 \qmlproperty Menu QtQuick.Controls::ContextMenu::menu
172
173 This property holds the context menu that will be opened. It can be set to
174 any \l Menu object.
175
176 \note The \l Menu assigned to this property cannot be given an id. See
177 \l {Sharing context menus} for more information.
178*/
179QQuickMenu *QQuickContextMenu::menu() const
180{
181 auto *d = const_cast<QQuickContextMenuPrivate *>(d_func());
182 if (!d->menu) {
183 qCDebug(lcContextMenu) << "creating menu via deferred execution"
184 << "- is component complete:" << d->complete;
185 d->executeMenu(d->complete);
186 }
187 return d->menu;
188}
189
190void QQuickContextMenu::setMenu(QQuickMenu *menu)
191{
192 Q_D(QQuickContextMenu);
193 if (!parent()->isQuickItemType())
194 return;
195
196 if (menu == d->menu)
197 return;
198
199 if (!d->menu.isExecuting())
200 d->cancelMenu();
201
202 d->menu = menu;
203
204 if (!d->menu.isExecuting())
205 emit menuChanged();
206}
207
208void QQuickContextMenu::classBegin()
209{
210}
211
212void QQuickContextMenu::componentComplete()
213{
214 Q_D(QQuickContextMenu);
215 d->complete = true;
216}
217
218bool QQuickContextMenu::event(QEvent *event)
219{
220 Q_D(QQuickContextMenu);
221 switch (event->type()) {
222 case QEvent::ContextMenu: {
223 qCDebug(lcContextMenu) << this << "handling" << event << "on behalf of" << parent();
224
225 auto *attacheeItem = qobject_cast<QQuickItem *>(parent());
226 Q_ASSERT(attacheeItem);
227 const auto *contextMenuEvent = static_cast<QContextMenuEvent *>(event);
228 const QPoint posRelativeToParent(attacheeItem->mapFromScene(contextMenuEvent->pos()).toPoint());
229
230 const bool isRequestedSignalConnected = d->isRequestedSignalConnected();
231 if (isRequestedSignalConnected)
232 Q_EMIT requested(posRelativeToParent);
233
234 auto *menu = this->menu();
235 if (!menu) {
236 if (isRequestedSignalConnected) {
237 qCDebug(lcContextMenu) << this << "no menu instance but accepting event anyway"
238 << "since requested signal has connections";
239 event->accept();
240 return true;
241 }
242
243 // No menu set and requested isn't connected; let the event propagate
244 // onwards and do nothing.
245 return QObject::event(event);
246 }
247
248 // On platforms like iOS, we want to use the native edit menu.
249 // TODO: test this on Android and desktop platforms (with popupType: Popup.Native)
250 if (menu && QQuickMenuPrivate::get(menu)->resolvedPopupType() == QQuickPopup::Native
251 && (qobject_cast<const QQuickTextField *>(attacheeItem)
252 || qobject_cast<const QQuickTextArea *>(attacheeItem))) {
253 QQuickMenuPrivate::get(menu)->makeEditMenu();
254 }
255
256 menu->setParentItem(attacheeItem);
257
258 qCDebug(lcContextMenu) << this << "showing" << menu << "at" << posRelativeToParent;
259 menu->popup(posRelativeToParent);
260 event->accept();
261 return true;
262 }
263 default:
264 break;
265 }
266 return QObject::event(event);
267}
268
269QT_END_NAMESPACE
270
271#include "moc_qquickcontextmenu_p.cpp"
Combined button and popup list for selecting options.
static const QString menuPropertyName