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