blob: c3d80aa0f5aa9da3c0dbcb23d6845634379a0466 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Quick Templates 2 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 "qquickmenuitem_p_p.h"
#include "qquickmenubaritem_p.h"
#include "qquickmenubar_p.h"
#include "qquickpopupitem_p_p.h"
#include "qquickpopuppositioner_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 <private/qqmlobjectmodel_p.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquickitemchangelistener_p.h>
#include <QtQuick/private/qquickitemview_p.h>
#include <QtQuick/private/qquickevents_p_p.h>
#include <QtQuick/private/qquickwindow_p.h>
QT_BEGIN_NAMESPACE
// copied from qfusionstyle.cpp
static const int SUBMENU_DELAY = 225;
/*!
\qmltype Menu
\inherits Popup
//! \instantiates QQuickMenu
\inqmlmodule QtQuick.Controls
\since 5.7
\ingroup qtquickcontrols2-menus
\ingroup qtquickcontrols2-popups
\brief Menu popup that can be used as a context menu or popup menu.
\image qtquickcontrols2-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
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
text: "File"
onClicked: menu.open()
Menu {
id: menu
y: fileButton.height
MenuItem {
text: "New..."
}
MenuItem {
text: "Open..."
}
MenuItem {
text: "Save"
}
}
}
\endcode
Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to create sub-menus
and declare Action objects inside Menu:
\code
Menu {
Action { text: "Cut" }
Action { text: "Copy" }
Action { text: "Paste" }
MenuSeparator { }
Menu {
title: "Find/Replace"
Action { text: "Find Next" }
Action { text: "Find Previous" }
Action { text: "Replace" }
}
}
\endcode
Sub-menus are \l {cascade}{cascading} by default on desktop platforms
that have a mouse cursor available. Non-cascading menus are shown one
menu at a time, and centered over the parent menu.
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
items in a menu can be accessed using \l itemAt() or
\l {Popup::}{contentChildren}.
Although \l {MenuItem}{MenuItems} are most commonly used with Menu, it can
contain any type of item.
\sa {Customizing Menu}, MenuItem, {Menu Controls}, {Popup Controls}
*/
/*!
\qmlproperty bool QtQuick.Controls::Menu::focus
This property holds whether the popup wants focus.
When the popup actually receives focus, \l{Popup::}{activeFocus}
will be \c true. For more information, see
\l {Keyboard Focus in Qt Quick}.
The default value is \c false.
\sa activeFocus
*/
static const QQuickPopup::ClosePolicy cascadingSubMenuClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent;
static bool shouldCascade()
{
#if QT_CONFIG(cursor)
return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows);
#else
return false;
#endif
}
class QQuickMenuPositioner : public QQuickPopupPositioner
{
public:
QQuickMenuPositioner(QQuickMenu *menu) : QQuickPopupPositioner(menu) { }
void reposition() override;
};
QQuickMenuPrivate::QQuickMenuPrivate()
{
Q_Q(QQuickMenu);
cascade = shouldCascade();
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);
if (qobject_cast<QQuickItemView *>(contentItem))
QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-53262
if (complete)
resizeItem(item);
QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent);
QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(this, QQuickGeometryChange::Width);
contentModel->insert(index, item);
QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item);
if (menuItem) {
Q_Q(QQuickMenu);
QQuickMenuItemPrivate::get(menuItem)->setMenu(q);
if (QQuickMenu *subMenu = menuItem->subMenu())
QQuickMenuPrivate::get(subMenu)->setParentMenu(q);
QObjectPrivate::connect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered);
QObjectPrivate::connect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged);
QObjectPrivate::connect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered);
}
}
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);
QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
item->setParentItem(nullptr);
contentModel->remove(index);
QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item);
if (menuItem) {
QQuickMenuItemPrivate::get(menuItem)->setMenu(nullptr);
if (QQuickMenu *subMenu = menuItem->subMenu())
QQuickMenuPrivate::get(subMenu)->setParentMenu(nullptr);
QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered);
QObjectPrivate::disconnect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged);
QObjectPrivate::disconnect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered);
}
}
QQuickItem *QQuickMenuPrivate::beginCreateItem()
{
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);
QQuickItem *item = qobject_cast<QQuickItem *>(object);
if (!item)
delete object;
QQml_setParent_noEvent(item, q);
return item;
}
void QQuickMenuPrivate::completeCreateItem()
{
if (!delegate)
return;
delegate->completeCreate();
}
QQuickItem *QQuickMenuPrivate::createItem(QQuickMenu *menu)
{
QQuickItem *item = beginCreateItem();
if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item))
QQuickMenuItemPrivate::get(menuItem)->setSubMenu(menu);
completeCreateItem();
return item;
}
QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action)
{
QQuickItem *item = beginCreateItem();
if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(item))
button->setAction(action);
completeCreateItem();
return item;
}
void QQuickMenuPrivate::resizeItem(QQuickItem *item)
{
if (!item || !contentItem)
return;
QQuickItemPrivate *p = QQuickItemPrivate::get(item);
if (!p->widthValid) {
item->setWidth(contentItem->width());
p->widthValid = false;
}
}
void QQuickMenuPrivate::resizeItems()
{
if (!contentModel)
return;
for (int i = 0; i < contentModel->count(); ++i)
resizeItem(itemAt(i));
}
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, nullptr), item);
}
void QQuickMenuPrivate::itemSiblingOrderChanged(QQuickItem *)
{
// reorder the restacked items (eg. by a Repeater)
Q_Q(QQuickMenu);
QList<QQuickItem *> siblings = contentItem->childItems();
int to = 0;
for (int i = 0; i < siblings.count(); ++i) {
QQuickItem* sibling = siblings.at(i);
if (QQuickItemPrivate::get(sibling)->isTransparentForPositioner())
continue;
int index = contentModel->indexOf(sibling, nullptr);
q->moveItem(index, to++);
}
}
void QQuickMenuPrivate::itemDestroyed(QQuickItem *item)
{
QQuickPopupPrivate::itemDestroyed(item);
int index = contentModel->indexOf(item, nullptr);
if (index != -1)
removeItem(index, item);
}
void QQuickMenuPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange, const QRectF &)
{
if (!complete)
return;
if (item == contentItem) {
// The contentItem's geometry changed, so resize any items
// that don't have explicit widths set so that they fill the width of the menu.
resizeItems();
} else {
// The geometry of an item in the menu changed. If the item
// doesn't have an explicit width set, make it fill the width of the menu.
resizeItem(item);
}
}
QQuickPopupPositioner *QQuickMenuPrivate::getPositioner()
{
Q_Q(QQuickMenu);
if (!positioner)
positioner = new QQuickMenuPositioner(q);
return positioner;
}
void QQuickMenuPositioner::reposition()
{
QQuickMenu *menu = static_cast<QQuickMenu *>(popup());
QQuickMenuPrivate *p = QQuickMenuPrivate::get(menu);
if (p->parentMenu) {
if (p->cascade) {
if (p->popupItem->isMirrored())
menu->setPosition(QPointF(-menu->width() - p->parentMenu->leftPadding() + menu->overlap(), -menu->topPadding()));
else if (p->parentItem)
menu->setPosition(QPointF(p->parentItem->width() + p->parentMenu->rightPadding() - menu->overlap(), -menu->topPadding()));
} else {
menu->setPosition(QPointF(p->parentMenu->x() + (p->parentMenu->width() - menu->width()) / 2,
p->parentMenu->y() + (p->parentMenu->height() - menu->height()) / 2));
}
}
QQuickPopupPositioner::reposition();
}
bool QQuickMenuPrivate::prepareEnterTransition()
{
Q_Q(QQuickMenu);
if (parentMenu && !cascade)
parentMenu->close();
// If a cascading sub-menu doesn't have enough space to open on
// the right, it flips on the other side of the parent menu.
allowHorizontalFlip = cascade && parentMenu;
if (!QQuickPopupPrivate::prepareEnterTransition())
return false;
if (!hasClosePolicy) {
if (cascade && parentMenu)
closePolicy = cascadingSubMenuClosePolicy;
else
q->resetClosePolicy();
}
return true;
}
bool QQuickMenuPrivate::prepareExitTransition()
{
if (!QQuickPopupPrivate::prepareExitTransition())
return false;
stopHoverTimer();
QQuickMenu *subMenu = currentSubMenu();
while (subMenu) {
QPointer<QQuickMenuItem> currentSubMenuItem = QQuickMenuPrivate::get(subMenu)->currentItem;
subMenu->close();
subMenu = currentSubMenuItem ? currentSubMenuItem->subMenu() : nullptr;
}
return true;
}
bool QQuickMenuPrivate::blockInput(QQuickItem *item, const QPointF &point) const
{
// keep the parent menu open when a cascading sub-menu (this menu) is interacted with
return (cascade && parentMenu && contains(point)) || QQuickPopupPrivate::blockInput(item, point);
}
void QQuickMenuPrivate::onItemHovered()
{
Q_Q(QQuickMenu);
QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender());
if (!button || !button->isHovered() || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
return;
QQuickMenuItem *oldCurrentItem = currentItem;
int index = contentModel->indexOf(button, nullptr);
if (index != -1) {
setCurrentIndex(index, Qt::OtherFocusReason);
if (oldCurrentItem != currentItem) {
if (oldCurrentItem) {
QQuickMenu *subMenu = oldCurrentItem->subMenu();
if (subMenu)
subMenu->close();
}
if (currentItem) {
QQuickMenu *subMenu = currentItem->menu();
if (subMenu && subMenu->cascade())
startHoverTimer();
}
}
}
}
void QQuickMenuPrivate::onItemTriggered()
{
Q_Q(QQuickMenu);
QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(q->sender());
if (!item)
return;
if (QQuickMenu *subMenu = item->subMenu()) {
auto subMenuPrivate = QQuickMenuPrivate::get(subMenu);
subMenu->popup(subMenuPrivate->firstEnabledMenuItem());
} else {
q->dismiss();
}
}
void QQuickMenuPrivate::onItemActiveFocusChanged()
{
Q_Q(QQuickMenu);
QQuickItem *item = qobject_cast<QQuickItem*>(q->sender());
if (!item->hasActiveFocus())
return;
int indexOfItem = contentModel->indexOf(item, nullptr);
QQuickControl *control = qobject_cast<QQuickControl *>(item);
setCurrentIndex(indexOfItem, control ? control->focusReason() : Qt::OtherFocusReason);
}
QQuickMenu *QQuickMenuPrivate::currentSubMenu() const
{
if (!currentItem)
return nullptr;
return currentItem->subMenu();
}
void QQuickMenuPrivate::setParentMenu(QQuickMenu *parent)
{
Q_Q(QQuickMenu);
if (parentMenu == parent)
return;
if (parentMenu) {
QObject::disconnect(parentMenu.data(), &QQuickMenu::cascadeChanged, q, &QQuickMenu::setCascade);
disconnect(parentMenu.data(), &QQuickMenu::parentChanged, this, &QQuickMenuPrivate::resolveParentItem);
}
if (parent) {
QObject::connect(parent, &QQuickMenu::cascadeChanged, q, &QQuickMenu::setCascade);
connect(parent, &QQuickMenu::parentChanged, this, &QQuickMenuPrivate::resolveParentItem);
}
parentMenu = parent;
q->resetCascade();
resolveParentItem();
}
static QQuickItem *findParentMenuItem(QQuickMenu *subMenu)
{
QQuickMenu *menu = QQuickMenuPrivate::get(subMenu)->parentMenu;
for (int i = 0; i < QQuickMenuPrivate::get(menu)->contentModel->count(); ++i) {
QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(menu->itemAt(i));
if (item && item->subMenu() == subMenu)
return item;
}
return nullptr;
}
void QQuickMenuPrivate::resolveParentItem()
{
Q_Q(QQuickMenu);
if (!parentMenu)
q->resetParentItem();
else if (!cascade)
q->setParentItem(parentMenu->parentItem());
else
q->setParentItem(findParentMenuItem(q));
}
void QQuickMenuPrivate::propagateKeyEvent(QKeyEvent *event)
{
if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(parentItem)) {
if (QQuickMenu *menu = menuItem->menu())
QQuickMenuPrivate::get(menu)->propagateKeyEvent(event);
} else if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(parentItem)) {
if (QQuickMenuBar *menuBar = menuBarItem->menuBar()) {
event->accept();
QCoreApplication::sendEvent(menuBar, event);
}
}
}
void QQuickMenuPrivate::startHoverTimer()
{
Q_Q(QQuickMenu);
stopHoverTimer();
hoverTimer = q->startTimer(SUBMENU_DELAY);
}
void QQuickMenuPrivate::stopHoverTimer()
{
Q_Q(QQuickMenu);
if (!hoverTimer)
return;
q->killTimer(hoverTimer);
hoverTimer = 0;
}
void QQuickMenuPrivate::setCurrentIndex(int index, Qt::FocusReason reason)
{
Q_Q(QQuickMenu);
if (currentIndex == index)
return;
QQuickMenuItem *newCurrentItem = qobject_cast<QQuickMenuItem *>(itemAt(index));
if (currentItem != newCurrentItem) {
stopHoverTimer();
if (currentItem) {
currentItem->setHighlighted(false);
if (!newCurrentItem && window) {
QQuickItem *focusItem = QQuickItemPrivate::get(contentItem)->subFocusItem;
if (focusItem)
QQuickWindowPrivate::get(window)->clearFocusInScope(contentItem, focusItem, Qt::OtherFocusReason);
}
}
if (newCurrentItem) {
newCurrentItem->setHighlighted(true);
newCurrentItem->forceActiveFocus(reason);
}
currentItem = newCurrentItem;
}
currentIndex = index;
emit q->currentIndexChanged();
}
bool QQuickMenuPrivate::activateNextItem()
{
int index = currentIndex;
int count = contentModel->count();
while (++index < count) {
QQuickItem *item = itemAt(index);
if (!item || !item->activeFocusOnTab() || !item->isEnabled())
continue;
setCurrentIndex(index, Qt::TabFocusReason);
return true;
}
return false;
}
bool QQuickMenuPrivate::activatePreviousItem()
{
int index = currentIndex;
while (--index >= 0) {
QQuickItem *item = itemAt(index);
if (!item || !item->activeFocusOnTab() || !item->isEnabled())
continue;
setCurrentIndex(index, Qt::BacktabFocusReason);
return true;
}
return false;
}
QQuickMenuItem *QQuickMenuPrivate::firstEnabledMenuItem() const
{
for (int i = 0; i < contentModel->count(); ++i) {
QQuickItem *item = itemAt(i);
if (!item || !item->isEnabled())
continue;
QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item);
if (!menuItem)
continue;
return menuItem;
}
return nullptr;
}
void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj)
{
QQuickMenu *q = qobject_cast<QQuickMenu *>(prop->object);
QQuickMenuPrivate *p = QQuickMenuPrivate::get(q);
QQuickItem *item = qobject_cast<QQuickItem *>(obj);
if (!item) {
if (QQuickAction *action = qobject_cast<QQuickAction *>(obj))
item = p->createItem(action);
else if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(obj))
item = p->createItem(menu);
}
if (item) {
if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) {
QQuickItemPrivate::get(item)->addItemChangeListener(p, QQuickItemPrivate::SiblingOrder);
item->setParentItem(p->contentItem);
} else if (p->contentModel->indexOf(item, nullptr) == -1) {
q->addItem(item);
}
} else {
p->contentData.append(obj);
}
}
int QQuickMenuPrivate::contentData_count(QQmlListProperty<QObject> *prop)
{
QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
return QQuickMenuPrivate::get(q)->contentData.count();
}
QObject *QQuickMenuPrivate::contentData_at(QQmlListProperty<QObject> *prop, int index)
{
QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
return QQuickMenuPrivate::get(q)->contentData.value(index);
}
void QQuickMenuPrivate::contentData_clear(QQmlListProperty<QObject> *prop)
{
QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
QQuickMenuPrivate::get(q)->contentData.clear();
}
QQuickMenu::QQuickMenu(QObject *parent)
: QQuickPopup(*(new QQuickMenuPrivate), parent)
{
Q_D(QQuickMenu);
setFocus(true);
connect(d->contentModel, &QQmlObjectModel::countChanged, this, &QQuickMenu::countChanged);
}
/*!
\qmlmethod Item QtQuick.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 QtQuick.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 QtQuick.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, nullptr);
if (oldIndex != -1) {
if (oldIndex < index)
--index;
if (oldIndex != index)
d->moveItem(oldIndex, index);
} else {
d->insertItem(index, item);
}
}
/*!
\qmlmethod void QtQuick.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);
}
/*!
\deprecated
\qmlmethod void QtQuick.Controls::Menu::removeItem(int 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.
*/
QQuickItem *QQuickMenu::takeItem(int index)
{
Q_D(QQuickMenu);
const int count = d->contentModel->count();
if (index < 0 || index >= count)
return nullptr;
QQuickItem *item = itemAt(index);
if (item)
d->removeItem(index, item);
return item;
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod Menu QtQuick.Controls::Menu::menuAt(int index)
Returns the sub-menu at \a index, or \c null if the index is not valid or
there is no sub-menu at the specified index.
*/
QQuickMenu *QQuickMenu::menuAt(int index) const
{
Q_D(const QQuickMenu);
QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index));
if (!item)
return nullptr;
return item->subMenu();
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::addMenu(Menu menu)
Adds \a menu as a sub-menu to the end of this menu.
*/
void QQuickMenu::addMenu(QQuickMenu *menu)
{
Q_D(QQuickMenu);
insertMenu(d->contentModel->count(), menu);
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::insertMenu(int index, Menu menu)
Inserts \a menu as a sub-menu at \a index. The index is within all items in the menu.
*/
void QQuickMenu::insertMenu(int index, QQuickMenu *menu)
{
Q_D(QQuickMenu);
if (!menu)
return;
insertItem(index, d->createItem(menu));
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::removeMenu(Menu menu)
Removes and destroys the specified \a menu.
*/
void QQuickMenu::removeMenu(QQuickMenu *menu)
{
Q_D(QQuickMenu);
if (!menu)
return;
const int count = d->contentModel->count();
for (int i = 0; i < count; ++i) {
QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(i));
if (!item || item->subMenu() != menu)
continue;
removeItem(item);
break;
}
menu->deleteLater();
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod Menu QtQuick.Controls::Menu::takeMenu(int index)
Removes and returns the menu at \a index. The index is within all items in the menu.
\note The ownership of the menu is transferred to the caller.
*/
QQuickMenu *QQuickMenu::takeMenu(int index)
{
Q_D(QQuickMenu);
QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index));
if (!item)
return nullptr;
QQuickMenu *subMenu = item->subMenu();
if (!subMenu)
return nullptr;
d->removeItem(index, item);
item->deleteLater();
return subMenu;
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod Action QtQuick.Controls::Menu::actionAt(int index)
Returns the action at \a index, or \c null if the index is not valid or
there is no action at the specified index.
*/
QQuickAction *QQuickMenu::actionAt(int index) const
{
Q_D(const QQuickMenu);
QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton *>(d->itemAt(index));
if (!item)
return nullptr;
return item->action();
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::addAction(Action action)
Adds \a action to the end of this menu.
*/
void QQuickMenu::addAction(QQuickAction *action)
{
Q_D(QQuickMenu);
insertAction(d->contentModel->count(), action);
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::insertAction(int index, Action action)
Inserts \a action at \a index. The index is within all items in the menu.
*/
void QQuickMenu::insertAction(int index, QQuickAction *action)
{
Q_D(QQuickMenu);
if (!action)
return;
insertItem(index, d->createItem(action));
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::removeAction(Action action)
Removes and destroys the specified \a action.
*/
void QQuickMenu::removeAction(QQuickAction *action)
{
Q_D(QQuickMenu);
if (!action)
return;
const int count = d->contentModel->count();
for (int i = 0; i < count; ++i) {
QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(i));
if (!item || item->action() != action)
continue;
removeItem(item);
break;
}
action->deleteLater();
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod Action QtQuick.Controls::Menu::takeAction(int index)
Removes and returns the action at \a index. The index is within all items in the menu.
\note The ownership of the action is transferred to the caller.
*/
QQuickAction *QQuickMenu::takeAction(int index)
{
Q_D(QQuickMenu);
QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index));
if (!item)
return nullptr;
QQuickAction *action = item->action();
if (!action)
return nullptr;
d->removeItem(index, item);
item->deleteLater();
return action;
}
/*!
\qmlproperty model QtQuick.Controls::Menu::contentModel
\readonly
This property holds the model used to display menu items.
The content model is provided for visualization purposes. It can be assigned
as a model to a content item that presents the contents of the menu.
\code
Menu {
id: menu
contentItem: ListView {
model: menu.contentModel
}
}
\endcode
The model allows menu items to be statically declared as children of the
menu.
*/
QVariant QQuickMenu::contentModel() const
{
Q_D(const QQuickMenu);
return QVariant::fromValue(d->contentModel);
}
/*!
\qmlproperty list<Object> QtQuick.Controls::Menu::contentData
\default
This property holds the list of content data.
The list contains all objects that have been declared in QML as children
of the menu, and also items that have been dynamically added or
inserted using the \l addItem() and \l insertItem() methods, respectively.
\note Unlike \c contentChildren, \c contentData does include non-visual QML
objects. It is not re-ordered when items are inserted or moved.
\sa Item::data, {Popup::}{contentChildren}
*/
QQmlListProperty<QObject> QQuickMenu::contentData()
{
Q_D(QQuickMenu);
if (!d->contentItem)
QQuickControlPrivate::get(d->popupItem)->executeContentItem();
return QQmlListProperty<QObject>(this, nullptr,
QQuickMenuPrivate::contentData_append,
QQuickMenuPrivate::contentData_count,
QQuickMenuPrivate::contentData_at,
QQuickMenuPrivate::contentData_clear);
}
/*!
\qmlproperty string QtQuick.Controls::Menu::title
This property holds the title for the menu.
The title of a menu is often displayed in the text of a menu item when the
menu is a submenu, and in the text of a tool button when it is in a
menubar.
*/
QString QQuickMenu::title() const
{
Q_D(const QQuickMenu);
return d->title;
}
void QQuickMenu::setTitle(QString &title)
{
Q_D(QQuickMenu);
if (title == d->title)
return;
d->title = title;
emit titleChanged(title);
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlproperty bool QtQuick.Controls::Menu::cascade
This property holds whether the menu cascades its sub-menus.
The default value is platform-specific. Menus are cascading by default on
desktop platforms that have a mouse cursor available. Non-cascading menus
are shown one menu at a time, and centered over the parent menu.
\note Changing the value of the property has no effect while the menu is open.
\sa overlap
*/
bool QQuickMenu::cascade() const
{
Q_D(const QQuickMenu);
return d->cascade;
}
void QQuickMenu::setCascade(bool cascade)
{
Q_D(QQuickMenu);
if (d->cascade == cascade)
return;
d->cascade = cascade;
if (d->parentMenu)
d->resolveParentItem();
emit cascadeChanged(cascade);
}
void QQuickMenu::resetCascade()
{
Q_D(QQuickMenu);
if (d->parentMenu)
setCascade(d->parentMenu->cascade());
else
setCascade(shouldCascade());
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlproperty real QtQuick.Controls::Menu::overlap
This property holds the amount of pixels by which the menu horizontally overlaps its parent menu.
The property only has effect when the menu is used as a cascading sub-menu.
The default value is style-specific.
\note Changing the value of the property has no effect while the menu is open.
\sa cascade
*/
qreal QQuickMenu::overlap() const
{
Q_D(const QQuickMenu);
return d->overlap;
}
void QQuickMenu::setOverlap(qreal overlap)
{
Q_D(QQuickMenu);
if (d->overlap == overlap)
return;
d->overlap = overlap;
emit overlapChanged();
}
/*!
\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)
\qmlproperty int QtQuick.Controls::Menu::currentIndex
This property holds the index of the currently highlighted item.
Menu items can be highlighted by mouse hover or keyboard navigation.
\sa MenuItem::highlighted
*/
int QQuickMenu::currentIndex() const
{
Q_D(const QQuickMenu);
return d->currentIndex;
}
void QQuickMenu::setCurrentIndex(int index)
{
Q_D(QQuickMenu);
d->setCurrentIndex(index, Qt::OtherFocusReason);
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlproperty int QtQuick.Controls::Menu::count
\readonly
This property holds the number of items.
*/
int QQuickMenu::count() const
{
Q_D(const QQuickMenu);
return d->contentModel->count();
}
void QQuickMenu::popup(QQuickItem *menuItem)
{
Q_D(QQuickMenu);
// No position has been explicitly specified, so position the menu at the mouse cursor
// on desktop platforms that have a mouse cursor available and support multiple windows.
QQmlNullableValue<QPointF> pos;
#if QT_CONFIG(cursor)
if (d->parentItem && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows))
pos = d->parentItem->mapFromGlobal(QCursor::pos());
#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);
popup(pos.isNull ? QPointF() : pos.value, menuItem);
}
void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem)
{
Q_D(QQuickMenu);
qreal offset = 0;
#if QT_CONFIG(cursor)
if (menuItem)
offset = d->popupItem->mapFromItem(menuItem, QPointF(0, 0)).y();
#endif
setPosition(pos - QPointF(0, offset));
if (menuItem)
d->setCurrentIndex(d->contentModel->indexOf(menuItem, nullptr), Qt::PopupFocusReason);
open();
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::popup(MenuItem item = null)
\qmlmethod void QtQuick.Controls::Menu::popup(Item parent, 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 \a 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)
\qmlmethod void QtQuick.Controls::Menu::popup(Item parent, 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 \a 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)
\qmlmethod void QtQuick.Controls::Menu::popup(Item parent, 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 \a parent item.
The menu can be optionally aligned to a specific menu \a item.
\sa dismiss(), Popup::open()
*/
void QQuickMenu::popup(QQmlV4Function *args)
{
Q_D(QQuickMenu);
const int len = args->length();
if (len > 4) {
args->v4engine()->throwTypeError();
return;
}
QV4::ExecutionEngine *v4 = args->v4engine();
QV4::Scope scope(v4);
QQmlNullableValue<QPointF> pos;
QQuickItem *menuItem = nullptr;
QQuickItem *parentItem = nullptr;
if (len > 0) {
// Item parent
QV4::ScopedValue firstArg(scope, (*args)[0]);
if (const QV4::QObjectWrapper *obj = firstArg->as<QV4::QObjectWrapper>()) {
QQuickItem *item = qobject_cast<QQuickItem *>(obj->object());
if (item && !d->popupItem->isAncestorOf(item))
parentItem = item;
} else if (firstArg->isUndefined()) {
resetParentItem();
parentItem = d->parentItem;
}
// MenuItem item
QV4::ScopedValue lastArg(scope, (*args)[len - 1]);
if (const QV4::QObjectWrapper *obj = lastArg->as<QV4::QObjectWrapper>()) {
QQuickItem *item = qobject_cast<QQuickItem *>(obj->object());
if (item && d->popupItem->isAncestorOf(item))
menuItem = item;
}
}
if (len >= 3 || (!parentItem && len >= 2)) {
// real x, real y
QV4::ScopedValue xArg(scope, (*args)[parentItem ? 1 : 0]);
QV4::ScopedValue yArg(scope, (*args)[parentItem ? 2 : 1]);
if (xArg->isNumber() && yArg->isNumber())
pos = QPointF(xArg->asDouble(), yArg->asDouble());
}
if (pos.isNull && (len >= 2 || (!parentItem && len >= 1))) {
// point pos
QV4::ScopedValue posArg(scope, (*args)[parentItem ? 1 : 0]);
const QVariant var = v4->toVariant(posArg, -1);
if (var.userType() == QMetaType::QPointF)
pos = var.toPointF();
}
if (parentItem)
setParentItem(parentItem);
if (pos.isNull)
popup(menuItem);
else
popup(pos, menuItem);
}
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
\qmlmethod void QtQuick.Controls::Menu::dismiss()
Closes all menus in the hierarchy that this menu belongs to.
\note Unlike \l {Popup::}{close()} that only closes a menu and its sub-menus,
\c dismiss() closes the whole hierarchy of menus, including the parent menus.
In practice, \c close() is suitable e.g. for implementing navigation in a
hierarchy of menus, and \c dismiss() is the appropriate method for closing
the whole hierarchy of menus.
\sa popup(), Popup::close()
*/
void QQuickMenu::dismiss()
{
QQuickMenu *menu = this;
while (menu) {
menu->close();
menu = QQuickMenuPrivate::get(menu)->parentMenu;
}
}
void QQuickMenu::componentComplete()
{
Q_D(QQuickMenu);
QQuickPopup::componentComplete();
d->resizeItems();
}
void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
{
Q_D(QQuickMenu);
QQuickPopup::contentItemChange(newItem, oldItem);
if (oldItem) {
QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Children);
QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Geometry);
}
if (newItem) {
QQuickItemPrivate::get(newItem)->addItemChangeListener(d, QQuickItemPrivate::Children);
QQuickItemPrivate::get(newItem)->updateOrAddGeometryChangeListener(d, QQuickGeometryChange::Width);
}
d->contentItem = newItem;
}
void QQuickMenu::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
{
Q_D(QQuickMenu);
QQuickPopup::itemChange(change, data);
if (change == QQuickItem::ItemVisibleHasChanged) {
if (!data.boolValue && d->cascade) {
// Ensure that when the menu isn't visible, there's no current item
// the next time it's opened.
d->setCurrentIndex(-1, Qt::OtherFocusReason);
}
}
}
void QQuickMenu::keyPressEvent(QKeyEvent *event)
{
Q_D(QQuickMenu);
QQuickPopup::keyPressEvent(event);
// 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.
switch (event->key()) {
case Qt::Key_Up:
if (!d->activatePreviousItem())
d->propagateKeyEvent(event);
break;
case Qt::Key_Down:
d->activateNextItem();
break;
case Qt::Key_Left:
case Qt::Key_Right:
event->ignore();
if (d->popupItem->isMirrored() == (event->key() == Qt::Key_Right)) {
if (d->parentMenu && d->currentItem) {
if (!d->cascade)
d->parentMenu->open();
close();
event->accept();
}
} else {
if (QQuickMenu *subMenu = d->currentSubMenu()) {
auto subMenuPrivate = QQuickMenuPrivate::get(subMenu);
subMenu->popup(subMenuPrivate->firstEnabledMenuItem());
event->accept();
}
}
if (!event->isAccepted())
d->propagateKeyEvent(event);
break;
default:
break;
}
}
void QQuickMenu::timerEvent(QTimerEvent *event)
{
Q_D(QQuickMenu);
if (event->timerId() == d->hoverTimer) {
if (QQuickMenu *subMenu = d->currentSubMenu())
subMenu->open();
d->stopHoverTimer();
return;
}
QQuickPopup::timerEvent(event);
}
QFont QQuickMenu::defaultFont() const
{
return QQuickTheme::font(QQuickTheme::Menu);
}
QPalette QQuickMenu::defaultPalette() const
{
return QQuickTheme::palette(QQuickTheme::Menu);
}
#if QT_CONFIG(accessibility)
QAccessible::Role QQuickMenu::accessibleRole() const
{
return QAccessible::PopupMenu;
}
#endif
QT_END_NAMESPACE
#include "moc_qquickmenu_p.cpp"