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