1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Quick Controls module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qquickmenu_p.h"
41 #include "qquickmenubar_p.h"
42 #include "qquickmenuitemcontainer_p.h"
43 #include "qquickmenupopupwindow_p.h"
44 
45 #include <qdebug.h>
46 #include <qabstractitemmodel.h>
47 #include <qcursor.h>
48 #include <private/qhighdpiscaling_p.h>
49 #include <private/qguiapplication_p.h>
50 #include <QtGui/qpa/qplatformtheme.h>
51 #include <QtGui/qpa/qplatformmenu.h>
52 #include <qquickitem.h>
53 #include <QtQuick/QQuickRenderControl>
54 
55 QT_BEGIN_NAMESPACE
56 
57 /*!
58   \class QQuickMenu1
59   \internal
60  */
61 
62 /*!
63   \qmltype MenuPrivate
64   \instantiates QQuickMenu1
65   \internal
66   \inqmlmodule QtQuick.Controls
67  */
68 
69 /*!
70     \qmlproperty list<Object> Menu::items
71     \default
72 
73     The list of items in the menu.
74 
75     \l Menu only accepts objects of type \l Menu, \l MenuItem, and \l MenuSeparator
76     as children. It also supports \l Instantiator objects as long as the insertion is
77     being done manually using \l insertItem().
78 
79     \qml
80     Menu {
81         id: recentFilesMenu
82 
83         Instantiator {
84             model: recentFilesModel
85             MenuItem {
86                 text: model.fileName
87             }
88             onObjectAdded: recentFilesMenu.insertItem(index, object)
89             onObjectRemoved: recentFilesMenu.removeItem(object)
90         }
91 
92         MenuSeparator {
93             visible: recentFilesModel.count > 0
94         }
95 
96         MenuItem {
97             text: "Clear menu"
98             enabled: recentFilesModel.count > 0
99             onTriggered: recentFilesModel.clear()
100         }
101     }
102     \endqml
103 
104     Note that in this case, the \c index parameter passed to \l insertItem() is relative
105     to the position of the \l Instantiator in the menu, as opposed to absolute position
106     in the menu.
107 
108     \sa MenuItem, MenuSeparator
109 */
110 
111 /*!
112     \qmlproperty bool Menu::visible
113 
114     Whether the menu should be visible as a submenu of another Menu, or as a menu on a MenuBar.
115     Its value defaults to \c true.
116 
117     \note This has nothing to do with the actual menu pop-up window being visible. Use
118     \l aboutToShow() and \l aboutToHide() if you need to know when the pop-up window will
119     be shown or hidden.
120 */
121 
122 /*!
123     \qmlproperty enumeration Menu::type
124 
125     This property is read-only and constant, and its value is \l {QtQuick.Controls::MenuItem::}{type}.
126 */
127 
128 /*!
129     \qmlproperty string Menu::title
130 
131     Title for the menu as a submenu or in a menubar.
132 
133     Mnemonics are supported by prefixing the shortcut letter with \&.
134     For instance, \c "\&File" will bind the \c Alt-F shortcut to the
135     \c "File" menu. Note that not all platforms support mnemonics.
136 
137     Its value defaults to an empty string.
138 */
139 
140 /*!
141     \qmlproperty bool Menu::enabled
142 
143     Whether the menu is enabled, and responsive to user interaction as a submenu.
144     Its value defaults to \c true.
145 */
146 
147 /*!
148     \qmlproperty url Menu::iconSource
149 
150     Sets the icon file or resource url for the menu icon as a submenu.
151     Defaults to an empty URL.
152 
153     \sa iconName
154 */
155 
156 /*!
157     \qmlproperty string Menu::iconName
158 
159     Sets the icon name for the menu icon. This will pick the icon
160     with the given name from the current theme. Only works as a submenu.
161 
162     Its value defaults to an empty string.
163 
164     \sa iconSource
165 */
166 
167 /*!
168     \qmlmethod void Menu::popup()
169 
170     Opens this menu under the mouse cursor.
171     It can block on some platforms, so test it accordingly.
172 */
173 
174 /*!
175     \qmlmethod MenuItem Menu::addItem(string text)
176 
177     Adds a \a text item to the menu. Returns the newly created \l MenuItem.
178 
179     \sa insertItem()
180 */
181 
182 /*!
183     \qmlmethod MenuItem Menu::insertItem(int before, string title)
184 
185     Creates and inserts an item with title \a title at the index \a before in the current menu.
186     Returns the newly created \l MenuItem.
187 
188     \sa addItem()
189 */
190 
191 /*!
192     \qmlmethod void Menu::addSeparator()
193 
194     Adds a separator to the menu.
195 
196     \sa insertSeparator()
197 */
198 
199 /*!
200     \qmlmethod void Menu::insertSeparator(int before)
201 
202     Creates and inserts a separator at the index \a before in the current menu.
203 
204     \sa addSeparator()
205 */
206 
207 /*!
208     \qmlmethod Menu Menu::addMenu(string title)
209     Adds a submenu with a title \a title to the menu. Returns the newly created \l Menu.
210 
211     \sa insertMenu()
212 */
213 
214 /*!
215     \qmlmethod MenuItem Menu::insertMenu(int before, string title)
216 
217     Creates and inserts a submenu with a title \a title at the index \a before in the current menu.
218     Returns the newly created \l Menu.
219 
220     \sa addMenu()
221 */
222 
223 /*!
224     \qmlmethod void Menu::insertItem(int before, object item)
225 
226     Inserts the \a item at the index \a before in the current menu.
227     In this case, \c item can be either a \l MenuItem, a \l MenuSeparator,
228     or a \l Menu.
229 
230     \sa removeItem()
231 */
232 
233 /*!
234     \qmlmethod void Menu::removeItem(item)
235 
236     Removes the \a item from the menu.
237     In this case, \a item can be either a \l MenuItem, a \l MenuSeparator,
238     or a \l Menu.
239 
240     \sa insertItem()
241 */
242 
243 
244 /*!
245     \qmlsignal Menu::aboutToShow()
246     \since QtQuick.Controls 1.4
247 
248     This signal is emitted just before the menu is shown to the user.
249 
250     \sa aboutToHide()
251 */
252 
253 /*!
254     \qmlsignal Menu::aboutToHide()
255     \since QtQuick.Controls 1.4
256 
257     This signal is emitted just before the menu is hidden from the user.
258 
259     \sa aboutToShow()
260 */
261 
QQuickMenu1(QObject * parent)262 QQuickMenu1::QQuickMenu1(QObject *parent)
263     : QQuickMenuText1(parent, QQuickMenuItemType1::Menu),
264       m_platformMenu(0),
265       m_itemsCount(0),
266       m_selectedIndex(-1),
267       m_parentWindow(0),
268       m_minimumWidth(0),
269       m_popupWindow(0),
270       m_menuContentItem(0),
271       m_popupVisible(false),
272       m_containersCount(0),
273       m_xOffset(0),
274       m_yOffset(0),
275       m_triggerCount(0),
276       m_proxy(false)
277 {
278     connect(this, SIGNAL(__textChanged()), this, SIGNAL(titleChanged()));
279 
280     if (QGuiApplication::platformName() != QStringLiteral("xcb")) { // QTBUG-51372
281         m_platformMenu = QGuiApplicationPrivate::platformTheme()->createPlatformMenu();
282         if (m_platformMenu) {
283             connect(m_platformMenu, SIGNAL(aboutToShow()), this, SIGNAL(aboutToShow()));
284             connect(m_platformMenu, SIGNAL(aboutToHide()), this, SLOT(hideMenu()));
285             if (platformItem())
286                 platformItem()->setMenu(m_platformMenu);
287         }
288     }
289     if (const QFont *font = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::MenuItemFont))
290         m_font = *const_cast<QFont*>(font);
291 }
292 
~QQuickMenu1()293 QQuickMenu1::~QQuickMenu1()
294 {
295     while (!m_menuItems.empty()) {
296         QQuickMenuBase1 *item = m_menuItems.takeFirst();
297         if (item)
298             item->setParentMenu(0);
299     }
300 
301     if (platformItem())
302         platformItem()->setMenu(0);
303 
304     delete m_platformMenu;
305     m_platformMenu = 0;
306 }
307 
syncParentMenuBar()308 void QQuickMenu1::syncParentMenuBar()
309 {
310     QQuickMenuBar1 *menubar = qobject_cast<QQuickMenuBar1 *>(parent());
311     if (menubar && menubar->platformMenuBar())
312         menubar->platformMenuBar()->syncMenu(m_platformMenu);
313 }
314 
setVisible(bool v)315 void QQuickMenu1::setVisible(bool v)
316 {
317     QQuickMenuBase1::setVisible(v);
318     if (m_platformMenu) {
319         m_platformMenu->setVisible(v);
320         syncParentMenuBar();
321     }
322 }
323 
setEnabled(bool e)324 void QQuickMenu1::setEnabled(bool e)
325 {
326     QQuickMenuText1::setEnabled(e);
327     if (m_platformMenu) {
328         m_platformMenu->setEnabled(e);
329         syncParentMenuBar();
330     }
331 }
332 
updateText()333 void QQuickMenu1::updateText()
334 {
335     if (m_platformMenu)
336         m_platformMenu->setText(this->text());
337     QQuickMenuText1::updateText();
338 }
339 
setMinimumWidth(int w)340 void QQuickMenu1::setMinimumWidth(int w)
341 {
342     if (w == m_minimumWidth)
343         return;
344 
345     m_minimumWidth = w;
346     if (m_platformMenu)
347         m_platformMenu->setMinimumWidth(w);
348 
349     emit minimumWidthChanged();
350 }
351 
setFont(const QFont & arg)352 void QQuickMenu1::setFont(const QFont &arg)
353 {
354     if (arg == m_font)
355         return;
356 
357     m_font = arg;
358     if (m_platformMenu)
359         m_platformMenu->setFont(arg);
360 }
361 
setXOffset(qreal x)362 void QQuickMenu1::setXOffset(qreal x)
363 {
364     m_xOffset = x;
365 }
366 
setYOffset(qreal y)367 void QQuickMenu1::setYOffset(qreal y)
368 {
369     m_yOffset = y;
370 }
371 
setSelectedIndex(int index)372 void QQuickMenu1::setSelectedIndex(int index)
373 {
374     if (m_selectedIndex == index)
375         return;
376 
377     m_selectedIndex = index;
378     emit __selectedIndexChanged();
379 }
380 
updateSelectedIndex()381 void QQuickMenu1::updateSelectedIndex()
382 {
383     if (QQuickMenuItem1 *menuItem = qobject_cast<QQuickMenuItem1*>(sender())) {
384         int index = indexOfMenuItem(menuItem);
385         setSelectedIndex(index);
386     }
387 }
388 
menuItems()389 QQuickMenuItems QQuickMenu1::menuItems()
390 {
391     return QQuickMenuItems(this, 0, &QQuickMenu1::append_menuItems, &QQuickMenu1::count_menuItems,
392                        &QQuickMenu1::at_menuItems, &QQuickMenu1::clear_menuItems);
393 }
394 
findParentWindow()395 QQuickWindow *QQuickMenu1::findParentWindow()
396 {
397     if (!m_parentWindow) {
398         QQuickItem *parentAsItem = qobject_cast<QQuickItem *>(parent());
399         m_parentWindow = visualItem() ? visualItem()->window() :    // Menu as menu item case
400                          parentAsItem ? parentAsItem->window() : 0; //Menu as context menu/popup case
401     }
402     return m_parentWindow;
403 }
404 
popup()405 void QQuickMenu1::popup()
406 {
407     QQuickWindow *quickWindow = findParentWindow();
408     QPoint renderOffset;
409     QWindow *renderWindow = QQuickRenderControl::renderWindowFor(quickWindow, &renderOffset);
410     QWindow *parentWindow = renderWindow ? renderWindow : quickWindow;
411     QScreen *screen = parentWindow ? parentWindow->screen() : qGuiApp->primaryScreen();
412     QPoint mousePos = QCursor::pos(screen);
413 
414     if (mousePos.x() == int(qInf())) {
415         // ### fixme: no mouse pos registered. Get pos from touch...
416         mousePos = screen->availableGeometry().center();
417     }
418 
419     if (parentWindow)
420         mousePos = parentWindow->mapFromGlobal(mousePos);
421 
422     __popup(QRectF(mousePos.x() - renderOffset.x(), mousePos.y() - renderOffset.y(), 0, 0));
423 }
424 
__popup(const QRectF & targetRect,int atItemIndex,MenuType menuType)425 void QQuickMenu1::__popup(const QRectF &targetRect, int atItemIndex, MenuType menuType)
426 {
427     if (popupVisible()) {
428         hideMenu();
429         // Mac and Windows would normally move the menu under the cursor, so we should not
430         // return here. However, very often we want to re-contextualize the menu, and this
431         // has to be done at the application level.
432         return;
433     }
434 
435     setPopupVisible(true);
436 
437     QQuickMenuBase1 *atItem = menuItemAtIndex(atItemIndex);
438 
439     QQuickWindow *quickWindow = findParentWindow();
440     QPoint renderOffset;
441     QWindow *renderWindow = QQuickRenderControl::renderWindowFor(quickWindow, &renderOffset);
442     QWindow *parentWindow = renderWindow ? renderWindow : quickWindow;
443     // parentWindow may not be a QQuickWindow (happens when using QQuickWidget)
444 
445     if (m_platformMenu) {
446         if (m_windowConnection)
447             QObject::disconnect(m_windowConnection);
448         m_windowConnection = connect(parentWindow, &QWindow::visibleChanged, this,
449                                      &QQuickMenu1::platformMenuWindowVisibleChanged, Qt::UniqueConnection);
450         QRectF globalTargetRect = targetRect.translated(m_xOffset, m_yOffset);
451         if (visualItem()) {
452             if (qGuiApp->isRightToLeft()) {
453                 qreal w = qMax(static_cast<qreal>(m_minimumWidth), m_menuContentItem->width());
454                 globalTargetRect.moveLeft(w - targetRect.x() - targetRect.width());
455             }
456             globalTargetRect = visualItem()->mapRectToScene(globalTargetRect);
457         }
458         globalTargetRect.translate(renderOffset);
459         m_platformMenu->setMenuType(QPlatformMenu::MenuType(menuType));
460         m_platformMenu->showPopup(parentWindow,
461                                   QHighDpi::toNativePixels(globalTargetRect.toRect(), parentWindow),
462                                   atItem ? atItem->platformItem() : 0);
463     } else {
464         m_popupWindow = new QQuickMenuPopupWindow1(this);
465         if (visualItem())
466             m_popupWindow->setParentItem(visualItem());
467         else
468             m_popupWindow->setParentWindow(parentWindow, quickWindow);
469         m_popupWindow->setPopupContentItem(m_menuContentItem);
470         m_popupWindow->setItemAt(atItem ? atItem->visualItem() : 0);
471 
472         connect(m_popupWindow, SIGNAL(visibleChanged(bool)), this, SLOT(windowVisibleChanged(bool)));
473         connect(m_popupWindow, SIGNAL(geometryChanged()), this, SIGNAL(__popupGeometryChanged()));
474         connect(m_popupWindow, SIGNAL(willBeDeletedLater()), this, SLOT(clearPopupWindow()));
475 
476         m_popupWindow->setPosition(targetRect.x() + m_xOffset + renderOffset.x(),
477                                    targetRect.y() + targetRect.height() + m_yOffset + renderOffset.y());
478         emit aboutToShow();
479         m_popupWindow->show();
480     }
481 }
482 
setMenuContentItem(QQuickItem * item)483 void QQuickMenu1::setMenuContentItem(QQuickItem *item)
484 {
485     if (m_menuContentItem != item) {
486         m_menuContentItem = item;
487         emit menuContentItemChanged();
488     }
489 }
490 
setPopupVisible(bool v)491 void QQuickMenu1::setPopupVisible(bool v)
492 {
493     if (m_popupVisible != v) {
494         m_popupVisible = v;
495         emit popupVisibleChanged();
496     }
497 }
498 
popupGeometry() const499 QRect QQuickMenu1::popupGeometry() const
500 {
501     if (!m_popupWindow || !m_popupVisible)
502         return QRect();
503 
504     return m_popupWindow->geometry();
505 }
506 
prepareItemTrigger(QQuickMenuItem1 *)507 void QQuickMenu1::prepareItemTrigger(QQuickMenuItem1 *)
508 {
509     m_triggerCount++;
510     __dismissMenu();
511 }
512 
concludeItemTrigger(QQuickMenuItem1 *)513 void QQuickMenu1::concludeItemTrigger(QQuickMenuItem1 *)
514 {
515     if (--m_triggerCount == 0)
516         destroyAllMenuPopups();
517 }
518 
519 /*!
520  * \internal
521  * Close this menu's popup window. Emits aboutToHide and sets __popupVisible to false.
522  */
hideMenu()523 void QQuickMenu1::hideMenu()
524 {
525     if (m_popupVisible) {
526         emit aboutToHide();
527         setPopupVisible(false);
528     }
529     if (m_popupWindow && m_popupWindow->isVisible())
530         m_popupWindow->hide();
531     m_parentWindow = 0;
532 }
533 
topMenuPopup() const534 QQuickMenuPopupWindow1 *QQuickMenu1::topMenuPopup() const
535 {
536     QQuickMenuPopupWindow1 *topMenuWindow = m_popupWindow;
537     while (topMenuWindow) {
538         QQuickMenuPopupWindow1 *pw = qobject_cast<QQuickMenuPopupWindow1 *>(topMenuWindow->transientParent());
539         if (!pw)
540             return topMenuWindow;
541         topMenuWindow = pw;
542     }
543 
544     return 0;
545 }
546 
547 /*!
548  * \internal
549  * Dismiss all the menus this menu is attached to, bottom-up.
550  * In QQuickPopupWindow, before closing, dismissPopup() emits popupDismissed()
551  * which is connected to dismissPopup() on any child popup.
552  */
__dismissMenu()553 void QQuickMenu1::__dismissMenu()
554 {
555     if (m_platformMenu) {
556         m_platformMenu->dismiss();
557     } else if (QQuickMenuPopupWindow1 *topPopup = topMenuPopup()) {
558         topPopup->dismissPopup();
559     }
560 }
561 
562 /*!
563  * \internal
564  * Called when the popup window visible property changes.
565  */
windowVisibleChanged(bool v)566 void QQuickMenu1::windowVisibleChanged(bool v)
567 {
568     if (!v) {
569         if (m_popupWindow) {
570             QQuickMenuPopupWindow1 *parentMenuPopup = qobject_cast<QQuickMenuPopupWindow1 *>(m_popupWindow->transientParent());
571             if (parentMenuPopup) {
572                 parentMenuPopup->setMouseGrabEnabled(true);
573                 parentMenuPopup->setKeyboardGrabEnabled(true);
574             }
575         }
576         if (m_popupVisible)
577             __closeAndDestroy();
578     }
579 }
580 
platformMenuWindowVisibleChanged(bool visible)581 void QQuickMenu1::platformMenuWindowVisibleChanged(bool visible)
582 {
583     if (!visible) {
584         if (m_windowConnection) {
585             QObject::disconnect(m_windowConnection);
586             m_windowConnection = QMetaObject::Connection();
587         }
588         if (m_platformMenu) {
589             m_platformMenu->dismiss();
590         }
591     }
592 }
593 
clearPopupWindow()594 void QQuickMenu1::clearPopupWindow()
595 {
596     m_popupWindow = 0;
597     emit __menuPopupDestroyed();
598 }
599 
destroyMenuPopup()600 void QQuickMenu1::destroyMenuPopup()
601 {
602     if (m_triggerCount > 0)
603         return;
604     if (m_popupWindow)
605         m_popupWindow->setToBeDeletedLater();
606 }
607 
destroyAllMenuPopups()608 void QQuickMenu1::destroyAllMenuPopups() {
609     if (m_triggerCount > 0)
610         return;
611     QQuickMenuPopupWindow1 *popup = topMenuPopup();
612     if (popup)
613         popup->setToBeDeletedLater();
614 }
615 
menuBar()616 QQuickMenuBar1 *QQuickMenu1::menuBar()
617 {
618     QObject *pi = parentMenuOrMenuBar();
619     while (pi) {
620         if (QQuickMenuBar1 *menuBar = qobject_cast<QQuickMenuBar1*>(pi))
621             return menuBar;
622         else if (QQuickMenu1 *menu = qobject_cast<QQuickMenu1*>(pi))
623             pi = menu->parentMenuOrMenuBar();
624         else
625             return 0;
626     }
627     return 0;
628 }
629 
__closeAndDestroy()630 void QQuickMenu1::__closeAndDestroy()
631 {
632     hideMenu();
633     destroyMenuPopup();
634 }
635 
__dismissAndDestroy()636 void QQuickMenu1::__dismissAndDestroy()
637 {
638     if (m_platformMenu)
639         return;
640 
641     __dismissMenu();
642     destroyAllMenuPopups();
643 }
644 
itemIndexToListIndex(int itemIndex,int * listIndex,int * containerIndex) const645 void QQuickMenu1::itemIndexToListIndex(int itemIndex, int *listIndex, int *containerIndex) const
646 {
647     *listIndex = -1;
648     QQuickMenuItemContainer1 *container = 0;
649     while (itemIndex >= 0 && ++*listIndex < m_menuItems.count())
650         if ((container = qobject_cast<QQuickMenuItemContainer1 *>(m_menuItems[*listIndex])))
651             itemIndex -= container->items().count();
652         else
653             --itemIndex;
654 
655     if (container)
656         *containerIndex = container->items().count() + itemIndex;
657     else
658         *containerIndex = -1;
659 }
660 
itemIndexForListIndex(int listIndex) const661 int QQuickMenu1::itemIndexForListIndex(int listIndex) const
662 {
663     int index = 0;
664     int i = 0;
665     while (i < listIndex && i < m_menuItems.count())
666         if (QQuickMenuItemContainer1 *container = qobject_cast<QQuickMenuItemContainer1 *>(m_menuItems[i++]))
667             index += container->items().count();
668         else
669             ++index;
670 
671     return index;
672 }
673 
nextMenuItem(QQuickMenu1::MenuItemIterator * it) const674 QQuickMenuBase1 *QQuickMenu1::nextMenuItem(QQuickMenu1::MenuItemIterator *it) const
675 {
676     if (it->containerIndex != -1) {
677         QQuickMenuItemContainer1 *container = qobject_cast<QQuickMenuItemContainer1 *>(m_menuItems[it->index]);
678         if (++it->containerIndex < container->items().count())
679             return container->items()[it->containerIndex];
680     }
681 
682     if (++it->index < m_menuItems.count()) {
683         if (QQuickMenuItemContainer1 *container = qobject_cast<QQuickMenuItemContainer1 *>(m_menuItems[it->index])) {
684             it->containerIndex = 0;
685             return container->items()[0];
686         } else {
687             it->containerIndex = -1;
688             return m_menuItems[it->index];
689         }
690     }
691 
692     return 0;
693 }
694 
menuItemAtIndex(int index) const695 QQuickMenuBase1 *QQuickMenu1::menuItemAtIndex(int index) const
696 {
697     if (0 <= index && index < m_itemsCount) {
698         if (!m_containersCount) {
699             return m_menuItems[index];
700         } else if (m_containersCount == 1 && m_menuItems.count() == 1) {
701             QQuickMenuItemContainer1 *container = qobject_cast<QQuickMenuItemContainer1 *>(m_menuItems[0]);
702             return container->items()[index];
703         } else {
704             int containerIndex;
705             int i;
706             itemIndexToListIndex(index, &i, &containerIndex);
707             if (containerIndex != -1) {
708                 QQuickMenuItemContainer1 *container = qobject_cast<QQuickMenuItemContainer1 *>(m_menuItems[i]);
709                 return container->items()[containerIndex];
710             } else {
711                 return m_menuItems[i];
712             }
713         }
714     }
715 
716     return 0;
717 }
718 
contains(QQuickMenuBase1 * item)719 bool QQuickMenu1::contains(QQuickMenuBase1 *item)
720 {
721     if (item->container())
722         return item->container()->items().contains(item);
723 
724     return m_menuItems.contains(item);
725 }
726 
indexOfMenuItem(QQuickMenuBase1 * item) const727 int QQuickMenu1::indexOfMenuItem(QQuickMenuBase1 *item) const
728 {
729     if (!item)
730         return -1;
731     if (item->container()) {
732         int containerIndex = m_menuItems.indexOf(item->container());
733         if (containerIndex == -1)
734             return -1;
735         int index = item->container()->items().indexOf(item);
736         return index == -1 ? -1 : itemIndexForListIndex(containerIndex) + index;
737     } else {
738         int index = m_menuItems.indexOf(item);
739         return index == -1 ? -1 : itemIndexForListIndex(index);
740     }
741 }
742 
addItem(const QString & title)743 QQuickMenuItem1 *QQuickMenu1::addItem(const QString &title)
744 {
745     return insertItem(m_itemsCount, title);
746 }
747 
insertItem(int index,const QString & title)748 QQuickMenuItem1 *QQuickMenu1::insertItem(int index, const QString &title)
749 {
750     QQuickMenuItem1 *item = new QQuickMenuItem1(this);
751     item->setText(title);
752     insertItem(index, item);
753     return item;
754 }
755 
addSeparator()756 void QQuickMenu1::addSeparator()
757 {
758     insertSeparator(m_itemsCount);
759 }
760 
insertSeparator(int index)761 void QQuickMenu1::insertSeparator(int index)
762 {
763     QQuickMenuSeparator1 *item = new QQuickMenuSeparator1(this);
764     insertItem(index, item);
765 }
766 
insertItem(int index,QQuickMenuBase1 * menuItem)767 void QQuickMenu1::insertItem(int index, QQuickMenuBase1 *menuItem)
768 {
769     if (!menuItem)
770         return;
771     int itemIndex;
772     if (m_containersCount) {
773         QQuickMenuItemContainer1 *container = menuItem->parent() != this ? m_containers[menuItem->parent()] : 0;
774         if (container) {
775             container->insertItem(index, menuItem);
776             itemIndex = itemIndexForListIndex(m_menuItems.indexOf(container)) + index;
777         } else {
778             itemIndex = itemIndexForListIndex(index);
779             m_menuItems.insert(itemIndex, menuItem);
780         }
781     } else {
782         itemIndex = index;
783         m_menuItems.insert(index, menuItem);
784     }
785 
786     setupMenuItem(menuItem, itemIndex);
787     emit itemsChanged();
788 }
789 
removeItem(QQuickMenuBase1 * menuItem)790 void QQuickMenu1::removeItem(QQuickMenuBase1 *menuItem)
791 {
792     // Removes the item, but if it's a container, the container is kept
793     if (menuItem) {
794         unparentItem(menuItem);
795         emit itemsChanged();
796     }
797 }
798 
clear()799 void QQuickMenu1::clear()
800 {
801     if (m_itemsCount > 0) {
802         while (m_itemsCount > 0)
803             unparentItem(menuItemAtIndex(0));
804 
805         // We can delete the containers now, as there cannot be any further items in them.
806         qDeleteAll(m_containers);
807         m_containers.clear();
808         m_containersCount = 0;
809 
810         // The containers are also kept in m_menuItems, so we have to clear explicitly.
811         m_menuItems.clear();
812 
813         emit itemsChanged();
814     }
815 }
816 
unparentItem(QQuickMenuBase1 * menuItem)817 void QQuickMenu1::unparentItem(QQuickMenuBase1 *menuItem)
818 {
819     menuItem->setParentMenu(nullptr);
820     QQuickMenuItemContainer1 *container = (menuItem->parent() != this)
821             ? m_containers[menuItem->parent()] : nullptr;
822     if (container)
823         container->removeItem(menuItem);
824     else
825         m_menuItems.removeOne(menuItem);
826     --m_itemsCount;
827 }
828 
setupMenuItem(QQuickMenuBase1 * item,int platformIndex)829 void QQuickMenu1::setupMenuItem(QQuickMenuBase1 *item, int platformIndex)
830 {
831     item->setParentMenu(this);
832     if (m_platformMenu) {
833         QPlatformMenuItem *before = 0;
834         if (platformIndex != -1)
835             before = m_platformMenu->menuItemAt(platformIndex);
836         m_platformMenu->insertMenuItem(item->platformItem(), before);
837     }
838     ++m_itemsCount;
839 }
840 
append_menuItems(QQuickMenuItems * list,QObject * o)841 void QQuickMenu1::append_menuItems(QQuickMenuItems *list, QObject *o)
842 {
843     if (QQuickMenu1 *menu = qobject_cast<QQuickMenu1 *>(list->object)) {
844         if (QQuickMenuBase1 *menuItem = qobject_cast<QQuickMenuBase1 *>(o)) {
845             menu->m_menuItems.append(menuItem);
846             menu->setupMenuItem(menuItem);
847         } else {
848             QQuickMenuItemContainer1 *menuItemContainer = new QQuickMenuItemContainer1(menu);
849             menu->m_menuItems.append(menuItemContainer);
850             menu->m_containers.insert(o, menuItemContainer);
851             menuItemContainer->setParentMenu(menu);
852             ++menu->m_containersCount;
853             const auto children = o->children();
854             for (QObject *child : children) {
855                 if (QQuickMenuBase1 *item = qobject_cast<QQuickMenuBase1 *>(child)) {
856                     menuItemContainer->insertItem(-1, item);
857                     menu->setupMenuItem(item);
858                 }
859             }
860         }
861     }
862 }
863 
count_menuItems(QQuickMenuItems * list)864 int QQuickMenu1::count_menuItems(QQuickMenuItems *list)
865 {
866     if (QQuickMenu1 *menu = qobject_cast<QQuickMenu1 *>(list->object))
867         return menu->m_itemsCount;
868 
869     return 0;
870 }
871 
at_menuItems(QQuickMenuItems * list,int index)872 QObject *QQuickMenu1::at_menuItems(QQuickMenuItems *list, int index)
873 {
874     if (QQuickMenu1 *menu = qobject_cast<QQuickMenu1 *>(list->object))
875         return menu->menuItemAtIndex(index);
876 
877     return 0;
878 }
879 
clear_menuItems(QQuickMenuItems * list)880 void QQuickMenu1::clear_menuItems(QQuickMenuItems *list)
881 {
882     if (QQuickMenu1 *menu = qobject_cast<QQuickMenu1 *>(list->object)) {
883         // There may be stray containers that don't appear in m_menuItems. This is because we may
884         // remove a container with removeItem(), which will only remove it from m_menuItems.
885         // Therefore, make sure that all containers are removed from m_menuItems first.
886         for (QQuickMenuItemContainer1 *container : menu->m_containers)
887             menu->m_menuItems.removeOne(container);
888 
889         // Delete or unparent the items first. They may have references to the containers.
890         // QTBUG-48927: a proxy menu (ApplicationWindowStyle.qml) must not
891         // delete its items, because they are owned by the menubar
892         // We still do own the containers, though. We create them on append_menuItems, after all.
893         while (!menu->m_menuItems.empty()) {
894             if (menu->m_proxy)
895                 menu->unparentItem(menu->m_menuItems[0]);
896             else
897                 delete menu->m_menuItems.takeFirst();
898         }
899         menu->m_menuItems.clear();
900 
901         qDeleteAll(menu->m_containers);
902         menu->m_containers.clear();
903         menu->m_containersCount = 0;
904 
905         menu->m_itemsCount = 0;
906     }
907 }
908 
909 QT_END_NAMESPACE
910