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 | |
32 | QT_BEGIN_NAMESPACE |
33 | |
34 | // copied from qfusionstyle.cpp |
35 | static const int = 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 | |
200 | static const QQuickPopup::ClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent; |
201 | |
202 | static 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 | |
211 | class : public QQuickPopupPositioner |
212 | { |
213 | public: |
214 | (QQuickMenu *) : QQuickPopupPositioner(menu) { } |
215 | |
216 | void reposition() override; |
217 | }; |
218 | |
219 | QQuickMenuPrivate::() |
220 | { |
221 | cascade = shouldCascade(); |
222 | } |
223 | |
224 | void QQuickMenuPrivate::() |
225 | { |
226 | Q_Q(QQuickMenu); |
227 | contentModel = new QQmlObjectModel(q); |
228 | } |
229 | |
230 | QQuickItem *QQuickMenuPrivate::(int index) const |
231 | { |
232 | return qobject_cast<QQuickItem *>(o: contentModel->get(index)); |
233 | } |
234 | |
235 | void QQuickMenuPrivate::(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 * = qobject_cast<QQuickMenuItem *>(object: item); |
247 | if (menuItem) { |
248 | Q_Q(QQuickMenu); |
249 | QQuickMenuItemPrivate::get(item: menuItem)->setMenu(q); |
250 | if (QQuickMenu * = 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 | |
258 | void QQuickMenuPrivate::(int from, int to) |
259 | { |
260 | contentModel->move(from, to); |
261 | } |
262 | |
263 | void QQuickMenuPrivate::(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 * = qobject_cast<QQuickMenuItem *>(object: item); |
273 | if (menuItem) { |
274 | QQuickMenuItemPrivate::get(item: menuItem)->setMenu(nullptr); |
275 | if (QQuickMenu * = 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 | |
283 | QQuickItem *QQuickMenuPrivate::() |
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 | |
303 | void QQuickMenuPrivate::() |
304 | { |
305 | if (!delegate) |
306 | return; |
307 | |
308 | delegate->completeCreate(); |
309 | } |
310 | |
311 | QQuickItem *QQuickMenuPrivate::(QQuickMenu *) |
312 | { |
313 | QQuickItem *item = beginCreateItem(); |
314 | if (QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: item)) |
315 | QQuickMenuItemPrivate::get(item: menuItem)->setSubMenu(menu); |
316 | completeCreateItem(); |
317 | return item; |
318 | } |
319 | |
320 | QQuickItem *QQuickMenuPrivate::(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 | |
329 | void QQuickMenuPrivate::(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 | |
341 | void QQuickMenuPrivate::() |
342 | { |
343 | if (!contentModel) |
344 | return; |
345 | |
346 | for (int i = 0; i < contentModel->count(); ++i) |
347 | resizeItem(item: itemAt(index: i)); |
348 | } |
349 | |
350 | void QQuickMenuPrivate::(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 | |
357 | void QQuickMenuPrivate::(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 | |
364 | void QQuickMenuPrivate::(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 | |
380 | void QQuickMenuPrivate::(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 | |
388 | void QQuickMenuPrivate::(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 | |
404 | QQuickPopupPositioner *QQuickMenuPrivate::() |
405 | { |
406 | Q_Q(QQuickMenu); |
407 | if (!positioner) |
408 | positioner = new QQuickMenuPositioner(q); |
409 | return positioner; |
410 | } |
411 | |
412 | void QQuickMenuPositioner::() |
413 | { |
414 | QQuickMenu * = 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 | |
430 | bool QQuickMenuPrivate::() |
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 | |
452 | bool QQuickMenuPrivate::() |
453 | { |
454 | if (!QQuickPopupPrivate::prepareExitTransition()) |
455 | return false; |
456 | |
457 | stopHoverTimer(); |
458 | |
459 | QQuickMenu * = currentSubMenu(); |
460 | while (subMenu) { |
461 | QPointer<QQuickMenuItem> = QQuickMenuPrivate::get(menu: subMenu)->currentItem; |
462 | subMenu->close(); |
463 | subMenu = currentSubMenuItem ? currentSubMenuItem->subMenu() : nullptr; |
464 | } |
465 | return true; |
466 | } |
467 | |
468 | bool QQuickMenuPrivate::(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 | |
474 | void QQuickMenuPrivate::() |
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 * = oldCurrentItem->subMenu(); |
489 | if (subMenu) |
490 | subMenu->close(); |
491 | } |
492 | if (currentItem) { |
493 | QQuickMenu * = currentItem->menu(); |
494 | if (subMenu && subMenu->cascade()) |
495 | startHoverTimer(); |
496 | } |
497 | } |
498 | } |
499 | } |
500 | |
501 | void QQuickMenuPrivate::() |
502 | { |
503 | Q_Q(QQuickMenu); |
504 | QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(object: q->sender()); |
505 | if (!item) |
506 | return; |
507 | |
508 | if (QQuickMenu * = item->subMenu()) { |
509 | auto = QQuickMenuPrivate::get(menu: subMenu); |
510 | subMenu->popup(menuItem: subMenuPrivate->firstEnabledMenuItem()); |
511 | } else { |
512 | q->dismiss(); |
513 | } |
514 | } |
515 | |
516 | void QQuickMenuPrivate::() |
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 | |
528 | QQuickMenu *QQuickMenuPrivate::() const |
529 | { |
530 | if (!currentItem) |
531 | return nullptr; |
532 | |
533 | return currentItem->subMenu(); |
534 | } |
535 | |
536 | void QQuickMenuPrivate::(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 | |
556 | static QQuickItem *(QQuickMenu *) |
557 | { |
558 | QQuickMenu * = 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 | |
567 | void QQuickMenuPrivate::() |
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 | |
578 | void QQuickMenuPrivate::(QKeyEvent *event) |
579 | { |
580 | if (QQuickMenuItem * = qobject_cast<QQuickMenuItem *>(object: parentItem)) { |
581 | if (QQuickMenu * = menuItem->menu()) |
582 | QQuickMenuPrivate::get(menu)->propagateKeyEvent(event); |
583 | } else if (QQuickMenuBarItem * = qobject_cast<QQuickMenuBarItem *>(object: parentItem)) { |
584 | if (QQuickMenuBar * = menuBarItem->menuBar()) { |
585 | event->accept(); |
586 | QCoreApplication::sendEvent(receiver: menuBar, event); |
587 | } |
588 | } |
589 | } |
590 | |
591 | void QQuickMenuPrivate::() |
592 | { |
593 | Q_Q(QQuickMenu); |
594 | stopHoverTimer(); |
595 | hoverTimer = q->startTimer(interval: SUBMENU_DELAY); |
596 | } |
597 | |
598 | void QQuickMenuPrivate::() |
599 | { |
600 | Q_Q(QQuickMenu); |
601 | if (!hoverTimer) |
602 | return; |
603 | |
604 | q->killTimer(id: hoverTimer); |
605 | hoverTimer = 0; |
606 | } |
607 | |
608 | void QQuickMenuPrivate::(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 | |
636 | bool QQuickMenuPrivate::() |
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 | |
650 | bool QQuickMenuPrivate::() |
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 | |
663 | QQuickMenuItem *QQuickMenuPrivate::() 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 * = qobject_cast<QQuickMenuItem *>(object: item); |
671 | if (!menuItem) |
672 | continue; |
673 | |
674 | return menuItem; |
675 | } |
676 | return nullptr; |
677 | } |
678 | |
679 | void QQuickMenuPrivate::(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 * = 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 | |
704 | qsizetype QQuickMenuPrivate::(QQmlListProperty<QObject> *prop) |
705 | { |
706 | QQuickMenu *q = static_cast<QQuickMenu *>(prop->object); |
707 | return QQuickMenuPrivate::get(menu: q)->contentData.size(); |
708 | } |
709 | |
710 | QObject *QQuickMenuPrivate::(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 | |
716 | QPalette QQuickMenuPrivate::() const |
717 | { |
718 | return QQuickTheme::palette(scope: QQuickTheme::Menu); |
719 | } |
720 | |
721 | void QQuickMenuPrivate::(QQmlListProperty<QObject> *prop) |
722 | { |
723 | QQuickMenu *q = static_cast<QQuickMenu *>(prop->object); |
724 | QQuickMenuPrivate::get(menu: q)->contentData.clear(); |
725 | } |
726 | |
727 | 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 | |
736 | 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 | */ |
760 | QQuickItem *QQuickMenu::(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 | */ |
771 | void QQuickMenu::(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 | */ |
782 | void QQuickMenu::(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 | */ |
807 | void QQuickMenu::(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 | */ |
826 | void QQuickMenu::(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 | */ |
848 | QQuickItem *QQuickMenu::(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 | */ |
868 | QQuickMenu *QQuickMenu::(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 | */ |
884 | void QQuickMenu::(QQuickMenu *) |
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 | */ |
896 | void QQuickMenu::(int index, QQuickMenu *) |
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 | */ |
911 | void QQuickMenu::(QQuickMenu *) |
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 | */ |
938 | QQuickMenu *QQuickMenu::(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 * = 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 | */ |
961 | QQuickAction *QQuickMenu::(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 | */ |
977 | void QQuickMenu::(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 | */ |
989 | void QQuickMenu::(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 | */ |
1004 | void QQuickMenu::(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 | */ |
1031 | QQuickAction *QQuickMenu::(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 | */ |
1068 | QVariant QQuickMenu::() 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 | */ |
1089 | QQmlListProperty<QObject> QQuickMenu::() |
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 | */ |
1110 | QString QQuickMenu::() const |
1111 | { |
1112 | Q_D(const QQuickMenu); |
1113 | return d->title; |
1114 | } |
1115 | |
1116 | void QQuickMenu::(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 | |
1140 | QQuickIcon QQuickMenu::() const |
1141 | { |
1142 | Q_D(const QQuickMenu); |
1143 | return d->icon; |
1144 | } |
1145 | |
1146 | void QQuickMenu::(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 | */ |
1170 | bool QQuickMenu::() const |
1171 | { |
1172 | Q_D(const QQuickMenu); |
1173 | return d->cascade; |
1174 | } |
1175 | |
1176 | void QQuickMenu::(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 | |
1187 | void QQuickMenu::() |
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 | */ |
1210 | qreal QQuickMenu::() const |
1211 | { |
1212 | Q_D(const QQuickMenu); |
1213 | return d->overlap; |
1214 | } |
1215 | |
1216 | void QQuickMenu::(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 | */ |
1242 | QQmlComponent *QQuickMenu::() const |
1243 | { |
1244 | Q_D(const QQuickMenu); |
1245 | return d->delegate; |
1246 | } |
1247 | |
1248 | void QQuickMenu::(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 | */ |
1268 | int QQuickMenu::() const |
1269 | { |
1270 | Q_D(const QQuickMenu); |
1271 | return d->currentIndex; |
1272 | } |
1273 | |
1274 | void QQuickMenu::(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 | */ |
1287 | int QQuickMenu::() const |
1288 | { |
1289 | Q_D(const QQuickMenu); |
1290 | return d->contentModel->count(); |
1291 | } |
1292 | |
1293 | void QQuickMenu::(QQuickItem *) |
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 | |
1311 | void QQuickMenu::(const QPointF &pos, QQuickItem *) |
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 | */ |
1364 | void QQuickMenu::(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 * = 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 | */ |
1440 | void QQuickMenu::() |
1441 | { |
1442 | QQuickMenu * = this; |
1443 | while (menu) { |
1444 | menu->close(); |
1445 | menu = QQuickMenuPrivate::get(menu)->parentMenu; |
1446 | } |
1447 | } |
1448 | |
1449 | void QQuickMenu::() |
1450 | { |
1451 | Q_D(QQuickMenu); |
1452 | QQuickPopup::componentComplete(); |
1453 | d->resizeItems(); |
1454 | } |
1455 | |
1456 | void QQuickMenu::(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 | |
1473 | void QQuickMenu::(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 | |
1487 | void QQuickMenu::(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 * = d->currentSubMenu()) { |
1520 | auto = 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 | |
1543 | void QQuickMenu::(QTimerEvent *event) |
1544 | { |
1545 | Q_D(QQuickMenu); |
1546 | if (event->timerId() == d->hoverTimer) { |
1547 | if (QQuickMenu * = d->currentSubMenu()) |
1548 | subMenu->open(); |
1549 | d->stopHoverTimer(); |
1550 | return; |
1551 | } |
1552 | QQuickPopup::timerEvent(event); |
1553 | } |
1554 | |
1555 | QFont QQuickMenu::() const |
1556 | { |
1557 | return QQuickTheme::font(scope: QQuickTheme::Menu); |
1558 | } |
1559 | |
1560 | #if QT_CONFIG(accessibility) |
1561 | QAccessible::Role QQuickMenu::() const |
1562 | { |
1563 | return QAccessible::PopupMenu; |
1564 | } |
1565 | #endif |
1566 | |
1567 | QT_END_NAMESPACE |
1568 | |
1569 | #include "moc_qquickmenu_p.cpp" |
1570 | |