diff options
Diffstat (limited to 'src/quicktemplates2/qquickmenu.cpp')
-rw-r--r-- | src/quicktemplates2/qquickmenu.cpp | 299 |
1 files changed, 292 insertions, 7 deletions
diff --git a/src/quicktemplates2/qquickmenu.cpp b/src/quicktemplates2/qquickmenu.cpp index e6f22e2b..fa6b88ef 100644 --- a/src/quicktemplates2/qquickmenu.cpp +++ b/src/quicktemplates2/qquickmenu.cpp @@ -36,10 +36,20 @@ #include "qquickmenu_p.h" #include "qquickmenu_p_p.h" -#include "qquickmenuitem_p.h" -#include "qquickcontrol_p_p.h" +#include "qquickmenuitem_p_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickaction_p.h" #include <QtGui/qevent.h> +#include <QtGui/qcursor.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/private/qqmlengine_p.h> +#include <QtQml/private/qv4scopedvalue_p.h> +#include <QtQml/private/qv4variantobject_p.h> +#include <QtQml/private/qv4qobjectwrapper_p.h> #include <QtQml/private/qqmlobjectmodel_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickitemchangelistener_p.h> @@ -67,6 +77,37 @@ QT_BEGIN_NAMESPACE \li Popup menus; for example, a menu that is shown after clicking a button \endlist + When used as a context menu, the recommended way of opening the menu is to call + \l popup(). Unless a position is explicitly specified, the menu is positioned at + the mouse cursor on desktop platforms that have a mouse cursor available, and + otherwise centered over its parent item. + + \code + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.RightButton) + contextMenu.popup() + } + onPressAndHold: { + if (mouse.source === Qt.MouseEventNotSynthesized) + contextMenu.popup() + } + + Menu { + id: contextMenu + MenuItem { text: "Cut" } + MenuItem { text: "Copy" } + MenuItem { text: "Paste" } + } + } + \endcode + + When used as a popup menu, it is easiest to specify the position by specifying + the desired \l {Popup::}{x} and \l {Popup::}{y} coordinates using the respective + properties, and call \l {Popup::}{open()} to open the menu. + \code Button { id: fileButton @@ -90,6 +131,17 @@ QT_BEGIN_NAMESPACE } \endcode + Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to declare + Action objects inside Menu: + + \code + Menu { + Action { text: "Cut" } + Action { text: "Copy" } + Action { text: "Paste" } + } + \endcode + Typically, menu items are statically declared as children of the menu, but Menu also provides API to \l {addItem}{add}, \l {insertItem}{insert}, \l {moveItem}{move} and \l {removeItem}{remove} items dynamically. The @@ -104,7 +156,8 @@ QT_BEGIN_NAMESPACE QQuickMenuPrivate::QQuickMenuPrivate() : contentItem(nullptr), - contentModel(nullptr) + contentModel(nullptr), + delegate(nullptr) { Q_Q(QQuickMenu); contentModel = new QQmlObjectModel(q); @@ -129,9 +182,11 @@ void QQuickMenuPrivate::insertItem(int index, QQuickItem *item) QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item); if (menuItem) { Q_Q(QQuickMenu); + QQuickMenuItemPrivate::get(menuItem)->setMenu(q); QObjectPrivate::connect(menuItem, &QQuickMenuItem::pressed, this, &QQuickMenuPrivate::onItemPressed); QObject::connect(menuItem, &QQuickMenuItem::triggered, q, &QQuickPopup::close); QObjectPrivate::connect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged); + QObjectPrivate::connect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered); } } @@ -151,10 +206,36 @@ void QQuickMenuPrivate::removeItem(int index, QQuickItem *item) QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item); if (menuItem) { Q_Q(QQuickMenu); + QQuickMenuItemPrivate::get(menuItem)->setMenu(nullptr); QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::pressed, this, &QQuickMenuPrivate::onItemPressed); QObject::disconnect(menuItem, &QQuickMenuItem::triggered, q, &QQuickPopup::close); QObjectPrivate::disconnect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged); + QObjectPrivate::disconnect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered); + } +} + +QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action) +{ + Q_Q(QQuickMenu); + if (!delegate) + return nullptr; + + QQmlContext *creationContext = delegate->creationContext(); + if (!creationContext) + creationContext = qmlContext(q); + QQmlContext *context = new QQmlContext(creationContext, q); + context->setContextObject(q); + + QObject *object = delegate->beginCreate(context); + if (QQuickItem *item = qobject_cast<QQuickItem *>(object)) { + if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object)) + button->setAction(action); + delegate->completeCreate(); + return item; } + + delete object; + return nullptr; } void QQuickMenuPrivate::resizeItem(QQuickItem *item) @@ -226,6 +307,18 @@ void QQuickMenuPrivate::onItemPressed() item->forceActiveFocus(); } +void QQuickMenuPrivate::onItemHovered() +{ + Q_Q(QQuickMenu); + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender()); + if (!button || !button->isHovered() || QQuickAbstractButtonPrivate::get(button)->touchId != -1) + return; + + int index = contentModel->indexOf(button, nullptr); + if (index != -1) + setCurrentIndex(index); +} + void QQuickMenuPrivate::onItemActiveFocusChanged() { Q_Q(QQuickMenu); @@ -248,13 +341,29 @@ int QQuickMenuPrivate::currentIndex() const void QQuickMenuPrivate::setCurrentIndex(int index) { contentItem->setProperty("currentIndex", index); + + QQuickMenuItem *newCurrentItem = contentItem->property("currentItem").value<QQuickMenuItem *>(); + + if (currentItem != newCurrentItem) { + if (currentItem) + currentItem->setHighlighted(false); + if (newCurrentItem) + newCurrentItem->setHighlighted(true); + currentItem = newCurrentItem; + } } 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 (QQuickAction *action = qobject_cast<QQuickAction *>(obj)) + item = p->createItem(action); + } + if (item) { if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) { QQuickItemPrivate::get(item)->addItemChangeListener(p, QQuickItemPrivate::SiblingOrder); @@ -358,22 +467,61 @@ void QQuickMenu::moveItem(int from, int to) } /*! + \deprecated \qmlmethod void QtQuick.Controls::Menu::removeItem(int index) - Removes the item at \a index. + Use Menu::removeItem(Item) or Menu::takeItem(int) instead. +*/ +void QQuickMenu::removeItem(const QVariant &var) +{ + if (var.userType() == QMetaType::Nullptr) + return; + + if (QQuickItem *item = var.value<QQuickItem *>()) + removeItem(item); + else + takeItem(var.toInt()); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::removeItem(Item item) + + Removes and destroys the specified \a item. +*/ +void QQuickMenu::removeItem(QQuickItem *item) +{ + Q_D(QQuickMenu); + if (!item) + return; + + const int index = d->contentModel->indexOf(item, nullptr); + if (index == -1) + return; + + d->removeItem(index, item); + item->deleteLater(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod MenuItem QtQuick.Controls::Menu::takeItem(int index) + + Removes and returns the item at \a index. \note The ownership of the item is transferred to the caller. */ -void QQuickMenu::removeItem(int index) +QQuickItem *QQuickMenu::takeItem(int index) { Q_D(QQuickMenu); const int count = d->contentModel->count(); if (index < 0 || index >= count) - return; + return nullptr; QQuickItem *item = itemAt(index); if (item) d->removeItem(index, item); + return item; } /*! @@ -449,7 +597,139 @@ void QQuickMenu::setTitle(QString &title) if (title == d->title) return; d->title = title; - emit titleChanged(); + emit titleChanged(title); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty Component QtQuick.Controls::Menu::delegate + + This property holds the component that is used to create items + to present actions. + + \code + Menu { + Action { text: "Cut" } + Action { text: "Copy" } + Action { text: "Paste" } + } + \endcode + + \sa Action +*/ +QQmlComponent *QQuickMenu::delegate() const +{ + Q_D(const QQuickMenu); + return d->delegate; +} + +void QQuickMenu::setDelegate(QQmlComponent *delegate) +{ + Q_D(QQuickMenu); + if (d->delegate == delegate) + return; + + d->delegate = delegate; + emit delegateChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::popup(MenuItem item = null) + + Opens the menu at the mouse cursor on desktop platforms that have a mouse cursor + available, and otherwise centers the menu over its parent item. + + The menu can be optionally aligned to a specific menu \a item. + + \sa Popup::open() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::popup(point pos, MenuItem item = null) + + Opens the menu at the specified position \a pos in the popups coordinate system, + that is, a coordinate relative to its parent item. + + The menu can be optionally aligned to a specific menu \a item. + + \sa Popup::open() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::popup(real x, real y, MenuItem item = null) + + Opens the menu at the specified position \a x, \a y in the popups coordinate system, + that is, a coordinate relative to its parent item. + + The menu can be optionally aligned to a specific menu \a item. + + \sa Popup::open() +*/ +void QQuickMenu::popup(QQmlV4Function *args) +{ + Q_D(QQuickMenu); + const int len = args->length(); + if (len > 3) { + args->v4engine()->throwTypeError(); + return; + } + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + QQmlNullableValue<QPointF> pos; + QQuickMenuItem *menuItem = nullptr; + + if (len > 0) { + // MenuItem item + QV4::ScopedValue lastArg(scope, (*args)[len - 1]); + const QV4::QObjectWrapper *obj = lastArg->as<QV4::QObjectWrapper>(); + if (obj) + menuItem = qobject_cast<QQuickMenuItem *>(obj->object()); + } + + if (len >= 2) { + // real x, real y + QV4::ScopedValue firstArg(scope, (*args)[0]); + QV4::ScopedValue secondArg(scope, (*args)[1]); + if (firstArg->isNumber() && secondArg->isNumber()) + pos = QPointF(firstArg->asDouble(), secondArg->asDouble()); + } + + if (pos.isNull && len >= 1) { + // point pos + QV4::ScopedValue firstArg(scope, (*args)[0]); + const QVariant var = v4->toVariant(firstArg, -1); + if (var.userType() == QMetaType::QPointF) + pos = var.toPointF(); + } + + // Unless the position has been explicitly specified, position the menu at + // the mouse cursor on desktop platforms that have a mouse cursor available + // and support multiple windows. +#if QT_CONFIG(cursor) + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows)) { + if (pos.isNull && d->parentItem) + pos = d->parentItem->mapFromGlobal(QCursor::pos()); + if (menuItem) + pos.value.ry() -= d->popupItem->mapFromItem(menuItem, QPointF(0, 0)).y(); + } +#endif + + // As a fallback, center the menu over its parent item. + if (pos.isNull && d->parentItem) + pos = QPointF((d->parentItem->width() - width()) / 2, (d->parentItem->height() - height()) / 2); + + if (!pos.isNull) + setPosition(pos); + + if (menuItem) + d->setCurrentIndex(d->contentModel->indexOf(menuItem, nullptr)); + + open(); } void QQuickMenu::componentComplete() @@ -531,6 +811,11 @@ QFont QQuickMenu::defaultFont() const return QQuickControlPrivate::themeFont(QPlatformTheme::MenuFont); } +QPalette QQuickMenu::defaultPalette() const +{ + return QQuickControlPrivate::themePalette(QPlatformTheme::MenuPalette); +} + #if QT_CONFIG(accessibility) QAccessible::Role QQuickMenu::accessibleRole() const { |