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