summaryrefslogtreecommitdiffstats
path: root/src/controls/qquickmenu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/controls/qquickmenu.cpp')
-rw-r--r--src/controls/qquickmenu.cpp642
1 files changed, 642 insertions, 0 deletions
diff --git a/src/controls/qquickmenu.cpp b/src/controls/qquickmenu.cpp
new file mode 100644
index 000000000..6da054a8e
--- /dev/null
+++ b/src/controls/qquickmenu.cpp
@@ -0,0 +1,642 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickmenu_p.h"
+#include "qquickmenuitemcontainer_p.h"
+#include "qquickmenupopupwindow_p.h"
+
+#include <qdebug.h>
+#include <qabstractitemmodel.h>
+#include <qcursor.h>
+#include <private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformtheme.h>
+#include <QtGui/qpa/qplatformmenu.h>
+#include <qquickitem.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QQuickMenu
+ \internal
+ */
+
+/*!
+ \qmltype MenuPrivate
+ \instantiates QQuickMenu
+ \internal
+ \inqmlmodule QtQuick.Controls 1.0
+ */
+
+/*!
+ \qmlproperty list<Object> Menu::items
+ \default
+
+ The list of items in the menu.
+
+ \l Menu only accepts objects of type \l Menu, \l MenuItem, and \l MenuSeparator
+ as children. It also supports \l Instantiator objects as long as the insertion is
+ being done manually using \l insertItem().
+
+ \code
+ Menu {
+ id: recentFilesMenu
+
+ Instantiator {
+ model: recentFilesModel
+ MenuItem {
+ text: model.fileName
+ }
+ onObjectAdded: recentFilesMenu.insertItem(index, object)
+ onObjectRemoved: recentFilesMenu.removeItem(object)
+ }
+
+ MenuSeparator {
+ visible: recentFilesModel.count > 0
+ }
+
+ MenuItem {
+ text: "Clear menu"
+ enabled: recentFilesModel.count > 0
+ onTriggered: recentFilesModel.clear()
+ }
+ \endcode
+
+ Note that in this case, the \c index parameter passed to \l insertItem() is relative
+ to the position of the \l Instatiator in the menu, as opposed to absolute position
+ in the menu.
+
+ \sa MenuItem, MenuSeparator
+*/
+
+/*!
+ \qmlproperty bool Menu::visible
+
+ Whether the menu should be visible. This is only enabled when the menu is used as
+ a submenu. Its value defaults to \c true.
+*/
+
+/*!
+ \qmlproperty enumeration Menu::type
+
+ This property is read-only and constant, and its value is \l MenuItemType.Menu.
+*/
+
+/*!
+ \qmlproperty string Menu::title
+
+ Title for the menu as a submenu or in a menubar.
+
+ Mnemonics are supported by prefixing the shortcut letter with \&.
+ For instance, \c "\&File" will bind the \c Alt-F shortcut to the
+ \c "File" menu. Note that not all platforms support mnemonics.
+
+ Its value defaults to the empty string.
+*/
+
+/*!
+ \qmlproperty bool Menu::enabled
+
+ Whether the menu is enabled, and responsive to user interaction as a submenu.
+ Its value defaults to \c true.
+*/
+
+/*!
+ \qmlproperty url Menu::iconSource
+
+ Sets the icon file or resource url for the menu icon as a submenu.
+ Defaults to the empty URL.
+
+ \sa iconName
+*/
+
+/*!
+ \qmlproperty string Menu::iconName
+
+ Sets the icon name for the menu icon. This will pick the icon
+ with the given name from the current theme. Only works as a submenu.
+
+ Its value defaults to the empty string.
+
+ \sa iconSource
+*/
+
+/*!
+ \qmlmethod void Menu::popup()
+
+ Opens this menu under the mouse cursor.
+ It can block on some platforms, so test it accordingly.
+*/
+
+/*!
+ \qmlmethod MenuItem Menu::addItem(string text)
+
+ Adds an item to the menu. Returns the newly created \l MenuItem.
+
+ \sa insertItem(int before, string title)
+*/
+
+/*!
+ \qmlmethod MenuItem Menu::insertItem(int before, string title)
+
+ Creates and inserts an item with title \c title at the index \c before in the current menu.
+ Returns the newly created \l MenuItem.
+
+ \sa addItem()
+*/
+
+/*!
+ \qmlmethod void Menu::addSeparator()
+
+ Adds a separator to the menu.
+
+ \sa insertSeparator()
+*/
+
+/*!
+ \qmlmethod void Menu::insertSeparator(int before)
+
+ Creates and inserts a separator at the index \c before in the current menu.
+
+ \sa addSeparator()
+*/
+
+/*!
+ \qmlmethod Menu Menu::addMenu(string title)
+ Adds a submenu to the menu. Returns the newly created \l Menu.
+
+ \sa insertMenu()
+*/
+
+/*!
+ \qmlmethod MenuItem Menu::insertMenu(int before, string title)
+
+ Creates and inserts a submenu with title \c title at the index \c before in the current menu.
+ Returns the newly created \l Menu.
+
+ \sa addMenu()
+*/
+
+/*!
+ \qmlmethod void Menu::insertItem(int before, object item)
+
+ Inserts the \c item at the index \c before in the current menu.
+ In this case, \c item can be either a \l MenuItem, a \l MenuSeparator,
+ or a \l Menu.
+
+ \sa removeItem()
+*/
+
+/*!
+ \qmlmethod void Menu::removeItem(item)
+
+ Removes the \c item from the menu.
+ In this case, \c item can be either a \l MenuItem, a \l MenuSeparator,
+ or a \l Menu.
+
+ \sa insertItem()
+*/
+
+QQuickMenu::QQuickMenu(QObject *parent)
+ : QQuickMenuText(parent),
+ m_itemsCount(0),
+ m_selectedIndex(-1),
+ m_parentWindow(0),
+ m_minimumWidth(0),
+ m_popupWindow(0),
+ m_menuContentItem(0),
+ m_popupVisible(false),
+ m_containersCount(0)
+{
+ connect(this, SIGNAL(__textChanged()), this, SIGNAL(titleChanged()));
+
+ m_platformMenu = QGuiApplicationPrivate::platformTheme()->createPlatformMenu();
+ if (m_platformMenu) {
+ connect(m_platformMenu, SIGNAL(aboutToHide()), this, SLOT(__closeMenu()));
+ if (platformItem())
+ platformItem()->setMenu(m_platformMenu);
+ }
+}
+
+QQuickMenu::~QQuickMenu()
+{
+ delete m_platformMenu;
+ m_platformMenu = 0;
+}
+
+void QQuickMenu::updateText()
+{
+ if (m_platformMenu)
+ m_platformMenu->setText(this->text());
+ QQuickMenuText::updateText();
+}
+
+void QQuickMenu::setMinimumWidth(int w)
+{
+ if (w == m_minimumWidth)
+ return;
+
+ m_minimumWidth = w;
+ if (m_platformMenu)
+ m_platformMenu->setMinimumWidth(w);
+}
+
+void QQuickMenu::setFont(const QFont &arg)
+{
+ if (m_platformMenu)
+ m_platformMenu->setFont(arg);
+}
+
+void QQuickMenu::setSelectedIndex(int index)
+{
+ if (m_selectedIndex == index)
+ return;
+
+ m_selectedIndex = index;
+ emit __selectedIndexChanged();
+}
+
+void QQuickMenu::updateSelectedIndex()
+{
+ if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem*>(sender())) {
+ int index = indexOfMenuItem(menuItem);
+ setSelectedIndex(index);
+ }
+}
+
+QQuickMenuItems QQuickMenu::menuItems()
+{
+ return QQuickMenuItems(this, 0, &QQuickMenu::append_menuItems, &QQuickMenu::count_menuItems,
+ &QQuickMenu::at_menuItems, &QQuickMenu::clear_menuItems);
+}
+
+QQuickWindow *QQuickMenu::findParentWindow()
+{
+ if (!m_parentWindow) {
+ QQuickItem *parentAsItem = qobject_cast<QQuickItem *>(parent());
+ m_parentWindow = visualItem() ? visualItem()->window() : // Menu as menu item case
+ parentAsItem ? parentAsItem->window() : 0; //Menu as context menu/popup case
+ }
+ return m_parentWindow;
+}
+
+void QQuickMenu::popup()
+{
+ QPoint mousePos = QCursor::pos();
+ if (QQuickWindow *parentWindow = findParentWindow())
+ mousePos = parentWindow->mapFromGlobal(mousePos);
+
+ __popup(mousePos.x(), mousePos.y());
+}
+
+void QQuickMenu::__popup(qreal x, qreal y, int atItemIndex)
+{
+ if (popupVisible()) {
+ __closeMenu();
+ // Mac and Windows would normally move the menu under the cursor, so we should not
+ // return here. However, very often we want to re-contextualize the menu, and this
+ // has to be done at the application level.
+ return;
+ }
+
+ setPopupVisible(true);
+
+ QQuickMenuBase *atItem = menuItemAtIndex(atItemIndex);
+
+ QQuickWindow *parentWindow = findParentWindow();
+
+ if (m_platformMenu) {
+ QPointF screenPosition(x, y);
+ if (visualItem())
+ screenPosition = visualItem()->mapToScene(screenPosition);
+ m_platformMenu->showPopup(parentWindow, screenPosition.toPoint(), atItem ? atItem->platformItem() : 0);
+ } else {
+ m_popupWindow = new QQuickMenuPopupWindow();
+ if (visualItem())
+ m_popupWindow->setParentItem(visualItem());
+ else
+ m_popupWindow->setParentWindow(parentWindow);
+ m_popupWindow->setMenuContentItem(m_menuContentItem);
+ m_popupWindow->setItemAt(atItem ? atItem->visualItem() : 0);
+
+ connect(m_popupWindow, SIGNAL(visibleChanged(bool)), this, SLOT(windowVisibleChanged(bool)));
+
+ m_popupWindow->setPosition(x, y);
+ m_popupWindow->show();
+ }
+}
+
+void QQuickMenu::setMenuContentItem(QQuickItem *item)
+{
+ if (m_menuContentItem != item)
+ m_menuContentItem = item;
+}
+
+void QQuickMenu::setPopupVisible(bool v)
+{
+ if (m_popupVisible != v) {
+ m_popupVisible = v;
+ emit popupVisibleChanged();
+ }
+}
+
+void QQuickMenu::__closeMenu()
+{
+ setPopupVisible(false);
+ if (m_popupWindow)
+ m_popupWindow->setVisible(false);
+ m_parentWindow = 0;
+ emit __menuClosed();
+}
+
+void QQuickMenu::__dismissMenu()
+{
+ QQuickMenuPopupWindow *topMenuWindow = m_popupWindow;
+ while (topMenuWindow) {
+ QQuickMenuPopupWindow *pw = qobject_cast<QQuickMenuPopupWindow *>(topMenuWindow->transientParent());
+ if (!pw)
+ topMenuWindow->dismissMenu();
+ topMenuWindow = pw;
+ }
+}
+
+void QQuickMenu::windowVisibleChanged(bool v)
+{
+ if (!v) {
+ if (qobject_cast<QQuickMenuPopupWindow *>(m_popupWindow->transientParent())) {
+ m_popupWindow->transientParent()->setMouseGrabEnabled(true);
+ m_popupWindow->transientParent()->setKeyboardGrabEnabled(true);
+ }
+ m_popupWindow->deleteLater();
+ m_popupWindow = 0;
+ __closeMenu();
+ }
+}
+
+void QQuickMenu::itemIndexToListIndex(int itemIndex, int *listIndex, int *containerIndex) const
+{
+ *listIndex = -1;
+ QQuickMenuItemContainer *container = 0;
+ while (itemIndex >= 0 && ++*listIndex < m_menuItems.count())
+ if ((container = qobject_cast<QQuickMenuItemContainer *>(m_menuItems[*listIndex])))
+ itemIndex -= container->items().count();
+ else
+ --itemIndex;
+
+ if (container)
+ *containerIndex = container->items().count() + itemIndex;
+ else
+ *containerIndex = -1;
+}
+
+int QQuickMenu::itemIndexForListIndex(int listIndex) const
+{
+ int index = 0;
+ int i = 0;
+ while (i < listIndex && i < m_menuItems.count())
+ if (QQuickMenuItemContainer *container = qobject_cast<QQuickMenuItemContainer *>(m_menuItems[i++]))
+ index += container->items().count();
+ else
+ ++index;
+
+ return index;
+}
+
+QQuickMenuBase *QQuickMenu::nextMenuItem(QQuickMenu::MenuItemIterator *it) const
+{
+ if (it->containerIndex != -1) {
+ QQuickMenuItemContainer *container = qobject_cast<QQuickMenuItemContainer *>(m_menuItems[it->index]);
+ if (++it->containerIndex < container->items().count())
+ return container->items()[it->containerIndex];
+ }
+
+ if (++it->index < m_menuItems.count()) {
+ if (QQuickMenuItemContainer *container = qobject_cast<QQuickMenuItemContainer *>(m_menuItems[it->index])) {
+ it->containerIndex = 0;
+ return container->items()[0];
+ } else {
+ it->containerIndex = -1;
+ return m_menuItems[it->index];
+ }
+ }
+
+ return 0;
+}
+
+QQuickMenuBase *QQuickMenu::menuItemAtIndex(int index) const
+{
+ if (0 <= index && index < m_itemsCount) {
+ if (!m_containersCount) {
+ return m_menuItems[index];
+ } else if (m_containersCount == 1 && m_menuItems.count() == 1) {
+ QQuickMenuItemContainer *container = qobject_cast<QQuickMenuItemContainer *>(m_menuItems[0]);
+ return container->items()[index];
+ } else {
+ int containerIndex;
+ int i;
+ itemIndexToListIndex(index, &i, &containerIndex);
+ if (containerIndex != -1) {
+ QQuickMenuItemContainer *container = qobject_cast<QQuickMenuItemContainer *>(m_menuItems[i]);
+ return container->items()[containerIndex];
+ } else {
+ return m_menuItems[i];
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool QQuickMenu::contains(QQuickMenuBase *item)
+{
+ if (item->container())
+ return item->container()->items().contains(item);
+
+ return m_menuItems.contains(item);
+}
+
+int QQuickMenu::indexOfMenuItem(QQuickMenuBase *item) const
+{
+ if (!item)
+ return -1;
+ if (item->container()) {
+ int containerIndex = m_menuItems.indexOf(item->container());
+ if (containerIndex == -1)
+ return -1;
+ int index = item->container()->items().indexOf(item);
+ return index == -1 ? -1 : itemIndexForListIndex(containerIndex) + index;
+ } else {
+ int index = m_menuItems.indexOf(item);
+ return index == -1 ? -1 : itemIndexForListIndex(index);
+ }
+}
+
+QQuickMenuItem *QQuickMenu::addItem(QString title)
+{
+ return insertItem(m_itemsCount, title);
+}
+
+QQuickMenuItem *QQuickMenu::insertItem(int index, QString title)
+{
+ QQuickMenuItem *item = new QQuickMenuItem(this);
+ item->setText(title);
+ insertItem(index, item);
+ return item;
+}
+
+void QQuickMenu::addSeparator()
+{
+ insertSeparator(m_itemsCount);
+}
+
+void QQuickMenu::insertSeparator(int index)
+{
+ QQuickMenuSeparator *item = new QQuickMenuSeparator(this);
+ insertItem(index, item);
+}
+
+void QQuickMenu::insertItem(int index, QQuickMenuBase *menuItem)
+{
+ if (!menuItem)
+ return;
+ int itemIndex;
+ if (m_containersCount) {
+ QQuickMenuItemContainer *container = menuItem->parent() != this ? m_containers[menuItem->parent()] : 0;
+ if (container) {
+ container->insertItem(index, menuItem);
+ itemIndex = itemIndexForListIndex(m_menuItems.indexOf(container)) + index;
+ } else {
+ itemIndex = itemIndexForListIndex(index);
+ m_menuItems.insert(itemIndex, menuItem);
+ }
+ } else {
+ itemIndex = index;
+ m_menuItems.insert(index, menuItem);
+ }
+
+ setupMenuItem(menuItem, itemIndex);
+ emit itemsChanged();
+}
+
+void QQuickMenu::removeItem(QQuickMenuBase *menuItem)
+{
+ if (!menuItem)
+ return;
+ menuItem->setParentMenu(0);
+
+ QQuickMenuItemContainer *container = menuItem->parent() != this ? m_containers[menuItem->parent()] : 0;
+ if (container)
+ container->removeItem(menuItem);
+ else
+ m_menuItems.removeOne(menuItem);
+
+ --m_itemsCount;
+ emit itemsChanged();
+}
+
+void QQuickMenu::clear()
+{
+ m_containers.clear();
+ m_containersCount = 0;
+
+ while (!m_menuItems.empty())
+ delete m_menuItems.takeFirst();
+ m_itemsCount = 0;
+}
+
+void QQuickMenu::setupMenuItem(QQuickMenuBase *item, int platformIndex)
+{
+ item->setParentMenu(this);
+ if (m_platformMenu) {
+ QPlatformMenuItem *before = 0;
+ if (platformIndex != -1)
+ before = m_platformMenu->menuItemAt(platformIndex);
+ m_platformMenu->insertMenuItem(item->platformItem(), before);
+ }
+ ++m_itemsCount;
+}
+
+void QQuickMenu::append_menuItems(QQuickMenuItems *list, QObject *o)
+{
+ if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(list->object)) {
+ Q_ASSERT(o->parent() == menu);
+
+ if (QQuickMenuBase *menuItem = qobject_cast<QQuickMenuBase *>(o)) {
+ menu->m_menuItems.append(menuItem);
+ menu->setupMenuItem(menuItem);
+ } else {
+ QQuickMenuItemContainer *menuItemContainer = new QQuickMenuItemContainer(menu);
+ menu->m_menuItems.append(menuItemContainer);
+ menu->m_containers.insert(o, menuItemContainer);
+ menuItemContainer->setParentMenu(menu);
+ ++menu->m_containersCount;
+ foreach (QObject *child, o->children()) {
+ if (QQuickMenuBase *item = qobject_cast<QQuickMenuBase *>(child)) {
+ menuItemContainer->insertItem(-1, item);
+ menu->setupMenuItem(item);
+ }
+ }
+ }
+ }
+}
+
+int QQuickMenu::count_menuItems(QQuickMenuItems *list)
+{
+ if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(list->object))
+ return menu->m_itemsCount;
+
+ return 0;
+}
+
+QObject *QQuickMenu::at_menuItems(QQuickMenuItems *list, int index)
+{
+ if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(list->object))
+ return menu->menuItemAtIndex(index);
+
+ return 0;
+}
+
+void QQuickMenu::clear_menuItems(QQuickMenuItems *list)
+{
+ if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(list->object))
+ menu->clear();
+}
+
+QT_END_NAMESPACE