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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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