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