| 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 | |