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
4#include "qquickmenu_p.h"
5#include "qquickmenu_p_p.h"
6#include "qquickmenuitem_p_p.h"
7#include "qquickmenubaritem_p.h"
8#include "qquickmenubar_p.h"
9#include "qquickpopupitem_p_p.h"
10#include "qquickpopuppositioner_p_p.h"
11#include "qquickaction_p.h"
12
13#include <QtGui/qevent.h>
14#include <QtGui/qcursor.h>
15#if QT_CONFIG(shortcut)
16#include <QtGui/qkeysequence.h>
17#endif
18#include <QtGui/qpa/qplatformintegration.h>
19#include <QtGui/private/qguiapplication_p.h>
20#include <QtQml/qqmlcontext.h>
21#include <QtQml/qqmlcomponent.h>
22#include <QtQml/private/qqmlengine_p.h>
23#include <QtQml/private/qv4scopedvalue_p.h>
24#include <QtQml/private/qv4variantobject_p.h>
25#include <QtQml/private/qv4qobjectwrapper_p.h>
26#include <private/qqmlobjectmodel_p.h>
27#include <QtQuick/private/qquickitem_p.h>
28#include <QtQuick/private/qquickitemchangelistener_p.h>
29#include <QtQuick/private/qquickevents_p_p.h>
30#include <QtQuick/private/qquickwindow_p.h>
31
32QT_BEGIN_NAMESPACE
33
34// copied from qfusionstyle.cpp
35static const int SUBMENU_DELAY = 225;
36
37/*!
38 \qmltype Menu
39 \inherits Popup
40//! \instantiates QQuickMenu
41 \inqmlmodule QtQuick.Controls
42 \since 5.7
43 \ingroup qtquickcontrols-menus
44 \ingroup qtquickcontrols-popups
45 \brief Menu popup that can be used as a context menu or popup menu.
46
47 \image qtquickcontrols-menu.png
48
49 Menu has two main use cases:
50 \list
51 \li Context menus; for example, a menu that is shown after right clicking
52 \li Popup menus; for example, a menu that is shown after clicking a button
53 \endlist
54
55 When used as a context menu, the recommended way of opening the menu is to call
56 \l popup(). Unless a position is explicitly specified, the menu is positioned at
57 the mouse cursor on desktop platforms that have a mouse cursor available, and
58 otherwise centered over its parent item.
59
60 \code
61 MouseArea {
62 anchors.fill: parent
63 acceptedButtons: Qt.LeftButton | Qt.RightButton
64 onClicked: {
65 if (mouse.button === Qt.RightButton)
66 contextMenu.popup()
67 }
68 onPressAndHold: {
69 if (mouse.source === Qt.MouseEventNotSynthesized)
70 contextMenu.popup()
71 }
72
73 Menu {
74 id: contextMenu
75 MenuItem { text: "Cut" }
76 MenuItem { text: "Copy" }
77 MenuItem { text: "Paste" }
78 }
79 }
80 \endcode
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 \code
87 Button {
88 id: fileButton
89 text: "File"
90 onClicked: menu.open()
91
92 Menu {
93 id: menu
94 y: fileButton.height
95
96 MenuItem {
97 text: "New..."
98 }
99 MenuItem {
100 text: "Open..."
101 }
102 MenuItem {
103 text: "Save"
104 }
105 }
106 }
107 \endcode
108
109 If the button should also close the menu when clicked, use the
110 \c Popup.CloseOnPressOutsideParent flag:
111 \code
112 onClicked: menu.visible = !menu.visible
113
114 Menu {
115 // ...
116 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
117 \endcode
118
119 Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to create sub-menus
120 and declare Action objects inside Menu:
121
122 \code
123 Menu {
124 Action { text: "Cut" }
125 Action { text: "Copy" }
126 Action { text: "Paste" }
127
128 MenuSeparator { }
129
130 Menu {
131 title: "Find/Replace"
132 Action { text: "Find Next" }
133 Action { text: "Find Previous" }
134 Action { text: "Replace" }
135 }
136 }
137 \endcode
138
139 Sub-menus are \l {cascade}{cascading} by default on desktop platforms
140 that have a mouse cursor available. Non-cascading menus are shown one
141 menu at a time, and centered over the parent menu.
142
143 Typically, menu items are statically declared as children of the menu, but
144 Menu also provides API to \l {addItem}{add}, \l {insertItem}{insert},
145 \l {moveItem}{move} and \l {removeItem}{remove} items dynamically. The
146 items in a menu can be accessed using \l itemAt() or
147 \l {Popup::}{contentChildren}.
148
149 Although \l {MenuItem}{MenuItems} are most commonly used with Menu, it can
150 contain any type of item.
151
152 \section1 Margins
153
154 As it is inherited from Popup, Menu supports \l {Popup::}{margins}. By
155 default, all of the built-in styles specify \c 0 for Menu's margins to
156 ensure that the menu is kept within the bounds of the window. To allow a
157 menu to go outside of the window (to animate it moving into view, for
158 example), set the margins property to \c -1.
159
160 \section1 Dynamically Generating Menu Items
161
162 You can dynamically create menu items with \l Instantiator or
163 \l {Dynamic QML Object Creation from JavaScript} {dynamic object creation}.
164
165 \section2 Using Instantiator
166
167 You can dynamically generate menu items with \l Instantiator. The
168 following code shows how you can implement a "Recent Files" submenu,
169 where the items come from a list of files stored in settings:
170
171 \snippet qtquickcontrols-menu-instantiator.qml menu
172
173 \section2 Using Dynamic Object Creation
174
175 You can also dynamically load a component from a QML file using
176 \l {QtQml::Qt::createComponent()} {Qt.createComponent()}. Once the component
177 is ready, you can call its \l {Component::createObject()} {createObject()}
178 method to create an instance of that component.
179
180 \snippet qtquickcontrols-menu-createObject.qml createObject
181
182 \sa {Customizing Menu}, MenuItem, {Menu Controls}, {Popup Controls},
183 {Dynamic QML Object Creation from JavaScript}
184*/
185
186/*!
187 \qmlproperty bool QtQuick.Controls::Menu::focus
188
189 This property holds whether the popup wants focus.
190
191 When the popup actually receives focus, \l{Popup::}{activeFocus}
192 will be \c true. For more information, see
193 \l {Keyboard Focus in Qt Quick}.
194
195 The default value is \c true.
196
197 \sa {Popup::}{activeFocus}
198*/
199
200static const QQuickPopup::ClosePolicy cascadingSubMenuClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent;
201
202static bool shouldCascade()
203{
204#if QT_CONFIG(cursor)
205 return QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::MultipleWindows);
206#else
207 return false;
208#endif
209}
210
211class QQuickMenuPositioner : public QQuickPopupPositioner
212{
213public:
214 QQuickMenuPositioner(QQuickMenu *menu) : QQuickPopupPositioner(menu) { }
215
216 void reposition() override;
217};
218
219QQuickMenuPrivate::QQuickMenuPrivate()
220{
221 cascade = shouldCascade();
222}
223
224void QQuickMenuPrivate::init()
225{
226 Q_Q(QQuickMenu);
227 contentModel = new QQmlObjectModel(q);
228}
229
230QQuickItem *QQuickMenuPrivate::itemAt(int index) const
231{
232 return qobject_cast<QQuickItem *>(o: contentModel->get(index));
233}
234
235void QQuickMenuPrivate::insertItem(int index, QQuickItem *item)
236{
237 contentData.append(t: item);
238 item->setParentItem(contentItem);
239 QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-53262
240 if (complete)
241 resizeItem(item);
242 QQuickItemPrivate::get(item)->addItemChangeListener(listener: this, types: QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent);
243 QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(listener: this, types: QQuickGeometryChange::Width);
244 contentModel->insert(index, object: item);
245
246 QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item);
247 if (menuItem) {
248 Q_Q(QQuickMenu);
249 QQuickMenuItemPrivate::get(item: menuItem)->setMenu(q);
250 if (QQuickMenu *subMenu = menuItem->subMenu())
251 QQuickMenuPrivate::get(menu: subMenu)->setParentMenu(q);
252 QObjectPrivate::connect(sender: menuItem, signal: &QQuickMenuItem::triggered, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemTriggered);
253 QObjectPrivate::connect(sender: menuItem, signal: &QQuickItem::activeFocusChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemActiveFocusChanged);
254 QObjectPrivate::connect(sender: menuItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemHovered);
255 }
256}
257
258void QQuickMenuPrivate::moveItem(int from, int to)
259{
260 contentModel->move(from, to);
261}
262
263void QQuickMenuPrivate::removeItem(int index, QQuickItem *item)
264{
265 contentData.removeOne(t: item);
266
267 QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent);
268 QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
269 item->setParentItem(nullptr);
270 contentModel->remove(index);
271
272 QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item);
273 if (menuItem) {
274 QQuickMenuItemPrivate::get(item: menuItem)->setMenu(nullptr);
275 if (QQuickMenu *subMenu = menuItem->subMenu())
276 QQuickMenuPrivate::get(menu: subMenu)->setParentMenu(nullptr);
277 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickMenuItem::triggered, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemTriggered);
278 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickItem::activeFocusChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemActiveFocusChanged);
279 QObjectPrivate::disconnect(sender: menuItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::onItemHovered);
280 }
281}
282
283QQuickItem *QQuickMenuPrivate::beginCreateItem()
284{
285 Q_Q(QQuickMenu);
286 if (!delegate)
287 return nullptr;
288
289 QQmlContext *context = delegate->creationContext();
290 if (!context)
291 context = qmlContext(q);
292
293 QObject *object = delegate->beginCreate(context);
294 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
295 if (!item)
296 delete object;
297 else
298 QQml_setParent_noEvent(object: item, parent: q);
299
300 return item;
301}
302
303void QQuickMenuPrivate::completeCreateItem()
304{
305 if (!delegate)
306 return;
307
308 delegate->completeCreate();
309}
310
311QQuickItem *QQuickMenuPrivate::createItem(QQuickMenu *menu)
312{
313 QQuickItem *item = beginCreateItem();
314 if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item))
315 QQuickMenuItemPrivate::get(item: menuItem)->setSubMenu(menu);
316 completeCreateItem();
317 return item;
318}
319
320QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action)
321{
322 QQuickItem *item = beginCreateItem();
323 if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object: item))
324 button->setAction(action);
325 completeCreateItem();
326 return item;
327}
328
329void QQuickMenuPrivate::resizeItem(QQuickItem *item)
330{
331 if (!item || !contentItem)
332 return;
333
334 QQuickItemPrivate *p = QQuickItemPrivate::get(item);
335 if (!p->widthValid()) {
336 item->setWidth(contentItem->width());
337 p->widthValidFlag = false;
338 }
339}
340
341void QQuickMenuPrivate::resizeItems()
342{
343 if (!contentModel)
344 return;
345
346 for (int i = 0; i < contentModel->count(); ++i)
347 resizeItem(item: itemAt(index: i));
348}
349
350void QQuickMenuPrivate::itemChildAdded(QQuickItem *, QQuickItem *child)
351{
352 // add dynamically reparented items (eg. by a Repeater)
353 if (!QQuickItemPrivate::get(item: child)->isTransparentForPositioner() && !contentData.contains(t: child))
354 insertItem(index: contentModel->count(), item: child);
355}
356
357void QQuickMenuPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent)
358{
359 // remove dynamically unparented items (eg. by a Repeater)
360 if (!parent)
361 removeItem(index: contentModel->indexOf(object: item, objectContext: nullptr), item);
362}
363
364void QQuickMenuPrivate::itemSiblingOrderChanged(QQuickItem *)
365{
366 // reorder the restacked items (eg. by a Repeater)
367 Q_Q(QQuickMenu);
368 QList<QQuickItem *> siblings = contentItem->childItems();
369
370 int to = 0;
371 for (int i = 0; i < siblings.size(); ++i) {
372 QQuickItem* sibling = siblings.at(i);
373 if (QQuickItemPrivate::get(item: sibling)->isTransparentForPositioner())
374 continue;
375 int index = contentModel->indexOf(object: sibling, objectContext: nullptr);
376 q->moveItem(from: index, to: to++);
377 }
378}
379
380void QQuickMenuPrivate::itemDestroyed(QQuickItem *item)
381{
382 QQuickPopupPrivate::itemDestroyed(item);
383 int index = contentModel->indexOf(object: item, objectContext: nullptr);
384 if (index != -1)
385 removeItem(index, item);
386}
387
388void QQuickMenuPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange, const QRectF &)
389{
390 if (!complete)
391 return;
392
393 if (item == contentItem) {
394 // The contentItem's geometry changed, so resize any items
395 // that don't have explicit widths set so that they fill the width of the menu.
396 resizeItems();
397 } else {
398 // The geometry of an item in the menu changed. If the item
399 // doesn't have an explicit width set, make it fill the width of the menu.
400 resizeItem(item);
401 }
402}
403
404QQuickPopupPositioner *QQuickMenuPrivate::getPositioner()
405{
406 Q_Q(QQuickMenu);
407 if (!positioner)
408 positioner = new QQuickMenuPositioner(q);
409 return positioner;
410}
411
412void QQuickMenuPositioner::reposition()
413{
414 QQuickMenu *menu = static_cast<QQuickMenu *>(popup());
415 QQuickMenuPrivate *p = QQuickMenuPrivate::get(menu);
416 if (p->parentMenu) {
417 if (p->cascade) {
418 if (p->popupItem->isMirrored())
419 menu->setPosition(QPointF(-menu->width() - p->parentMenu->leftPadding() + menu->overlap(), -menu->topPadding()));
420 else if (p->parentItem)
421 menu->setPosition(QPointF(p->parentItem->width() + p->parentMenu->rightPadding() - menu->overlap(), -menu->topPadding()));
422 } else {
423 menu->setPosition(QPointF(p->parentMenu->x() + (p->parentMenu->width() - menu->width()) / 2,
424 p->parentMenu->y() + (p->parentMenu->height() - menu->height()) / 2));
425 }
426 }
427 QQuickPopupPositioner::reposition();
428}
429
430bool QQuickMenuPrivate::prepareEnterTransition()
431{
432 Q_Q(QQuickMenu);
433 if (parentMenu && !cascade)
434 parentMenu->close();
435
436 // If a cascading sub-menu doesn't have enough space to open on
437 // the right, it flips on the other side of the parent menu.
438 allowHorizontalFlip = cascade && parentMenu;
439
440 if (!QQuickPopupPrivate::prepareEnterTransition())
441 return false;
442
443 if (!hasClosePolicy) {
444 if (cascade && parentMenu)
445 closePolicy = cascadingSubMenuClosePolicy;
446 else
447 q->resetClosePolicy();
448 }
449 return true;
450}
451
452bool QQuickMenuPrivate::prepareExitTransition()
453{
454 if (!QQuickPopupPrivate::prepareExitTransition())
455 return false;
456
457 stopHoverTimer();
458
459 QQuickMenu *subMenu = currentSubMenu();
460 while (subMenu) {
461 QPointer<QQuickMenuItem> currentSubMenuItem = QQuickMenuPrivate::get(menu: subMenu)->currentItem;
462 subMenu->close();
463 subMenu = currentSubMenuItem ? currentSubMenuItem->subMenu() : nullptr;
464 }
465 return true;
466}
467
468bool QQuickMenuPrivate::blockInput(QQuickItem *item, const QPointF &point) const
469{
470 // keep the parent menu open when a cascading sub-menu (this menu) is interacted with
471 return (cascade && parentMenu && contains(scenePos: point)) || QQuickPopupPrivate::blockInput(item, point);
472}
473
474void QQuickMenuPrivate::onItemHovered()
475{
476 Q_Q(QQuickMenu);
477 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object: q->sender());
478 if (!button || !button->isHovered() || !button->isEnabled() || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
479 return;
480
481 QQuickMenuItem *oldCurrentItem = currentItem;
482
483 int index = contentModel->indexOf(object: button, objectContext: nullptr);
484 if (index != -1) {
485 setCurrentIndex(index, reason: Qt::OtherFocusReason);
486 if (oldCurrentItem != currentItem) {
487 if (oldCurrentItem) {
488 QQuickMenu *subMenu = oldCurrentItem->subMenu();
489 if (subMenu)
490 subMenu->close();
491 }
492 if (currentItem) {
493 QQuickMenu *subMenu = currentItem->menu();
494 if (subMenu && subMenu->cascade())
495 startHoverTimer();
496 }
497 }
498 }
499}
500
501void QQuickMenuPrivate::onItemTriggered()
502{
503 Q_Q(QQuickMenu);
504 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: q->sender());
505 if (!item)
506 return;
507
508 if (QQuickMenu *subMenu = item->subMenu()) {
509 auto subMenuPrivate = QQuickMenuPrivate::get(menu: subMenu);
510 subMenu->popup(menuItem: subMenuPrivate->firstEnabledMenuItem());
511 } else {
512 q->dismiss();
513 }
514}
515
516void QQuickMenuPrivate::onItemActiveFocusChanged()
517{
518 Q_Q(QQuickMenu);
519 QQuickItem *item = qobject_cast<QQuickItem*>(o: q->sender());
520 if (!item->hasActiveFocus())
521 return;
522
523 int indexOfItem = contentModel->indexOf(object: item, objectContext: nullptr);
524 QQuickControl *control = qobject_cast<QQuickControl *>(object: item);
525 setCurrentIndex(index: indexOfItem, reason: control ? control->focusReason() : Qt::OtherFocusReason);
526}
527
528QQuickMenu *QQuickMenuPrivate::currentSubMenu() const
529{
530 if (!currentItem)
531 return nullptr;
532
533 return currentItem->subMenu();
534}
535
536void QQuickMenuPrivate::setParentMenu(QQuickMenu *parent)
537{
538 Q_Q(QQuickMenu);
539 if (parentMenu == parent)
540 return;
541
542 if (parentMenu) {
543 QObject::disconnect(sender: parentMenu.data(), signal: &QQuickMenu::cascadeChanged, receiver: q, slot: &QQuickMenu::setCascade);
544 disconnect(sender: parentMenu.data(), signal: &QQuickMenu::parentChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::resolveParentItem);
545 }
546 if (parent) {
547 QObject::connect(sender: parent, signal: &QQuickMenu::cascadeChanged, context: q, slot: &QQuickMenu::setCascade);
548 connect(sender: parent, signal: &QQuickMenu::parentChanged, receiverPrivate: this, slot: &QQuickMenuPrivate::resolveParentItem);
549 }
550
551 parentMenu = parent;
552 q->resetCascade();
553 resolveParentItem();
554}
555
556static QQuickItem *findParentMenuItem(QQuickMenu *subMenu)
557{
558 QQuickMenu *menu = QQuickMenuPrivate::get(menu: subMenu)->parentMenu;
559 for (int i = 0; i < QQuickMenuPrivate::get(menu)->contentModel->count(); ++i) {
560 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: i));
561 if (item && item->subMenu() == subMenu)
562 return item;
563 }
564 return nullptr;
565}
566
567void QQuickMenuPrivate::resolveParentItem()
568{
569 Q_Q(QQuickMenu);
570 if (!parentMenu)
571 q->resetParentItem();
572 else if (!cascade)
573 q->setParentItem(parentMenu->parentItem());
574 else
575 q->setParentItem(findParentMenuItem(subMenu: q));
576}
577
578void QQuickMenuPrivate::propagateKeyEvent(QKeyEvent *event)
579{
580 if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: parentItem)) {
581 if (QQuickMenu *menu = menuItem->menu())
582 QQuickMenuPrivate::get(menu)->propagateKeyEvent(event);
583 } else if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: parentItem)) {
584 if (QQuickMenuBar *menuBar = menuBarItem->menuBar()) {
585 event->accept();
586 QCoreApplication::sendEvent(receiver: menuBar, event);
587 }
588 }
589}
590
591void QQuickMenuPrivate::startHoverTimer()
592{
593 Q_Q(QQuickMenu);
594 stopHoverTimer();
595 hoverTimer = q->startTimer(interval: SUBMENU_DELAY);
596}
597
598void QQuickMenuPrivate::stopHoverTimer()
599{
600 Q_Q(QQuickMenu);
601 if (!hoverTimer)
602 return;
603
604 q->killTimer(id: hoverTimer);
605 hoverTimer = 0;
606}
607
608void QQuickMenuPrivate::setCurrentIndex(int index, Qt::FocusReason reason)
609{
610 Q_Q(QQuickMenu);
611 if (currentIndex == index)
612 return;
613
614 QQuickMenuItem *newCurrentItem = qobject_cast<QQuickMenuItem *>(object: itemAt(index));
615 if (currentItem != newCurrentItem) {
616 stopHoverTimer();
617 if (currentItem) {
618 currentItem->setHighlighted(false);
619 if (!newCurrentItem && window) {
620 QQuickItem *focusItem = QQuickItemPrivate::get(item: contentItem)->subFocusItem;
621 if (focusItem)
622 QQuickWindowPrivate::get(c: window)->clearFocusInScope(scope: contentItem, item: focusItem, reason: Qt::OtherFocusReason);
623 }
624 }
625 if (newCurrentItem) {
626 newCurrentItem->setHighlighted(true);
627 newCurrentItem->forceActiveFocus(reason);
628 }
629 currentItem = newCurrentItem;
630 }
631
632 currentIndex = index;
633 emit q->currentIndexChanged();
634}
635
636bool QQuickMenuPrivate::activateNextItem()
637{
638 int index = currentIndex;
639 int count = contentModel->count();
640 while (++index < count) {
641 QQuickItem *item = itemAt(index);
642 if (!item || !item->activeFocusOnTab() || !item->isEnabled())
643 continue;
644 setCurrentIndex(index, reason: Qt::TabFocusReason);
645 return true;
646 }
647 return false;
648}
649
650bool QQuickMenuPrivate::activatePreviousItem()
651{
652 int index = currentIndex;
653 while (--index >= 0) {
654 QQuickItem *item = itemAt(index);
655 if (!item || !item->activeFocusOnTab() || !item->isEnabled())
656 continue;
657 setCurrentIndex(index, reason: Qt::BacktabFocusReason);
658 return true;
659 }
660 return false;
661}
662
663QQuickMenuItem *QQuickMenuPrivate::firstEnabledMenuItem() const
664{
665 for (int i = 0; i < contentModel->count(); ++i) {
666 QQuickItem *item = itemAt(index: i);
667 if (!item || !item->isEnabled())
668 continue;
669
670 QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(object: item);
671 if (!menuItem)
672 continue;
673
674 return menuItem;
675 }
676 return nullptr;
677}
678
679void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj)
680{
681 QQuickMenu *q = qobject_cast<QQuickMenu *>(object: prop->object);
682 QQuickMenuPrivate *p = QQuickMenuPrivate::get(menu: q);
683
684 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj);
685 if (!item) {
686 if (QQuickAction *action = qobject_cast<QQuickAction *>(object: obj))
687 item = p->createItem(action);
688 else if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(object: obj))
689 item = p->createItem(menu);
690 }
691
692 if (item) {
693 if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) {
694 QQuickItemPrivate::get(item)->addItemChangeListener(listener: p, types: QQuickItemPrivate::SiblingOrder);
695 item->setParentItem(p->contentItem);
696 } else if (p->contentModel->indexOf(object: item, objectContext: nullptr) == -1) {
697 q->addItem(item);
698 }
699 } else {
700 p->contentData.append(t: obj);
701 }
702}
703
704qsizetype QQuickMenuPrivate::contentData_count(QQmlListProperty<QObject> *prop)
705{
706 QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
707 return QQuickMenuPrivate::get(menu: q)->contentData.size();
708}
709
710QObject *QQuickMenuPrivate::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
711{
712 QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
713 return QQuickMenuPrivate::get(menu: q)->contentData.value(i: index);
714}
715
716QPalette QQuickMenuPrivate::defaultPalette() const
717{
718 return QQuickTheme::palette(scope: QQuickTheme::Menu);
719}
720
721void QQuickMenuPrivate::contentData_clear(QQmlListProperty<QObject> *prop)
722{
723 QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
724 QQuickMenuPrivate::get(menu: q)->contentData.clear();
725}
726
727QQuickMenu::QQuickMenu(QObject *parent)
728 : QQuickPopup(*(new QQuickMenuPrivate), parent)
729{
730 Q_D(QQuickMenu);
731 setFocus(true);
732 d->init();
733 connect(sender: d->contentModel, signal: &QQmlObjectModel::countChanged, context: this, slot: &QQuickMenu::countChanged);
734}
735
736QQuickMenu::~QQuickMenu()
737{
738 Q_D(QQuickMenu);
739 // We have to do this to ensure that the change listeners are removed.
740 // It's too late to do this in ~QQuickMenuPrivate, as contentModel has already
741 // been destroyed before that is called.
742 while (d->contentModel->count() > 0)
743 d->removeItem(index: 0, item: d->itemAt(index: 0));
744
745 if (d->contentItem) {
746 QQuickItemPrivate::get(item: d->contentItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children);
747 QQuickItemPrivate::get(item: d->contentItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Geometry);
748
749 const auto children = d->contentItem->childItems();
750 for (QQuickItem *child : std::as_const(t: children))
751 QQuickItemPrivate::get(item: child)->removeItemChangeListener(d, types: QQuickItemPrivate::SiblingOrder);
752 }
753}
754
755/*!
756 \qmlmethod Item QtQuick.Controls::Menu::itemAt(int index)
757
758 Returns the item at \a index, or \c null if it does not exist.
759*/
760QQuickItem *QQuickMenu::itemAt(int index) const
761{
762 Q_D(const QQuickMenu);
763 return d->itemAt(index);
764}
765
766/*!
767 \qmlmethod void QtQuick.Controls::Menu::addItem(Item item)
768
769 Adds \a item to the end of the list of items.
770*/
771void QQuickMenu::addItem(QQuickItem *item)
772{
773 Q_D(QQuickMenu);
774 insertItem(index: d->contentModel->count(), item);
775}
776
777/*!
778 \qmlmethod void QtQuick.Controls::Menu::insertItem(int index, Item item)
779
780 Inserts \a item at \a index.
781*/
782void QQuickMenu::insertItem(int index, QQuickItem *item)
783{
784 Q_D(QQuickMenu);
785 if (!item)
786 return;
787 const int count = d->contentModel->count();
788 if (index < 0 || index > count)
789 index = count;
790
791 int oldIndex = d->contentModel->indexOf(object: item, objectContext: nullptr);
792 if (oldIndex != -1) {
793 if (oldIndex < index)
794 --index;
795 if (oldIndex != index)
796 d->moveItem(from: oldIndex, to: index);
797 } else {
798 d->insertItem(index, item);
799 }
800}
801
802/*!
803 \qmlmethod void QtQuick.Controls::Menu::moveItem(int from, int to)
804
805 Moves an item \a from one index \a to another.
806*/
807void QQuickMenu::moveItem(int from, int to)
808{
809 Q_D(QQuickMenu);
810 const int count = d->contentModel->count();
811 if (from < 0 || from > count - 1)
812 return;
813 if (to < 0 || to > count - 1)
814 to = count - 1;
815
816 if (from != to)
817 d->moveItem(from, to);
818}
819
820/*!
821 \since QtQuick.Controls 2.3 (Qt 5.10)
822 \qmlmethod void QtQuick.Controls::Menu::removeItem(Item item)
823
824 Removes and destroys the specified \a item.
825*/
826void QQuickMenu::removeItem(QQuickItem *item)
827{
828 Q_D(QQuickMenu);
829 if (!item)
830 return;
831
832 const int index = d->contentModel->indexOf(object: item, objectContext: nullptr);
833 if (index == -1)
834 return;
835
836 d->removeItem(index, item);
837 item->deleteLater();
838}
839
840/*!
841 \since QtQuick.Controls 2.3 (Qt 5.10)
842 \qmlmethod MenuItem QtQuick.Controls::Menu::takeItem(int index)
843
844 Removes and returns the item at \a index.
845
846 \note The ownership of the item is transferred to the caller.
847*/
848QQuickItem *QQuickMenu::takeItem(int index)
849{
850 Q_D(QQuickMenu);
851 const int count = d->contentModel->count();
852 if (index < 0 || index >= count)
853 return nullptr;
854
855 QQuickItem *item = itemAt(index);
856 if (item)
857 d->removeItem(index, item);
858 return item;
859}
860
861/*!
862 \since QtQuick.Controls 2.3 (Qt 5.10)
863 \qmlmethod Menu QtQuick.Controls::Menu::menuAt(int index)
864
865 Returns the sub-menu at \a index, or \c null if the index is not valid or
866 there is no sub-menu at the specified index.
867*/
868QQuickMenu *QQuickMenu::menuAt(int index) const
869{
870 Q_D(const QQuickMenu);
871 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index));
872 if (!item)
873 return nullptr;
874
875 return item->subMenu();
876}
877
878/*!
879 \since QtQuick.Controls 2.3 (Qt 5.10)
880 \qmlmethod void QtQuick.Controls::Menu::addMenu(Menu menu)
881
882 Adds \a menu as a sub-menu to the end of this menu.
883*/
884void QQuickMenu::addMenu(QQuickMenu *menu)
885{
886 Q_D(QQuickMenu);
887 insertMenu(index: d->contentModel->count(), menu);
888}
889
890/*!
891 \since QtQuick.Controls 2.3 (Qt 5.10)
892 \qmlmethod void QtQuick.Controls::Menu::insertMenu(int index, Menu menu)
893
894 Inserts \a menu as a sub-menu at \a index. The index is within all items in the menu.
895*/
896void QQuickMenu::insertMenu(int index, QQuickMenu *menu)
897{
898 Q_D(QQuickMenu);
899 if (!menu)
900 return;
901
902 insertItem(index, item: d->createItem(menu));
903}
904
905/*!
906 \since QtQuick.Controls 2.3 (Qt 5.10)
907 \qmlmethod void QtQuick.Controls::Menu::removeMenu(Menu menu)
908
909 Removes and destroys the specified \a menu.
910*/
911void QQuickMenu::removeMenu(QQuickMenu *menu)
912{
913 Q_D(QQuickMenu);
914 if (!menu)
915 return;
916
917 const int count = d->contentModel->count();
918 for (int i = 0; i < count; ++i) {
919 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index: i));
920 if (!item || item->subMenu() != menu)
921 continue;
922
923 removeItem(item);
924 break;
925 }
926
927 menu->deleteLater();
928}
929
930/*!
931 \since QtQuick.Controls 2.3 (Qt 5.10)
932 \qmlmethod Menu QtQuick.Controls::Menu::takeMenu(int index)
933
934 Removes and returns the menu at \a index. The index is within all items in the menu.
935
936 \note The ownership of the menu is transferred to the caller.
937*/
938QQuickMenu *QQuickMenu::takeMenu(int index)
939{
940 Q_D(QQuickMenu);
941 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index));
942 if (!item)
943 return nullptr;
944
945 QQuickMenu *subMenu = item->subMenu();
946 if (!subMenu)
947 return nullptr;
948
949 d->removeItem(index, item);
950 item->deleteLater();
951 return subMenu;
952}
953
954/*!
955 \since QtQuick.Controls 2.3 (Qt 5.10)
956 \qmlmethod Action QtQuick.Controls::Menu::actionAt(int index)
957
958 Returns the action at \a index, or \c null if the index is not valid or
959 there is no action at the specified index.
960*/
961QQuickAction *QQuickMenu::actionAt(int index) const
962{
963 Q_D(const QQuickMenu);
964 QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton *>(object: d->itemAt(index));
965 if (!item)
966 return nullptr;
967
968 return item->action();
969}
970
971/*!
972 \since QtQuick.Controls 2.3 (Qt 5.10)
973 \qmlmethod void QtQuick.Controls::Menu::addAction(Action action)
974
975 Adds \a action to the end of this menu.
976*/
977void QQuickMenu::addAction(QQuickAction *action)
978{
979 Q_D(QQuickMenu);
980 insertAction(index: d->contentModel->count(), action);
981}
982
983/*!
984 \since QtQuick.Controls 2.3 (Qt 5.10)
985 \qmlmethod void QtQuick.Controls::Menu::insertAction(int index, Action action)
986
987 Inserts \a action at \a index. The index is within all items in the menu.
988*/
989void QQuickMenu::insertAction(int index, QQuickAction *action)
990{
991 Q_D(QQuickMenu);
992 if (!action)
993 return;
994
995 insertItem(index, item: d->createItem(action));
996}
997
998/*!
999 \since QtQuick.Controls 2.3 (Qt 5.10)
1000 \qmlmethod void QtQuick.Controls::Menu::removeAction(Action action)
1001
1002 Removes and destroys the specified \a action.
1003*/
1004void QQuickMenu::removeAction(QQuickAction *action)
1005{
1006 Q_D(QQuickMenu);
1007 if (!action)
1008 return;
1009
1010 const int count = d->contentModel->count();
1011 for (int i = 0; i < count; ++i) {
1012 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index: i));
1013 if (!item || item->action() != action)
1014 continue;
1015
1016 removeItem(item);
1017 break;
1018 }
1019
1020 action->deleteLater();
1021}
1022
1023/*!
1024 \since QtQuick.Controls 2.3 (Qt 5.10)
1025 \qmlmethod Action QtQuick.Controls::Menu::takeAction(int index)
1026
1027 Removes and returns the action at \a index. The index is within all items in the menu.
1028
1029 \note The ownership of the action is transferred to the caller.
1030*/
1031QQuickAction *QQuickMenu::takeAction(int index)
1032{
1033 Q_D(QQuickMenu);
1034 QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: d->itemAt(index));
1035 if (!item)
1036 return nullptr;
1037
1038 QQuickAction *action = item->action();
1039 if (!action)
1040 return nullptr;
1041
1042 d->removeItem(index, item);
1043 item->deleteLater();
1044 return action;
1045}
1046
1047/*!
1048 \qmlproperty model QtQuick.Controls::Menu::contentModel
1049 \readonly
1050
1051 This property holds the model used to display menu items.
1052
1053 The content model is provided for visualization purposes. It can be assigned
1054 as a model to a content item that presents the contents of the menu.
1055
1056 \code
1057 Menu {
1058 id: menu
1059 contentItem: ListView {
1060 model: menu.contentModel
1061 }
1062 }
1063 \endcode
1064
1065 The model allows menu items to be statically declared as children of the
1066 menu.
1067*/
1068QVariant QQuickMenu::contentModel() const
1069{
1070 Q_D(const QQuickMenu);
1071 return QVariant::fromValue(value: d->contentModel);
1072}
1073
1074/*!
1075 \qmlproperty list<QtObject> QtQuick.Controls::Menu::contentData
1076 \qmldefault
1077
1078 This property holds the list of content data.
1079
1080 The list contains all objects that have been declared in QML as children
1081 of the menu, and also items that have been dynamically added or
1082 inserted using the \l addItem() and \l insertItem() methods, respectively.
1083
1084 \note Unlike \c contentChildren, \c contentData does include non-visual QML
1085 objects. It is not re-ordered when items are inserted or moved.
1086
1087 \sa Item::data, {Popup::}{contentChildren}
1088*/
1089QQmlListProperty<QObject> QQuickMenu::contentData()
1090{
1091 Q_D(QQuickMenu);
1092 if (!d->contentItem)
1093 QQuickControlPrivate::get(control: d->popupItem)->executeContentItem();
1094 return QQmlListProperty<QObject>(this, nullptr,
1095 QQuickMenuPrivate::contentData_append,
1096 QQuickMenuPrivate::contentData_count,
1097 QQuickMenuPrivate::contentData_at,
1098 QQuickMenuPrivate::contentData_clear);
1099}
1100
1101/*!
1102 \qmlproperty string QtQuick.Controls::Menu::title
1103
1104 This property holds the title for the menu.
1105
1106 The title of a menu is often displayed in the text of a menu item when the
1107 menu is a submenu, and in the text of a tool button when it is in a
1108 menubar.
1109*/
1110QString QQuickMenu::title() const
1111{
1112 Q_D(const QQuickMenu);
1113 return d->title;
1114}
1115
1116void QQuickMenu::setTitle(QString &title)
1117{
1118 Q_D(QQuickMenu);
1119 if (title == d->title)
1120 return;
1121 d->title = title;
1122 emit titleChanged(title);
1123}
1124
1125/*!
1126 \qmlproperty string QtQuick.Controls::Menu::icon.name
1127 \qmlproperty url QtQuick.Controls::Menu::icon.source
1128 \qmlproperty int QtQuick.Controls::Menu::icon.width
1129 \qmlproperty int QtQuick.Controls::Menu::icon.height
1130 \qmlproperty color QtQuick.Controls::Menu::icon.color
1131 \qmlproperty bool QtQuick.Controls::Menu::icon.cache
1132
1133 This property group was added in QtQuick.Controls 6.5.
1134
1135 \include qquickicon.qdocinc grouped-properties
1136
1137 \sa AbstractButton::text, AbstractButton::display, {Icons in Qt Quick Controls}
1138*/
1139
1140QQuickIcon QQuickMenu::icon() const
1141{
1142 Q_D(const QQuickMenu);
1143 return d->icon;
1144}
1145
1146void QQuickMenu::setIcon(const QQuickIcon &icon)
1147{
1148 Q_D(QQuickMenu);
1149 if (icon == d->icon)
1150 return;
1151 d->icon = icon;
1152 d->icon.ensureRelativeSourceResolved(owner: this);
1153 emit iconChanged(icon);
1154}
1155
1156/*!
1157 \since QtQuick.Controls 2.3 (Qt 5.10)
1158 \qmlproperty bool QtQuick.Controls::Menu::cascade
1159
1160 This property holds whether the menu cascades its sub-menus.
1161
1162 The default value is platform-specific. Menus are cascading by default on
1163 desktop platforms that have a mouse cursor available. Non-cascading menus
1164 are shown one menu at a time, and centered over the parent menu.
1165
1166 \note Changing the value of the property has no effect while the menu is open.
1167
1168 \sa overlap
1169*/
1170bool QQuickMenu::cascade() const
1171{
1172 Q_D(const QQuickMenu);
1173 return d->cascade;
1174}
1175
1176void QQuickMenu::setCascade(bool cascade)
1177{
1178 Q_D(QQuickMenu);
1179 if (d->cascade == cascade)
1180 return;
1181 d->cascade = cascade;
1182 if (d->parentMenu)
1183 d->resolveParentItem();
1184 emit cascadeChanged(cascade);
1185}
1186
1187void QQuickMenu::resetCascade()
1188{
1189 Q_D(QQuickMenu);
1190 if (d->parentMenu)
1191 setCascade(d->parentMenu->cascade());
1192 else
1193 setCascade(shouldCascade());
1194}
1195
1196/*!
1197 \since QtQuick.Controls 2.3 (Qt 5.10)
1198 \qmlproperty real QtQuick.Controls::Menu::overlap
1199
1200 This property holds the amount of pixels by which the menu horizontally overlaps its parent menu.
1201
1202 The property only has effect when the menu is used as a cascading sub-menu.
1203
1204 The default value is style-specific.
1205
1206 \note Changing the value of the property has no effect while the menu is open.
1207
1208 \sa cascade
1209*/
1210qreal QQuickMenu::overlap() const
1211{
1212 Q_D(const QQuickMenu);
1213 return d->overlap;
1214}
1215
1216void QQuickMenu::setOverlap(qreal overlap)
1217{
1218 Q_D(QQuickMenu);
1219 if (d->overlap == overlap)
1220 return;
1221 d->overlap = overlap;
1222 emit overlapChanged();
1223}
1224
1225/*!
1226 \since QtQuick.Controls 2.3 (Qt 5.10)
1227 \qmlproperty Component QtQuick.Controls::Menu::delegate
1228
1229 This property holds the component that is used to create items
1230 to present actions.
1231
1232 \code
1233 Menu {
1234 Action { text: "Cut" }
1235 Action { text: "Copy" }
1236 Action { text: "Paste" }
1237 }
1238 \endcode
1239
1240 \sa Action
1241*/
1242QQmlComponent *QQuickMenu::delegate() const
1243{
1244 Q_D(const QQuickMenu);
1245 return d->delegate;
1246}
1247
1248void QQuickMenu::setDelegate(QQmlComponent *delegate)
1249{
1250 Q_D(QQuickMenu);
1251 if (d->delegate == delegate)
1252 return;
1253
1254 d->delegate = delegate;
1255 emit delegateChanged();
1256}
1257
1258/*!
1259 \since QtQuick.Controls 2.3 (Qt 5.10)
1260 \qmlproperty int QtQuick.Controls::Menu::currentIndex
1261
1262 This property holds the index of the currently highlighted item.
1263
1264 Menu items can be highlighted by mouse hover or keyboard navigation.
1265
1266 \sa MenuItem::highlighted
1267*/
1268int QQuickMenu::currentIndex() const
1269{
1270 Q_D(const QQuickMenu);
1271 return d->currentIndex;
1272}
1273
1274void QQuickMenu::setCurrentIndex(int index)
1275{
1276 Q_D(QQuickMenu);
1277 d->setCurrentIndex(index, reason: Qt::OtherFocusReason);
1278}
1279
1280/*!
1281 \since QtQuick.Controls 2.3 (Qt 5.10)
1282 \qmlproperty int QtQuick.Controls::Menu::count
1283 \readonly
1284
1285 This property holds the number of items.
1286*/
1287int QQuickMenu::count() const
1288{
1289 Q_D(const QQuickMenu);
1290 return d->contentModel->count();
1291}
1292
1293void QQuickMenu::popup(QQuickItem *menuItem)
1294{
1295 Q_D(QQuickMenu);
1296 // No position has been explicitly specified, so position the menu at the mouse cursor
1297 // on desktop platforms that have a mouse cursor available and support multiple windows.
1298 QQmlNullableValue<QPointF> pos;
1299#if QT_CONFIG(cursor)
1300 if (d->parentItem && QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::MultipleWindows))
1301 pos = d->parentItem->mapFromGlobal(point: QCursor::pos());
1302#endif
1303
1304 // As a fallback, center the menu over its parent item.
1305 if (pos.isNull && d->parentItem)
1306 pos = QPointF((d->parentItem->width() - width()) / 2, (d->parentItem->height() - height()) / 2);
1307
1308 popup(pos: pos.isNull ? QPointF() : pos.value, menuItem);
1309}
1310
1311void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem)
1312{
1313 Q_D(QQuickMenu);
1314 qreal offset = 0;
1315#if QT_CONFIG(cursor)
1316 if (menuItem)
1317 offset = d->popupItem->mapFromItem(item: menuItem, point: QPointF(0, 0)).y();
1318#endif
1319 setPosition(pos - QPointF(0, offset));
1320
1321 if (menuItem)
1322 d->setCurrentIndex(index: d->contentModel->indexOf(object: menuItem, objectContext: nullptr), reason: Qt::PopupFocusReason);
1323 open();
1324}
1325
1326/*!
1327 \since QtQuick.Controls 2.3 (Qt 5.10)
1328 \qmlmethod void QtQuick.Controls::Menu::popup(MenuItem item = null)
1329 \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, MenuItem item = null)
1330
1331 Opens the menu at the mouse cursor on desktop platforms that have a mouse cursor
1332 available, and otherwise centers the menu over its \a parent item.
1333
1334 The menu can be optionally aligned to a specific menu \a item.
1335
1336 \sa Popup::open()
1337*/
1338
1339/*!
1340 \since QtQuick.Controls 2.3 (Qt 5.10)
1341 \qmlmethod void QtQuick.Controls::Menu::popup(point pos, MenuItem item = null)
1342 \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, point pos, MenuItem item = null)
1343
1344 Opens the menu at the specified position \a pos in the popups coordinate system,
1345 that is, a coordinate relative to its \a parent item.
1346
1347 The menu can be optionally aligned to a specific menu \a item.
1348
1349 \sa Popup::open()
1350*/
1351
1352/*!
1353 \since QtQuick.Controls 2.3 (Qt 5.10)
1354 \qmlmethod void QtQuick.Controls::Menu::popup(real x, real y, MenuItem item = null)
1355 \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, real x, real y, MenuItem item = null)
1356
1357 Opens the menu at the specified position \a x, \a y in the popups coordinate system,
1358 that is, a coordinate relative to its \a parent item.
1359
1360 The menu can be optionally aligned to a specific menu \a item.
1361
1362 \sa dismiss(), Popup::open()
1363*/
1364void QQuickMenu::popup(QQmlV4Function *args)
1365{
1366 Q_D(QQuickMenu);
1367 const int len = args->length();
1368 if (len > 4) {
1369 args->v4engine()->throwTypeError();
1370 return;
1371 }
1372
1373 QV4::ExecutionEngine *v4 = args->v4engine();
1374 QV4::Scope scope(v4);
1375
1376 QQmlNullableValue<QPointF> pos;
1377 QQuickItem *menuItem = nullptr;
1378 QQuickItem *parentItem = nullptr;
1379
1380 if (len > 0) {
1381 // Item parent
1382 QV4::ScopedValue firstArg(scope, (*args)[0]);
1383 if (const QV4::QObjectWrapper *obj = firstArg->as<QV4::QObjectWrapper>()) {
1384 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj->object());
1385 if (item && !d->popupItem->isAncestorOf(child: item))
1386 parentItem = item;
1387 } else if (firstArg->isUndefined()) {
1388 resetParentItem();
1389 parentItem = d->parentItem;
1390 }
1391
1392 // MenuItem item
1393 QV4::ScopedValue lastArg(scope, (*args)[len - 1]);
1394 if (const QV4::QObjectWrapper *obj = lastArg->as<QV4::QObjectWrapper>()) {
1395 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj->object());
1396 if (item && d->popupItem->isAncestorOf(child: item))
1397 menuItem = item;
1398 }
1399 }
1400
1401 if (len >= 3 || (!parentItem && len >= 2)) {
1402 // real x, real y
1403 QV4::ScopedValue xArg(scope, (*args)[parentItem ? 1 : 0]);
1404 QV4::ScopedValue yArg(scope, (*args)[parentItem ? 2 : 1]);
1405 if (xArg->isNumber() && yArg->isNumber())
1406 pos = QPointF(xArg->asDouble(), yArg->asDouble());
1407 }
1408
1409 if (pos.isNull && (len >= 2 || (!parentItem && len >= 1))) {
1410 // point pos
1411 QV4::ScopedValue posArg(scope, (*args)[parentItem ? 1 : 0]);
1412 const QVariant var = QV4::ExecutionEngine::toVariant(value: posArg, typeHint: QMetaType {});
1413 if (var.userType() == QMetaType::QPointF)
1414 pos = var.toPointF();
1415 }
1416
1417 if (parentItem)
1418 setParentItem(parentItem);
1419
1420 if (pos.isNull)
1421 popup(menuItem);
1422 else
1423 popup(pos, menuItem);
1424}
1425
1426/*!
1427 \since QtQuick.Controls 2.3 (Qt 5.10)
1428 \qmlmethod void QtQuick.Controls::Menu::dismiss()
1429
1430 Closes all menus in the hierarchy that this menu belongs to.
1431
1432 \note Unlike \l {Popup::}{close()} that only closes a menu and its sub-menus,
1433 \c dismiss() closes the whole hierarchy of menus, including the parent menus.
1434 In practice, \c close() is suitable e.g. for implementing navigation in a
1435 hierarchy of menus, and \c dismiss() is the appropriate method for closing
1436 the whole hierarchy of menus.
1437
1438 \sa popup(), Popup::close()
1439*/
1440void QQuickMenu::dismiss()
1441{
1442 QQuickMenu *menu = this;
1443 while (menu) {
1444 menu->close();
1445 menu = QQuickMenuPrivate::get(menu)->parentMenu;
1446 }
1447}
1448
1449void QQuickMenu::componentComplete()
1450{
1451 Q_D(QQuickMenu);
1452 QQuickPopup::componentComplete();
1453 d->resizeItems();
1454}
1455
1456void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
1457{
1458 Q_D(QQuickMenu);
1459 QQuickPopup::contentItemChange(newItem, oldItem);
1460
1461 if (oldItem) {
1462 QQuickItemPrivate::get(item: oldItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children);
1463 QQuickItemPrivate::get(item: oldItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Geometry);
1464 }
1465 if (newItem) {
1466 QQuickItemPrivate::get(item: newItem)->addItemChangeListener(listener: d, types: QQuickItemPrivate::Children);
1467 QQuickItemPrivate::get(item: newItem)->updateOrAddGeometryChangeListener(listener: d, types: QQuickGeometryChange::Width);
1468 }
1469
1470 d->contentItem = newItem;
1471}
1472
1473void QQuickMenu::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
1474{
1475 Q_D(QQuickMenu);
1476 QQuickPopup::itemChange(change, data);
1477
1478 if (change == QQuickItem::ItemVisibleHasChanged) {
1479 if (!data.boolValue && d->cascade) {
1480 // Ensure that when the menu isn't visible, there's no current item
1481 // the next time it's opened.
1482 d->setCurrentIndex(index: -1, reason: Qt::OtherFocusReason);
1483 }
1484 }
1485}
1486
1487void QQuickMenu::keyPressEvent(QKeyEvent *event)
1488{
1489 Q_D(QQuickMenu);
1490 QQuickPopup::keyPressEvent(event);
1491
1492 // QTBUG-17051
1493 // Work around the fact that ListView has no way of distinguishing between
1494 // mouse and keyboard interaction, thanks to the "interactive" bool in Flickable.
1495 // What we actually want is to have a way to always allow keyboard interaction but
1496 // only allow flicking with the mouse when there are too many menu items to be
1497 // shown at once.
1498 switch (event->key()) {
1499 case Qt::Key_Up:
1500 if (!d->activatePreviousItem())
1501 d->propagateKeyEvent(event);
1502 break;
1503
1504 case Qt::Key_Down:
1505 d->activateNextItem();
1506 break;
1507
1508 case Qt::Key_Left:
1509 case Qt::Key_Right:
1510 event->ignore();
1511 if (d->popupItem->isMirrored() == (event->key() == Qt::Key_Right)) {
1512 if (d->parentMenu && d->currentItem) {
1513 if (!d->cascade)
1514 d->parentMenu->open();
1515 close();
1516 event->accept();
1517 }
1518 } else {
1519 if (QQuickMenu *subMenu = d->currentSubMenu()) {
1520 auto subMenuPrivate = QQuickMenuPrivate::get(menu: subMenu);
1521 subMenu->popup(menuItem: subMenuPrivate->firstEnabledMenuItem());
1522 event->accept();
1523 }
1524 }
1525 if (!event->isAccepted())
1526 d->propagateKeyEvent(event);
1527 break;
1528
1529#if QT_CONFIG(shortcut)
1530 case Qt::Key_Alt:
1531 // If &mnemonic shortcut is enabled, go back to (possibly) the parent
1532 // menu bar so the shortcut key will be processed by the menu bar.
1533 if (!QKeySequence::mnemonic(QStringLiteral("&A")).isEmpty())
1534 close();
1535 break;
1536#endif
1537
1538 default:
1539 break;
1540 }
1541}
1542
1543void QQuickMenu::timerEvent(QTimerEvent *event)
1544{
1545 Q_D(QQuickMenu);
1546 if (event->timerId() == d->hoverTimer) {
1547 if (QQuickMenu *subMenu = d->currentSubMenu())
1548 subMenu->open();
1549 d->stopHoverTimer();
1550 return;
1551 }
1552 QQuickPopup::timerEvent(event);
1553}
1554
1555QFont QQuickMenu::defaultFont() const
1556{
1557 return QQuickTheme::font(scope: QQuickTheme::Menu);
1558}
1559
1560#if QT_CONFIG(accessibility)
1561QAccessible::Role QQuickMenu::accessibleRole() const
1562{
1563 return QAccessible::PopupMenu;
1564}
1565#endif
1566
1567QT_END_NAMESPACE
1568
1569#include "moc_qquickmenu_p.cpp"
1570

source code of qtdeclarative/src/quicktemplates/qquickmenu.cpp