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