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