1// Copyright (C) 2020 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 "qquickmenu_p.h"
5#include "qquickmenu_p_p.h"
6#include "qquickmenuitem_p_p.h"
7#include <private/qtquicktemplates2-config_p.h>
8#if QT_CONFIG(quicktemplates2_container)
9#include "qquickmenubaritem_p.h"
10#include "qquickmenubar_p_p.h"
11#endif
12#include "qquickmenuseparator_p.h"
13#include "qquicknativemenuitem_p.h"
14#include "qquickpopupitem_p_p.h"
15#include "qquickpopuppositioner_p_p.h"
16#include "qquickaction_p.h"
17
18#include <QtCore/qloggingcategory.h>
19#include <QtGui/qevent.h>
20#include <QtGui/qcursor.h>
21#if QT_CONFIG(shortcut)
22#include <QtGui/qkeysequence.h>
23#endif
24#include <QtGui/qpa/qplatformintegration.h>
25#include <QtGui/qpa/qplatformtheme.h>
26#include <QtGui/private/qhighdpiscaling_p.h>
27#include <QtGui/private/qguiapplication_p.h>
28#include <QtQml/qqmlcontext.h>
29#include <QtQml/qqmlcomponent.h>
30#include <QtQml/private/qqmlengine_p.h>
31#include <QtQml/private/qv4scopedvalue_p.h>
32#include <QtQml/private/qv4variantobject_p.h>
33#include <QtQml/private/qv4qobjectwrapper_p.h>
34#include <private/qqmlobjectmodel_p.h>
35#include <QtQuick/private/qquickitem_p.h>
36#include <QtQuick/private/qquickitemchangelistener_p.h>
37#include <QtQuick/private/qquickevents_p_p.h>
38#include <QtQuick/private/qquicklistview_p.h>
39#include <QtQuick/private/qquickrendercontrol_p.h>
40#include <QtQuick/private/qquickwindow_p.h>
41
42QT_BEGIN_NAMESPACE
43
44Q_LOGGING_CATEGORY(lcMenu, "qt.quick.controls.menu")
45Q_LOGGING_CATEGORY(lcNativeMenus, "qt.quick.controls.nativemenus")
46
47// copied from qfusionstyle.cpp
48static const int SUBMENU_DELAY = 225;
49
50/*!
51 \qmltype Menu
52 \inherits Popup
53//! \nativetype QQuickMenu
54 \inqmlmodule QtQuick.Controls
55 \since 5.7
56 \ingroup qtquickcontrols-menus
57 \ingroup qtquickcontrols-popups
58 \brief Menu popup that can be used as a context menu or popup menu.
59
60 \table
61 \row
62 \li \image qtquickcontrols-menu-native.png
63 \caption Native macOS menu.
64 \li \image qtquickcontrols-menu.png
65 \caption Non-native \l {Material Style}{Material style} menu.
66 \endtable
67
68 Menu has two main use cases:
69 \list
70 \li Context menus; for example, a menu that is shown after right clicking
71 \li Popup menus; for example, a menu that is shown after clicking a button
72 \endlist
73
74 When used as a context menu, the recommended way of opening the menu is to call
75 \l popup(). Unless a position is explicitly specified, the menu is positioned at
76 the mouse cursor on desktop platforms that have a mouse cursor available, and
77 otherwise centered over its parent item.
78
79 \code
80 MouseArea {
81 anchors.fill: parent
82 acceptedButtons: Qt.LeftButton | Qt.RightButton
83 onClicked: {
84 if (mouse.button === Qt.RightButton)
85 contextMenu.popup()
86 }
87 onPressAndHold: {
88 if (mouse.source === Qt.MouseEventNotSynthesized)
89 contextMenu.popup()
90 }
91
92 Menu {
93 id: contextMenu
94 MenuItem { text: "Cut" }
95 MenuItem { text: "Copy" }
96 MenuItem { text: "Paste" }
97 }
98 }
99 \endcode
100
101 When used as a popup menu, it is easiest to specify the position by specifying
102 the desired \l {Popup::}{x} and \l {Popup::}{y} coordinates using the respective
103 properties, and call \l {Popup::}{open()} to open the menu.
104
105 \code
106 Button {
107 id: fileButton
108 text: "File"
109 onClicked: menu.open()
110
111 Menu {
112 id: menu
113 y: fileButton.height
114
115 MenuItem {
116 text: "New..."
117 }
118 MenuItem {
119 text: "Open..."
120 }
121 MenuItem {
122 text: "Save"
123 }
124 }
125 }
126 \endcode
127
128 If the button should also close the menu when clicked, use the
129 \c Popup.CloseOnPressOutsideParent flag:
130 \code
131 onClicked: menu.visible = !menu.visible
132
133 Menu {
134 // ...
135 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
136 \endcode
137
138 Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to create sub-menus
139 and declare Action objects inside Menu:
140
141 \code
142 Menu {
143 Action { text: "Cut" }
144 Action { text: "Copy" }
145 Action { text: "Paste" }
146
147 MenuSeparator { }
148
149 Menu {
150 title: "Find/Replace"
151 Action { text: "Find Next" }
152 Action { text: "Find Previous" }
153 Action { text: "Replace" }
154 }
155 }
156 \endcode
157
158 Sub-menus are \l {cascade}{cascading} by default on desktop platforms
159 that have a mouse cursor available. Non-cascading menus are shown one
160 menu at a time, and centered over the parent menu.
161
162 Typically, menu items are statically declared as children of the menu, but
163 Menu also provides API to \l {addItem}{add}, \l {insertItem}{insert},
164 \l {moveItem}{move} and \l {removeItem}{remove} items dynamically. The
165 items in a menu can be accessed using \l itemAt() or
166 \l {Popup::}{contentChildren}.
167
168 Although \l {MenuItem}{MenuItems} are most commonly used with Menu, it can
169 contain any type of item.
170
171 \section1 Margins
172
173 As it is inherited from Popup, Menu supports \l {Popup::}{margins}. By
174 default, all of the built-in styles specify \c 0 for Menu's margins to
175 ensure that the menu is kept within the bounds of the window. To allow a
176 menu to go outside of the window (to animate it moving into view, for
177 example), set the margins property to \c -1.
178
179 \section1 Dynamically Generating Menu Items
180
181 You can dynamically create menu items with \l Instantiator or
182 \l {Dynamic QML Object Creation from JavaScript} {dynamic object creation}.
183
184 \section2 Using Instantiator
185
186 You can dynamically generate menu items with \l Instantiator. The
187 following code shows how you can implement a "Recent Files" submenu,
188 where the items come from a list of files stored in settings:
189
190 \snippet qtquickcontrols-menu-instantiator.qml menu
191
192 \section2 Using Dynamic Object Creation
193
194 You can also dynamically load a component from a QML file using
195 \l {QtQml::Qt::createComponent()} {Qt.createComponent()}. Once the component
196 is ready, you can call its \l {Component::createObject()} {createObject()}
197 method to create an instance of that component.
198
199 \snippet qtquickcontrols-menu-createObject.qml createObject
200
201 \sa {Customizing Menu}, MenuItem, {Menu Controls}, {Popup Controls},
202 {Dynamic QML Object Creation from JavaScript}
203
204 \section1 Menu types
205
206 Since Qt 6.8, a menu offers three different implementations, depending on the
207 platform. You can choose which one should be preferred by setting
208 \l [QML] {Popup::} {popupType}. This will let you control if a menu should
209 be shown as a separate window, as an item inside the parent window, or as a
210 native menu. You can read more about these options \l{Popup type}{here}.
211
212 The default \l [QML] {Popup::}{popupType} is decided by the style. The \l {macOS Style}, for example,
213 sets it to be \c Popup.Native, while the \l{Imagine Style} uses \c Popup.Window (which
214 is the default when the style doesn't set a popup type).
215 If you add customizations to a menu, and want those to be used regardless of the
216 style, you should set the popup type to be \c Popup.Window (or \c Popup.Item) explicitly.
217 Another alternative is to set the \c Qt::AA_DontUseNativeMenuWindows
218 \l {Qt::ApplicationAttribute}{application attribute}. This will disable native context
219 menus for the whole application, irrespective of the style.
220
221 Whether a menu will be able to use the preferred type depends on the platform.
222 \c Popup.Item is supported on all platforms, but \c Popup.Window is
223 normally only supported on desktop platforms. Additionally, if the menu is inside
224 a \l {Native menu bars}{native menubar}, the menu will be native as well. And if
225 the menu is a sub-menu inside another menu, the parent (or root) menu will decide the type.
226
227 \section2 Limitations when using native menus
228
229 When setting \l [QML] {Popup::} {popupType} to \c Popup.Native
230 there are some limitations and differences compared to using \c Popup.Item
231 and \c Popup.Window.
232
233 \section3 API differences
234
235 When using native menus, only a subset of the Menu API is supported on all platforms:
236
237 \list
238 \li \l {Popup::}{x}
239 \li \l {Popup::}{y}
240 \li \l {Popup::}{visible}
241 \li \l {Popup::}{opened}
242 \li \l title
243 \li \l count
244 \li \l {Popup::}{contentData}
245 \li \l {Popup::}{contentChildren} (visual children will not be visible)
246 \li \l contentModel
247 \li \l {Popup::}{open()}
248 \li \l popup()
249 \li \l {Popup::}{close()}
250 \li \l {Popup::}{opened()}
251 \li \l {Popup::}{closed()}
252 \li \l {Popup::}{aboutToShow()}
253 \li \l {Popup::}{aboutToHide()}
254 \endlist
255
256 In addition, showing a popup (using, for example, \l {Popup::}{open()} or
257 \l {popup()}) will, on some platforms, be a blocking call. This means that the
258 call will not return before the menu is closed again, which can affect the
259 logic in your application. This is especially important to take into
260 consideration if your application is targeting multiple
261 platforms, and as such, sometimes run on platforms where native menus are
262 not supported. In that case the popupType will fall back to \c Popup.Item,
263 for example, and calls to \l {Popup::}{open()} will not be blocking.
264
265 Items like \l MenuItem will still react to clicks in the corresponding
266 native menu item by emitting signals, for example, but will be replaced by
267 their native counterpart.
268
269 \section3 Rendering differences
270
271 Native menus are implemented using the available native menu APIs on the platform.
272 Those menus, and all of their contents, will therefore be rendered by the platform, and
273 not by QML. This means that the \l delegate will \e not be used for rendering. It will,
274 however, always be instantiated (but hidden), so that functions such as
275 \l [QML] {QtQml::Component::completed}{onCompleted()} execute regardless of platform and
276 \l [QML] {Popup::} {popupType}.
277
278 \section3 Supported platforms
279
280 Native menus are currently supported on the following platforms:
281
282 \list
283 \li Android
284 \li iOS
285 \li Linux (only available as a stand-alone context menu when running with the GTK+ platform theme)
286 \li macOS
287 \li Windows
288 \endlist
289
290 \sa {Popup type}, [QML] {Popup::}{popupType}
291*/
292
293/*!
294 \qmlproperty bool QtQuick.Controls::Menu::focus
295
296 This property holds whether the popup wants focus.
297
298 When the popup actually receives focus, \l{Popup::}{activeFocus}
299 will be \c true. For more information, see
300 \l {Keyboard Focus in Qt Quick}.
301
302 The default value is \c true.
303
304 \include qquickmenu.qdocinc non-native-only-property
305
306 \sa {Popup::}{activeFocus}
307*/
308
309static const QQuickPopup::ClosePolicy cascadingSubMenuClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent;
310
311static bool shouldCascade()
312{
313#if QT_CONFIG(cursor)
314 return QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::MultipleWindows);
315#else
316 return false;
317#endif
318}
319
320class QQuickMenuPositioner : public QQuickPopupPositioner
321{
322public:
323 QQuickMenuPositioner(QQuickMenu *menu) : QQuickPopupPositioner(menu) { }
324
325 void reposition() override;
326};
327
328QQuickMenuPrivate::QQuickMenuPrivate()
329{
330 cascade = shouldCascade();
331}
332
333void QQuickMenuPrivate::init()
334{
335 Q_Q(QQuickMenu);
336 contentModel = new QQmlObjectModel(q);
337}
338
339QQuickMenu *QQuickMenuPrivate::rootMenu() const
340{
341 Q_Q(const QQuickMenu);
342 const QQuickMenu *rootMenu = q;
343 const QObject *p = q->parent();
344 while (p) {
345 if (auto menu = qobject_cast<const QQuickMenu *>(object: p))
346 rootMenu = menu;
347 p = p->parent();
348 }
349
350 return const_cast<QQuickMenu *>(rootMenu);
351}
352
353QQuickPopup::PopupType QQuickMenuPrivate::resolvedPopupType() const
354{
355 // The root menu (which can be this menu, unless it's a
356 // sub menu) decides the popup type for all sub menus.
357 QQuickMenu *root = rootMenu();
358 QQuickMenuPrivate *root_d = QQuickMenuPrivate::get(menu: rootMenu());
359
360 if (auto menuBar = QQuickMenuPrivate::get(menu: root)->menuBar.get()) {
361 // When a menu is inside a MenuBar, the MenuBar decides if the menu
362 // should be native or not. The menu's popupType is therefore ignored.
363 // Basically, a native MenuBar can only contain native Menus, and
364 // a non-native MenuBar can only contain non-native Menus.
365 if (QQuickMenuBarPrivate::get(menuBar)->useNativeMenu(menu: q_func()))
366 return QQuickPopup::Native;
367 } else {
368 // If the root menu is native, this menu needs to be native as well
369 if (root_d->maybeNativeHandle()) {
370 return QQuickPopup::Native;
371 } else if (!root_d->triedToCreateNativeMenu) {
372 // If popupType is Native, and native popups seems to be available, then we return
373 // the resolved type to be Native. But note that this doesn't guarantee that the
374 // menu will actually become native. QPA can still refuse to create a QPlaformMenu,
375 // if the QPA plugin or theme doesn't support it. The only way to know for sure if
376 // a menu became native, is to also check QQuickMenuPrivate::nativeHandle().
377 if (root->popupType() == QQuickPopup::Native
378 && !QGuiApplication::testAttribute(attribute: Qt::AA_DontUseNativeMenuWindows)) {
379 return QQuickPopup::Native;
380 }
381 }
382 }
383
384 // Let QQuickPopup decide if the menu should be Popup.Window or Popup.Item, based
385 // on e.g popuptype and platform limitations. QQuickPopupPrivate should never resolve
386 // the popup type to be Native, since that's up to the individual subclasses to decide.
387 const auto type = root_d->QQuickPopupPrivate::resolvedPopupType();
388 Q_ASSERT(type != QQuickPopup::Native);
389 return type;
390}
391
392bool QQuickMenuPrivate::useNativeMenu() const
393{
394 return resolvedPopupType() == QQuickPopup::Native;
395}
396
397QPlatformMenu *QQuickMenuPrivate::nativeHandle()
398{
399 Q_ASSERT(handle || useNativeMenu());
400 if (!handle && !triedToCreateNativeMenu)
401 createNativeMenu();
402 return handle.get();
403}
404
405QPlatformMenu *QQuickMenuPrivate::maybeNativeHandle() const
406{
407 return handle.get();
408}
409
410bool QQuickMenuPrivate::createNativeMenu()
411{
412 Q_ASSERT(!handle);
413 Q_Q(QQuickMenu);
414 qCDebug(lcNativeMenus) << "createNativeMenu called on" << q;
415
416 if (auto menuBar = QQuickMenuPrivate::get(menu: rootMenu())->menuBar) {
417 auto menuBarPrivate = QQuickMenuBarPrivate::get(menuBar);
418 if (menuBarPrivate->useNativeMenuBar()) {
419 qCDebug(lcNativeMenus) << "- creating native menu from native menubar";
420 if (QPlatformMenuBar *menuBarHandle = menuBarPrivate->nativeHandle())
421 handle.reset(p: menuBarHandle->createMenu());
422 }
423 }
424
425 if (!handle) {
426 QPlatformMenu *parentMenuHandle(parentMenu ? get(menu: parentMenu)->handle.get() : nullptr);
427 if (parentMenu && parentMenuHandle) {
428 qCDebug(lcNativeMenus) << "- creating native sub-menu";
429 handle.reset(p: parentMenuHandle->createSubMenu());
430 } else {
431 qCDebug(lcNativeMenus) << "- creating native menu";
432 handle.reset(p: QGuiApplicationPrivate::platformTheme()->createPlatformMenu());
433 }
434 }
435
436 triedToCreateNativeMenu = true;
437
438 if (!handle)
439 return false;
440
441 q->connect(sender: handle.get(), signal: &QPlatformMenu::aboutToShow, context: q, slot: [q, this](){
442 emit q->aboutToShow();
443 visible = true;
444 emit q->visibleChanged();
445 emit q->openedChanged();
446 opened();
447 });
448 q->connect(sender: handle.get(), signal: &QPlatformMenu::aboutToHide, context: q, slot: [q, this](){
449 qCDebug(lcNativeMenus) << "QPlatformMenu::aboutToHide called; about to call setVisible(false) on Menu";
450 emit q->aboutToHide();
451 visible = false;
452 emit q->visibleChanged();
453 emit q->openedChanged();
454 emit q->closed();
455 });
456
457 recursivelyCreateNativeMenuItems(menu: q);
458 syncWithNativeMenu();
459
460 return true;
461}
462
463QString nativeMenuItemListToString(const QList<QQuickNativeMenuItem *> &nativeItems)
464{
465 if (nativeItems.isEmpty())
466 return QStringLiteral("(Empty)");
467
468 QString str;
469 QTextStream debug(&str);
470 for (const auto *nativeItem : nativeItems)
471 debug << nativeItem->debugText() << ", ";
472 // Remove trailing space and comma.
473 if (!nativeItems.isEmpty())
474 str.chop(n: 2);
475 return str;
476}
477
478void QQuickMenuPrivate::syncWithNativeMenu()
479{
480 Q_Q(QQuickMenu);
481 if (!complete || !handle)
482 return;
483
484 qCDebug(lcNativeMenus).nospace() << "syncWithNativeMenu called on " << q
485 << " (complete: " << complete << " visible: " << visible << ") - "
486 << "syncing " << nativeItems.size() << " item(s)...";
487
488 // TODO: call this function when any of the variables below change
489
490 handle->setText(title);
491 handle->setEnabled(q->isEnabled());
492 handle->setMinimumWidth(q->implicitWidth());
493// nativeHandle->setMenuType(m_type);
494 handle->setFont(q->font());
495
496 // Note: the QQuickMenu::visible property is used to open or close the menu.
497 // This is in contrast to QPlatformMenu::visible, which tells if the menu
498 // should be visible in the menubar or not (if it belongs to one). To control
499 // if a QPlatformMenu should be open, we instead use QPlatformMenu::showPopup()
500 // and dismiss(). As such, we don't want to call handle->setVisible(visible)
501 // from this function since we always want the menu to be visible in the menubar
502 // (if it belongs to one). The currently only way to hide a menu from a menubar is
503 // to instead call MenuBar.removeMenu(menu).
504
505// if (m_menuBar && m_menuBar->handle())
506// m_menuBar->handle()->syncMenu(handle);
507//#if QT_CONFIG(systemtrayicon)
508// else if (m_systemTrayIcon && m_systemTrayIcon->handle())
509// m_systemTrayIcon->handle()->updateMenu(handle);
510//#endif
511
512 for (QQuickNativeMenuItem *item : std::as_const(t&: nativeItems)) {
513 qCDebug(lcNativeMenus) << "- syncing" << item << "action" << item->action()
514 << "sub-menu" << item->subMenu() << item->debugText();
515 item->sync();
516 }
517
518 qCDebug(lcNativeMenus) << "... finished syncing" << q;
519}
520
521void QQuickMenuPrivate::removeNativeMenu()
522{
523 // Remove the native menu, including it's native menu items
524 Q_Q(QQuickMenu);
525 const int qtyItemsToRemove = nativeItems.size();
526 if (qtyItemsToRemove != 0)
527 Q_ASSERT(q->count() == qtyItemsToRemove);
528 for (int i = 0; i < qtyItemsToRemove; ++i)
529 removeNativeItem(index: 0);
530 Q_ASSERT(nativeItems.isEmpty());
531
532 // removeNativeItem will take care of destroying sub-menus and resetting their native data,
533 // but as the root menu, we have to take care of our own.
534 resetNativeData();
535}
536
537void QQuickMenuPrivate::syncWithUseNativeMenu()
538{
539 Q_Q(QQuickMenu);
540 // Users can change AA_DontUseNativeMenuWindows while a menu is visible,
541 // but the changes won't take affect until the menu is re-opened.
542 if (q->isVisible() || parentMenu)
543 return;
544
545 if (maybeNativeHandle() && !useNativeMenu()) {
546 // Switch to a non-native menu by removing the native menu and its native items.
547 // Note that there's nothing to do if a native menu was requested but we failed to create it.
548 removeNativeMenu();
549 } else if (useNativeMenu()) {
550 Q_ASSERT(nativeItems.isEmpty());
551 // Try to create a native menu.
552 nativeHandle();
553 }
554}
555
556/*!
557 \internal
558
559 Recursively destroys native sub-menus of \a menu.
560
561 This function checks if each native item in \c menu has a sub-menu,
562 and if so:
563 \list
564 \li Calls itself with that sub-menu
565 \li Resets the item's data (important to avoid accessing a deleted QQuickAction
566 when printing in QQuickNativeMenuItem's destructor)
567 \li Deletes (eventually) the native item
568 \endlist
569
570 Similar (besides the recursion) to removeNativeItem(), except that
571 we can avoid repeated calls to syncWithNativeMenu().
572*/
573void QQuickMenuPrivate::recursivelyDestroyNativeSubMenus(QQuickMenu *menu)
574{
575 auto *menuPrivate = QQuickMenuPrivate::get(menu);
576 Q_ASSERT(menuPrivate->handle);
577 qCDebug(lcNativeMenus) << "recursivelyDestroyNativeSubMenus called with" << menu << "...";
578
579 while (!menuPrivate->nativeItems.isEmpty()) {
580 std::unique_ptr<QQuickNativeMenuItem> item(menuPrivate->nativeItems.takeFirst());
581 qCDebug(lcNativeMenus) << "- taking and destroying" << item->debugText();
582 if (QQuickMenu *subMenu = item->subMenu())
583 recursivelyDestroyNativeSubMenus(menu: subMenu);
584
585 if (item->handle())
586 menuPrivate->handle->removeMenuItem(menuItem: item->handle());
587 }
588
589 menuPrivate->resetNativeData();
590
591 qCDebug(lcNativeMenus) << "... finished destroying native sub-menus of" << menu;
592}
593
594static QWindow *effectiveWindow(QWindow *window, QPoint *offset)
595{
596 QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(object: window);
597 if (quickWindow) {
598 QWindow *renderWindow = QQuickRenderControl::renderWindowFor(win: quickWindow, offset);
599 if (renderWindow)
600 return renderWindow;
601 }
602 return window;
603}
604
605void QQuickMenuPrivate::setNativeMenuVisible(bool visible)
606{
607 Q_Q(QQuickMenu);
608 qCDebug(lcNativeMenus) << "setNativeMenuVisible called with visible" << visible;
609 if (visible)
610 emit q->aboutToShow();
611 else
612 emit q->aboutToHide();
613
614 this->visible = visible;
615 syncWithNativeMenu();
616
617 QPoint offset;
618 QWindow *window = effectiveWindow(qGuiApp->topLevelWindows().first(), offset: &offset);
619
620 if (visible) {
621 lastDevicePixelRatio = window->devicePixelRatio();
622
623 const QPointF globalPos = parentItem->mapToGlobal(x, y);
624 const QPoint windowPos = window->mapFromGlobal(pos: globalPos.toPoint());
625 QRect targetRect(windowPos, QSize(0, 0));
626 auto *daPriv = QQuickItemPrivate::get(item: parentItem)->deliveryAgentPrivate();
627 Q_ASSERT(daPriv);
628 // A menu is typically opened when some event-handling object (like TapHandler) calls
629 // QQuickMenu::popup(). We don't have the event or the caller available directly here.
630 // But showPopup() below is expected to "eat" the release event, so
631 // the caller will not see it. Cancel all grabs so that the object that
632 // handled the press event will not get stuck in pressed state.
633 if (QPointerEvent *openingEvent = daPriv->eventInDelivery()) {
634 auto *devPriv = QPointingDevicePrivate::get(q: const_cast<QPointingDevice *>(openingEvent->pointingDevice()));
635 for (const auto &pt : std::as_const(t: openingEvent->points())) {
636 qCDebug(lcNativeMenus) << "popup over" << window << "its DA" << daPriv->q_func() << "opening due to" << openingEvent
637 << "with grabbers" << openingEvent->exclusiveGrabber(point: pt) << openingEvent->passiveGrabbers(point: pt);
638
639 if (auto *opener = openingEvent->exclusiveGrabber(point: pt))
640 devPriv->removeGrabber(grabber: opener, cancel: true); // cancel
641 for (auto passiveGrabber : openingEvent->passiveGrabbers(point: pt)) {
642 if (auto *opener = passiveGrabber.get())
643 devPriv->removeGrabber(grabber: opener, cancel: true); // cancel
644 }
645 }
646 }
647 handle->showPopup(parentWindow: window, targetRect: QHighDpi::toNativeLocalPosition(value: targetRect, context: window),
648 /*menuItem ? menuItem->handle() : */item: nullptr);
649 } else {
650 handle->dismiss();
651 }
652}
653
654QQuickItem *QQuickMenuPrivate::itemAt(int index) const
655{
656 return qobject_cast<QQuickItem *>(o: contentModel->get(index));
657}
658
659void QQuickMenuPrivate::insertItem(int index, QQuickItem *item)
660{
661 qCDebug(lcMenu) << "insert called with index" << index << "item" << item;
662
663 Q_Q(QQuickMenu);
664 contentData.append(t: item);
665 item->setParentItem(contentItem);
666 QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-53262
667 if (complete)
668 resizeItem(item);
669 QQuickItemPrivate::get(item)->addItemChangeListener(listener: this, types: QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent);
670 QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(listener: this, types: QQuickGeometryChange::Width);
671 contentModel->insert(index, object: item);
672
673 QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item);
674 if (menuItem) {
675 QQuickMenuItemPrivate::get(item: menuItem)->setMenu(q);
676 if (QQuickMenu *subMenu = menuItem->subMenu())
677 QQuickMenuPrivate::get(menu: subMenu)->setParentMenu(q);
678 QObjectPrivate::connect(sender: menuItem, signal: &QQuickMenuItem::triggered, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemTriggered);
679 QObjectPrivate::connect(sender: menuItem, signal: &QQuickMenuItem::implicitTextPaddingChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::updateTextPadding);
680 QObjectPrivate::connect(sender: menuItem, signal: &QQuickMenuItem::visibleChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::updateTextPadding);
681 QObjectPrivate::connect(sender: menuItem, signal: &QQuickItem::activeFocusChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemActiveFocusChanged);
682 QObjectPrivate::connect(sender: menuItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemHovered);
683 }
684
685 if (maybeNativeHandle() && complete)
686 maybeCreateAndInsertNativeItem(index, item);
687
688 if (lcMenu().isDebugEnabled())
689 printContentModelItems();
690
691 updateTextPadding();
692}
693
694void QQuickMenuPrivate::maybeCreateAndInsertNativeItem(int index, QQuickItem *item)
695{
696 Q_Q(QQuickMenu);
697 Q_ASSERT(complete);
698 Q_ASSERT_X(handle, Q_FUNC_INFO, qPrintable(QString::fromLatin1(
699 "Expected %1 to be using a native menu").arg(QDebug::toString(q))));
700 std::unique_ptr<QQuickNativeMenuItem> nativeMenuItem(QQuickNativeMenuItem::createFromNonNativeItem(parentMenu: q, nonNativeItem: item));
701 if (!nativeMenuItem) {
702 // TODO: fall back to non-native menu
703 qmlWarning(me: q) << "Native menu failed to create a native menu item for item at index" << index;
704 return;
705 }
706
707 nativeItems.insert(i: index, t: nativeMenuItem.get());
708
709 // Having a QQuickNativeMenuItem doesn't mean that we were able to create a native handle:
710 // it could be e.g. a Rectangle. See comment in QQuickNativeMenuItem::createFromNonNativeItem.
711 if (nativeMenuItem->handle()) {
712 QQuickNativeMenuItem *before = nativeItems.value(i: index + 1);
713 handle->insertMenuItem(menuItem: nativeMenuItem->handle(), before: before ? before->handle() : nullptr);
714 qCDebug(lcNativeMenus) << "inserted native menu item at index" << index
715 << "before" << (before ? before->debugText() : QStringLiteral("null"));
716
717 if (nativeMenuItem->subMenu() && QQuickMenuPrivate::get(menu: nativeMenuItem->subMenu())->nativeItems.count()
718 < nativeMenuItem->subMenu()->count()) {
719 // We're inserting a sub-menu item, and it hasn't had native items added yet,
720 // which probably means it's a menu that's been added back in after being removed
721 // with takeMenu(). Sub-menus added for the first time have their native items already
722 // constructed by virtue of contentData_append. Sub-menus that are removed always
723 // have their native items destroyed and removed too.
724 recursivelyCreateNativeMenuItems(menu: nativeMenuItem->subMenu());
725 }
726 }
727
728 nativeMenuItem.release();
729
730 qCDebug(lcNativeMenus) << "nativeItems now contains the following items:"
731 << nativeMenuItemListToString(nativeItems);
732}
733
734void QQuickMenuPrivate::moveItem(int from, int to)
735{
736 contentModel->move(from, to);
737
738 if (maybeNativeHandle())
739 nativeItems.move(from, to);
740}
741
742/*!
743 \internal
744
745 Removes the specified \a item, potentially destroying it depending on
746 \a destructionPolicy.
747
748 \note the native menu item is destroyed regardless of the destruction
749 policy, because it's an implementation detail and hence is not created by
750 or available to the user.
751*/
752void QQuickMenuPrivate::removeItem(int index, QQuickItem *item, DestructionPolicy destructionPolicy)
753{
754 qCDebug(lcMenu) << "removeItem called with index" << index << "item" << item;
755
756 if (maybeNativeHandle())
757 removeNativeItem(index);
758
759 contentData.removeOne(t: item);
760
761 QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent);
762 QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
763 item->setParentItem(nullptr);
764 contentModel->remove(index);
765
766 QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item);
767 if (menuItem) {
768 QQuickMenuItemPrivate::get(item: menuItem)->setMenu(nullptr);
769 if (QQuickMenu *subMenu = menuItem->subMenu())
770 QQuickMenuPrivate::get(menu: subMenu)->setParentMenu(nullptr);
771 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickMenuItem::triggered, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemTriggered);
772 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickMenuItem::implicitTextPaddingChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::updateTextPadding);
773 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickMenuItem::visibleChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::updateTextPadding);
774 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickItem::activeFocusChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemActiveFocusChanged);
775 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemHovered);
776 }
777
778 if (destructionPolicy == DestructionPolicy::Destroy)
779 item->deleteLater();
780
781 if (lcMenu().isDebugEnabled())
782 printContentModelItems();
783}
784
785void QQuickMenuPrivate::removeNativeItem(int index)
786{
787 // Either we're still using native menus and are removing item(s), or we've switched
788 // to a non-native menu; either way, we should actually have items to remove before we're called.
789 Q_ASSERT(handle);
790 Q_ASSERT_X(index >= 0 && index < nativeItems.size(), Q_FUNC_INFO, qPrintable(QString::fromLatin1(
791 "index %1 is less than 0 or greater than or equal to %2").arg(index).arg(nativeItems.size())));
792
793 // We can delete the item synchronously because there aren't any external (e.g. QML)
794 // references to it.
795 std::unique_ptr<QQuickNativeMenuItem> nativeItem(nativeItems.takeAt(i: index));
796 qCDebug(lcNativeMenus) << "removing native item" << nativeItem->debugText() << "at index" << index
797 << "from" << q_func() << "...";
798 if (QQuickMenu *subMenu = nativeItem->subMenu())
799 recursivelyDestroyNativeSubMenus(menu: subMenu);
800
801 if (nativeItem->handle()) {
802 handle->removeMenuItem(menuItem: nativeItem->handle());
803 syncWithNativeMenu();
804 }
805
806 qCDebug(lcNativeMenus).nospace() << "... after removing item at index " << index
807 << ", nativeItems now contains the following items: " << nativeMenuItemListToString(nativeItems);
808}
809
810void QQuickMenuPrivate::resetNativeData()
811{
812 qCDebug(lcNativeMenus) << "resetNativeData called on" << q_func();
813 handle.reset();
814 triedToCreateNativeMenu = false;
815}
816
817void QQuickMenuPrivate::recursivelyCreateNativeMenuItems(QQuickMenu *menu)
818{
819 auto *menuPrivate = QQuickMenuPrivate::get(menu);
820 // If we're adding a sub-menu, we need to ensure its handle has been created
821 // before trying to create native items for it.
822 if (!menuPrivate->triedToCreateNativeMenu)
823 menuPrivate->createNativeMenu();
824
825 const int qtyItemsToCreate = menuPrivate->contentModel->count();
826 if (menuPrivate->nativeItems.count() == qtyItemsToCreate)
827 return;
828
829 qCDebug(lcNativeMenus) << "recursively creating" << qtyItemsToCreate << "menu item(s) for" << menu;
830 Q_ASSERT(menuPrivate->nativeItems.count() == 0);
831 for (int i = 0; i < qtyItemsToCreate; ++i) {
832 QQuickItem *item = menu->itemAt(index: i);
833 menuPrivate->maybeCreateAndInsertNativeItem(index: i, item);
834 auto *menuItem = qobject_cast<QQuickMenuItem *>(object: item);
835 if (menuItem && menuItem->subMenu())
836 recursivelyCreateNativeMenuItems(menu: menuItem->subMenu());
837 }
838}
839
840void QQuickMenuPrivate::printContentModelItems() const
841{
842 qCDebug(lcMenu) << "contentModel now contains:";
843 for (int i = 0; i < contentModel->count(); ++i)
844 qCDebug(lcMenu) << "-" << itemAt(index: i);
845}
846
847QQuickItem *QQuickMenuPrivate::beginCreateItem()
848{
849 Q_Q(QQuickMenu);
850 if (!delegate)
851 return nullptr;
852
853 QQmlContext *context = delegate->creationContext();
854 if (!context)
855 context = qmlContext(q);
856
857 QObject *object = delegate->beginCreate(context);
858 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
859 if (!item)
860 delete object;
861 else
862 QQml_setParent_noEvent(object: item, parent: q);
863
864 return item;
865}
866
867void QQuickMenuPrivate::completeCreateItem()
868{
869 if (!delegate)
870 return;
871
872 delegate->completeCreate();
873}
874
875QQuickItem *QQuickMenuPrivate::createItem(QQuickMenu *menu)
876{
877 QQuickItem *item = beginCreateItem();
878 if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item))
879 QQuickMenuItemPrivate::get(item: menuItem)->setSubMenu(menu);
880 completeCreateItem();
881 return item;
882}
883
884QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action)
885{
886 QQuickItem *item = beginCreateItem();
887 if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object: item))
888 button->setAction(action);
889 completeCreateItem();
890 return item;
891}
892
893void QQuickMenuPrivate::resizeItem(QQuickItem *item)
894{
895 if (!item || !contentItem)
896 return;
897
898 QQuickItemPrivate *p = QQuickItemPrivate::get(item);
899 if (!p->widthValid()) {
900 item->setWidth(contentItem->width());
901 p->widthValidFlag = false;
902 }
903}
904
905void QQuickMenuPrivate::resizeItems()
906{
907 if (!contentModel)
908 return;
909
910 for (int i = 0; i < contentModel->count(); ++i)
911 resizeItem(item: itemAt(index: i));
912}
913
914void QQuickMenuPrivate::itemChildAdded(QQuickItem *, QQuickItem *child)
915{
916 // add dynamically reparented items (eg. by a Repeater)
917 if (!QQuickItemPrivate::get(item: child)->isTransparentForPositioner() && !contentData.contains(t: child))
918 insertItem(index: contentModel->count(), item: child);
919}
920
921void QQuickMenuPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent)
922{
923 // remove dynamically unparented items (eg. by a Repeater)
924 if (!parent)
925 removeItem(index: contentModel->indexOf(object: item, objectContext: nullptr), item);
926}
927
928void QQuickMenuPrivate::itemSiblingOrderChanged(QQuickItem *)
929{
930 // reorder the restacked items (eg. by a Repeater)
931 Q_Q(QQuickMenu);
932 QList<QQuickItem *> siblings = contentItem->childItems();
933
934 int to = 0;
935 for (int i = 0; i < siblings.size(); ++i) {
936 QQuickItem* sibling = siblings.at(i);
937 if (QQuickItemPrivate::get(item: sibling)->isTransparentForPositioner())
938 continue;
939 int index = contentModel->indexOf(object: sibling, objectContext: nullptr);
940 q->moveItem(from: index, to: to++);
941 }
942}
943
944void QQuickMenuPrivate::itemDestroyed(QQuickItem *item)
945{
946 QQuickPopupPrivate::itemDestroyed(item);
947 int index = contentModel->indexOf(object: item, objectContext: nullptr);
948 if (index != -1)
949 removeItem(index, item);
950}
951
952void QQuickMenuPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange, const QRectF &)
953{
954 if (!complete)
955 return;
956
957 if (item == contentItem) {
958 // The contentItem's geometry changed, so resize any items
959 // that don't have explicit widths set so that they fill the width of the menu.
960 resizeItems();
961 } else {
962 // The geometry of an item in the menu changed. If the item
963 // doesn't have an explicit width set, make it fill the width of the menu.
964 resizeItem(item);
965 }
966}
967
968QQuickPopupPositioner *QQuickMenuPrivate::getPositioner()
969{
970 Q_Q(QQuickMenu);
971 if (!positioner)
972 positioner = new QQuickMenuPositioner(q);
973 return positioner;
974}
975
976void QQuickMenuPositioner::reposition()
977{
978 QQuickMenu *menu = static_cast<QQuickMenu *>(popup());
979 QQuickMenuPrivate *menu_d = QQuickMenuPrivate::get(menu);
980
981 if (QQuickMenu *parentMenu = menu_d->parentMenu) {
982 if (menu_d->cascade) {
983 // Align the menu to the frame of the parent menuItem, minus overlap. The position
984 // should be in the coordinate system of the parentItem.
985 if (menu_d->popupItem->isMirrored()) {
986 const qreal distanceToFrame = parentMenu->leftPadding();
987 const qreal menuX = -menu->width() - distanceToFrame + menu->overlap();
988 menu->setPosition({menuX, -menu->topPadding()});
989 } else if (menu_d->parentItem) {
990 const qreal distanceToFrame = parentMenu->rightPadding();
991 const qreal menuX = menu_d->parentItem->width() + distanceToFrame - menu->overlap();
992 menu->setPosition({menuX, -menu->topPadding()});
993 }
994 } else {
995 const qreal menuX = parentMenu->x() + (parentMenu->width() - menu->width()) / 2;
996 const qreal menuY = parentMenu->y() + (parentMenu->height() - menu->height()) / 2;
997 menu->setPosition({menuX, menuY});
998 }
999 }
1000
1001 QQuickPopupPositioner::reposition();
1002}
1003
1004bool QQuickMenuPrivate::prepareEnterTransition()
1005{
1006 Q_Q(QQuickMenu);
1007 if (parentMenu && !cascade)
1008 parentMenu->close();
1009
1010 // If a cascading sub-menu doesn't have enough space to open on
1011 // the right, it flips on the other side of the parent menu.
1012 allowHorizontalFlip = cascade && parentMenu;
1013
1014 if (!QQuickPopupPrivate::prepareEnterTransition())
1015 return false;
1016
1017 if (!hasClosePolicy) {
1018 if (cascade && parentMenu)
1019 closePolicy = cascadingSubMenuClosePolicy;
1020 else
1021 q->resetClosePolicy();
1022 }
1023 return true;
1024}
1025
1026bool QQuickMenuPrivate::prepareExitTransition()
1027{
1028 if (!QQuickPopupPrivate::prepareExitTransition())
1029 return false;
1030
1031 stopHoverTimer();
1032
1033 QQuickMenu *subMenu = currentSubMenu();
1034 while (subMenu) {
1035 QPointer<QQuickMenuItem> currentSubMenuItem = QQuickMenuPrivate::get(menu: subMenu)->currentItem;
1036 subMenu->close();
1037 subMenu = currentSubMenuItem ? currentSubMenuItem->subMenu() : nullptr;
1038 }
1039 return true;
1040}
1041
1042bool QQuickMenuPrivate::blockInput(QQuickItem *item, const QPointF &point) const
1043{
1044 // keep the parent menu open when a cascading sub-menu (this menu) is interacted with
1045 return (cascade && parentMenu && contains(scenePos: point)) || QQuickPopupPrivate::blockInput(item, point);
1046}
1047
1048bool QQuickMenuPrivate::handlePress(QQuickItem *item, const QPointF &point, ulong timestamp)
1049{
1050 // Don't propagate mouse event as it can cause underlying item to receive
1051 // events
1052 return QQuickPopupPrivate::handlePress(item, point, timestamp)
1053 || (popupItem == item);
1054}
1055
1056
1057/*! \internal
1058 QQuickPopupWindow::event() calls this to handle the release event of a
1059 menu drag-press-release gesture, because the \a eventPoint does not have
1060 a grabber within the popup window. This override finds and activates the
1061 appropriate menu item, as if it had been pressed and released.
1062 Returns true on success, to indicate that handling \a eventPoint is done.
1063 */
1064bool QQuickMenuPrivate::handleReleaseWithoutGrab(const QEventPoint &eventPoint)
1065{
1066 const QPointF scenePos = eventPoint.scenePosition();
1067 if (!contains(scenePos))
1068 return false;
1069
1070 auto *list = qobject_cast<QQuickListView *>(object: contentItem);
1071 if (!list)
1072 return false;
1073
1074 const QPointF listPos = list->mapFromScene(point: scenePos);
1075 if (auto *menuItem = qobject_cast<QQuickMenuItem *>(object: list->itemAt(x: listPos.x(), y: listPos.y()))) {
1076 menuItem->animateClick();
1077 return true;
1078 }
1079
1080 return false;
1081}
1082
1083void QQuickMenuPrivate::onItemHovered()
1084{
1085 Q_Q(QQuickMenu);
1086 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object: q->sender());
1087 if (!button || !button->isHovered() || !button->isEnabled() || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
1088 return;
1089
1090 QQuickMenuItem *oldCurrentItem = currentItem;
1091
1092 int index = contentModel->indexOf(object: button, objectContext: nullptr);
1093 if (index != -1) {
1094 setCurrentIndex(index, reason: Qt::OtherFocusReason);
1095 if (oldCurrentItem != currentItem) {
1096 if (oldCurrentItem) {
1097 QQuickMenu *subMenu = oldCurrentItem->subMenu();
1098 if (subMenu)
1099 subMenu->close();
1100 }
1101 if (currentItem) {
1102 QQuickMenu *subMenu = currentItem->menu();
1103 if (subMenu && subMenu->cascade())
1104 startHoverTimer();
1105 }
1106 }
1107 }
1108}
1109
1110void QQuickMenuPrivate::onItemTriggered()
1111{
1112 Q_Q(QQuickMenu);
1113 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: q->sender());
1114 if (!item)
1115 return;
1116
1117 if (QQuickMenu *subMenu = item->subMenu()) {
1118 auto subMenuPrivate = QQuickMenuPrivate::get(menu: subMenu);
1119 subMenu->popup(menuItem: subMenuPrivate->firstEnabledMenuItem());
1120 } else {
1121 q->dismiss();
1122 }
1123}
1124
1125void QQuickMenuPrivate::onItemActiveFocusChanged()
1126{
1127 Q_Q(QQuickMenu);
1128 QQuickItem *item = qobject_cast<QQuickItem*>(o: q->sender());
1129 if (!item->hasActiveFocus())
1130 return;
1131
1132 int indexOfItem = contentModel->indexOf(object: item, objectContext: nullptr);
1133 QQuickControl *control = qobject_cast<QQuickControl *>(object: item);
1134 setCurrentIndex(index: indexOfItem, reason: control ? control->focusReason() : Qt::OtherFocusReason);
1135}
1136
1137void QQuickMenuPrivate::updateTextPadding()
1138{
1139 Q_Q(QQuickMenu);
1140 if (!complete)
1141 return;
1142
1143 qreal padding = 0;
1144 for (int i = 0; i < q->count(); ++i) {
1145 if (const auto menuItem = qobject_cast<QQuickMenuItem *>(object: itemAt(index: i)))
1146 if (menuItem->isVisible())
1147 padding = qMax(a: padding, b: menuItem->implicitTextPadding());
1148 }
1149
1150 if (padding == textPadding)
1151 return;
1152
1153 textPadding = padding;
1154
1155 for (int i = 0; i < q->count(); ++i) {
1156 if (const auto menuItem = qobject_cast<QQuickMenuItem *>(object: itemAt(index: i)))
1157 emit menuItem->textPaddingChanged();
1158 }
1159}
1160
1161QQuickMenu *QQuickMenuPrivate::currentSubMenu() const
1162{
1163 if (!currentItem)
1164 return nullptr;
1165
1166 return currentItem->subMenu();
1167}
1168
1169void QQuickMenuPrivate::setParentMenu(QQuickMenu *parent)
1170{
1171 Q_Q(QQuickMenu);
1172 if (parentMenu == parent)
1173 return;
1174
1175 if (parentMenu) {
1176 QObject::disconnect(sender: parentMenu.data(), signal: &QQuickMenu::cascadeChanged, receiver: q, slot: &QQuickMenu::setCascade);
1177 disconnect(sender: parentMenu.data(), signal: &QQuickMenu::parentChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::resolveParentItem);
1178 }
1179 if (parent) {
1180 QObject::connect(sender: parent, signal: &QQuickMenu::cascadeChanged, context: q, slot: &QQuickMenu::setCascade);
1181 connect(sender: parent, signal: &QQuickMenu::parentChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::resolveParentItem);
1182 }
1183
1184 parentMenu = parent;
1185 q->resetCascade();
1186 resolveParentItem();
1187}
1188
1189static QQuickItem *findParentMenuItem(QQuickMenu *subMenu)
1190{
1191 QQuickMenu *menu = QQuickMenuPrivate::get(menu: subMenu)->parentMenu;
1192 for (int i = 0; i < QQuickMenuPrivate::get(menu)->contentModel->count(); ++i) {
1193 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: i));
1194 if (item && item->subMenu() == subMenu)
1195 return item;
1196 }
1197 return nullptr;
1198}
1199
1200void QQuickMenuPrivate::resolveParentItem()
1201{
1202 Q_Q(QQuickMenu);
1203 if (!parentMenu)
1204 q->resetParentItem();
1205 else if (!cascade)
1206 q->setParentItem(parentMenu->parentItem());
1207 else
1208 q->setParentItem(findParentMenuItem(subMenu: q));
1209}
1210
1211void QQuickMenuPrivate::propagateKeyEvent(QKeyEvent *event)
1212{
1213 if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: parentItem)) {
1214 if (QQuickMenu *menu = menuItem->menu())
1215 QQuickMenuPrivate::get(menu)->propagateKeyEvent(event);
1216#if QT_CONFIG(quicktemplates2_container)
1217 } else if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: parentItem)) {
1218 if (QQuickMenuBar *menuBar = menuBarItem->menuBar()) {
1219 event->accept();
1220 QCoreApplication::sendEvent(receiver: menuBar, event);
1221 }
1222#endif
1223 }
1224}
1225
1226void QQuickMenuPrivate::startHoverTimer()
1227{
1228 Q_Q(QQuickMenu);
1229 stopHoverTimer();
1230 hoverTimer = q->startTimer(interval: SUBMENU_DELAY);
1231}
1232
1233void QQuickMenuPrivate::stopHoverTimer()
1234{
1235 Q_Q(QQuickMenu);
1236 if (!hoverTimer)
1237 return;
1238
1239 q->killTimer(id: hoverTimer);
1240 hoverTimer = 0;
1241}
1242
1243void QQuickMenuPrivate::setCurrentIndex(int index, Qt::FocusReason reason)
1244{
1245 Q_Q(QQuickMenu);
1246 if (currentIndex == index)
1247 return;
1248
1249 QQuickMenuItem *newCurrentItem = qobject_cast<QQuickMenuItem *>(object: itemAt(index));
1250 if (currentItem != newCurrentItem) {
1251 stopHoverTimer();
1252 if (currentItem) {
1253 currentItem->setHighlighted(false);
1254 if (!newCurrentItem && window) {
1255 QQuickItem *focusItem = QQuickItemPrivate::get(item: contentItem)->subFocusItem;
1256 if (focusItem)
1257 QQuickWindowPrivate::get(c: window)->clearFocusInScope(scope: contentItem, item: focusItem, reason: Qt::OtherFocusReason);
1258 }
1259 }
1260 if (newCurrentItem) {
1261 newCurrentItem->setHighlighted(true);
1262 newCurrentItem->forceActiveFocus(reason);
1263 }
1264 currentItem = newCurrentItem;
1265 }
1266
1267 currentIndex = index;
1268 emit q->currentIndexChanged();
1269}
1270
1271bool QQuickMenuPrivate::activateNextItem()
1272{
1273 int index = currentIndex;
1274 int count = contentModel->count();
1275 while (++index < count) {
1276 QQuickItem *item = itemAt(index);
1277 if (!item || !item->activeFocusOnTab() || !item->isEnabled())
1278 continue;
1279 setCurrentIndex(index, reason: Qt::TabFocusReason);
1280 return true;
1281 }
1282 return false;
1283}
1284
1285bool QQuickMenuPrivate::activatePreviousItem()
1286{
1287 int index = currentIndex;
1288 while (--index >= 0) {
1289 QQuickItem *item = itemAt(index);
1290 if (!item || !item->activeFocusOnTab() || !item->isEnabled())
1291 continue;
1292 setCurrentIndex(index, reason: Qt::BacktabFocusReason);
1293 return true;
1294 }
1295 return false;
1296}
1297
1298QQuickMenuItem *QQuickMenuPrivate::firstEnabledMenuItem() const
1299{
1300 for (int i = 0; i < contentModel->count(); ++i) {
1301 QQuickItem *item = itemAt(index: i);
1302 if (!item || !item->isEnabled())
1303 continue;
1304
1305 QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item);
1306 if (!menuItem)
1307 continue;
1308
1309 return menuItem;
1310 }
1311 return nullptr;
1312}
1313
1314void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj)
1315{
1316 QQuickMenu *q = qobject_cast<QQuickMenu *>(object: prop->object);
1317 QQuickMenuPrivate *p = QQuickMenuPrivate::get(menu: q);
1318
1319 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj);
1320 if (!item) {
1321 if (QQuickAction *action = qobject_cast<QQuickAction *>(object: obj))
1322 item = p->createItem(action);
1323 else if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(object: obj))
1324 item = p->createItem(menu);
1325 }
1326
1327 if (item) {
1328 if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) {
1329 QQuickItemPrivate::get(item)->addItemChangeListener(listener: p, types: QQuickItemPrivate::SiblingOrder);
1330 item->setParentItem(p->contentItem);
1331 } else if (p->contentModel->indexOf(object: item, objectContext: nullptr) == -1) {
1332 q->addItem(item);
1333 }
1334 } else {
1335 p->contentData.append(t: obj);
1336 }
1337}
1338
1339qsizetype QQuickMenuPrivate::contentData_count(QQmlListProperty<QObject> *prop)
1340{
1341 QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
1342 return QQuickMenuPrivate::get(menu: q)->contentData.size();
1343}
1344
1345QObject *QQuickMenuPrivate::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
1346{
1347 QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
1348 return QQuickMenuPrivate::get(menu: q)->contentData.value(i: index);
1349}
1350
1351QPalette QQuickMenuPrivate::defaultPalette() const
1352{
1353 return QQuickTheme::palette(scope: QQuickTheme::Menu);
1354}
1355
1356void QQuickMenuPrivate::contentData_clear(QQmlListProperty<QObject> *prop)
1357{
1358 QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
1359 QQuickMenuPrivate::get(menu: q)->contentData.clear();
1360}
1361
1362QQuickMenu::QQuickMenu(QObject *parent)
1363 : QQuickPopup(*(new QQuickMenuPrivate), parent)
1364{
1365 Q_D(QQuickMenu);
1366 setFocus(true);
1367 d->init();
1368 connect(sender: d->contentModel, signal: &QQmlObjectModel::countChanged, context: this, slot: &QQuickMenu::countChanged);
1369}
1370
1371QQuickMenu::~QQuickMenu()
1372{
1373 Q_D(QQuickMenu);
1374 qCDebug(lcNativeMenus) << "destroying" << this
1375 << "item count:"
1376 << d->contentModel->count()
1377 << "native item count:" << d->nativeItems.count();
1378 // We have to remove items to ensure that our change listeners on the item
1379 // are removed. It's too late to do this in ~QQuickMenuPrivate, as
1380 // contentModel has already been destroyed before that is called.
1381 // Destruction isn't necessary for the QQuickItems themselves, but it is
1382 // required for the native menus (see comment in removeItem()).
1383 while (d->contentModel->count() > 0)
1384 d->removeItem(index: 0, item: d->itemAt(index: 0), destructionPolicy: QQuickMenuPrivate::DestructionPolicy::Destroy);
1385
1386 if (d->contentItem) {
1387 QQuickItemPrivate::get(item: d->contentItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children);
1388 QQuickItemPrivate::get(item: d->contentItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Geometry);
1389
1390 const auto children = d->contentItem->childItems();
1391 for (QQuickItem *child : std::as_const(t: children))
1392 QQuickItemPrivate::get(item: child)->removeItemChangeListener(d, types: QQuickItemPrivate::SiblingOrder);
1393 }
1394}
1395
1396/*!
1397 \qmlmethod Item QtQuick.Controls::Menu::itemAt(int index)
1398
1399 Returns the item at \a index, or \c null if it does not exist.
1400*/
1401QQuickItem *QQuickMenu::itemAt(int index) const
1402{
1403 Q_D(const QQuickMenu);
1404 return d->itemAt(index);
1405}
1406
1407/*!
1408 \qmlmethod void QtQuick.Controls::Menu::addItem(Item item)
1409
1410 Adds \a item to the end of the list of items.
1411*/
1412void QQuickMenu::addItem(QQuickItem *item)
1413{
1414 Q_D(QQuickMenu);
1415 insertItem(index: d->contentModel->count(), item);
1416}
1417
1418/*!
1419 \qmlmethod void QtQuick.Controls::Menu::insertItem(int index, Item item)
1420
1421 Inserts \a item at \a index.
1422*/
1423void QQuickMenu::insertItem(int index, QQuickItem *item)
1424{
1425 Q_D(QQuickMenu);
1426 if (!item)
1427 return;
1428 const int count = d->contentModel->count();
1429 if (index < 0 || index > count)
1430 index = count;
1431
1432 int oldIndex = d->contentModel->indexOf(object: item, objectContext: nullptr);
1433 if (oldIndex != -1) {
1434 if (oldIndex < index)
1435 --index;
1436 if (oldIndex != index) {
1437 d->moveItem(from: oldIndex, to: index);
1438 }
1439 } else {
1440 d->insertItem(index, item);
1441 }
1442}
1443
1444/*!
1445 \qmlmethod void QtQuick.Controls::Menu::moveItem(int from, int to)
1446
1447 Moves an item \a from one index \a to another.
1448*/
1449void QQuickMenu::moveItem(int from, int to)
1450{
1451 Q_D(QQuickMenu);
1452 const int count = d->contentModel->count();
1453 if (from < 0 || from > count - 1)
1454 return;
1455 if (to < 0 || to > count - 1)
1456 to = count - 1;
1457
1458 if (from != to)
1459 d->moveItem(from, to);
1460}
1461
1462/*!
1463 \since QtQuick.Controls 2.3 (Qt 5.10)
1464 \qmlmethod void QtQuick.Controls::Menu::removeItem(Item item)
1465
1466 Removes and destroys the specified \a item.
1467*/
1468void QQuickMenu::removeItem(QQuickItem *item)
1469{
1470 Q_D(QQuickMenu);
1471 if (!item)
1472 return;
1473
1474 const int index = d->contentModel->indexOf(object: item, objectContext: nullptr);
1475 if (index == -1)
1476 return;
1477
1478 d->removeItem(index, item, destructionPolicy: QQuickMenuPrivate::DestructionPolicy::Destroy);
1479}
1480
1481/*!
1482 \since QtQuick.Controls 2.3 (Qt 5.10)
1483 \qmlmethod MenuItem QtQuick.Controls::Menu::takeItem(int index)
1484
1485 Removes and returns the item at \a index.
1486
1487 \note The ownership of the item is transferred to the caller.
1488*/
1489QQuickItem *QQuickMenu::takeItem(int index)
1490{
1491 Q_D(QQuickMenu);
1492 const int count = d->contentModel->count();
1493 if (index < 0 || index >= count)
1494 return nullptr;
1495
1496 QQuickItem *item = itemAt(index);
1497 if (item)
1498 d->removeItem(index, item);
1499 return item;
1500}
1501
1502/*!
1503 \since QtQuick.Controls 2.3 (Qt 5.10)
1504 \qmlmethod Menu QtQuick.Controls::Menu::menuAt(int index)
1505
1506 Returns the sub-menu at \a index, or \c null if the index is not valid or
1507 there is no sub-menu at the specified index.
1508*/
1509QQuickMenu *QQuickMenu::menuAt(int index) const
1510{
1511 Q_D(const QQuickMenu);
1512 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index));
1513 if (!item)
1514 return nullptr;
1515
1516 return item->subMenu();
1517}
1518
1519/*!
1520 \since QtQuick.Controls 2.3 (Qt 5.10)
1521 \qmlmethod void QtQuick.Controls::Menu::addMenu(Menu menu)
1522
1523 Adds \a menu as a sub-menu to the end of this menu.
1524*/
1525void QQuickMenu::addMenu(QQuickMenu *menu)
1526{
1527 Q_D(QQuickMenu);
1528 insertMenu(index: d->contentModel->count(), menu);
1529}
1530
1531/*!
1532 \since QtQuick.Controls 2.3 (Qt 5.10)
1533 \qmlmethod void QtQuick.Controls::Menu::insertMenu(int index, Menu menu)
1534
1535 Inserts \a menu as a sub-menu at \a index. The index is within all items in the menu.
1536*/
1537void QQuickMenu::insertMenu(int index, QQuickMenu *menu)
1538{
1539 Q_D(QQuickMenu);
1540 if (!menu)
1541 return;
1542
1543 insertItem(index, item: d->createItem(menu));
1544}
1545
1546/*!
1547 \since QtQuick.Controls 2.3 (Qt 5.10)
1548 \qmlmethod void QtQuick.Controls::Menu::removeMenu(Menu menu)
1549
1550 Removes and destroys the specified \a menu.
1551*/
1552void QQuickMenu::removeMenu(QQuickMenu *menu)
1553{
1554 Q_D(QQuickMenu);
1555 if (!menu)
1556 return;
1557
1558 const int count = d->contentModel->count();
1559 for (int i = 0; i < count; ++i) {
1560 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index: i));
1561 if (!item || item->subMenu() != menu)
1562 continue;
1563
1564 removeItem(item);
1565 break;
1566 }
1567
1568 menu->deleteLater();
1569}
1570
1571/*!
1572 \since QtQuick.Controls 2.3 (Qt 5.10)
1573 \qmlmethod Menu QtQuick.Controls::Menu::takeMenu(int index)
1574
1575 Removes and returns the menu at \a index. The index is within all items in the menu.
1576
1577 \note The ownership of the menu is transferred to the caller.
1578*/
1579QQuickMenu *QQuickMenu::takeMenu(int index)
1580{
1581 Q_D(QQuickMenu);
1582 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index));
1583 if (!item)
1584 return nullptr;
1585
1586 QQuickMenu *subMenu = item->subMenu();
1587 if (!subMenu)
1588 return nullptr;
1589
1590 d->removeItem(index, item);
1591 item->deleteLater();
1592
1593 return subMenu;
1594}
1595
1596/*!
1597 \since QtQuick.Controls 2.3 (Qt 5.10)
1598 \qmlmethod Action QtQuick.Controls::Menu::actionAt(int index)
1599
1600 Returns the action at \a index, or \c null if the index is not valid or
1601 there is no action at the specified index.
1602*/
1603QQuickAction *QQuickMenu::actionAt(int index) const
1604{
1605 Q_D(const QQuickMenu);
1606 if (!const_cast<QQuickMenuPrivate *>(d)->maybeNativeHandle()) {
1607 QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton *>(object: d->itemAt(index));
1608 if (!item)
1609 return nullptr;
1610
1611 return item->action();
1612 } else {
1613 if (index < 0 || index >= d->nativeItems.size())
1614 return nullptr;
1615
1616 return d->nativeItems.at(i: index)->action();
1617 }
1618}
1619
1620/*!
1621 \since QtQuick.Controls 2.3 (Qt 5.10)
1622 \qmlmethod void QtQuick.Controls::Menu::addAction(Action action)
1623
1624 Adds \a action to the end of this menu.
1625*/
1626void QQuickMenu::addAction(QQuickAction *action)
1627{
1628 Q_D(QQuickMenu);
1629 insertAction(index: d->contentModel->count(), action);
1630}
1631
1632/*!
1633 \since QtQuick.Controls 2.3 (Qt 5.10)
1634 \qmlmethod void QtQuick.Controls::Menu::insertAction(int index, Action action)
1635
1636 Inserts \a action at \a index. The index is within all items in the menu.
1637*/
1638void QQuickMenu::insertAction(int index, QQuickAction *action)
1639{
1640 Q_D(QQuickMenu);
1641 if (!action)
1642 return;
1643
1644 insertItem(index, item: d->createItem(action));
1645}
1646
1647/*!
1648 \since QtQuick.Controls 2.3 (Qt 5.10)
1649 \qmlmethod void QtQuick.Controls::Menu::removeAction(Action action)
1650
1651 Removes and destroys the specified \a action.
1652*/
1653void QQuickMenu::removeAction(QQuickAction *action)
1654{
1655 Q_D(QQuickMenu);
1656 if (!action)
1657 return;
1658
1659 const int count = d->contentModel->count();
1660 for (int i = 0; i < count; ++i) {
1661 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index: i));
1662 if (!item || item->action() != action)
1663 continue;
1664
1665 removeItem(item);
1666 break;
1667 }
1668
1669 action->deleteLater();
1670}
1671
1672/*!
1673 \since QtQuick.Controls 2.3 (Qt 5.10)
1674 \qmlmethod Action QtQuick.Controls::Menu::takeAction(int index)
1675
1676 Removes and returns the action at \a index. The index is within all items in the menu.
1677
1678 \note The ownership of the action is transferred to the caller.
1679*/
1680QQuickAction *QQuickMenu::takeAction(int index)
1681{
1682 Q_D(QQuickMenu);
1683 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index));
1684 if (!item)
1685 return nullptr;
1686
1687 QQuickAction *action = item->action();
1688 if (!action)
1689 return nullptr;
1690
1691 d->removeItem(index, item);
1692 item->deleteLater();
1693 return action;
1694}
1695
1696bool QQuickMenu::isVisible() const
1697{
1698 Q_D(const QQuickMenu);
1699 if (d->maybeNativeHandle())
1700 return d->visible;
1701 return QQuickPopup::isVisible();
1702}
1703
1704void QQuickMenu::setVisible(bool visible)
1705{
1706 Q_D(QQuickMenu);
1707 if (visible == d->visible)
1708 return;
1709 if (visible && !parentItem()) {
1710 qmlWarning(me: this) << "cannot show menu: parent is null";
1711 return;
1712 }
1713
1714 if (visible && ((d->useNativeMenu() && !d->maybeNativeHandle())
1715 || (!d->useNativeMenu() && d->maybeNativeHandle()))) {
1716 // We've been made visible, and our actual native state doesn't match our requested state,
1717 // which means AA_DontUseNativeMenuWindows was set while we were visible or had a parent.
1718 // Try to sync our state again now that we're about to be re-opened.
1719 qCDebug(lcNativeMenus) << "setVisible called - useNativeMenu:" << d->useNativeMenu()
1720 << "maybeNativeHandle:" << d->maybeNativeHandle();
1721 d->syncWithUseNativeMenu();
1722 }
1723 if (d->maybeNativeHandle()) {
1724 d->setNativeMenuVisible(visible);
1725 return;
1726 }
1727
1728 // Either the native menu wasn't wanted, or it couldn't be created;
1729 // show the non-native menu.
1730 QQuickPopup::setVisible(visible);
1731}
1732
1733/*!
1734 \qmlproperty model QtQuick.Controls::Menu::contentModel
1735 \readonly
1736
1737 This property holds the model used to display menu items.
1738
1739 The content model is provided for visualization purposes. It can be assigned
1740 as a model to a content item that presents the contents of the menu.
1741
1742 \code
1743 Menu {
1744 id: menu
1745 contentItem: ListView {
1746 model: menu.contentModel
1747 }
1748 }
1749 \endcode
1750
1751 The model allows menu items to be statically declared as children of the
1752 menu.
1753*/
1754QVariant QQuickMenu::contentModel() const
1755{
1756 Q_D(const QQuickMenu);
1757 return QVariant::fromValue(value: d->contentModel);
1758}
1759
1760/*!
1761 \qmlproperty list<QtObject> QtQuick.Controls::Menu::contentData
1762 \qmldefault
1763
1764 This property holds the list of content data.
1765
1766 The list contains all objects that have been declared in QML as children
1767 of the menu, and also items that have been dynamically added or
1768 inserted using the \l addItem() and \l insertItem() methods, respectively.
1769
1770 \note Unlike \c contentChildren, \c contentData does include non-visual QML
1771 objects. It is not re-ordered when items are inserted or moved.
1772
1773 \sa Item::data, {Popup::}{contentChildren}
1774*/
1775QQmlListProperty<QObject> QQuickMenu::contentData()
1776{
1777 Q_D(QQuickMenu);
1778 if (!d->contentItem)
1779 QQuickControlPrivate::get(control: d->popupItem)->executeContentItem();
1780 return QQmlListProperty<QObject>(this, nullptr,
1781 QQuickMenuPrivate::contentData_append,
1782 QQuickMenuPrivate::contentData_count,
1783 QQuickMenuPrivate::contentData_at,
1784 QQuickMenuPrivate::contentData_clear);
1785}
1786
1787/*!
1788 \qmlproperty string QtQuick.Controls::Menu::title
1789
1790 This property holds the title for the menu.
1791
1792 The title of a menu is often displayed in the text of a menu item when the
1793 menu is a submenu, and in the text of a tool button when it is in a
1794 menubar.
1795*/
1796QString QQuickMenu::title() const
1797{
1798 Q_D(const QQuickMenu);
1799 return d->title;
1800}
1801
1802void QQuickMenu::setTitle(const QString &title)
1803{
1804 Q_D(QQuickMenu);
1805 if (title == d->title)
1806 return;
1807 d->title = title;
1808 if (d->handle)
1809 d->handle->setText(title);
1810 emit titleChanged(title);
1811}
1812
1813/*!
1814 \qmlproperty string QtQuick.Controls::Menu::icon.name
1815 \qmlproperty url QtQuick.Controls::Menu::icon.source
1816 \qmlproperty int QtQuick.Controls::Menu::icon.width
1817 \qmlproperty int QtQuick.Controls::Menu::icon.height
1818 \qmlproperty color QtQuick.Controls::Menu::icon.color
1819 \qmlproperty bool QtQuick.Controls::Menu::icon.cache
1820
1821 This property group was added in QtQuick.Controls 6.5.
1822
1823 \include qquickicon.qdocinc grouped-properties
1824
1825 \include qquickmenu.qdocinc non-native-only-property
1826
1827 \sa AbstractButton::text, AbstractButton::display, {Icons in Qt Quick Controls}
1828*/
1829
1830QQuickIcon QQuickMenu::icon() const
1831{
1832 Q_D(const QQuickMenu);
1833 return d->icon;
1834}
1835
1836void QQuickMenu::setIcon(const QQuickIcon &icon)
1837{
1838 Q_D(QQuickMenu);
1839 if (icon == d->icon)
1840 return;
1841 d->icon = icon;
1842 d->icon.ensureRelativeSourceResolved(owner: this);
1843 emit iconChanged(icon);
1844}
1845
1846/*!
1847 \since QtQuick.Controls 2.3 (Qt 5.10)
1848 \qmlproperty bool QtQuick.Controls::Menu::cascade
1849
1850 This property holds whether the menu cascades its sub-menus.
1851
1852 The default value is platform-specific. Menus are cascading by default on
1853 desktop platforms that have a mouse cursor available. Non-cascading menus
1854 are shown one menu at a time, and centered over the parent menu.
1855
1856 \note Changing the value of the property has no effect while the menu is open.
1857
1858 \include qquickmenu.qdocinc non-native-only-property
1859
1860 \sa overlap
1861*/
1862bool QQuickMenu::cascade() const
1863{
1864 Q_D(const QQuickMenu);
1865 return d->cascade;
1866}
1867
1868void QQuickMenu::setCascade(bool cascade)
1869{
1870 Q_D(QQuickMenu);
1871 if (d->cascade == cascade)
1872 return;
1873 d->cascade = cascade;
1874 if (d->parentMenu)
1875 d->resolveParentItem();
1876 emit cascadeChanged(cascade);
1877}
1878
1879void QQuickMenu::resetCascade()
1880{
1881 Q_D(QQuickMenu);
1882 if (d->parentMenu)
1883 setCascade(d->parentMenu->cascade());
1884 else
1885 setCascade(shouldCascade());
1886}
1887
1888/*!
1889 \since QtQuick.Controls 2.3 (Qt 5.10)
1890 \qmlproperty real QtQuick.Controls::Menu::overlap
1891
1892 This property holds the amount of pixels by which the menu horizontally overlaps its parent menu.
1893
1894 The property only has effect when the menu is used as a cascading sub-menu.
1895
1896 The default value is style-specific.
1897
1898 \note Changing the value of the property has no effect while the menu is open.
1899
1900 \include qquickmenu.qdocinc non-native-only-property
1901
1902 \sa cascade
1903*/
1904qreal QQuickMenu::overlap() const
1905{
1906 Q_D(const QQuickMenu);
1907 return d->overlap;
1908}
1909
1910void QQuickMenu::setOverlap(qreal overlap)
1911{
1912 Q_D(QQuickMenu);
1913 if (d->overlap == overlap)
1914 return;
1915 d->overlap = overlap;
1916 emit overlapChanged();
1917}
1918
1919/*!
1920 \since QtQuick.Controls 2.3 (Qt 5.10)
1921 \qmlproperty Component QtQuick.Controls::Menu::delegate
1922
1923 This property holds the component that is used to create items
1924 to present actions.
1925
1926 \code
1927 Menu {
1928 Action { text: "Cut" }
1929 Action { text: "Copy" }
1930 Action { text: "Paste" }
1931 }
1932 \endcode
1933
1934 \note delegates will only be visible when using a \l {Menu types}
1935 {non-native Menu}.
1936
1937 \sa Action
1938*/
1939QQmlComponent *QQuickMenu::delegate() const
1940{
1941 Q_D(const QQuickMenu);
1942 return d->delegate;
1943}
1944
1945void QQuickMenu::setDelegate(QQmlComponent *delegate)
1946{
1947 Q_D(QQuickMenu);
1948 if (d->delegate == delegate)
1949 return;
1950
1951 d->delegate = delegate;
1952 emit delegateChanged();
1953}
1954
1955/*!
1956 \since QtQuick.Controls 2.3 (Qt 5.10)
1957 \qmlproperty int QtQuick.Controls::Menu::currentIndex
1958
1959 This property holds the index of the currently highlighted item.
1960
1961 Menu items can be highlighted by mouse hover or keyboard navigation.
1962
1963 \include qquickmenu.qdocinc non-native-only-property
1964
1965 \sa MenuItem::highlighted
1966*/
1967int QQuickMenu::currentIndex() const
1968{
1969 Q_D(const QQuickMenu);
1970 return d->currentIndex;
1971}
1972
1973void QQuickMenu::setCurrentIndex(int index)
1974{
1975 Q_D(QQuickMenu);
1976 d->setCurrentIndex(index, reason: Qt::OtherFocusReason);
1977}
1978
1979/*!
1980 \since QtQuick.Controls 2.3 (Qt 5.10)
1981 \qmlproperty int QtQuick.Controls::Menu::count
1982 \readonly
1983
1984 This property holds the number of items.
1985*/
1986int QQuickMenu::count() const
1987{
1988 Q_D(const QQuickMenu);
1989 return d->contentModel->count();
1990}
1991
1992void QQuickMenu::popup(QQuickItem *menuItem)
1993{
1994 Q_D(QQuickMenu);
1995 // No position has been explicitly specified, so position the menu at the mouse cursor
1996 // on desktop platforms that have a mouse cursor available and support multiple windows.
1997 QQmlNullableValue<QPointF> pos;
1998#if QT_CONFIG(cursor)
1999 if (d->parentItem && QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::MultipleWindows))
2000 pos = d->parentItem->mapFromGlobal(point: QCursor::pos());
2001#endif
2002
2003 // As a fallback, center the menu over its parent item.
2004 if (!pos.isValid() && d->parentItem)
2005 pos = QPointF((d->parentItem->width() - width()) / 2, (d->parentItem->height() - height()) / 2);
2006
2007 popup(pos: pos.isValid() ? pos.value() : QPointF(), menuItem);
2008}
2009
2010void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem)
2011{
2012 Q_D(QQuickMenu);
2013 qreal offset = 0;
2014#if QT_CONFIG(cursor)
2015 if (menuItem)
2016 offset = d->popupItem->mapFromItem(item: menuItem, point: QPointF(0, 0)).y();
2017#endif
2018 setPosition(pos - QPointF(0, offset));
2019
2020 if (menuItem)
2021 d->setCurrentIndex(index: d->contentModel->indexOf(object: menuItem, objectContext: nullptr), reason: Qt::PopupFocusReason);
2022 else
2023 d->setCurrentIndex(index: -1, reason: Qt::PopupFocusReason);
2024
2025 open();
2026}
2027
2028/*!
2029 \since QtQuick.Controls 2.3 (Qt 5.10)
2030 \qmlmethod void QtQuick.Controls::Menu::popup(MenuItem item = null)
2031 \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, MenuItem item = null)
2032
2033 Opens the menu at the mouse cursor on desktop platforms that have a mouse cursor
2034 available, and otherwise centers the menu over its \a parent item.
2035
2036 The menu can be optionally aligned to a specific menu \a item. This item will
2037 then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex
2038 will be set to \c -1.
2039
2040 \sa Popup::open()
2041*/
2042
2043/*!
2044 \since QtQuick.Controls 2.3 (Qt 5.10)
2045 \qmlmethod void QtQuick.Controls::Menu::popup(point pos, MenuItem item = null)
2046 \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, point pos, MenuItem item = null)
2047
2048 Opens the menu at the specified position \a pos in the popups coordinate system,
2049 that is, a coordinate relative to its \a parent item.
2050
2051 The menu can be optionally aligned to a specific menu \a item. This item will
2052 then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex
2053 will be set to \c -1.
2054
2055 \sa Popup::open()
2056*/
2057
2058/*!
2059 \since QtQuick.Controls 2.3 (Qt 5.10)
2060 \qmlmethod void QtQuick.Controls::Menu::popup(real x, real y, MenuItem item = null)
2061 \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, real x, real y, MenuItem item = null)
2062
2063 Opens the menu at the specified position \a x, \a y in the popups coordinate system,
2064 that is, a coordinate relative to its \a parent item.
2065
2066 The menu can be optionally aligned to a specific menu \a item. This item will
2067 then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex
2068 will be set to \c -1.
2069
2070 \sa dismiss(), Popup::open()
2071*/
2072void QQuickMenu::popup(QQmlV4FunctionPtr args)
2073{
2074 Q_D(QQuickMenu);
2075 const int len = args->length();
2076 if (len > 4) {
2077 args->v4engine()->throwTypeError();
2078 return;
2079 }
2080
2081 QV4::ExecutionEngine *v4 = args->v4engine();
2082 QV4::Scope scope(v4);
2083
2084 QQmlNullableValue<QPointF> pos;
2085 QQuickItem *menuItem = nullptr;
2086 QQuickItem *parentItem = nullptr;
2087
2088 if (len > 0) {
2089 // Item parent
2090 QV4::ScopedValue firstArg(scope, (*args)[0]);
2091 if (const QV4::QObjectWrapper *obj = firstArg->as<QV4::QObjectWrapper>()) {
2092 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj->object());
2093 if (item && !d->popupItem->isAncestorOf(child: item))
2094 parentItem = item;
2095 } else if (firstArg->isUndefined()) {
2096 resetParentItem();
2097 parentItem = d->parentItem;
2098 }
2099
2100 // MenuItem item
2101 QV4::ScopedValue lastArg(scope, (*args)[len - 1]);
2102 if (const QV4::QObjectWrapper *obj = lastArg->as<QV4::QObjectWrapper>()) {
2103 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj->object());
2104 if (item && d->popupItem->isAncestorOf(child: item))
2105 menuItem = item;
2106 }
2107 }
2108
2109 if (len >= 3 || (!parentItem && len >= 2)) {
2110 // real x, real y
2111 QV4::ScopedValue xArg(scope, (*args)[parentItem ? 1 : 0]);
2112 QV4::ScopedValue yArg(scope, (*args)[parentItem ? 2 : 1]);
2113 if (xArg->isNumber() && yArg->isNumber())
2114 pos = QPointF(xArg->asDouble(), yArg->asDouble());
2115 }
2116
2117 if (!pos.isValid() && (len >= 2 || (!parentItem && len >= 1))) {
2118 // point pos
2119 QV4::ScopedValue posArg(scope, (*args)[parentItem ? 1 : 0]);
2120 const QVariant var = QV4::ExecutionEngine::toVariant(value: posArg, typeHint: QMetaType {});
2121 if (var.userType() == QMetaType::QPointF)
2122 pos = var.toPointF();
2123 }
2124
2125 if (parentItem)
2126 setParentItem(parentItem);
2127
2128 if (pos.isValid())
2129 popup(pos, menuItem);
2130 else
2131 popup(menuItem);
2132}
2133
2134/*!
2135 \since QtQuick.Controls 2.3 (Qt 5.10)
2136 \qmlmethod void QtQuick.Controls::Menu::dismiss()
2137
2138 Closes all menus in the hierarchy that this menu belongs to.
2139
2140 \note Unlike \l {Popup::}{close()} that only closes a menu and its
2141 sub-menus (when using \l {Menu types}{non-native menus}), \c dismiss()
2142 closes the whole hierarchy of menus, including the parent menus. In
2143 practice, \c close() is suitable e.g. for implementing navigation in a
2144 hierarchy of menus, and \c dismiss() is the appropriate method for closing
2145 the whole hierarchy of menus.
2146
2147 \sa popup(), Popup::close()
2148*/
2149void QQuickMenu::dismiss()
2150{
2151 QQuickMenu *menu = this;
2152 while (menu) {
2153 menu->close();
2154 menu = QQuickMenuPrivate::get(menu)->parentMenu;
2155 }
2156}
2157
2158void QQuickMenu::componentComplete()
2159{
2160 Q_D(QQuickMenu);
2161 QQuickPopup::componentComplete();
2162 d->resizeItems();
2163 d->updateTextPadding();
2164 d->syncWithUseNativeMenu();
2165}
2166
2167void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
2168{
2169 Q_D(QQuickMenu);
2170 QQuickPopup::contentItemChange(newItem, oldItem);
2171
2172 if (oldItem) {
2173 QQuickItemPrivate::get(item: oldItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children);
2174 QQuickItemPrivate::get(item: oldItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Geometry);
2175 }
2176 if (newItem) {
2177 QQuickItemPrivate::get(item: newItem)->addItemChangeListener(listener: d, types: QQuickItemPrivate::Children);
2178 QQuickItemPrivate::get(item: newItem)->updateOrAddGeometryChangeListener(listener: d, types: QQuickGeometryChange::Width);
2179 }
2180
2181 d->contentItem = newItem;
2182}
2183
2184void QQuickMenu::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
2185{
2186 Q_D(QQuickMenu);
2187 QQuickPopup::itemChange(change, data);
2188
2189 switch (change) {
2190 case QQuickItem::ItemVisibleHasChanged:
2191 if (!data.boolValue && d->cascade) {
2192 // Ensure that when the menu isn't visible, there's no current item
2193 // the next time it's opened.
2194 d->setCurrentIndex(index: -1, reason: Qt::OtherFocusReason);
2195 }
2196 break;
2197 default:
2198 break;
2199 }
2200}
2201
2202void QQuickMenu::keyPressEvent(QKeyEvent *event)
2203{
2204 Q_D(QQuickMenu);
2205 QQuickPopup::keyPressEvent(event);
2206
2207 // QTBUG-17051
2208 // Work around the fact that ListView has no way of distinguishing between
2209 // mouse and keyboard interaction, thanks to the "interactive" bool in Flickable.
2210 // What we actually want is to have a way to always allow keyboard interaction but
2211 // only allow flicking with the mouse when there are too many menu items to be
2212 // shown at once.
2213 switch (event->key()) {
2214 case Qt::Key_Up:
2215 if (!d->activatePreviousItem())
2216 d->propagateKeyEvent(event);
2217 break;
2218
2219 case Qt::Key_Down:
2220 d->activateNextItem();
2221 break;
2222
2223 case Qt::Key_Left:
2224 case Qt::Key_Right:
2225 event->ignore();
2226 if (d->popupItem->isMirrored() == (event->key() == Qt::Key_Right)) {
2227 if (d->parentMenu && d->currentItem) {
2228 if (!d->cascade)
2229 d->parentMenu->open();
2230 close();
2231 event->accept();
2232 }
2233 } else {
2234 if (QQuickMenu *subMenu = d->currentSubMenu()) {
2235 auto subMenuPrivate = QQuickMenuPrivate::get(menu: subMenu);
2236 subMenu->popup(menuItem: subMenuPrivate->firstEnabledMenuItem());
2237 event->accept();
2238 }
2239 }
2240 if (!event->isAccepted())
2241 d->propagateKeyEvent(event);
2242 break;
2243
2244#if QT_CONFIG(shortcut)
2245 case Qt::Key_Alt:
2246 // If &mnemonic shortcut is enabled, go back to (possibly) the parent
2247 // menu bar so the shortcut key will be processed by the menu bar.
2248 if (!QKeySequence::mnemonic(QStringLiteral("&A")).isEmpty())
2249 close();
2250 break;
2251#endif
2252
2253 default:
2254 break;
2255 }
2256
2257#if QT_CONFIG(shortcut)
2258 if (event->modifiers() == Qt::NoModifier) {
2259 for (int i = 0; i < count(); ++i) {
2260 QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton*>(object: d->itemAt(index: i));
2261 if (!item)
2262 continue;
2263 const QKeySequence keySequence = QKeySequence::mnemonic(text: item->text());
2264 if (keySequence.isEmpty())
2265 continue;
2266 if (keySequence[0].key() == event->key()) {
2267 item->click();
2268 break;
2269 }
2270 }
2271 }
2272#endif
2273}
2274
2275void QQuickMenu::timerEvent(QTimerEvent *event)
2276{
2277 Q_D(QQuickMenu);
2278 if (event->timerId() == d->hoverTimer) {
2279 if (QQuickMenu *subMenu = d->currentSubMenu())
2280 subMenu->open();
2281 d->stopHoverTimer();
2282 return;
2283 }
2284 QQuickPopup::timerEvent(event);
2285}
2286
2287QFont QQuickMenu::defaultFont() const
2288{
2289 return QQuickTheme::font(scope: QQuickTheme::Menu);
2290}
2291
2292#if QT_CONFIG(accessibility)
2293QAccessible::Role QQuickMenu::accessibleRole() const
2294{
2295 return QAccessible::PopupMenu;
2296}
2297#endif
2298
2299QT_END_NAMESPACE
2300
2301#include "moc_qquickmenu_p.cpp"
2302

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