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