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