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