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