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

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