diff options
author | Mitch Curtis <mitch.curtis@theqtcompany.com> | 2015-11-16 16:06:07 +0100 |
---|---|---|
committer | J-P Nurmi <jpnurmi@theqtcompany.com> | 2015-12-02 16:16:31 +0000 |
commit | b185fc1ac02d4887d2b187a4043b1fdedb95305e (patch) | |
tree | 9650c29786a86d7272541702d59cfa285216f7f0 | |
parent | 5823d6230f5b1fbbd27c3b00b334e062a21b3d65 (diff) |
Add Menu
An item-based menu derived from QQuickPanel. Eventually we'd like to
make Panel itself a QQuickItem, as it makes both the implementation
and the actual usage of Menu a lot easier.
Change-Id: Ic1bf2a05ab98d9e17824c402ed8326ef65d26c69
Reviewed-by: J-P Nurmi <jpnurmi@theqtcompany.com>
25 files changed, 1664 insertions, 0 deletions
diff --git a/src/imports/controls/Menu.qml b/src/imports/controls/Menu.qml new file mode 100644 index 00000000..f4a4243e --- /dev/null +++ b/src/imports/controls/Menu.qml @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import Qt.labs.controls 1.0 +import Qt.labs.templates 1.0 as T + +T.Menu { + id: control + + //! [contentItem] + contentItem: ListView { + implicitWidth: 200 + implicitHeight: Math.min(contentHeight, 200) + model: control.contentModel + // TODO: improve this? + interactive: ApplicationWindow.window ? contentHeight > ApplicationWindow.window.height : false + clip: true + keyNavigationWraps: false + currentIndex: -1 + + ScrollIndicator.vertical: ScrollIndicator {} + + Rectangle { + width: parent.width + height: parent.height + color: "#ffffff" + border.color: "#353637" + z: -1 + } + } + //! [contentItem] +} diff --git a/src/imports/controls/MenuItem.qml b/src/imports/controls/MenuItem.qml new file mode 100644 index 00000000..df51fc72 --- /dev/null +++ b/src/imports/controls/MenuItem.qml @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import Qt.labs.templates 1.0 as T + +T.MenuItem { + id: control + + implicitWidth: background ? background.implicitWidth + : (label ? label.implicitWidth : 0) + (indicator ? indicator.implicitWidth : 0) + + (label && indicator ? spacing : 0) + leftPadding + rightPadding + implicitHeight: background ? background.implicitHeight + : (label ? label.implicitHeight : 0) + (indicator ? indicator.implicitHeight : 0) + topPadding + bottomPadding + + padding: 12 + spacing: 12 + + //! [label] + label: Text { + x: control.mirrored ? control.width - width - control.rightPadding : control.leftPadding + y: control.topPadding + width: control.availableWidth - (control.checkable ? indicator.width + control.spacing : 0) + height: control.availableHeight + + text: control.text + font: control.font + color: control.enabled ? "#26282a" : "#bdbebf" + elide: Text.ElideRight + visible: control.text + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + //! [label] + + //! [indicator] + indicator: Image { + x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding + y: control.topPadding + (control.availableHeight - height) / 2 + + visible: control.checked + source: control.checkable ? "qrc:/images/check.png" : "" + } + //! [indicator] + + //! [background] + background: Item { + implicitWidth: 200 + implicitHeight: 40 + + Rectangle { + x: 1 + y: 1 + width: parent.width - 2 + height: parent.height - 2 + color: control.activeFocus || control.pressed ? "#eeeeee" : "transparent" + } + } + //! [background] +} diff --git a/src/imports/controls/controls.pri b/src/imports/controls/controls.pri index d0cf0550..78c9a1d0 100644 --- a/src/imports/controls/controls.pri +++ b/src/imports/controls/controls.pri @@ -9,6 +9,8 @@ QML_FILES = \ GroupBox.qml \ ItemDelegate.qml \ Label.qml \ + Menu.qml \ + MenuItem.qml \ PageIndicator.qml \ ProgressBar.qml \ RadioButton.qml \ diff --git a/src/imports/controls/doc/images/qtlabscontrols-menu-contentItem.png b/src/imports/controls/doc/images/qtlabscontrols-menu-contentItem.png Binary files differnew file mode 100644 index 00000000..e16e196e --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-menu-contentItem.png diff --git a/src/imports/controls/doc/images/qtlabscontrols-menu.png b/src/imports/controls/doc/images/qtlabscontrols-menu.png Binary files differnew file mode 100644 index 00000000..b782b9fe --- /dev/null +++ b/src/imports/controls/doc/images/qtlabscontrols-menu.png diff --git a/src/imports/controls/doc/snippets/qtlabscontrols-menu-contentItem.qml b/src/imports/controls/doc/snippets/qtlabscontrols-menu-contentItem.qml new file mode 100644 index 00000000..0c270d34 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtlabscontrols-menu-contentItem.qml @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +Item { + id: window + width: menu.contentItem.width + height: menu.contentItem.height + visible: true + + Menu { + id: menu + contentItem.parent: window + + MenuItem { + text: "New..." + } + MenuItem { + text: "Open..." + } + MenuItem { + text: "Save" + } + } + + Rectangle { + parent: menu.contentItem + anchors.fill: parent + color: 'transparent' + border.color: 'red' + } +} diff --git a/src/imports/controls/doc/snippets/qtlabscontrols-menu.qml b/src/imports/controls/doc/snippets/qtlabscontrols-menu.qml new file mode 100644 index 00000000..b0f32203 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtlabscontrols-menu.qml @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +Item { + id: window + width: menu.contentItem.width + height: menu.contentItem.height + visible: true + + Menu { + id: menu + contentItem.parent: window + + MenuItem { + text: "New..." + } + MenuItem { + text: "Open..." + } + MenuItem { + text: "Save" + } + } +} diff --git a/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc b/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc index dddb3a8a..a7bd7d8b 100644 --- a/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc +++ b/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc @@ -206,6 +206,23 @@ Label has no background item by default. + \section1 Customizing Menu + + Menu consists of a \l {Panel::}{contentItem}. + + \section3 Content item + + \image qtlabscontrols-menu-contentItem.png + + \snippet Menu.qml contentItem + + + \section1 Customizing MenuItem + + MenuItem can be customized in the same manner as + \l {Customizing Button}{Button}. + + \section1 Customizing PageIndicator TODO diff --git a/src/imports/controls/doc/src/qtlabscontrols-index.qdoc b/src/imports/controls/doc/src/qtlabscontrols-index.qdoc index 55d89344..936e65e8 100644 --- a/src/imports/controls/doc/src/qtlabscontrols-index.qdoc +++ b/src/imports/controls/doc/src/qtlabscontrols-index.qdoc @@ -231,6 +231,9 @@ \li \l [QtQuickControls] {Label} \li \l [QtLabsControls] {Label} \row + \li \l [QtQuickControls] {Menu} + \li \l [QtLabsControls] {Menu} + \row \li \l [QtQuickControls] {ProgressBar} \li \l [QtLabsControls] {ProgressBar} \row diff --git a/src/imports/controls/doc/src/qtlabscontrols-menus.qdoc b/src/imports/controls/doc/src/qtlabscontrols-menus.qdoc new file mode 100644 index 00000000..65b4f04c --- /dev/null +++ b/src/imports/controls/doc/src/qtlabscontrols-menus.qdoc @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qtlabscontrols-menus.html + \title Menu Controls + + \annotatedlist qtlabscontrols-menus + + Each type of menu control has its own specific target use case. The + following sections offer guidelines for choosing the appropriate type + of menu control, depending on the use case. + + \section1 Menu Control + + \image qtlabscontrols-menu.png + + \l Menu is a traditional menu. +*/ diff --git a/src/imports/controls/qtlabscontrolsplugin.cpp b/src/imports/controls/qtlabscontrolsplugin.cpp index cf6ad6de..39525ee6 100644 --- a/src/imports/controls/qtlabscontrolsplugin.cpp +++ b/src/imports/controls/qtlabscontrolsplugin.cpp @@ -77,6 +77,8 @@ void QtLabsControlsPlugin::registerTypes(const char *uri) qmlRegisterType(selector->select(QStringLiteral("/GroupBox.qml")), uri, 1, 0, "GroupBox"); qmlRegisterType(selector->select(QStringLiteral("/ItemDelegate.qml")), uri, 1, 0, "ItemDelegate"); qmlRegisterType(selector->select(QStringLiteral("/Label.qml")), uri, 1, 0, "Label"); + qmlRegisterType(selector->select(QStringLiteral("/Menu.qml")), uri, 1, 0, "Menu"); + qmlRegisterType(selector->select(QStringLiteral("/MenuItem.qml")), uri, 1, 0, "MenuItem"); qmlRegisterType(selector->select(QStringLiteral("/PageIndicator.qml")), uri, 1, 0, "PageIndicator"); qmlRegisterType(selector->select(QStringLiteral("/ProgressBar.qml")), uri, 1, 0, "ProgressBar"); qmlRegisterType(selector->select(QStringLiteral("/RadioButton.qml")), uri, 1, 0, "RadioButton"); diff --git a/src/imports/templates/qtlabstemplatesplugin.cpp b/src/imports/templates/qtlabstemplatesplugin.cpp index 6db254b9..721e4cab 100644 --- a/src/imports/templates/qtlabstemplatesplugin.cpp +++ b/src/imports/templates/qtlabstemplatesplugin.cpp @@ -49,6 +49,8 @@ #include <QtLabsTemplates/private/qquickgroupbox_p.h> #include <QtLabsTemplates/private/qquickitemdelegate_p.h> #include <QtLabsTemplates/private/qquicklabel_p.h> +#include <QtLabsTemplates/private/qquickmenu_p.h> +#include <QtLabsTemplates/private/qquickmenuitem_p.h> #include <QtLabsTemplates/private/qquickpageindicator_p.h> #include <QtLabsTemplates/private/qquickpanel_p.h> #include <QtLabsTemplates/private/qquickprogressbar_p.h> @@ -96,6 +98,8 @@ void QtLabsTemplatesPlugin::registerTypes(const char *uri) qmlRegisterType<QQuickGroupBox>(uri, 1, 0, "GroupBox"); qmlRegisterType<QQuickItemDelegate>(uri, 1, 0, "ItemDelegate"); qmlRegisterType<QQuickLabel>(uri, 1, 0, "Label"); + qmlRegisterType<QQuickMenu>(uri, 1, 0, "Menu"); + qmlRegisterType<QQuickMenuItem>(uri, 1, 0, "MenuItem"); qmlRegisterType<QQuickPageIndicator>(uri, 1, 0, "PageIndicator"); qmlRegisterType<QQuickPanel>(uri, 1, 0, "Panel"); qmlRegisterType<QQuickProgressBar>(uri, 1, 0, "ProgressBar"); diff --git a/src/templates/qquickmenu.cpp b/src/templates/qquickmenu.cpp new file mode 100644 index 00000000..b21a7a3e --- /dev/null +++ b/src/templates/qquickmenu.cpp @@ -0,0 +1,455 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmenu_p.h" +#include "qquickmenu_p_p.h" +#include "qquickpanel_p_p.h" +#include "qquickmenuitem_p.h" + +#include <QtGui/qevent.h> +#include <QtQml/private/qqmlobjectmodel_p.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuick/private/qquickflickable_p.h> +#include <QtQuick/private/qquickevents_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Menu + \inherits Panel + \instantiates QQuickMenu + \inqmlmodule Qt.labs.controls + \ingroup qtlabscontrols-menus + \brief A menu control. + + \image qtlabscontrols-menu.png + + Menu has two main use cases: + \list + \li Context menus; for example, a menu that is shown after right clicking + \li Popup menus; for example, a menu that is shown after clicking a button + \endlist + + \code + Button { + id: fileButton + text: "File" + onClicked: menu.show() + } + Menu { + id: menu + contentItem.y: fileButton.height + + MenuItem { + text: "New..." + } + MenuItem { + text: "Open..." + } + MenuItem { + text: "Save" + } + } + \endcode + + \sa {Customizing Menu}, {Menu Controls} +*/ + +QQuickMenuPrivate::QQuickMenuPrivate() : + contentModel(Q_NULLPTR), + dummyFocusItem(Q_NULLPTR), + ignoreActiveFocusChanges(false) +{ + Q_Q(QQuickMenu); + contentModel = new QQmlObjectModel(q); +} + +QQuickItem *QQuickMenuPrivate::itemAt(int index) const +{ + return qobject_cast<QQuickItem *>(contentModel->get(index)); +} + +void QQuickMenuPrivate::insertItem(int index, QQuickItem *item) +{ + contentData.append(item); + item->setParentItem(contentItem); + QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent); + contentModel->insert(index, item); +} + +void QQuickMenuPrivate::moveItem(int from, int to) +{ + contentModel->move(from, to); +} + +void QQuickMenuPrivate::removeItem(int index, QQuickItem *item) +{ + contentData.removeOne(item); + + QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent); + item->setParentItem(Q_NULLPTR); + contentModel->remove(index); +} + +void QQuickMenuPrivate::itemChildAdded(QQuickItem *, QQuickItem *child) +{ + // add dynamically reparented items (eg. by a Repeater) + if (!QQuickItemPrivate::get(child)->isTransparentForPositioner() && !contentData.contains(child)) + insertItem(contentModel->count(), child); +} + +void QQuickMenuPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent) +{ + // remove dynamically unparented items (eg. by a Repeater) + if (!parent) + removeItem(contentModel->indexOf(item, Q_NULLPTR), item); +} + +void QQuickMenuPrivate::itemSiblingOrderChanged(QQuickItem *) +{ + // reorder the restacked items (eg. by a Repeater) + Q_Q(QQuickMenu); + QList<QQuickItem *> siblings = contentItem->childItems(); + for (int i = 0; i < siblings.count(); ++i) { + QQuickItem* sibling = siblings.at(i); + int index = contentModel->indexOf(sibling, Q_NULLPTR); + q->moveItem(index, i); + } +} + +void QQuickMenuPrivate::itemDestroyed(QQuickItem *item) +{ + int index = contentModel->indexOf(item, Q_NULLPTR); + if (index != -1) + removeItem(index, item); +} + +void QQuickMenuPrivate::onContentItemChanged() +{ + Q_Q(QQuickMenu); + if (contentItem) { + contentItem->installEventFilter(q); + contentItem->setFlag(QQuickItem::ItemIsFocusScope); + contentItem->setActiveFocusOnTab(true); + + // Trying to give active focus to the contentItem (ListView, by default) + // when the menu first opens, without also giving it to the first delegate item + // doesn't seem to be possible, but this is what we need to do. QMenu behaves + // similarly to this; it receives focus if a button that has it as a menu is clicked, + // and only after pressing tab is the first menu item then given active focus. + if (!dummyFocusItem) { + dummyFocusItem = new QQuickItem(contentItem); + dummyFocusItem->setObjectName(QStringLiteral("dummyMenuFocusItem")); + } else { + dummyFocusItem->setParentItem(contentItem); + } + + dummyFocusItem->setActiveFocusOnTab(true); + dummyFocusItem->stackBefore(contentItem->childItems().first()); + + QObjectPrivate::connect(q, &QQuickMenu::visibleChanged, this, &QQuickMenuPrivate::onMenuVisibleChanged); + QObjectPrivate::connect(dummyFocusItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::maybeUnsetDummyFocusOnTab); + } +} + +void QQuickMenuPrivate::onItemPressed() +{ + Q_Q(QQuickMenu); + QQuickItem *item = qobject_cast<QQuickItem*>(q->sender()); + int itemIndex = contentModel->indexOf(item, Q_NULLPTR); + Q_ASSERT(itemIndex != -1); + + if (!contentItem->property("currentIndex").isValid()) + return; + + contentItem->setProperty("currentIndex", itemIndex); +} + +void QQuickMenuPrivate::onItemActiveFocusChanged() +{ + if (ignoreActiveFocusChanges) + return; + + Q_Q(QQuickMenu); + QQuickItem *item = qobject_cast<QQuickItem*>(q->sender()); + if (!item->hasActiveFocus()) + return; + + if (!contentItem->property("currentIndex").isValid()) + return; + + int indexOfItem = contentModel->indexOf(item, Q_NULLPTR); + contentItem->setProperty("currentIndex", indexOfItem); +} + +void QQuickMenuPrivate::onMenuVisibleChanged() +{ + Q_Q(QQuickMenu); + if (q->isVisible()) { + // Don't react to active focus changes here, as we're causing them. + ignoreActiveFocusChanges = true; + for (int i = 0; i < contentModel->count(); ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->get(i)); + item->setFocus(true); + } + ignoreActiveFocusChanges = false; + + // We must do this last so that none of the menu items have focus. + dummyFocusItem->forceActiveFocus(); + } else { + // Ensure that when the menu isn't visible, there's no current item + // the next time it's opened. + if (contentItem->property("currentIndex").isValid()) + contentItem->setProperty("currentIndex", -1); + + // The menu items are sneaky and will steal the focus if they can. + for (int i = 0; i < contentModel->count(); ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->get(i)); + item->setFocus(false); + } + } + +} + +void QQuickMenuPrivate::maybeUnsetDummyFocusOnTab() +{ + if (!dummyFocusItem->hasActiveFocus()) { + // Only unset the flag once the dummy item no longer has focus, otherwise we get warnings. + dummyFocusItem->setActiveFocusOnTab(false); + } +} + +void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) +{ + QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data); + QQuickMenu *q = static_cast<QQuickMenu *>(prop->object); + QQuickItem *item = qobject_cast<QQuickItem *>(obj); + if (item) { + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) { + QQuickItemPrivate::get(item)->addItemChangeListener(p, QQuickItemPrivate::SiblingOrder); + item->setParentItem(p->contentItem); + } else if (p->contentModel->indexOf(item, Q_NULLPTR) == -1) { + q->addItem(item); + + QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item); + if (menuItem) { + QObjectPrivate::connect(menuItem, &QQuickMenuItem::pressed, p, &QQuickMenuPrivate::onItemPressed); + QObject::connect(menuItem, &QQuickMenuItem::triggered, q, &QQuickPanel::hide); + QObjectPrivate::connect(menuItem, &QQuickItem::activeFocusChanged, p, &QQuickMenuPrivate::onItemActiveFocusChanged); + } + } + } else { + p->contentData.append(obj); + } +} + +int QQuickMenuPrivate::contentData_count(QQmlListProperty<QObject> *prop) +{ + QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data); + return p->contentData.count(); +} + +QObject *QQuickMenuPrivate::contentData_at(QQmlListProperty<QObject> *prop, int index) +{ + QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data); + return p->contentData.value(index); +} + +void QQuickMenuPrivate::contentData_clear(QQmlListProperty<QObject> *prop) +{ + QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data); + p->contentData.clear(); +} + +QQuickMenu::QQuickMenu(QObject *parent) : + QQuickPanel(*(new QQuickMenuPrivate), parent) +{ + Q_D(QQuickMenu); + connect(this, &QQuickMenu::pressedOutside, this, &QQuickMenu::hide); + connect(this, &QQuickMenu::releasedOutside, this, &QQuickMenu::hide); + QObjectPrivate::connect(this, &QQuickMenu::contentItemChanged, d, &QQuickMenuPrivate::onContentItemChanged); +} + +/*! + \qmlmethod Item Qt.labs.controls::Menu::itemAt(int index) + + Returns the item at \a index, or \c null if it does not exist. +*/ +QQuickItem *QQuickMenu::itemAt(int index) const +{ + Q_D(const QQuickMenu); + return d->itemAt(index); +} + +/*! + \qmlmethod void Qt.labs.controls::Menu::addItem(Item item) + + Adds \a item to the end of the list of items. +*/ +void QQuickMenu::addItem(QQuickItem *item) +{ + Q_D(QQuickMenu); + insertItem(d->contentModel->count(), item); +} + +/*! + \qmlmethod void Qt.labs.controls::Menu::insertItem(int index, Item item) + + Inserts \a item at \a index. +*/ +void QQuickMenu::insertItem(int index, QQuickItem *item) +{ + Q_D(QQuickMenu); + if (!item) + return; + const int count = d->contentModel->count(); + if (index < 0 || index > count) + index = count; + + int oldIndex = d->contentModel->indexOf(item, Q_NULLPTR); + if (oldIndex != -1) { + if (oldIndex < index) + --index; + if (oldIndex != index) + d->moveItem(oldIndex, index); + } else { + d->insertItem(index, item); + } +} + +/*! + \qmlmethod void Qt.labs.controls::Menu::moveItem(int from, int to) + + Moves an item \a from one index \a to another. +*/ +void QQuickMenu::moveItem(int from, int to) +{ + Q_D(QQuickMenu); + const int count = d->contentModel->count(); + if (from < 0 || from > count - 1) + return; + if (to < 0 || to > count - 1) + to = count - 1; + + if (from != to) + d->moveItem(from, to); +} + +/*! + \qmlmethod void Qt.labs.controls::Menu::removeItem(int index) + + Removes an item at \a index. + + \note The ownership of the item is transferred to the caller. +*/ +void QQuickMenu::removeItem(int index) +{ + Q_D(QQuickMenu); + const int count = d->contentModel->count(); + if (index < 0 || index >= count) + return; + + QQuickItem *item = itemAt(index); + if (item) + d->removeItem(index, item); +} + +/*! + \qmlproperty model Qt.labs.controls::Menu::contentModel + \readonly + + This property holds the model used to display menu items. + + By default, the model is an \l ObjectModel, in order to allow declaring + menu items as children of the menu. +*/ +QVariant QQuickMenu::contentModel() const +{ + Q_D(const QQuickMenu); + return QVariant::fromValue(d->contentModel); +} + +/*! + \qmlproperty list<Object> Qt.labs.controls::Menu::contentData + \default + + This property holds the list of content data. + + \sa Item::data +*/ +QQmlListProperty<QObject> QQuickMenu::contentData() +{ + Q_D(QQuickMenu); + return QQmlListProperty<QObject>(this, d, + QQuickMenuPrivate::contentData_append, + QQuickMenuPrivate::contentData_count, + QQuickMenuPrivate::contentData_at, + QQuickMenuPrivate::contentData_clear); +} + +bool QQuickMenu::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QQuickMenu); + if (d->contentModel->count() == 0) + return false; + + if (object != d->contentItem || event->type() != QEvent::KeyRelease) + return false; + + // QTBUG-17051 + // Work around the fact that ListView has no way of distinguishing between + // mouse and keyboard interaction, thanks to the "interactive" bool in Flickable. + // What we actually want is to have a way to always allow keyboard interaction but + // only allow flicking with the mouse when there are too many menu items to be + // shown at once. + QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Up) { + if (d->contentItem->metaObject()->indexOfMethod("decrementCurrentIndex()") != -1) + QMetaObject::invokeMethod(d->contentItem, "decrementCurrentIndex"); + return true; + } else if (keyEvent->key() == Qt::Key_Down) { + if (d->contentItem->metaObject()->indexOfMethod("incrementCurrentIndex()") != -1) + QMetaObject::invokeMethod(d->contentItem, "incrementCurrentIndex"); + return true; + } + + return false; +} + +QT_END_NAMESPACE + +#include "moc_qquickmenu_p.cpp" diff --git a/src/templates/qquickmenu_p.h b/src/templates/qquickmenu_p.h new file mode 100644 index 00000000..cd079d4e --- /dev/null +++ b/src/templates/qquickmenu_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENU_P_H +#define QQUICKMENU_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/qqmllist.h> + +#include "qquickpanel_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickMenuItem; +class QQuickMenuPrivate; + +class Q_LABSTEMPLATES_EXPORT QQuickMenu : public QQuickPanel +{ + Q_OBJECT + Q_PROPERTY(QVariant contentModel READ contentModel CONSTANT FINAL) + Q_PROPERTY(QQmlListProperty<QObject> contentData READ contentData FINAL) + Q_CLASSINFO("DefaultProperty", "contentData") + +public: + explicit QQuickMenu(QObject *parent = Q_NULLPTR); + + Q_INVOKABLE QQuickItem *itemAt(int index) const; + Q_INVOKABLE void addItem(QQuickItem *item); + Q_INVOKABLE void insertItem(int index, QQuickItem *item); + Q_INVOKABLE void moveItem(int from, int to); + Q_INVOKABLE void removeItem(int index); + + QVariant contentModel() const; + QQmlListProperty<QObject> contentData(); + +protected: + bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QQuickMenu) + Q_DECLARE_PRIVATE(QQuickMenu) +}; + +Q_DECLARE_TYPEINFO(QQuickMenu, Q_COMPLEX_TYPE); + +QT_END_NAMESPACE + +#endif // QQUICKMENU_P_H diff --git a/src/templates/qquickmenu_p_p.h b/src/templates/qquickmenu_p_p.h new file mode 100644 index 00000000..166d184b --- /dev/null +++ b/src/templates/qquickmenu_p_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENU_P_P_H +#define QQUICKMENU_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvector.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> + +#include <QtLabsTemplates/private/qquickpanel_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlObjectModel; + +class Q_LABSTEMPLATES_EXPORT QQuickMenuPrivate : public QQuickPanelPrivate, public QQuickItemChangeListener +{ + Q_DECLARE_PUBLIC(QQuickMenu) + +public: + QQuickMenuPrivate(); + + QQuickItem *itemAt(int index) const; + void insertItem(int index, QQuickItem *item); + void moveItem(int from, int to); + void removeItem(int index, QQuickItem *item); + + void itemChildAdded(QQuickItem *item, QQuickItem *child) Q_DECL_OVERRIDE; + void itemSiblingOrderChanged(QQuickItem *item) Q_DECL_OVERRIDE; + void itemParentChanged(QQuickItem *item, QQuickItem *parent) Q_DECL_OVERRIDE; + void itemDestroyed(QQuickItem *item) Q_DECL_OVERRIDE; + + void onContentItemChanged(); + void onItemPressed(); + void onItemActiveFocusChanged(); + void onMenuVisibleChanged(); + void maybeUnsetDummyFocusOnTab(); + + static void contentData_append(QQmlListProperty<QObject> *prop, QObject *obj); + static int contentData_count(QQmlListProperty<QObject> *prop); + static QObject *contentData_at(QQmlListProperty<QObject> *prop, int index); + static void contentData_clear(QQmlListProperty<QObject> *prop); + + QVector<QObject *> contentData; + QQmlObjectModel *contentModel; + QQuickItem *dummyFocusItem; + bool ignoreActiveFocusChanges; +}; + +Q_DECLARE_TYPEINFO(QQuickMenuPrivate, Q_COMPLEX_TYPE); + +QT_END_NAMESPACE + +#endif // QQUICKMENU_P_P_H + diff --git a/src/templates/qquickmenuitem.cpp b/src/templates/qquickmenuitem.cpp new file mode 100644 index 00000000..83696535 --- /dev/null +++ b/src/templates/qquickmenuitem.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmenuitem_p.h" +#include "qquickabstractbutton_p_p.h" + +#include <QtQuick/private/qquickevents_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MenuItem + \inherits Control + \instantiates QQuickMenuItem + \inqmlmodule Qt.labs.controls + \ingroup qtlabscontrols-menus + \brief A menu item within a Menu. + + MenuItem is a convenience type that implements the AbstractButton API, + providing an easy way to respond to menu items being clicked, for example. + + \code + Button { + id: fileButton + text: "File" + onClicked: menu.show() + } + Menu { + id: menu + anchor.target: fileButton + + MenuItem { + text: "New..." + } + MenuItem { + text: "Open..." + } + MenuItem { + text: "Save" + } + } + \endcode + + \sa {Customizing MenuItem}, {Menu Controls} +*/ + +/*! + \qmlsignal void Qt.labs.controls::MenuItem::triggered() + + This signal is emitted when the menu item is triggered by the user. +*/ + +QQuickMenuItem::QQuickMenuItem(QQuickItem *parent) : + QQuickAbstractButton(parent) +{ + connect(this, &QQuickAbstractButton::clicked, this, &QQuickMenuItem::triggered); +} + +#ifndef QT_NO_ACCESSIBILITY +QAccessible::Role QQuickMenuItem::accessibleRole() const +{ + return QAccessible::MenuItem; +} +#endif + +QT_END_NAMESPACE diff --git a/src/templates/qquickmenuitem_p.h b/src/templates/qquickmenuitem_p.h new file mode 100644 index 00000000..3f86fe2e --- /dev/null +++ b/src/templates/qquickmenuitem_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Templates module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUITEM_P_H +#define QQUICKMENUITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLabsTemplates/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickMenuItemPrivate; + +class Q_LABSTEMPLATES_EXPORT QQuickMenuItem : public QQuickAbstractButton +{ + Q_OBJECT + +public: + explicit QQuickMenuItem(QQuickItem *parent = Q_NULLPTR); + +Q_SIGNALS: + void triggered(); + +protected: +#ifndef QT_NO_ACCESSIBILITY + QAccessible::Role accessibleRole() const Q_DECL_OVERRIDE; +#endif + +private: + Q_DISABLE_COPY(QQuickMenuItem) + Q_DECLARE_PRIVATE(QQuickMenuItem) +}; + +Q_DECLARE_TYPEINFO(QQuickMenuItem, Q_COMPLEX_TYPE); + +QT_END_NAMESPACE + +#endif // QQUICKMENUITEM_P_H diff --git a/src/templates/templates.pri b/src/templates/templates.pri index 0bcde3da..22fad255 100644 --- a/src/templates/templates.pri +++ b/src/templates/templates.pri @@ -20,6 +20,9 @@ HEADERS += \ $$PWD/qquickitemdelegate_p.h \ $$PWD/qquicklabel_p.h \ $$PWD/qquicklabel_p_p.h \ + $$PWD/qquickmenu_p.h \ + $$PWD/qquickmenu_p_p.h \ + $$PWD/qquickmenuitem_p.h \ $$PWD/qquickoverlay_p.h \ $$PWD/qquickpageindicator_p.h \ $$PWD/qquickpanel_p.h \ @@ -61,6 +64,8 @@ SOURCES += \ $$PWD/qquickgroupbox.cpp \ $$PWD/qquickitemdelegate.cpp \ $$PWD/qquicklabel.cpp \ + $$PWD/qquickmenu.cpp \ + $$PWD/qquickmenuitem.cpp \ $$PWD/qquickoverlay.cpp \ $$PWD/qquickpageindicator.cpp \ $$PWD/qquickpanel.cpp \ diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 88fcb334..3b3ee04a 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -6,6 +6,7 @@ SUBDIRS += \ calendar \ controls \ material \ + menu \ pressandhold \ sanity \ snippets \ diff --git a/tests/auto/menu/data/applicationwindow.qml b/tests/auto/menu/data/applicationwindow.qml new file mode 100644 index 00000000..6855d66e --- /dev/null +++ b/tests/auto/menu/data/applicationwindow.qml @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import Qt.labs.controls 1.0 + +ApplicationWindow { + title: "Test Application Window" + width: 400 + height: 400 + + property alias emptyMenu: emptyMenu + property alias menu: menu + property alias menuButton: menuButton + + Menu { + id: emptyMenu + } + + Menu { + id: menu + + MenuItem { + objectName: "firstMenuItem" + text: "A" + } + MenuItem { + objectName: "secondMenuItem" + text: "B" + } + MenuItem { + objectName: "thirdMenuItem" + text: "C" + } + } + + Button { + id: menuButton + x: 250 + visible: false + text: "Open Menu" + onClicked: menu.show() + } +} diff --git a/tests/auto/menu/menu.pro b/tests/auto/menu/menu.pro new file mode 100644 index 00000000..2dd2ac7f --- /dev/null +++ b/tests/auto/menu/menu.pro @@ -0,0 +1,15 @@ +CONFIG += testcase +TARGET = tst_menu +SOURCES += tst_menu.cpp + +osx:CONFIG -= app_bundle + +QT += core-private gui-private qml-private quick-private testlib labstemplates-private + +include (../shared/util.pri) + +TESTDATA = data/* + +OTHER_FILES += \ + data/* + diff --git a/tests/auto/menu/tst_menu.cpp b/tests/auto/menu/tst_menu.cpp new file mode 100644 index 00000000..226a8588 --- /dev/null +++ b/tests/auto/menu/tst_menu.cpp @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qtest.h> +#include <QtTest/QSignalSpy> +#include <QtGui/qstylehints.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlcontext.h> +#include <QtQuick/qquickview.h> +#include <QtQuick/private/qquickitem_p.h> +#include "../shared/util.h" +#include "../shared/visualtestutil.h" + +#include <QtLabsTemplates/private/qquickapplicationwindow_p.h> +#include <QtLabsTemplates/private/qquickbutton_p.h> +#include <QtLabsTemplates/private/qquickmenu_p.h> +#include <QtLabsTemplates/private/qquickmenuitem_p.h> + +using namespace QQuickVisualTestUtil; + +class ApplicationHelper +{ +public: + ApplicationHelper(QQmlDataTest *testCase, const QString &testFilePath = QLatin1String("applicationwindow.qml")) : + component(&engine) + { + component.loadUrl(testCase->testFileUrl(testFilePath)); + QObject *rootObject = component.create(); + cleanup.reset(rootObject); + QVERIFY2(rootObject, qPrintable(QString::fromLatin1("Failed to create ApplicationWindow: %1").arg(component.errorString()))); + + window = qobject_cast<QQuickApplicationWindow*>(rootObject); + QVERIFY(window); + QVERIFY(!window->isVisible()); + } + + QQmlEngine engine; + QQmlComponent component; + QScopedPointer<QObject> cleanup; + QQuickApplicationWindow *window; +}; + +class tst_menu : public QQmlDataTest +{ + Q_OBJECT + +public: + +private slots: + void defaults(); + void mouse(); + void contextMenuKeyboard(); + void menuButton(); +}; + +void tst_menu::defaults() +{ + ApplicationHelper helper(this); + + QQuickMenu *emptyMenu = helper.window->property("emptyMenu").value<QQuickMenu*>(); + QCOMPARE(emptyMenu->isVisible(), false); + QCOMPARE(emptyMenu->contentItem()->property("currentIndex"), QVariant(-1)); +} + +void tst_menu::mouse() +{ + ApplicationHelper helper(this); + + QQuickApplicationWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); + menu->show(); + QVERIFY(menu->isVisible()); + QVERIFY(window->overlay()->childItems().contains(menu->contentItem())); + + QQuickItem *firstItem = menu->itemAt(0); + QSignalSpy clickedSpy(firstItem, SIGNAL(clicked(QQuickMouseEvent*))); + QSignalSpy triggeredSpy(firstItem, SIGNAL(triggered())); + QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); + + // Ensure that presses cause the current index to change, + // so that the highlight acts as a way of illustrating press state. + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2)); + QVERIFY(firstItem->hasActiveFocus()); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); + QVERIFY(menu->isVisible()); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2)); + QCOMPARE(clickedSpy.count(), 1); + QCOMPARE(triggeredSpy.count(), 1); + QCOMPARE(visibleSpy.count(), 1); + QVERIFY(!menu->isVisible()); + QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); + + menu->show(); + QCOMPARE(visibleSpy.count(), 2); + QVERIFY(menu->isVisible()); + QVERIFY(window->overlay()->childItems().contains(menu->contentItem())); + + // Ensure that we have enough space to click outside of the menu. + QVERIFY(window->width() > menu->contentItem()->width()); + QVERIFY(window->height() > menu->contentItem()->height()); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, + QPoint(menu->contentItem()->width() + 1, menu->contentItem()->height() + 1)); + QCOMPARE(visibleSpy.count(), 3); + QVERIFY(!menu->isVisible()); + QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); + + menu->show(); + QCOMPARE(visibleSpy.count(), 4); + QVERIFY(menu->isVisible()); + QVERIFY(window->overlay()->childItems().contains(menu->contentItem())); + + // Try pressing within the menu and releasing outside of it; it should close. + // TODO: won't work until QQuickPanel::releasedOutside() actually gets emitted +// QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2)); +// QVERIFY(firstItem->hasActiveFocus()); +// QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); +// QVERIFY(menu->isVisible()); +// QCOMPARE(triggeredSpy.count(), 1); + +// QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->contentItem()->width() + 1, firstItem->height() / 2)); +// QCOMPARE(clickedSpy.count(), 1); +// QCOMPARE(triggeredSpy.count(), 1); +// QCOMPARE(visibleSpy.count(), 5); +// QVERIFY(!menu->isVisible()); +// QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); +// QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); +} + +void tst_menu::contextMenuKeyboard() +{ + if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) + QSKIP("This platform only allows tab focus for text controls"); + + ApplicationHelper helper(this); + + QQuickApplicationWindow *window = helper.window; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QGuiApplication::focusWindow() == window); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); + + QQuickItem *firstItem = menu->itemAt(0); + QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); + + menu->setFocus(true); + menu->show(); + QCOMPARE(visibleSpy.count(), 1); + QVERIFY(menu->isVisible()); + QVERIFY(window->overlay()->childItems().contains(menu->contentItem())); + QVERIFY(!firstItem->hasActiveFocus()); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); + + QTest::keyClick(window, Qt::Key_Tab); + QVERIFY(firstItem->hasActiveFocus()); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); + + QQuickItem *secondItem = menu->itemAt(1); + QTest::keyClick(window, Qt::Key_Tab); + QVERIFY(!firstItem->hasActiveFocus()); + QVERIFY(secondItem->hasActiveFocus()); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(1)); + + QSignalSpy secondTriggeredSpy(secondItem, SIGNAL(triggered())); + QTest::keyClick(window, Qt::Key_Space); + QCOMPARE(secondTriggeredSpy.count(), 1); + QCOMPARE(visibleSpy.count(), 2); + QVERIFY(!menu->isVisible()); + QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); + QVERIFY(!firstItem->hasActiveFocus()); + QVERIFY(!secondItem->hasActiveFocus()); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); + + menu->show(); + QCOMPARE(visibleSpy.count(), 3); + QVERIFY(menu->isVisible()); + QVERIFY(window->overlay()->childItems().contains(menu->contentItem())); + QVERIFY(!firstItem->hasActiveFocus()); + QVERIFY(!secondItem->hasActiveFocus()); + QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); + + QTest::keyClick(window, Qt::Key_Down); + QVERIFY(firstItem->hasActiveFocus()); + + QTest::keyClick(window, Qt::Key_Down); + QVERIFY(secondItem->hasActiveFocus()); + + QTest::keyClick(window, Qt::Key_Down); + QQuickItem *thirdItem = menu->itemAt(2); + QVERIFY(!firstItem->hasActiveFocus()); + QVERIFY(!secondItem->hasActiveFocus()); + QVERIFY(thirdItem->hasActiveFocus()); + + // Key navigation shouldn't wrap by default. + QTest::keyClick(window, Qt::Key_Down); + QVERIFY(!firstItem->hasActiveFocus()); + QVERIFY(!secondItem->hasActiveFocus()); + QVERIFY(thirdItem->hasActiveFocus()); +} + +void tst_menu::menuButton() +{ + if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) + QSKIP("This platform only allows tab focus for text controls"); + + ApplicationHelper helper(this); + + QQuickApplicationWindow *window = helper.window; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QGuiApplication::focusWindow() == window); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); + QQuickButton *menuButton = window->property("menuButton").value<QQuickButton*>(); + QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); + + menuButton->setVisible(true); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, + menuButton->mapToScene(QPointF(menuButton->width() / 2, menuButton->height() / 2)).toPoint()); + QCOMPARE(visibleSpy.count(), 1); + QVERIFY(menu->isVisible()); + + QTest::keyClick(window, Qt::Key_Tab); + QQuickItem *firstItem = menu->itemAt(0); + QVERIFY(firstItem->hasActiveFocus()); +} + +QTEST_MAIN(tst_menu) + +#include "tst_menu.moc" diff --git a/tests/manual/gifs/data/qtlabscontrols-menu.qml b/tests/manual/gifs/data/qtlabscontrols-menu.qml new file mode 100644 index 00000000..65cd3563 --- /dev/null +++ b/tests/manual/gifs/data/qtlabscontrols-menu.qml @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import Qt.labs.controls 1.0 + +// TODO: restore and finish https://codereview.qt-project.org/#/c/123948/ +ApplicationWindow { + width: menu.contentItem.width + 20 + height: menu.contentItem.height + fileButton.height + 20 + + property alias fileButton: fileButton + property alias menu: menu + + Button { + id: fileButton + text: "File" + onClicked: menu.show() + x: 10 + y: 10 + } + Menu { + id: menu + // TODO + contentItem.x: fileButton.x + contentItem.y: fileButton.y + fileButton.height + + MenuItem { + text: "New..." + } + MenuItem { + text: "Open..." + } + MenuItem { + text: "Save" + } + } +} diff --git a/tests/manual/gifs/tst_gifs.cpp b/tests/manual/gifs/tst_gifs.cpp index 5ae15e79..bd0cc61f 100644 --- a/tests/manual/gifs/tst_gifs.cpp +++ b/tests/manual/gifs/tst_gifs.cpp @@ -56,6 +56,7 @@ private slots: void switchGif(); void button(); void tabBar(); + void menu(); private: void moveSmoothly(QQuickWindow *window, const QPoint &from, const QPoint &to, int movements, @@ -338,6 +339,39 @@ void tst_Gifs::tabBar() gifRecorder.waitForFinish(); } +void tst_Gifs::menu() +{ + const QString qmlFileName = QStringLiteral("qtlabscontrols-menu.qml"); + + GifRecorder gifRecorder; + gifRecorder.setDataDirPath(dataDirPath); + gifRecorder.setOutputDir(outputDir); + gifRecorder.setRecordingDuration(3); + gifRecorder.setQmlFileName(qmlFileName); + gifRecorder.setHighQuality(true); + + gifRecorder.start(); + + QQuickWindow *window = gifRecorder.window(); + const QQuickItem *fileButton = window->property("fileButton").value<QQuickItem*>(); + QVERIFY(fileButton); + + const QPoint fileButtonCenter = fileButton->mapToScene(QPointF(fileButton->width() / 2, fileButton->height() / 2)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, fileButtonCenter, 0); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, fileButtonCenter, 200); + + const QObject *menu = window->property("menu").value<QObject*>(); + QVERIFY(menu); + const QQuickItem *menuContentItem = menu->property("contentItem").value<QQuickItem*>(); + QVERIFY(menuContentItem); + + const QPoint lastItemPos = menuContentItem->mapToScene(QPointF(menuContentItem->width() / 2, menuContentItem->height() - 10)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, lastItemPos, 1000); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, lastItemPos, 300); + + gifRecorder.waitForFinish(); +} + QTEST_MAIN(tst_Gifs) #include "tst_gifs.moc" diff --git a/tests/manual/testbench/main.qml b/tests/manual/testbench/main.qml index 6f842c9c..b375a098 100644 --- a/tests/manual/testbench/main.qml +++ b/tests/manual/testbench/main.qml @@ -62,6 +62,7 @@ ApplicationWindow { ToolButton { text: "Normal" + onClicked: menu.visible ? menu.hide() : menu.show() } ToolButton { text: "Pressed" @@ -97,6 +98,22 @@ ApplicationWindow { } } + Menu { + id: menu + contentItem.x: 1 + contentItem.y: header.height + + MenuItem { + text: "Option 1" + } + MenuItem { + text: "Option 2" + } + MenuItem { + text: "Option 3" + } + } + Flickable { anchors.fill: parent topMargin: 30 |