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