aboutsummaryrefslogtreecommitdiffstats
path: root/src/quicktemplates/qquickmenu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quicktemplates/qquickmenu.cpp')
-rw-r--r--src/quicktemplates/qquickmenu.cpp2229
1 files changed, 2229 insertions, 0 deletions
diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp
new file mode 100644
index 0000000000..9580dea2c0
--- /dev/null
+++ b/src/quicktemplates/qquickmenu.cpp
@@ -0,0 +1,2229 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickmenu_p.h"
+#include "qquickmenu_p_p.h"
+#include "qquickmenuitem_p_p.h"
+#include <private/qtquicktemplates2-config_p.h>
+#if QT_CONFIG(quicktemplates2_container)
+#include "qquickmenubaritem_p.h"
+#include "qquickmenubar_p_p.h"
+#endif
+#include "qquickmenuseparator_p.h"
+#include "qquicknativemenuitem_p.h"
+#include "qquickpopupitem_p_p.h"
+#include "qquickpopuppositioner_p_p.h"
+#include "qquickaction_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qcursor.h>
+#if QT_CONFIG(shortcut)
+#include <QtGui/qkeysequence.h>
+#endif
+#include <QtGui/qpa/qplatformintegration.h>
+#include <QtGui/qpa/qplatformtheme.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/private/qqmlengine_p.h>
+#include <QtQml/private/qv4scopedvalue_p.h>
+#include <QtQml/private/qv4variantobject_p.h>
+#include <QtQml/private/qv4qobjectwrapper_p.h>
+#include <private/qqmlobjectmodel_p.h>
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquickitemchangelistener_p.h>
+#include <QtQuick/private/qquickevents_p_p.h>
+#include <QtQuick/private/qquicklistview_p.h>
+#include <QtQuick/private/qquickrendercontrol_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_STATIC_LOGGING_CATEGORY(lcMenu, "qt.quick.controls.menu")
+Q_STATIC_LOGGING_CATEGORY(lcNativeMenus, "qt.quick.controls.nativemenus")
+
+// copied from qfusionstyle.cpp
+static const int SUBMENU_DELAY = 225;
+
+/*!
+ \qmltype Menu
+ \inherits Popup
+//! \instantiates QQuickMenu
+ \inqmlmodule QtQuick.Controls
+ \since 5.7
+ \ingroup qtquickcontrols-menus
+ \ingroup qtquickcontrols-popups
+ \brief Menu popup that can be used as a context menu or popup menu.
+
+ \table
+ \row
+ \li \image qtquickcontrols-menu-native.png
+ \caption Native macOS menu.
+ \li \image qtquickcontrols-menu.png
+ \caption Non-native \l {Material Style}{Material style} menu.
+ \endtable
+
+ Menu has two main use cases:
+ \list
+ \li Context menus; for example, a menu that is shown after right clicking
+ \li Popup menus; for example, a menu that is shown after clicking a button
+ \endlist
+
+ When used as a context menu, the recommended way of opening the menu is to call
+ \l popup(). Unless a position is explicitly specified, the menu is positioned at
+ the mouse cursor on desktop platforms that have a mouse cursor available, and
+ otherwise centered over its parent item.
+
+ \code
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: {
+ if (mouse.button === Qt.RightButton)
+ contextMenu.popup()
+ }
+ onPressAndHold: {
+ if (mouse.source === Qt.MouseEventNotSynthesized)
+ contextMenu.popup()
+ }
+
+ Menu {
+ id: contextMenu
+ MenuItem { text: "Cut" }
+ MenuItem { text: "Copy" }
+ MenuItem { text: "Paste" }
+ }
+ }
+ \endcode
+
+ When used as a popup menu, it is easiest to specify the position by specifying
+ the desired \l {Popup::}{x} and \l {Popup::}{y} coordinates using the respective
+ properties, and call \l {Popup::}{open()} to open the menu.
+
+ \code
+ Button {
+ id: fileButton
+ text: "File"
+ onClicked: menu.open()
+
+ Menu {
+ id: menu
+ y: fileButton.height
+
+ MenuItem {
+ text: "New..."
+ }
+ MenuItem {
+ text: "Open..."
+ }
+ MenuItem {
+ text: "Save"
+ }
+ }
+ }
+ \endcode
+
+ If the button should also close the menu when clicked, use the
+ \c Popup.CloseOnPressOutsideParent flag:
+ \code
+ onClicked: menu.visible = !menu.visible
+
+ Menu {
+ // ...
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+ \endcode
+
+ Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to create sub-menus
+ and declare Action objects inside Menu:
+
+ \code
+ Menu {
+ Action { text: "Cut" }
+ Action { text: "Copy" }
+ Action { text: "Paste" }
+
+ MenuSeparator { }
+
+ Menu {
+ title: "Find/Replace"
+ Action { text: "Find Next" }
+ Action { text: "Find Previous" }
+ Action { text: "Replace" }
+ }
+ }
+ \endcode
+
+ Sub-menus are \l {cascade}{cascading} by default on desktop platforms
+ that have a mouse cursor available. Non-cascading menus are shown one
+ menu at a time, and centered over the parent menu.
+
+ Typically, menu items are statically declared as children of the menu, but
+ Menu also provides API to \l {addItem}{add}, \l {insertItem}{insert},
+ \l {moveItem}{move} and \l {removeItem}{remove} items dynamically. The
+ items in a menu can be accessed using \l itemAt() or
+ \l {Popup::}{contentChildren}.
+
+ Although \l {MenuItem}{MenuItems} are most commonly used with Menu, it can
+ contain any type of item.
+
+ \section1 Margins
+
+ As it is inherited from Popup, Menu supports \l {Popup::}{margins}. By
+ default, all of the built-in styles specify \c 0 for Menu's margins to
+ ensure that the menu is kept within the bounds of the window. To allow a
+ menu to go outside of the window (to animate it moving into view, for
+ example), set the margins property to \c -1.
+
+ \section1 Dynamically Generating Menu Items
+
+ You can dynamically create menu items with \l Instantiator or
+ \l {Dynamic QML Object Creation from JavaScript} {dynamic object creation}.
+
+ \section2 Using Instantiator
+
+ You can dynamically generate menu items with \l Instantiator. The
+ following code shows how you can implement a "Recent Files" submenu,
+ where the items come from a list of files stored in settings:
+
+ \snippet qtquickcontrols-menu-instantiator.qml menu
+
+ \section2 Using Dynamic Object Creation
+
+ You can also dynamically load a component from a QML file using
+ \l {QtQml::Qt::createComponent()} {Qt.createComponent()}. Once the component
+ is ready, you can call its \l {Component::createObject()} {createObject()}
+ method to create an instance of that component.
+
+ \snippet qtquickcontrols-menu-createObject.qml createObject
+
+ \sa {Customizing Menu}, MenuItem, {Menu Controls}, {Popup Controls},
+ {Dynamic QML Object Creation from JavaScript}
+
+ \section1 Menu types
+
+ Since Qt 6.8, a menu offers three different implementations, depending on the
+ platform. You can choose which one should be preferred by setting
+ \l popupType. This will let you control if a menu should be shown as a separate
+ window, as an item inside the parent window, or as a native menu. You can read
+ more about these options \l{Popup type}{here.}
+
+ Whether a menu will be able to use the preferred type depends on the platform.
+ \c Popup.Item is supported on all platforms, but \c Popup.Window and \c Popup.Native
+ are normally only supported on desktop platforms. Additionally, if the menu is inside
+ a \l {Native menu bars}{native menubar}, the menu will be native as well. And if
+ the menu is a sub-menu inside another menu, the parent (or root) menu will decide the type.
+
+ \section2 Limitations when using native menus
+
+ When setting \l popupType to \c Popup.Native, there are some limitations and
+ differences compared to using \c Popup.Item and \c Popup.Window.
+
+ \section3 API differences
+
+ When using native menus, only a subset of the Menu API is supported on all platforms:
+
+ \list
+ \li \l {Popup::}{x}
+ \li \l {Popup::}{y}
+ \li \l {Popup::}{visible}
+ \li \l {Popup::}{opened}
+ \li \l title
+ \li \l count
+ \li \l {Popup::}{contentData}
+ \li \l {Popup::}{contentChildren} (visual children will not be visible)
+ \li \l contentModel
+ \li \l {Popup::}{open()}
+ \li \l {Popup::}{popup()}
+ \li \l {Popup::}{close()}
+ \li \l {Popup::}{opened()}
+ \li \l {Popup::}{closed()}
+ \li \l {Popup::}{aboutToShow()}
+ \li \l {Popup::}{aboutToHide()}
+ \endlist
+
+ In addition, showing a popup (using for example \l {Popup::}{open()} or \l {Popup::}{popup()}
+ will, on some platforms, be a blocking call. This means that the call will not return
+ before the menu is closed again, which can affect the logic in your application. This is
+ especially important to take into consideration if your application is targeting multiple
+ platforms, and as such, sometimes run on platforms where native menus are not supported.
+ In that case the popupType will fall back to \c Popup.Item, for example, and calls to
+ \l {Popup::}{open()} will not be blocking.
+
+ Items like \l MenuItem will still react to clicks in the corresponding
+ native menu item by emitting signals, for example, but will be replaced by
+ their native counterpart.
+
+ \section3 Rendering differences
+
+ Native menus are implemented using the available native menu APIs on the platform.
+ Those menus, and all of their contents, will therefore be rendered by the platform, and
+ not by QML. This means that the \l delegate will \e not be used for rendering. It will,
+ however, always be instantiated (but hidden), so that functions such as
+ \l {Component.onCompleted()} execute regardless of platform and \l popupType.
+
+ \section3 Supported platforms
+
+ Native menus are currently supported on the following platforms:
+
+ \list
+ \li Android
+ \li iOS
+ \li Linux (only available as a stand-alone context menu when running with the GTK+ platform theme)
+ \li macOS
+ \li Windows
+ \endlist
+
+ \sa {Popup type}, popupType
+*/
+
+/*!
+ \qmlproperty bool QtQuick.Controls::Menu::focus
+
+ This property holds whether the popup wants focus.
+
+ When the popup actually receives focus, \l{Popup::}{activeFocus}
+ will be \c true. For more information, see
+ \l {Keyboard Focus in Qt Quick}.
+
+ The default value is \c true.
+
+ \include qquickmenu.qdocinc non-native-only-property
+
+ \sa {Popup::}{activeFocus}
+*/
+
+static const QQuickPopup::ClosePolicy cascadingSubMenuClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent;
+
+static bool shouldCascade()
+{
+#if QT_CONFIG(cursor)
+ return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows);
+#else
+ return false;
+#endif
+}
+
+class QQuickMenuPositioner : public QQuickPopupPositioner
+{
+public:
+ QQuickMenuPositioner(QQuickMenu *menu) : QQuickPopupPositioner(menu) { }
+
+ void reposition() override;
+};
+
+QQuickMenuPrivate::QQuickMenuPrivate()
+{
+ cascade = shouldCascade();
+}
+
+void QQuickMenuPrivate::init()
+{
+ Q_Q(QQuickMenu);
+ contentModel = new QQmlObjectModel(q);
+}
+
+QQuickMenu *QQuickMenuPrivate::rootMenu() const
+{
+ Q_Q(const QQuickMenu);
+ const QQuickMenu *rootMenu = q;
+ const QObject *p = q->parent();
+ while (p) {
+ if (auto menu = qobject_cast<const QQuickMenu *>(p))
+ rootMenu = menu;
+ p = p->parent();
+ }
+
+ return const_cast<QQuickMenu *>(rootMenu);
+}
+
+ QQuickPopup::PopupType QQuickMenuPrivate::resolvedPopupType() const
+{
+ // The resolved popup type is decided by the root
+ // menu (which can be this menu, unless it's a child menu).
+ QQuickMenuPrivate *root_d = QQuickMenuPrivate::get(rootMenu());
+
+ // If the root menu is native, then so should we. We assume here that
+ // the root menu is always shown and created first, before we try to
+ // show and create a child menu.
+ if (root_d->maybeNativeHandle())
+ return QQuickPopup::PopupType::Native;
+
+ return root_d->QQuickPopupPrivate::resolvedPopupType();
+}
+
+bool QQuickMenuPrivate::useNativeMenu() const
+{
+ // If we're inside a MenuBar, it'll decide whether or not we
+ // should be native or not. Otherwise, the root menu (which
+ // might be this menu) will decide.
+ QQuickMenu *root = rootMenu();
+ if (auto menuBar = QQuickMenuPrivate::get(root)->menuBar.get())
+ return QQuickMenuBarPrivate::get(menuBar)->useNativeMenu(q_func());
+ return root->popupType() == QQuickPopup::Native;
+}
+
+QPlatformMenu *QQuickMenuPrivate::nativeHandle()
+{
+ Q_ASSERT(handle || useNativeMenu());
+ if (!handle && !triedToCreateNativeMenu)
+ createNativeMenu();
+ return handle.get();
+}
+
+QPlatformMenu *QQuickMenuPrivate::maybeNativeHandle() const
+{
+ return handle.get();
+}
+
+bool QQuickMenuPrivate::createNativeMenu()
+{
+ Q_ASSERT(!handle);
+ Q_Q(QQuickMenu);
+ qCDebug(lcNativeMenus) << "createNativeMenu called on" << q;
+
+ if (auto menuBar = QQuickMenuPrivate::get(rootMenu())->menuBar) {
+ auto menuBarPrivate = QQuickMenuBarPrivate::get(menuBar);
+ if (menuBarPrivate->useNativeMenuBar()) {
+ qCDebug(lcNativeMenus) << "- creating native menu from native menubar";
+ if (QPlatformMenuBar *menuBarHandle = menuBarPrivate->nativeHandle())
+ handle.reset(menuBarHandle->createMenu());
+ }
+ }
+
+ if (!handle) {
+ QPlatformMenu *parentMenuHandle(parentMenu ? get(parentMenu)->handle.get() : nullptr);
+ if (parentMenu && parentMenuHandle) {
+ qCDebug(lcNativeMenus) << "- creating native sub-menu";
+ handle.reset(parentMenuHandle->createSubMenu());
+ } else {
+ qCDebug(lcNativeMenus) << "- creating native menu";
+ handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenu());
+ }
+ }
+
+ triedToCreateNativeMenu = true;
+
+ if (!handle)
+ return false;
+
+ q->connect(handle.get(), &QPlatformMenu::aboutToShow, q, [q, this](){
+ emit q->aboutToShow();
+ visible = true;
+ emit q->visibleChanged();
+ emit q->openedChanged();
+ opened();
+ });
+ q->connect(handle.get(), &QPlatformMenu::aboutToHide, q, [q, this](){
+ qCDebug(lcNativeMenus) << "QPlatformMenu::aboutToHide called; about to call setVisible(false) on Menu";
+ emit q->aboutToHide();
+ visible = false;
+ emit q->visibleChanged();
+ emit q->openedChanged();
+ emit q->closed();
+ });
+
+ recursivelyCreateNativeMenuItems(q);
+ syncWithNativeMenu();
+
+ return true;
+}
+
+QString nativeMenuItemListToString(const QList<QQuickNativeMenuItem *> &nativeItems)
+{
+ if (nativeItems.isEmpty())
+ return QStringLiteral("(Empty)");
+
+ QString str;
+ QTextStream debug(&str);
+ for (const auto *nativeItem : nativeItems)
+ debug << nativeItem->debugText() << ", ";
+ // Remove trailing space and comma.
+ if (!nativeItems.isEmpty())
+ str.chop(2);
+ return str;
+}
+
+void QQuickMenuPrivate::syncWithNativeMenu()
+{
+ Q_Q(QQuickMenu);
+ if (!complete || !handle)
+ return;
+
+ qCDebug(lcNativeMenus).nospace() << "syncWithNativeMenu called on " << q
+ << " (complete: " << complete << " visible: " << visible << ") - "
+ << "syncing " << nativeItems.size() << " item(s)...";
+
+ // TODO: call this function when any of the variables below change
+
+ handle->setText(title);
+ handle->setEnabled(q->isEnabled());
+ handle->setMinimumWidth(q->implicitWidth());
+// nativeHandle->setMenuType(m_type);
+ handle->setFont(q->font());
+
+ // Note: the QQuickMenu::visible property is used to open or close the menu.
+ // This is in contrast to QPlatformMenu::visible, which tells if the menu
+ // should be visible in the menubar or not (if it belongs to one). To control
+ // if a QPlatformMenu should be open, we instead use QPlatformMenu::showPopup()
+ // and dismiss(). As such, we don't want to call handle->setVisible(visible)
+ // from this function since we always want the menu to be visible in the menubar
+ // (if it belongs to one). The currently only way to hide a menu from a menubar is
+ // to instead call MenuBar.removeMenu(menu).
+
+// if (m_menuBar && m_menuBar->handle())
+// m_menuBar->handle()->syncMenu(handle);
+//#if QT_CONFIG(systemtrayicon)
+// else if (m_systemTrayIcon && m_systemTrayIcon->handle())
+// m_systemTrayIcon->handle()->updateMenu(handle);
+//#endif
+
+ for (QQuickNativeMenuItem *item : std::as_const(nativeItems)) {
+ qCDebug(lcNativeMenus) << "- syncing" << item << "action" << item->action()
+ << "sub-menu" << item->subMenu() << item->debugText();
+ item->sync();
+ }
+
+ qCDebug(lcNativeMenus) << "... finished syncing" << q;
+}
+
+void QQuickMenuPrivate::removeNativeMenu()
+{
+ // Remove the native menu, including it's native menu items
+ Q_Q(QQuickMenu);
+ const int qtyItemsToRemove = nativeItems.size();
+ if (qtyItemsToRemove != 0)
+ Q_ASSERT(q->count() == qtyItemsToRemove);
+ for (int i = 0; i < qtyItemsToRemove; ++i)
+ removeNativeItem(0);
+ Q_ASSERT(nativeItems.isEmpty());
+
+ // removeNativeItem will take care of destroying sub-menus and resetting their native data,
+ // but as the root menu, we have to take care of our own.
+ resetNativeData();
+}
+
+void QQuickMenuPrivate::syncWithUseNativeMenu()
+{
+ Q_Q(QQuickMenu);
+ // Users can change AA_DontUseNativeMenuWindows while a menu is visible,
+ // but the changes won't take affect until the menu is re-opened.
+ if (q->isVisible() || parentMenu)
+ return;
+
+ if (maybeNativeHandle() && !useNativeMenu()) {
+ // Switch to a non-native menu by removing the native menu and its native items.
+ // Note that there's nothing to do if a native menu was requested but we failed to create it.
+ removeNativeMenu();
+ } else if (useNativeMenu()) {
+ Q_ASSERT(nativeItems.isEmpty());
+ // Try to create a native menu.
+ nativeHandle();
+ }
+}
+
+/*!
+ \internal
+
+ Recursively destroys native sub-menus of \a menu.
+
+ This function checks if each native item in \c menu has a sub-menu,
+ and if so:
+ \list
+ \li Calls itself with that sub-menu
+ \li Resets the item's data (important to avoid accessing a deleted QQuickAction
+ when printing in QQuickNativeMenuItem's destructor)
+ \li Deletes (eventually) the native item
+ \endlist
+
+ Similar (besides the recursion) to removeNativeItem(), except that
+ we can avoid repeated calls to syncWithNativeMenu().
+*/
+void QQuickMenuPrivate::recursivelyDestroyNativeSubMenus(QQuickMenu *menu)
+{
+ auto *menuPrivate = QQuickMenuPrivate::get(menu);
+ Q_ASSERT(menuPrivate->handle);
+ qCDebug(lcNativeMenus) << "recursivelyDestroyNativeSubMenus called with" << menu << "...";
+
+ while (!menuPrivate->nativeItems.isEmpty()) {
+ std::unique_ptr<QQuickNativeMenuItem> item(menuPrivate->nativeItems.takeFirst());
+ qCDebug(lcNativeMenus) << "- taking and destroying" << item->debugText();
+ if (QQuickMenu *subMenu = item->subMenu())
+ recursivelyDestroyNativeSubMenus(subMenu);
+
+ if (item->handle())
+ menuPrivate->handle->removeMenuItem(item->handle());
+ }
+
+ menuPrivate->resetNativeData();
+
+ qCDebug(lcNativeMenus) << "... finished destroying native sub-menus of" << menu;
+}
+
+static QWindow *effectiveWindow(QWindow *window, QPoint *offset)
+{
+ QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window);
+ if (quickWindow) {
+ QWindow *renderWindow = QQuickRenderControl::renderWindowFor(quickWindow, offset);
+ if (renderWindow)
+ return renderWindow;
+ }
+ return window;
+}
+
+void QQuickMenuPrivate::setNativeMenuVisible(bool visible)
+{
+ Q_Q(QQuickMenu);
+ qCDebug(lcNativeMenus) << "setNativeMenuVisible called with visible" << visible;
+ if (visible)
+ emit q->aboutToShow();
+ else
+ emit q->aboutToHide();
+
+ this->visible = visible;
+ syncWithNativeMenu();
+
+ QPoint offset;
+ QWindow *window = effectiveWindow(qGuiApp->topLevelWindows().first(), &offset);
+
+ if (visible) {
+ lastDevicePixelRatio = window->devicePixelRatio();
+
+ const QPointF globalPos = parentItem->mapToGlobal(x, y);
+ const QPoint windowPos = window->mapFromGlobal(globalPos.toPoint());
+ QRect targetRect(windowPos, QSize(0, 0));
+ handle->showPopup(window, QHighDpi::toNativePixels(targetRect, window),
+ /*menuItem ? menuItem->handle() : */nullptr);
+ } else {
+ handle->dismiss();
+ }
+}
+
+QQuickItem *QQuickMenuPrivate::itemAt(int index) const
+{
+ return qobject_cast<QQuickItem *>(contentModel->get(index));
+}
+
+void QQuickMenuPrivate::insertItem(int index, QQuickItem *item)
+{
+ qCDebug(lcMenu) << "insert called with index" << index << "item" << item;
+
+ Q_Q(QQuickMenu);
+ contentData.append(item);
+ item->setParentItem(contentItem);
+ QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-53262
+ if (complete)
+ resizeItem(item);
+ QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent);
+ QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(this, QQuickGeometryChange::Width);
+ contentModel->insert(index, item);
+
+ QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item);
+ if (menuItem) {
+ QQuickMenuItemPrivate::get(menuItem)->setMenu(q);
+ if (QQuickMenu *subMenu = menuItem->subMenu())
+ QQuickMenuPrivate::get(subMenu)->setParentMenu(q);
+ QObjectPrivate::connect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered);
+ QObjectPrivate::connect(menuItem, &QQuickMenuItem::implicitTextPaddingChanged, this, &QQuickMenuPrivate::updateTextPadding);
+ QObjectPrivate::connect(menuItem, &QQuickMenuItem::visibleChanged, this, &QQuickMenuPrivate::updateTextPadding);
+ QObjectPrivate::connect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged);
+ QObjectPrivate::connect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered);
+ }
+
+ if (maybeNativeHandle() && complete)
+ maybeCreateAndInsertNativeItem(index, item);
+
+ if (lcMenu().isDebugEnabled())
+ printContentModelItems();
+
+ updateTextPadding();
+}
+
+void QQuickMenuPrivate::maybeCreateAndInsertNativeItem(int index, QQuickItem *item)
+{
+ Q_Q(QQuickMenu);
+ Q_ASSERT(complete);
+ Q_ASSERT_X(handle, Q_FUNC_INFO, qPrintable(QString::fromLatin1(
+ "Expected %1 to be using a native menu").arg(QDebug::toString(q))));
+ std::unique_ptr<QQuickNativeMenuItem> nativeMenuItem(QQuickNativeMenuItem::createFromNonNativeItem(q, item));
+ if (!nativeMenuItem) {
+ // TODO: fall back to non-native menu
+ qmlWarning(q) << "Native menu failed to create a native menu item for item at index" << index;
+ return;
+ }
+
+ nativeItems.insert(index, nativeMenuItem.get());
+
+ // Having a QQuickNativeMenuItem doesn't mean that we were able to create a native handle:
+ // it could be e.g. a Rectangle. See comment in QQuickNativeMenuItem::createFromNonNativeItem.
+ if (nativeMenuItem->handle()) {
+ QQuickNativeMenuItem *before = nativeItems.value(index + 1);
+ handle->insertMenuItem(nativeMenuItem->handle(), before ? before->handle() : nullptr);
+ qCDebug(lcNativeMenus) << "inserted native menu item at index" << index
+ << "before" << (before ? before->debugText() : QStringLiteral("null"));
+
+ if (nativeMenuItem->subMenu() && QQuickMenuPrivate::get(nativeMenuItem->subMenu())->nativeItems.count()
+ < nativeMenuItem->subMenu()->count()) {
+ // We're inserting a sub-menu item, and it hasn't had native items added yet,
+ // which probably means it's a menu that's been added back in after being removed
+ // with takeMenu(). Sub-menus added for the first time have their native items already
+ // constructed by virtue of contentData_append. Sub-menus that are removed always
+ // have their native items destroyed and removed too.
+ recursivelyCreateNativeMenuItems(nativeMenuItem->subMenu());
+ }
+ }
+
+ nativeMenuItem.release();
+
+ qCDebug(lcNativeMenus) << "nativeItems now contains the following items:"
+ << nativeMenuItemListToString(nativeItems);
+}
+
+void QQuickMenuPrivate::moveItem(int from, int to)
+{
+ contentModel->move(from, to);
+
+ if (maybeNativeHandle())
+ nativeItems.move(from, to);
+}
+
+/*!
+ \internal
+
+ Removes the specified \a item, potentially destroying it depending on
+ \a destructionPolicy.
+
+ \note the native menu item is destroyed regardless of the destruction
+ policy, because it's an implementation detail and hence is not created by
+ or available to the user.
+*/
+void QQuickMenuPrivate::removeItem(int index, QQuickItem *item, DestructionPolicy destructionPolicy)
+{
+ qCDebug(lcMenu) << "removeItem called with index" << index << "item" << item;
+
+ if (maybeNativeHandle())
+ removeNativeItem(index);
+
+ contentData.removeOne(item);
+
+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent);
+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
+ item->setParentItem(nullptr);
+ contentModel->remove(index);
+
+ QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item);
+ if (menuItem) {
+ QQuickMenuItemPrivate::get(menuItem)->setMenu(nullptr);
+ if (QQuickMenu *subMenu = menuItem->subMenu())
+ QQuickMenuPrivate::get(subMenu)->setParentMenu(nullptr);
+ QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered);
+ QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::implicitTextPaddingChanged, this, &QQuickMenuPrivate::updateTextPadding);
+ QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::visibleChanged, this, &QQuickMenuPrivate::updateTextPadding);
+ QObjectPrivate::disconnect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged);
+ QObjectPrivate::disconnect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered);
+ }
+
+ if (destructionPolicy == DestructionPolicy::Destroy)
+ item->deleteLater();
+
+ if (lcMenu().isDebugEnabled())
+ printContentModelItems();
+}
+
+void QQuickMenuPrivate::removeNativeItem(int index)
+{
+ // Either we're still using native menus and are removing item(s), or we've switched
+ // to a non-native menu; either way, we should actually have items to remove before we're called.
+ Q_ASSERT(handle);
+ Q_ASSERT_X(index >= 0 && index < nativeItems.size(), Q_FUNC_INFO, qPrintable(QString::fromLatin1(
+ "index %1 is less than 0 or greater than or equal to %2").arg(index).arg(nativeItems.size())));
+
+ // We can delete the item synchronously because there aren't any external (e.g. QML)
+ // references to it.
+ std::unique_ptr<QQuickNativeMenuItem> nativeItem(nativeItems.takeAt(index));
+ qCDebug(lcNativeMenus) << "removing native item" << nativeItem->debugText() << "at index" << index
+ << "from" << q_func() << "...";
+ if (QQuickMenu *subMenu = nativeItem->subMenu())
+ recursivelyDestroyNativeSubMenus(subMenu);
+
+ if (nativeItem->handle()) {
+ handle->removeMenuItem(nativeItem->handle());
+ syncWithNativeMenu();
+ }
+
+ qCDebug(lcNativeMenus).nospace() << "... after removing item at index " << index
+ << ", nativeItems now contains the following items: " << nativeMenuItemListToString(nativeItems);
+}
+
+void QQuickMenuPrivate::resetNativeData()
+{
+ qCDebug(lcNativeMenus) << "resetNativeData called on" << q_func();
+ handle.reset();
+ triedToCreateNativeMenu = false;
+}
+
+void QQuickMenuPrivate::recursivelyCreateNativeMenuItems(QQuickMenu *menu)
+{
+ auto *menuPrivate = QQuickMenuPrivate::get(menu);
+ // If we're adding a sub-menu, we need to ensure its handle has been created
+ // before trying to create native items for it.
+ if (!menuPrivate->triedToCreateNativeMenu)
+ menuPrivate->createNativeMenu();
+
+ const int qtyItemsToCreate = menuPrivate->contentModel->count();
+ if (menuPrivate->nativeItems.count() == qtyItemsToCreate)
+ return;
+
+ qCDebug(lcNativeMenus) << "recursively creating" << qtyItemsToCreate << "menu item(s) for" << menu;
+ Q_ASSERT(menuPrivate->nativeItems.count() == 0);
+ for (int i = 0; i < qtyItemsToCreate; ++i) {
+ QQuickItem *item = menu->itemAt(i);
+ menuPrivate->maybeCreateAndInsertNativeItem(i, item);
+ auto *menuItem = qobject_cast<QQuickMenuItem *>(item);
+ if (menuItem && menuItem->subMenu())
+ recursivelyCreateNativeMenuItems(menuItem->subMenu());
+ }
+}
+
+void QQuickMenuPrivate::printContentModelItems() const
+{
+ qCDebug(lcMenu) << "contentModel now contains:";
+ for (int i = 0; i < contentModel->count(); ++i)
+ qCDebug(lcMenu) << "-" << itemAt(i);
+}
+
+QQuickItem *QQuickMenuPrivate::beginCreateItem()
+{
+ Q_Q(QQuickMenu);
+ if (!delegate)
+ return nullptr;
+
+ QQmlContext *context = delegate->creationContext();
+ if (!context)
+ context = qmlContext(q);
+
+ QObject *object = delegate->beginCreate(context);
+ QQuickItem *item = qobject_cast<QQuickItem *>(object);
+ if (!item)
+ delete object;
+ else
+ QQml_setParent_noEvent(item, q);
+
+ return item;
+}
+
+void QQuickMenuPrivate::completeCreateItem()
+{
+ if (!delegate)
+ return;
+
+ delegate->completeCreate();
+}
+
+QQuickItem *QQuickMenuPrivate::createItem(QQuickMenu *menu)
+{
+ QQuickItem *item = beginCreateItem();
+ if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item))
+ QQuickMenuItemPrivate::get(menuItem)->setSubMenu(menu);
+ completeCreateItem();
+ return item;
+}
+
+QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action)
+{
+ QQuickItem *item = beginCreateItem();
+ if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(item))
+ button->setAction(action);
+ completeCreateItem();
+ return item;
+}
+
+void QQuickMenuPrivate::resizeItem(QQuickItem *item)
+{
+ if (!item || !contentItem)
+ return;
+
+ QQuickItemPrivate *p = QQuickItemPrivate::get(item);
+ if (!p->widthValid()) {
+ item->setWidth(contentItem->width());
+ p->widthValidFlag = false;
+ }
+}
+
+void QQuickMenuPrivate::resizeItems()
+{
+ if (!contentModel)
+ return;
+
+ for (int i = 0; i < contentModel->count(); ++i)
+ resizeItem(itemAt(i));
+}
+
+void QQuickMenuPrivate::itemChildAdded(QQuickItem *, QQuickItem *child)
+{
+ // add dynamically reparented items (eg. by a Repeater)
+ if (!QQuickItemPrivate::get(child)->isTransparentForPositioner() && !contentData.contains(child))
+ insertItem(contentModel->count(), child);
+}
+
+void QQuickMenuPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent)
+{
+ // remove dynamically unparented items (eg. by a Repeater)
+ if (!parent)
+ removeItem(contentModel->indexOf(item, nullptr), item);
+}
+
+void QQuickMenuPrivate::itemSiblingOrderChanged(QQuickItem *)
+{
+ // reorder the restacked items (eg. by a Repeater)
+ Q_Q(QQuickMenu);
+ QList<QQuickItem *> siblings = contentItem->childItems();
+
+ int to = 0;
+ for (int i = 0; i < siblings.size(); ++i) {
+ QQuickItem* sibling = siblings.at(i);
+ if (QQuickItemPrivate::get(sibling)->isTransparentForPositioner())
+ continue;
+ int index = contentModel->indexOf(sibling, nullptr);
+ q->moveItem(index, to++);
+ }
+}
+
+void QQuickMenuPrivate::itemDestroyed(QQuickItem *item)
+{
+ QQuickPopupPrivate::itemDestroyed(item);
+ int index = contentModel->indexOf(item, nullptr);
+ if (index != -1)
+ removeItem(index, item);
+}
+
+void QQuickMenuPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange, const QRectF &)
+{
+ if (!complete)
+ return;
+
+ if (item == contentItem) {
+ // The contentItem's geometry changed, so resize any items
+ // that don't have explicit widths set so that they fill the width of the menu.
+ resizeItems();
+ } else {
+ // The geometry of an item in the menu changed. If the item
+ // doesn't have an explicit width set, make it fill the width of the menu.
+ resizeItem(item);
+ }
+}
+
+QQuickPopupPositioner *QQuickMenuPrivate::getPositioner()
+{
+ Q_Q(QQuickMenu);
+ if (!positioner)
+ positioner = new QQuickMenuPositioner(q);
+ return positioner;
+}
+
+void QQuickMenuPositioner::reposition()
+{
+ QQuickMenu *menu = static_cast<QQuickMenu *>(popup());
+ QQuickMenuPrivate *menu_d = QQuickMenuPrivate::get(menu);
+
+ if (QQuickMenu *parentMenu = menu_d->parentMenu) {
+ if (menu_d->cascade) {
+ // Align the menu to the frame of the parent menu, minus overlap. The position
+ // should be in the coordinate system of the parentItem.
+ if (menu_d->popupItem->isMirrored()) {
+ menu->setPosition({-menu->width()
+ - parentMenu->leftPadding() + parentMenu->leftInset()
+ + menu->overlap(),
+ menu->topInset() - menu->topPadding()});
+ } else if (menu_d->parentItem) {
+ menu->setPosition({menu_d->parentItem->width()
+ + parentMenu->rightPadding() - parentMenu->rightInset()
+ - menu->overlap(),
+ menu->topInset() - menu->topPadding()});
+ }
+ } else {
+ menu->setPosition(QPointF(parentMenu->x() + (parentMenu->width() - menu->width()) / 2,
+ parentMenu->y() + (parentMenu->height() - menu->height()) / 2));
+ }
+ }
+
+ QQuickPopupPositioner::reposition();
+}
+
+bool QQuickMenuPrivate::prepareEnterTransition()
+{
+ Q_Q(QQuickMenu);
+ if (parentMenu && !cascade)
+ parentMenu->close();
+
+ // If a cascading sub-menu doesn't have enough space to open on
+ // the right, it flips on the other side of the parent menu.
+ allowHorizontalFlip = cascade && parentMenu;
+
+ if (!QQuickPopupPrivate::prepareEnterTransition())
+ return false;
+
+ if (!hasClosePolicy) {
+ if (cascade && parentMenu)
+ closePolicy = cascadingSubMenuClosePolicy;
+ else
+ q->resetClosePolicy();
+ }
+ return true;
+}
+
+bool QQuickMenuPrivate::prepareExitTransition()
+{
+ if (!QQuickPopupPrivate::prepareExitTransition())
+ return false;
+
+ stopHoverTimer();
+
+ QQuickMenu *subMenu = currentSubMenu();
+ while (subMenu) {
+ QPointer<QQuickMenuItem> currentSubMenuItem = QQuickMenuPrivate::get(subMenu)->currentItem;
+ subMenu->close();
+ subMenu = currentSubMenuItem ? currentSubMenuItem->subMenu() : nullptr;
+ }
+ return true;
+}
+
+bool QQuickMenuPrivate::blockInput(QQuickItem *item, const QPointF &point) const
+{
+ // keep the parent menu open when a cascading sub-menu (this menu) is interacted with
+ return (cascade && parentMenu && contains(point)) || QQuickPopupPrivate::blockInput(item, point);
+}
+
+/*! \internal
+ QQuickPopupWindow::event() calls this to handle the release event of a
+ menu drag-press-release gesture, because the \a eventPoint does not have
+ a grabber within the popup window. This override finds and activates the
+ appropriate menu item, as if it had been pressed and released.
+ Returns true on success, to indicate that handling \a eventPoint is done.
+ */
+bool QQuickMenuPrivate::handleReleaseWithoutGrab(const QEventPoint &eventPoint)
+{
+ if (!contains(eventPoint.scenePosition()))
+ return false;
+
+ QQuickMenuItem *menuItem = nullptr;
+ // Usually, hover events have occurred, and currentIndex is set.
+ // If not, use eventPoint.position() for picking.
+ if (currentIndex < 0) {
+ auto *list = qobject_cast<QQuickListView *>(contentItem);
+ if (!list)
+ return false;
+ menuItem = qobject_cast<QQuickMenuItem *>(list->itemAt(eventPoint.position().x(), eventPoint.position().y()));
+ } else {
+ menuItem = qobject_cast<QQuickMenuItem *>(itemAt(currentIndex));
+ }
+ if (Q_LIKELY(menuItem)) {
+ menuItem->animateClick();
+ return true;
+ }
+ return false;
+}
+
+void QQuickMenuPrivate::onItemHovered()
+{
+ Q_Q(QQuickMenu);
+ QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender());
+ if (!button || !button->isHovered() || !button->isEnabled() || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
+ return;
+
+ QQuickMenuItem *oldCurrentItem = currentItem;
+
+ int index = contentModel->indexOf(button, nullptr);
+ if (index != -1) {
+ setCurrentIndex(index, Qt::OtherFocusReason);
+ if (oldCurrentItem != currentItem) {
+ if (oldCurrentItem) {
+ QQuickMenu *subMenu = oldCurrentItem->subMenu();
+ if (subMenu)
+ subMenu->close();
+ }
+ if (currentItem) {
+ QQuickMenu *subMenu = currentItem->menu();
+ if (subMenu && subMenu->cascade())
+ startHoverTimer();
+ }
+ }
+ }
+}
+
+void QQuickMenuPrivate::onItemTriggered()
+{
+ Q_Q(QQuickMenu);
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(q->sender());
+ if (!item)
+ return;
+
+ if (QQuickMenu *subMenu = item->subMenu()) {
+ auto subMenuPrivate = QQuickMenuPrivate::get(subMenu);
+ subMenu->popup(subMenuPrivate->firstEnabledMenuItem());
+ } else {
+ q->dismiss();
+ }
+}
+
+void QQuickMenuPrivate::onItemActiveFocusChanged()
+{
+ Q_Q(QQuickMenu);
+ QQuickItem *item = qobject_cast<QQuickItem*>(q->sender());
+ if (!item->hasActiveFocus())
+ return;
+
+ int indexOfItem = contentModel->indexOf(item, nullptr);
+ QQuickControl *control = qobject_cast<QQuickControl *>(item);
+ setCurrentIndex(indexOfItem, control ? control->focusReason() : Qt::OtherFocusReason);
+}
+
+void QQuickMenuPrivate::updateTextPadding()
+{
+ Q_Q(QQuickMenu);
+ if (!complete)
+ return;
+
+ qreal padding = 0;
+ for (int i = 0; i < q->count(); ++i) {
+ if (const auto menuItem = qobject_cast<QQuickMenuItem *>(itemAt(i)))
+ if (menuItem->isVisible())
+ padding = qMax(padding, menuItem->implicitTextPadding());
+ }
+
+ if (padding == textPadding)
+ return;
+
+ textPadding = padding;
+
+ for (int i = 0; i < q->count(); ++i) {
+ if (const auto menuItem = qobject_cast<QQuickMenuItem *>(itemAt(i)))
+ emit menuItem->textPaddingChanged();
+ }
+}
+
+QQuickMenu *QQuickMenuPrivate::currentSubMenu() const
+{
+ if (!currentItem)
+ return nullptr;
+
+ return currentItem->subMenu();
+}
+
+void QQuickMenuPrivate::setParentMenu(QQuickMenu *parent)
+{
+ Q_Q(QQuickMenu);
+ if (parentMenu == parent)
+ return;
+
+ if (parentMenu) {
+ QObject::disconnect(parentMenu.data(), &QQuickMenu::cascadeChanged, q, &QQuickMenu::setCascade);
+ disconnect(parentMenu.data(), &QQuickMenu::parentChanged, this, &QQuickMenuPrivate::resolveParentItem);
+ }
+ if (parent) {
+ QObject::connect(parent, &QQuickMenu::cascadeChanged, q, &QQuickMenu::setCascade);
+ connect(parent, &QQuickMenu::parentChanged, this, &QQuickMenuPrivate::resolveParentItem);
+ }
+
+ parentMenu = parent;
+ q->resetCascade();
+ resolveParentItem();
+}
+
+static QQuickItem *findParentMenuItem(QQuickMenu *subMenu)
+{
+ QQuickMenu *menu = QQuickMenuPrivate::get(subMenu)->parentMenu;
+ for (int i = 0; i < QQuickMenuPrivate::get(menu)->contentModel->count(); ++i) {
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(menu->itemAt(i));
+ if (item && item->subMenu() == subMenu)
+ return item;
+ }
+ return nullptr;
+}
+
+void QQuickMenuPrivate::resolveParentItem()
+{
+ Q_Q(QQuickMenu);
+ if (!parentMenu)
+ q->resetParentItem();
+ else if (!cascade)
+ q->setParentItem(parentMenu->parentItem());
+ else
+ q->setParentItem(findParentMenuItem(q));
+}
+
+void QQuickMenuPrivate::propagateKeyEvent(QKeyEvent *event)
+{
+ if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(parentItem)) {
+ if (QQuickMenu *menu = menuItem->menu())
+ QQuickMenuPrivate::get(menu)->propagateKeyEvent(event);
+#if QT_CONFIG(quicktemplates2_container)
+ } else if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(parentItem)) {
+ if (QQuickMenuBar *menuBar = menuBarItem->menuBar()) {
+ event->accept();
+ QCoreApplication::sendEvent(menuBar, event);
+ }
+#endif
+ }
+}
+
+void QQuickMenuPrivate::startHoverTimer()
+{
+ Q_Q(QQuickMenu);
+ stopHoverTimer();
+ hoverTimer = q->startTimer(SUBMENU_DELAY);
+}
+
+void QQuickMenuPrivate::stopHoverTimer()
+{
+ Q_Q(QQuickMenu);
+ if (!hoverTimer)
+ return;
+
+ q->killTimer(hoverTimer);
+ hoverTimer = 0;
+}
+
+void QQuickMenuPrivate::setCurrentIndex(int index, Qt::FocusReason reason)
+{
+ Q_Q(QQuickMenu);
+ if (currentIndex == index)
+ return;
+
+ QQuickMenuItem *newCurrentItem = qobject_cast<QQuickMenuItem *>(itemAt(index));
+ if (currentItem != newCurrentItem) {
+ stopHoverTimer();
+ if (currentItem) {
+ currentItem->setHighlighted(false);
+ if (!newCurrentItem && window) {
+ QQuickItem *focusItem = QQuickItemPrivate::get(contentItem)->subFocusItem;
+ if (focusItem)
+ QQuickWindowPrivate::get(window)->clearFocusInScope(contentItem, focusItem, Qt::OtherFocusReason);
+ }
+ }
+ if (newCurrentItem) {
+ newCurrentItem->setHighlighted(true);
+ newCurrentItem->forceActiveFocus(reason);
+ }
+ currentItem = newCurrentItem;
+ }
+
+ currentIndex = index;
+ emit q->currentIndexChanged();
+}
+
+bool QQuickMenuPrivate::activateNextItem()
+{
+ int index = currentIndex;
+ int count = contentModel->count();
+ while (++index < count) {
+ QQuickItem *item = itemAt(index);
+ if (!item || !item->activeFocusOnTab() || !item->isEnabled())
+ continue;
+ setCurrentIndex(index, Qt::TabFocusReason);
+ return true;
+ }
+ return false;
+}
+
+bool QQuickMenuPrivate::activatePreviousItem()
+{
+ int index = currentIndex;
+ while (--index >= 0) {
+ QQuickItem *item = itemAt(index);
+ if (!item || !item->activeFocusOnTab() || !item->isEnabled())
+ continue;
+ setCurrentIndex(index, Qt::BacktabFocusReason);
+ return true;
+ }
+ return false;
+}
+
+QQuickMenuItem *QQuickMenuPrivate::firstEnabledMenuItem() const
+{
+ for (int i = 0; i < contentModel->count(); ++i) {
+ QQuickItem *item = itemAt(i);
+ if (!item || !item->isEnabled())
+ continue;
+
+ QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item);
+ if (!menuItem)
+ continue;
+
+ return menuItem;
+ }
+ return nullptr;
+}
+
+void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj)
+{
+ QQuickMenu *q = qobject_cast<QQuickMenu *>(prop->object);
+ QQuickMenuPrivate *p = QQuickMenuPrivate::get(q);
+
+ QQuickItem *item = qobject_cast<QQuickItem *>(obj);
+ if (!item) {
+ if (QQuickAction *action = qobject_cast<QQuickAction *>(obj))
+ item = p->createItem(action);
+ else if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(obj))
+ item = p->createItem(menu);
+ }
+
+ if (item) {
+ if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) {
+ QQuickItemPrivate::get(item)->addItemChangeListener(p, QQuickItemPrivate::SiblingOrder);
+ item->setParentItem(p->contentItem);
+ } else if (p->contentModel->indexOf(item, nullptr) == -1) {
+ q->addItem(item);
+ }
+ } else {
+ p->contentData.append(obj);
+ }
+}
+
+qsizetype QQuickMenuPrivate::contentData_count(QQmlListProperty<QObject> *prop)
+{
+ QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
+ return QQuickMenuPrivate::get(q)->contentData.size();
+}
+
+QObject *QQuickMenuPrivate::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
+{
+ QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
+ return QQuickMenuPrivate::get(q)->contentData.value(index);
+}
+
+QPalette QQuickMenuPrivate::defaultPalette() const
+{
+ return QQuickTheme::palette(QQuickTheme::Menu);
+}
+
+void QQuickMenuPrivate::contentData_clear(QQmlListProperty<QObject> *prop)
+{
+ QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
+ QQuickMenuPrivate::get(q)->contentData.clear();
+}
+
+QQuickMenu::QQuickMenu(QObject *parent)
+ : QQuickPopup(*(new QQuickMenuPrivate), parent)
+{
+ Q_D(QQuickMenu);
+ setFocus(true);
+ d->init();
+ connect(d->contentModel, &QQmlObjectModel::countChanged, this, &QQuickMenu::countChanged);
+}
+
+QQuickMenu::~QQuickMenu()
+{
+ Q_D(QQuickMenu);
+ qCDebug(lcNativeMenus) << "destroying" << this
+ << "item count:"
+ << d->contentModel->count()
+ << "native item count:" << d->nativeItems.count();
+ // We have to remove items to ensure that our change listeners on the item
+ // are removed. It's too late to do this in ~QQuickMenuPrivate, as
+ // contentModel has already been destroyed before that is called.
+ // Destruction isn't necessary for the QQuickItems themselves, but it is
+ // required for the native menus (see comment in removeItem()).
+ while (d->contentModel->count() > 0)
+ d->removeItem(0, d->itemAt(0), QQuickMenuPrivate::DestructionPolicy::Destroy);
+
+ if (d->contentItem) {
+ QQuickItemPrivate::get(d->contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Children);
+ QQuickItemPrivate::get(d->contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Geometry);
+
+ const auto children = d->contentItem->childItems();
+ for (QQuickItem *child : std::as_const(children))
+ QQuickItemPrivate::get(child)->removeItemChangeListener(d, QQuickItemPrivate::SiblingOrder);
+ }
+}
+
+/*!
+ \qmlmethod Item QtQuick.Controls::Menu::itemAt(int index)
+
+ Returns the item at \a index, or \c null if it does not exist.
+*/
+QQuickItem *QQuickMenu::itemAt(int index) const
+{
+ Q_D(const QQuickMenu);
+ return d->itemAt(index);
+}
+
+/*!
+ \qmlmethod void QtQuick.Controls::Menu::addItem(Item item)
+
+ Adds \a item to the end of the list of items.
+*/
+void QQuickMenu::addItem(QQuickItem *item)
+{
+ Q_D(QQuickMenu);
+ insertItem(d->contentModel->count(), item);
+}
+
+/*!
+ \qmlmethod void QtQuick.Controls::Menu::insertItem(int index, Item item)
+
+ Inserts \a item at \a index.
+*/
+void QQuickMenu::insertItem(int index, QQuickItem *item)
+{
+ Q_D(QQuickMenu);
+ if (!item)
+ return;
+ const int count = d->contentModel->count();
+ if (index < 0 || index > count)
+ index = count;
+
+ int oldIndex = d->contentModel->indexOf(item, nullptr);
+ if (oldIndex != -1) {
+ if (oldIndex < index)
+ --index;
+ if (oldIndex != index) {
+ d->moveItem(oldIndex, index);
+ }
+ } else {
+ d->insertItem(index, item);
+ }
+}
+
+/*!
+ \qmlmethod void QtQuick.Controls::Menu::moveItem(int from, int to)
+
+ Moves an item \a from one index \a to another.
+*/
+void QQuickMenu::moveItem(int from, int to)
+{
+ Q_D(QQuickMenu);
+ const int count = d->contentModel->count();
+ if (from < 0 || from > count - 1)
+ return;
+ if (to < 0 || to > count - 1)
+ to = count - 1;
+
+ if (from != to)
+ d->moveItem(from, to);
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::removeItem(Item item)
+
+ Removes and destroys the specified \a item.
+*/
+void QQuickMenu::removeItem(QQuickItem *item)
+{
+ Q_D(QQuickMenu);
+ if (!item)
+ return;
+
+ const int index = d->contentModel->indexOf(item, nullptr);
+ if (index == -1)
+ return;
+
+ d->removeItem(index, item, QQuickMenuPrivate::DestructionPolicy::Destroy);
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod MenuItem QtQuick.Controls::Menu::takeItem(int index)
+
+ Removes and returns the item at \a index.
+
+ \note The ownership of the item is transferred to the caller.
+*/
+QQuickItem *QQuickMenu::takeItem(int index)
+{
+ Q_D(QQuickMenu);
+ const int count = d->contentModel->count();
+ if (index < 0 || index >= count)
+ return nullptr;
+
+ QQuickItem *item = itemAt(index);
+ if (item)
+ d->removeItem(index, item);
+ return item;
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod Menu QtQuick.Controls::Menu::menuAt(int index)
+
+ Returns the sub-menu at \a index, or \c null if the index is not valid or
+ there is no sub-menu at the specified index.
+*/
+QQuickMenu *QQuickMenu::menuAt(int index) const
+{
+ Q_D(const QQuickMenu);
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index));
+ if (!item)
+ return nullptr;
+
+ return item->subMenu();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::addMenu(Menu menu)
+
+ Adds \a menu as a sub-menu to the end of this menu.
+*/
+void QQuickMenu::addMenu(QQuickMenu *menu)
+{
+ Q_D(QQuickMenu);
+ insertMenu(d->contentModel->count(), menu);
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::insertMenu(int index, Menu menu)
+
+ Inserts \a menu as a sub-menu at \a index. The index is within all items in the menu.
+*/
+void QQuickMenu::insertMenu(int index, QQuickMenu *menu)
+{
+ Q_D(QQuickMenu);
+ if (!menu)
+ return;
+
+ insertItem(index, d->createItem(menu));
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::removeMenu(Menu menu)
+
+ Removes and destroys the specified \a menu.
+*/
+void QQuickMenu::removeMenu(QQuickMenu *menu)
+{
+ Q_D(QQuickMenu);
+ if (!menu)
+ return;
+
+ const int count = d->contentModel->count();
+ for (int i = 0; i < count; ++i) {
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(i));
+ if (!item || item->subMenu() != menu)
+ continue;
+
+ removeItem(item);
+ break;
+ }
+
+ menu->deleteLater();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod Menu QtQuick.Controls::Menu::takeMenu(int index)
+
+ Removes and returns the menu at \a index. The index is within all items in the menu.
+
+ \note The ownership of the menu is transferred to the caller.
+*/
+QQuickMenu *QQuickMenu::takeMenu(int index)
+{
+ Q_D(QQuickMenu);
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index));
+ if (!item)
+ return nullptr;
+
+ QQuickMenu *subMenu = item->subMenu();
+ if (!subMenu)
+ return nullptr;
+
+ d->removeItem(index, item);
+ item->deleteLater();
+
+ return subMenu;
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod Action QtQuick.Controls::Menu::actionAt(int index)
+
+ Returns the action at \a index, or \c null if the index is not valid or
+ there is no action at the specified index.
+*/
+QQuickAction *QQuickMenu::actionAt(int index) const
+{
+ Q_D(const QQuickMenu);
+ if (!const_cast<QQuickMenuPrivate *>(d)->maybeNativeHandle()) {
+ QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton *>(d->itemAt(index));
+ if (!item)
+ return nullptr;
+
+ return item->action();
+ } else {
+ if (index < 0 || index >= d->nativeItems.size())
+ return nullptr;
+
+ return d->nativeItems.at(index)->action();
+ }
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::addAction(Action action)
+
+ Adds \a action to the end of this menu.
+*/
+void QQuickMenu::addAction(QQuickAction *action)
+{
+ Q_D(QQuickMenu);
+ insertAction(d->contentModel->count(), action);
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::insertAction(int index, Action action)
+
+ Inserts \a action at \a index. The index is within all items in the menu.
+*/
+void QQuickMenu::insertAction(int index, QQuickAction *action)
+{
+ Q_D(QQuickMenu);
+ if (!action)
+ return;
+
+ insertItem(index, d->createItem(action));
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::removeAction(Action action)
+
+ Removes and destroys the specified \a action.
+*/
+void QQuickMenu::removeAction(QQuickAction *action)
+{
+ Q_D(QQuickMenu);
+ if (!action)
+ return;
+
+ const int count = d->contentModel->count();
+ for (int i = 0; i < count; ++i) {
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(i));
+ if (!item || item->action() != action)
+ continue;
+
+ removeItem(item);
+ break;
+ }
+
+ action->deleteLater();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod Action QtQuick.Controls::Menu::takeAction(int index)
+
+ Removes and returns the action at \a index. The index is within all items in the menu.
+
+ \note The ownership of the action is transferred to the caller.
+*/
+QQuickAction *QQuickMenu::takeAction(int index)
+{
+ Q_D(QQuickMenu);
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index));
+ if (!item)
+ return nullptr;
+
+ QQuickAction *action = item->action();
+ if (!action)
+ return nullptr;
+
+ d->removeItem(index, item);
+ item->deleteLater();
+ return action;
+}
+
+bool QQuickMenu::isVisible() const
+{
+ Q_D(const QQuickMenu);
+ if (d->maybeNativeHandle())
+ return d->visible;
+ return QQuickPopup::isVisible();
+}
+
+void QQuickMenu::setVisible(bool visible)
+{
+ Q_D(QQuickMenu);
+ if (visible == d->visible)
+ return;
+ if (visible && !parentItem()) {
+ qmlWarning(this) << "cannot show menu: parent is null";
+ return;
+ }
+
+ if (visible && ((d->useNativeMenu() && !d->maybeNativeHandle())
+ || (!d->useNativeMenu() && d->maybeNativeHandle()))) {
+ // We've been made visible, and our actual native state doesn't match our requested state,
+ // which means AA_DontUseNativeMenuWindows was set while we were visible or had a parent.
+ // Try to sync our state again now that we're about to be re-opened.
+ qCDebug(lcNativeMenus) << "setVisible called - useNativeMenu:" << d->useNativeMenu()
+ << "maybeNativeHandle:" << d->maybeNativeHandle();
+ d->syncWithUseNativeMenu();
+ }
+ if (d->maybeNativeHandle()) {
+ d->setNativeMenuVisible(visible);
+ return;
+ }
+
+ // Either the native menu wasn't wanted, or it couldn't be created;
+ // show the non-native menu.
+ QQuickPopup::setVisible(visible);
+}
+
+/*!
+ \qmlproperty model QtQuick.Controls::Menu::contentModel
+ \readonly
+
+ This property holds the model used to display menu items.
+
+ The content model is provided for visualization purposes. It can be assigned
+ as a model to a content item that presents the contents of the menu.
+
+ \code
+ Menu {
+ id: menu
+ contentItem: ListView {
+ model: menu.contentModel
+ }
+ }
+ \endcode
+
+ The model allows menu items to be statically declared as children of the
+ menu.
+*/
+QVariant QQuickMenu::contentModel() const
+{
+ Q_D(const QQuickMenu);
+ return QVariant::fromValue(d->contentModel);
+}
+
+/*!
+ \qmlproperty list<QtObject> QtQuick.Controls::Menu::contentData
+ \qmldefault
+
+ This property holds the list of content data.
+
+ The list contains all objects that have been declared in QML as children
+ of the menu, and also items that have been dynamically added or
+ inserted using the \l addItem() and \l insertItem() methods, respectively.
+
+ \note Unlike \c contentChildren, \c contentData does include non-visual QML
+ objects. It is not re-ordered when items are inserted or moved.
+
+ \sa Item::data, {Popup::}{contentChildren}
+*/
+QQmlListProperty<QObject> QQuickMenu::contentData()
+{
+ Q_D(QQuickMenu);
+ if (!d->contentItem)
+ QQuickControlPrivate::get(d->popupItem)->executeContentItem();
+ return QQmlListProperty<QObject>(this, nullptr,
+ QQuickMenuPrivate::contentData_append,
+ QQuickMenuPrivate::contentData_count,
+ QQuickMenuPrivate::contentData_at,
+ QQuickMenuPrivate::contentData_clear);
+}
+
+/*!
+ \qmlproperty string QtQuick.Controls::Menu::title
+
+ This property holds the title for the menu.
+
+ The title of a menu is often displayed in the text of a menu item when the
+ menu is a submenu, and in the text of a tool button when it is in a
+ menubar.
+*/
+QString QQuickMenu::title() const
+{
+ Q_D(const QQuickMenu);
+ return d->title;
+}
+
+void QQuickMenu::setTitle(const QString &title)
+{
+ Q_D(QQuickMenu);
+ if (title == d->title)
+ return;
+ d->title = title;
+ if (d->handle)
+ d->handle->setText(title);
+ emit titleChanged(title);
+}
+
+/*!
+ \qmlproperty string QtQuick.Controls::Menu::icon.name
+ \qmlproperty url QtQuick.Controls::Menu::icon.source
+ \qmlproperty int QtQuick.Controls::Menu::icon.width
+ \qmlproperty int QtQuick.Controls::Menu::icon.height
+ \qmlproperty color QtQuick.Controls::Menu::icon.color
+ \qmlproperty bool QtQuick.Controls::Menu::icon.cache
+
+ This property group was added in QtQuick.Controls 6.5.
+
+ \include qquickicon.qdocinc grouped-properties
+
+ \include qquickmenu.qdocinc non-native-only-property
+
+ \sa AbstractButton::text, AbstractButton::display, {Icons in Qt Quick Controls}
+*/
+
+QQuickIcon QQuickMenu::icon() const
+{
+ Q_D(const QQuickMenu);
+ return d->icon;
+}
+
+void QQuickMenu::setIcon(const QQuickIcon &icon)
+{
+ Q_D(QQuickMenu);
+ if (icon == d->icon)
+ return;
+ d->icon = icon;
+ d->icon.ensureRelativeSourceResolved(this);
+ emit iconChanged(icon);
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty bool QtQuick.Controls::Menu::cascade
+
+ This property holds whether the menu cascades its sub-menus.
+
+ The default value is platform-specific. Menus are cascading by default on
+ desktop platforms that have a mouse cursor available. Non-cascading menus
+ are shown one menu at a time, and centered over the parent menu.
+
+ \note Changing the value of the property has no effect while the menu is open.
+
+ \include qquickmenu.qdocinc non-native-only-property
+
+ \sa overlap
+*/
+bool QQuickMenu::cascade() const
+{
+ Q_D(const QQuickMenu);
+ return d->cascade;
+}
+
+void QQuickMenu::setCascade(bool cascade)
+{
+ Q_D(QQuickMenu);
+ if (d->cascade == cascade)
+ return;
+ d->cascade = cascade;
+ if (d->parentMenu)
+ d->resolveParentItem();
+ emit cascadeChanged(cascade);
+}
+
+void QQuickMenu::resetCascade()
+{
+ Q_D(QQuickMenu);
+ if (d->parentMenu)
+ setCascade(d->parentMenu->cascade());
+ else
+ setCascade(shouldCascade());
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty real QtQuick.Controls::Menu::overlap
+
+ This property holds the amount of pixels by which the menu horizontally overlaps its parent menu.
+
+ The property only has effect when the menu is used as a cascading sub-menu.
+
+ The default value is style-specific.
+
+ \note Changing the value of the property has no effect while the menu is open.
+
+ \include qquickmenu.qdocinc non-native-only-property
+
+ \sa cascade
+*/
+qreal QQuickMenu::overlap() const
+{
+ Q_D(const QQuickMenu);
+ return d->overlap;
+}
+
+void QQuickMenu::setOverlap(qreal overlap)
+{
+ Q_D(QQuickMenu);
+ if (d->overlap == overlap)
+ return;
+ d->overlap = overlap;
+ emit overlapChanged();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty Component QtQuick.Controls::Menu::delegate
+
+ This property holds the component that is used to create items
+ to present actions.
+
+ \code
+ Menu {
+ Action { text: "Cut" }
+ Action { text: "Copy" }
+ Action { text: "Paste" }
+ }
+ \endcode
+
+ \note delegates will only be visible when using a \l {Native Menus}
+ {non-native Menu}.
+
+ \sa Action
+*/
+QQmlComponent *QQuickMenu::delegate() const
+{
+ Q_D(const QQuickMenu);
+ return d->delegate;
+}
+
+void QQuickMenu::setDelegate(QQmlComponent *delegate)
+{
+ Q_D(QQuickMenu);
+ if (d->delegate == delegate)
+ return;
+
+ d->delegate = delegate;
+ emit delegateChanged();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty int QtQuick.Controls::Menu::currentIndex
+
+ This property holds the index of the currently highlighted item.
+
+ Menu items can be highlighted by mouse hover or keyboard navigation.
+
+ \include qquickmenu.qdocinc non-native-only-property
+
+ \sa MenuItem::highlighted
+*/
+int QQuickMenu::currentIndex() const
+{
+ Q_D(const QQuickMenu);
+ return d->currentIndex;
+}
+
+void QQuickMenu::setCurrentIndex(int index)
+{
+ Q_D(QQuickMenu);
+ d->setCurrentIndex(index, Qt::OtherFocusReason);
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty int QtQuick.Controls::Menu::count
+ \readonly
+
+ This property holds the number of items.
+*/
+int QQuickMenu::count() const
+{
+ Q_D(const QQuickMenu);
+ return d->contentModel->count();
+}
+
+void QQuickMenu::popup(QQuickItem *menuItem)
+{
+ Q_D(QQuickMenu);
+ // No position has been explicitly specified, so position the menu at the mouse cursor
+ // on desktop platforms that have a mouse cursor available and support multiple windows.
+ QQmlNullableValue<QPointF> pos;
+#if QT_CONFIG(cursor)
+ if (d->parentItem && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows))
+ pos = d->parentItem->mapFromGlobal(QCursor::pos());
+#endif
+
+ // As a fallback, center the menu over its parent item.
+ if (!pos.isValid() && d->parentItem)
+ pos = QPointF((d->parentItem->width() - width()) / 2, (d->parentItem->height() - height()) / 2);
+
+ popup(pos.isValid() ? pos.value() : QPointF(), menuItem);
+}
+
+void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem)
+{
+ Q_D(QQuickMenu);
+ qreal offset = 0;
+#if QT_CONFIG(cursor)
+ if (menuItem)
+ offset = d->popupItem->mapFromItem(menuItem, QPointF(0, 0)).y();
+#endif
+ setPosition(pos - QPointF(0, offset));
+
+ if (menuItem)
+ d->setCurrentIndex(d->contentModel->indexOf(menuItem, nullptr), Qt::PopupFocusReason);
+ else
+ d->setCurrentIndex(-1, Qt::PopupFocusReason);
+
+ open();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::popup(MenuItem item = null)
+ \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, MenuItem item = null)
+
+ Opens the menu at the mouse cursor on desktop platforms that have a mouse cursor
+ available, and otherwise centers the menu over its \a parent item.
+
+ The menu can be optionally aligned to a specific menu \a item. This item will
+ then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex
+ will be set to \c -1.
+
+ \sa Popup::open()
+*/
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::popup(point pos, MenuItem item = null)
+ \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, point pos, MenuItem item = null)
+
+ Opens the menu at the specified position \a pos in the popups coordinate system,
+ that is, a coordinate relative to its \a parent item.
+
+ The menu can be optionally aligned to a specific menu \a item. This item will
+ then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex
+ will be set to \c -1.
+
+ \sa Popup::open()
+*/
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::popup(real x, real y, MenuItem item = null)
+ \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, real x, real y, MenuItem item = null)
+
+ Opens the menu at the specified position \a x, \a y in the popups coordinate system,
+ that is, a coordinate relative to its \a parent item.
+
+ The menu can be optionally aligned to a specific menu \a item. This item will
+ then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex
+ will be set to \c -1.
+
+ \sa dismiss(), Popup::open()
+*/
+void QQuickMenu::popup(QQmlV4FunctionPtr args)
+{
+ Q_D(QQuickMenu);
+ const int len = args->length();
+ if (len > 4) {
+ args->v4engine()->throwTypeError();
+ return;
+ }
+
+ QV4::ExecutionEngine *v4 = args->v4engine();
+ QV4::Scope scope(v4);
+
+ QQmlNullableValue<QPointF> pos;
+ QQuickItem *menuItem = nullptr;
+ QQuickItem *parentItem = nullptr;
+
+ if (len > 0) {
+ // Item parent
+ QV4::ScopedValue firstArg(scope, (*args)[0]);
+ if (const QV4::QObjectWrapper *obj = firstArg->as<QV4::QObjectWrapper>()) {
+ QQuickItem *item = qobject_cast<QQuickItem *>(obj->object());
+ if (item && !d->popupItem->isAncestorOf(item))
+ parentItem = item;
+ } else if (firstArg->isUndefined()) {
+ resetParentItem();
+ parentItem = d->parentItem;
+ }
+
+ // MenuItem item
+ QV4::ScopedValue lastArg(scope, (*args)[len - 1]);
+ if (const QV4::QObjectWrapper *obj = lastArg->as<QV4::QObjectWrapper>()) {
+ QQuickItem *item = qobject_cast<QQuickItem *>(obj->object());
+ if (item && d->popupItem->isAncestorOf(item))
+ menuItem = item;
+ }
+ }
+
+ if (len >= 3 || (!parentItem && len >= 2)) {
+ // real x, real y
+ QV4::ScopedValue xArg(scope, (*args)[parentItem ? 1 : 0]);
+ QV4::ScopedValue yArg(scope, (*args)[parentItem ? 2 : 1]);
+ if (xArg->isNumber() && yArg->isNumber())
+ pos = QPointF(xArg->asDouble(), yArg->asDouble());
+ }
+
+ if (!pos.isValid() && (len >= 2 || (!parentItem && len >= 1))) {
+ // point pos
+ QV4::ScopedValue posArg(scope, (*args)[parentItem ? 1 : 0]);
+ const QVariant var = QV4::ExecutionEngine::toVariant(posArg, QMetaType {});
+ if (var.userType() == QMetaType::QPointF)
+ pos = var.toPointF();
+ }
+
+ if (parentItem)
+ setParentItem(parentItem);
+
+ if (pos.isValid())
+ popup(pos, menuItem);
+ else
+ popup(menuItem);
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlmethod void QtQuick.Controls::Menu::dismiss()
+
+ Closes all menus in the hierarchy that this menu belongs to.
+
+ \note Unlike \l {Popup::}{close()} that only closes a menu and its
+ sub-menus (when using \l {Native Menus}{non-native menus}), \c dismiss()
+ closes the whole hierarchy of menus, including the parent menus. In
+ practice, \c close() is suitable e.g. for implementing navigation in a
+ hierarchy of menus, and \c dismiss() is the appropriate method for closing
+ the whole hierarchy of menus.
+
+ \sa popup(), Popup::close()
+*/
+void QQuickMenu::dismiss()
+{
+ QQuickMenu *menu = this;
+ while (menu) {
+ menu->close();
+ menu = QQuickMenuPrivate::get(menu)->parentMenu;
+ }
+}
+
+void QQuickMenu::componentComplete()
+{
+ Q_D(QQuickMenu);
+ QQuickPopup::componentComplete();
+ d->resizeItems();
+ d->updateTextPadding();
+ d->syncWithUseNativeMenu();
+}
+
+void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
+{
+ Q_D(QQuickMenu);
+ QQuickPopup::contentItemChange(newItem, oldItem);
+
+ if (oldItem) {
+ QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Children);
+ QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Geometry);
+ }
+ if (newItem) {
+ QQuickItemPrivate::get(newItem)->addItemChangeListener(d, QQuickItemPrivate::Children);
+ QQuickItemPrivate::get(newItem)->updateOrAddGeometryChangeListener(d, QQuickGeometryChange::Width);
+ }
+
+ d->contentItem = newItem;
+}
+
+void QQuickMenu::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
+{
+ Q_D(QQuickMenu);
+ QQuickPopup::itemChange(change, data);
+
+ switch (change) {
+ case QQuickItem::ItemVisibleHasChanged:
+ if (!data.boolValue && d->cascade) {
+ // Ensure that when the menu isn't visible, there's no current item
+ // the next time it's opened.
+ d->setCurrentIndex(-1, Qt::OtherFocusReason);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void QQuickMenu::keyPressEvent(QKeyEvent *event)
+{
+ Q_D(QQuickMenu);
+ QQuickPopup::keyPressEvent(event);
+
+ // QTBUG-17051
+ // Work around the fact that ListView has no way of distinguishing between
+ // mouse and keyboard interaction, thanks to the "interactive" bool in Flickable.
+ // What we actually want is to have a way to always allow keyboard interaction but
+ // only allow flicking with the mouse when there are too many menu items to be
+ // shown at once.
+ switch (event->key()) {
+ case Qt::Key_Up:
+ if (!d->activatePreviousItem())
+ d->propagateKeyEvent(event);
+ break;
+
+ case Qt::Key_Down:
+ d->activateNextItem();
+ break;
+
+ case Qt::Key_Left:
+ case Qt::Key_Right:
+ event->ignore();
+ if (d->popupItem->isMirrored() == (event->key() == Qt::Key_Right)) {
+ if (d->parentMenu && d->currentItem) {
+ if (!d->cascade)
+ d->parentMenu->open();
+ close();
+ event->accept();
+ }
+ } else {
+ if (QQuickMenu *subMenu = d->currentSubMenu()) {
+ auto subMenuPrivate = QQuickMenuPrivate::get(subMenu);
+ subMenu->popup(subMenuPrivate->firstEnabledMenuItem());
+ event->accept();
+ }
+ }
+ if (!event->isAccepted())
+ d->propagateKeyEvent(event);
+ break;
+
+#if QT_CONFIG(shortcut)
+ case Qt::Key_Alt:
+ // If &mnemonic shortcut is enabled, go back to (possibly) the parent
+ // menu bar so the shortcut key will be processed by the menu bar.
+ if (!QKeySequence::mnemonic(QStringLiteral("&A")).isEmpty())
+ close();
+ break;
+#endif
+
+ default:
+ break;
+ }
+}
+
+void QQuickMenu::timerEvent(QTimerEvent *event)
+{
+ Q_D(QQuickMenu);
+ if (event->timerId() == d->hoverTimer) {
+ if (QQuickMenu *subMenu = d->currentSubMenu())
+ subMenu->open();
+ d->stopHoverTimer();
+ return;
+ }
+ QQuickPopup::timerEvent(event);
+}
+
+QFont QQuickMenu::defaultFont() const
+{
+ return QQuickTheme::font(QQuickTheme::Menu);
+}
+
+#if QT_CONFIG(accessibility)
+QAccessible::Role QQuickMenu::accessibleRole() const
+{
+ return QAccessible::PopupMenu;
+}
+#endif
+
+QT_END_NAMESPACE
+
+#include "moc_qquickmenu_p.cpp"