aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJ-P Nurmi <jpnurmi@qt.io>2017-06-01 23:36:57 +0200
committerJ-P Nurmi <jpnurmi@qt.io>2017-06-13 09:34:39 +0000
commit92fcd0a3fb2ee0d1ba242607c1aa5a29864d3650 (patch)
treee55ace5afc75cfaa17f2571fda8cfae2d8f4e9f8 /src
parent23a80af1b5d31621bf1d342706c9541dd5273b86 (diff)
Add support for cascading sub-menus
This commit adds initial support for cascading sub-menus by allowing one to nest declarative Menu declarations. A follow-up commit adds support for adding, inserting, and removing menus programmatically. [ChangeLog][Controls][Menu] Added support for cascading sub-menus. Task-number: QTBUG-60351 Change-Id: I0eee4f74d92a97c09333fcc4348b019782448535 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/imports/controls/Menu.qml1
-rw-r--r--src/imports/controls/MenuItem.qml15
-rw-r--r--src/imports/controls/fusion/Menu.qml1
-rw-r--r--src/imports/controls/fusion/MenuItem.qml18
-rw-r--r--src/imports/controls/images/arrow-indicator.pngbin0 -> 260 bytes
-rw-r--r--src/imports/controls/images/arrow-indicator@2x.pngbin0 -> 343 bytes
-rw-r--r--src/imports/controls/images/arrow-indicator@3x.pngbin0 -> 438 bytes
-rw-r--r--src/imports/controls/images/arrow-indicator@4x.pngbin0 -> 545 bytes
-rw-r--r--src/imports/controls/material/MenuItem.qml16
-rw-r--r--src/imports/controls/material/images/arrow-indicator.pngbin0 -> 200 bytes
-rw-r--r--src/imports/controls/material/images/arrow-indicator.svg56
-rw-r--r--src/imports/controls/material/images/arrow-indicator@2x.pngbin0 -> 239 bytes
-rw-r--r--src/imports/controls/material/images/arrow-indicator@3x.pngbin0 -> 277 bytes
-rw-r--r--src/imports/controls/material/images/arrow-indicator@4x.pngbin0 -> 317 bytes
-rw-r--r--src/imports/controls/material/qtquickcontrols2materialstyleplugin.qrc4
-rw-r--r--src/imports/controls/qtquickcontrols2plugin.qrc4
-rw-r--r--src/imports/controls/universal/Menu.qml1
-rw-r--r--src/imports/controls/universal/MenuItem.qml16
-rw-r--r--src/quicktemplates2/qquickmenu.cpp273
-rw-r--r--src/quicktemplates2/qquickmenu_p.h10
-rw-r--r--src/quicktemplates2/qquickmenu_p_p.h21
-rw-r--r--src/quicktemplates2/qquickmenuitem.cpp64
-rw-r--r--src/quicktemplates2/qquickmenuitem_p.h8
-rw-r--r--src/quicktemplates2/qquickmenuitem_p_p.h3
24 files changed, 473 insertions, 38 deletions
diff --git a/src/imports/controls/Menu.qml b/src/imports/controls/Menu.qml
index 31c9230a..1fbf9a92 100644
--- a/src/imports/controls/Menu.qml
+++ b/src/imports/controls/Menu.qml
@@ -48,6 +48,7 @@ T.Menu {
contentItem ? contentItem.implicitHeight : 0) + topPadding + bottomPadding
margins: 0
+ overlap: 1
delegate: MenuItem { }
diff --git a/src/imports/controls/MenuItem.qml b/src/imports/controls/MenuItem.qml
index 97c3f965..e0532719 100644
--- a/src/imports/controls/MenuItem.qml
+++ b/src/imports/controls/MenuItem.qml
@@ -57,8 +57,10 @@ T.MenuItem {
icon.color: enabled ? Default.textDarkColor : Default.textDisabledColor
contentItem: IconLabel {
- leftPadding: control.checkable && !control.mirrored ? control.indicator.width + control.spacing : 0
- rightPadding: control.checkable && control.mirrored ? control.indicator.width + control.spacing : 0
+ readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
+ readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
+ leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
+ rightPadding: control.mirrored ? indicatorPadding : arrowPadding
spacing: control.spacing
mirrored: control.mirrored
@@ -79,6 +81,15 @@ T.MenuItem {
source: control.checkable ? "qrc:/qt-project.org/imports/QtQuick/Controls.2/images/check.png" : ""
}
+ arrow: Image {
+ x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding
+ y: control.topPadding + (control.availableHeight - height) / 2
+
+ visible: control.subMenu
+ mirror: control.mirrored
+ source: control.subMenu ? "qrc:/qt-project.org/imports/QtQuick/Controls.2/images/arrow-indicator.png" : ""
+ }
+
background: Item {
implicitWidth: 200
implicitHeight: 40
diff --git a/src/imports/controls/fusion/Menu.qml b/src/imports/controls/fusion/Menu.qml
index 3fd4caa2..d7fceb92 100644
--- a/src/imports/controls/fusion/Menu.qml
+++ b/src/imports/controls/fusion/Menu.qml
@@ -51,6 +51,7 @@ T.Menu {
margins: 0
padding: 1
+ overlap: 2
delegate: MenuItem { }
diff --git a/src/imports/controls/fusion/MenuItem.qml b/src/imports/controls/fusion/MenuItem.qml
index c0127ed7..ca828c40 100644
--- a/src/imports/controls/fusion/MenuItem.qml
+++ b/src/imports/controls/fusion/MenuItem.qml
@@ -58,8 +58,10 @@ T.MenuItem {
icon.height: 16
contentItem: IconLabel {
- leftPadding: !control.mirrored ? control.indicator.width + control.spacing : 0
- rightPadding: control.mirrored ? control.indicator.width + control.spacing : 0
+ readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
+ readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
+ leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
+ rightPadding: control.mirrored ? indicatorPadding : arrowPadding
spacing: control.spacing
mirrored: control.mirrored
@@ -72,6 +74,18 @@ T.MenuItem {
color: control.down || control.highlighted ? Fusion.highlightedText(control.palette) : control.palette.text
}
+ arrow: ColorImage {
+ x: control.mirrored ? control.padding : control.width - width - control.padding
+ y: control.topPadding + (control.availableHeight - height) / 2
+ width: 20
+
+ visible: control.subMenu
+ rotation: control.mirrored ? 90 : -90
+ color: control.down || control.hovered || control.highlighted ? Fusion.highlightedText(control.palette) : control.palette.text
+ source: "qrc:/qt-project.org/imports/QtQuick/Controls.2/Fusion/images/arrow.png"
+ fillMode: Image.Pad
+ }
+
indicator: CheckIndicator {
x: control.mirrored ? control.width - width - control.rightPadding : control.leftPadding
y: control.topPadding + (control.availableHeight - height) / 2
diff --git a/src/imports/controls/images/arrow-indicator.png b/src/imports/controls/images/arrow-indicator.png
new file mode 100644
index 00000000..50f230dc
--- /dev/null
+++ b/src/imports/controls/images/arrow-indicator.png
Binary files differ
diff --git a/src/imports/controls/images/arrow-indicator@2x.png b/src/imports/controls/images/arrow-indicator@2x.png
new file mode 100644
index 00000000..457cdde0
--- /dev/null
+++ b/src/imports/controls/images/arrow-indicator@2x.png
Binary files differ
diff --git a/src/imports/controls/images/arrow-indicator@3x.png b/src/imports/controls/images/arrow-indicator@3x.png
new file mode 100644
index 00000000..8d624154
--- /dev/null
+++ b/src/imports/controls/images/arrow-indicator@3x.png
Binary files differ
diff --git a/src/imports/controls/images/arrow-indicator@4x.png b/src/imports/controls/images/arrow-indicator@4x.png
new file mode 100644
index 00000000..7d2c49e2
--- /dev/null
+++ b/src/imports/controls/images/arrow-indicator@4x.png
Binary files differ
diff --git a/src/imports/controls/material/MenuItem.qml b/src/imports/controls/material/MenuItem.qml
index a5bf8a54..743f1e12 100644
--- a/src/imports/controls/material/MenuItem.qml
+++ b/src/imports/controls/material/MenuItem.qml
@@ -67,9 +67,21 @@ T.MenuItem {
control: control
}
+ arrow: ColorImage {
+ x: control.mirrored ? control.padding : control.width - width - control.padding
+ y: control.topPadding + (control.availableHeight - height) / 2
+
+ visible: control.subMenu
+ mirror: control.mirrored
+ color: control.enabled ? control.Material.foreground : control.Material.hintTextColor
+ source: "qrc:/qt-project.org/imports/QtQuick/Controls.2/Material/images/arrow-indicator.png"
+ }
+
contentItem: IconLabel {
- leftPadding: control.checkable && !control.mirrored ? control.indicator.width + control.spacing : 0
- rightPadding: control.checkable && control.mirrored ? control.indicator.width + control.spacing : 0
+ readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
+ readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
+ leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
+ rightPadding: control.mirrored ? indicatorPadding : arrowPadding
spacing: control.spacing
mirrored: control.mirrored
diff --git a/src/imports/controls/material/images/arrow-indicator.png b/src/imports/controls/material/images/arrow-indicator.png
new file mode 100644
index 00000000..4a942849
--- /dev/null
+++ b/src/imports/controls/material/images/arrow-indicator.png
Binary files differ
diff --git a/src/imports/controls/material/images/arrow-indicator.svg b/src/imports/controls/material/images/arrow-indicator.svg
new file mode 100644
index 00000000..1e7217c8
--- /dev/null
+++ b/src/imports/controls/material/images/arrow-indicator.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="5"
+ height="10"
+ viewBox="0 0 5 10"
+ version="1.1"
+ id="svg2"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="arrow-indicator.svg">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="2560"
+ inkscape:window-height="1571"
+ id="namedview6"
+ showgrid="false"
+ inkscape:zoom="27.812867"
+ inkscape:cx="13.137558"
+ inkscape:cy="12.83583"
+ inkscape:window-x="0"
+ inkscape:window-y="55"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <path
+ d="M 0,10 5,5 0,0 Z"
+ id="path4"
+ inkscape:connector-curvature="0"
+ style="fill:#757575" />
+</svg>
diff --git a/src/imports/controls/material/images/arrow-indicator@2x.png b/src/imports/controls/material/images/arrow-indicator@2x.png
new file mode 100644
index 00000000..b31b56c1
--- /dev/null
+++ b/src/imports/controls/material/images/arrow-indicator@2x.png
Binary files differ
diff --git a/src/imports/controls/material/images/arrow-indicator@3x.png b/src/imports/controls/material/images/arrow-indicator@3x.png
new file mode 100644
index 00000000..637e9674
--- /dev/null
+++ b/src/imports/controls/material/images/arrow-indicator@3x.png
Binary files differ
diff --git a/src/imports/controls/material/images/arrow-indicator@4x.png b/src/imports/controls/material/images/arrow-indicator@4x.png
new file mode 100644
index 00000000..15b9a902
--- /dev/null
+++ b/src/imports/controls/material/images/arrow-indicator@4x.png
Binary files differ
diff --git a/src/imports/controls/material/qtquickcontrols2materialstyleplugin.qrc b/src/imports/controls/material/qtquickcontrols2materialstyleplugin.qrc
index e2cc90fa..6e29aea4 100644
--- a/src/imports/controls/material/qtquickcontrols2materialstyleplugin.qrc
+++ b/src/imports/controls/material/qtquickcontrols2materialstyleplugin.qrc
@@ -1,5 +1,9 @@
<RCC>
<qresource prefix="/qt-project.org/imports/QtQuick/Controls.2/Material">
+ <file>images/arrow-indicator.png</file>
+ <file>images/arrow-indicator@2x.png</file>
+ <file>images/arrow-indicator@3x.png</file>
+ <file>images/arrow-indicator@4x.png</file>
<file>images/check.png</file>
<file>images/check@2x.png</file>
<file>images/check@3x.png</file>
diff --git a/src/imports/controls/qtquickcontrols2plugin.qrc b/src/imports/controls/qtquickcontrols2plugin.qrc
index 0948a529..c2dd9d39 100644
--- a/src/imports/controls/qtquickcontrols2plugin.qrc
+++ b/src/imports/controls/qtquickcontrols2plugin.qrc
@@ -1,5 +1,9 @@
<RCC>
<qresource prefix="/qt-project.org/imports/QtQuick/Controls.2">
+ <file>images/arrow-indicator.png</file>
+ <file>images/arrow-indicator@2x.png</file>
+ <file>images/arrow-indicator@3x.png</file>
+ <file>images/arrow-indicator@4x.png</file>
<file>images/check.png</file>
<file>images/check@2x.png</file>
<file>images/check@3x.png</file>
diff --git a/src/imports/controls/universal/Menu.qml b/src/imports/controls/universal/Menu.qml
index d7a27563..b2e71106 100644
--- a/src/imports/controls/universal/Menu.qml
+++ b/src/imports/controls/universal/Menu.qml
@@ -48,6 +48,7 @@ T.Menu {
contentItem ? contentItem.implicitHeight : 0) + topPadding + bottomPadding
margins: 0
+ overlap: 1
delegate: MenuItem { }
diff --git a/src/imports/controls/universal/MenuItem.qml b/src/imports/controls/universal/MenuItem.qml
index 7924e025..56f2eafa 100644
--- a/src/imports/controls/universal/MenuItem.qml
+++ b/src/imports/controls/universal/MenuItem.qml
@@ -60,8 +60,10 @@ T.MenuItem {
icon.color: !enabled ? Universal.baseLowColor : Universal.baseHighColor
contentItem: IconLabel {
- leftPadding: control.checkable && !control.mirrored ? control.indicator.width + control.spacing : 0
- rightPadding: control.checkable && control.mirrored ? control.indicator.width + control.spacing : 0
+ readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0
+ readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0
+ leftPadding: !control.mirrored ? indicatorPadding : arrowPadding
+ rightPadding: control.mirrored ? indicatorPadding : arrowPadding
spacing: control.spacing
mirrored: control.mirrored
@@ -74,6 +76,16 @@ T.MenuItem {
color: !control.enabled ? control.Universal.baseLowColor : control.Universal.baseHighColor
}
+ arrow: ColorImage {
+ x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding
+ y: control.topPadding + (control.availableHeight - height) / 2
+
+ visible: control.subMenu
+ mirror: control.mirrored
+ color: !enabled ? control.Universal.baseLowColor : control.Universal.baseHighColor
+ source: "qrc:/qt-project.org/imports/QtQuick/Controls.2/Universal/images/rightarrow.png"
+ }
+
indicator: ColorImage {
x: control.text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2
diff --git a/src/quicktemplates2/qquickmenu.cpp b/src/quicktemplates2/qquickmenu.cpp
index fa6b88ef..64f293d8 100644
--- a/src/quicktemplates2/qquickmenu.cpp
+++ b/src/quicktemplates2/qquickmenu.cpp
@@ -131,17 +131,30 @@ QT_BEGIN_NAMESPACE
}
\endcode
- Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to declare
- Action objects inside Menu:
+ 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
@@ -154,8 +167,24 @@ QT_BEGIN_NAMESPACE
\sa {Customizing Menu}, {Menu Controls}, {Popup Controls}
*/
+static const QQuickPopup::ClosePolicy defaultMenuClosePolicy =
+ QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnReleaseOutside;
+static const QQuickPopup::ClosePolicy cascadingSubMenuClosePolicy =
+ QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent | QQuickPopup::CloseOnReleaseOutsideParent;
+
+static bool shouldCascade()
+{
+#if QT_CONFIG(cursor)
+ return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows);
+#else
+ return false;
+#endif
+}
+
QQuickMenuPrivate::QQuickMenuPrivate()
- : contentItem(nullptr),
+ : cascade(shouldCascade()),
+ overlap(0),
+ contentItem(nullptr),
contentModel(nullptr),
delegate(nullptr)
{
@@ -184,7 +213,7 @@ void QQuickMenuPrivate::insertItem(int index, QQuickItem *item)
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, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered);
QObjectPrivate::connect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged);
QObjectPrivate::connect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered);
}
@@ -205,16 +234,15 @@ 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, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered);
QObjectPrivate::disconnect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged);
QObjectPrivate::disconnect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered);
}
}
-QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action)
+QQuickItem *QQuickMenuPrivate::beginCreateItem()
{
Q_Q(QQuickMenu);
if (!delegate)
@@ -227,15 +255,37 @@ QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action)
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;
- }
+ QQuickItem *item = qobject_cast<QQuickItem *>(object);
+ if (!item)
+ delete object;
+
+ return item;
+}
+
+void QQuickMenuPrivate::completeCreateItem()
+{
+ if (!delegate)
+ return;
- delete object;
- return nullptr;
+ 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)
@@ -299,6 +349,12 @@ void QQuickMenuPrivate::itemGeometryChanged(QQuickItem *, QQuickGeometryChange,
resizeItems();
}
+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::onItemPressed()
{
Q_Q(QQuickMenu);
@@ -314,9 +370,40 @@ void QQuickMenuPrivate::onItemHovered()
if (!button || !button->isHovered() || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
return;
+ QQuickMenuItem *oldCurrentItem = currentItem;
+
int index = contentModel->indexOf(button, nullptr);
- if (index != -1)
+ if (index != -1) {
setCurrentIndex(index);
+ if (oldCurrentItem != currentItem) {
+ if (oldCurrentItem)
+ closeSubMenu(oldCurrentItem->subMenu());
+ if (currentItem) {
+ QQuickMenu *subMenu = currentItem->menu();
+ if (subMenu && subMenu->cascade())
+ openSubMenu(currentItem, false);
+ }
+ }
+ }
+}
+
+void QQuickMenuPrivate::onItemTriggered()
+{
+ Q_Q(QQuickMenu);
+ QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(q->sender());
+ if (!item)
+ return;
+
+ if (item->subMenu()) {
+ openSubMenu(item, true);
+ } else {
+ // close the whole chain of menus
+ q->close();
+ while (parentMenu) {
+ parentMenu->close();
+ parentMenu = QQuickMenuPrivate::get(parentMenu)->parentMenu;
+ }
+ }
}
void QQuickMenuPrivate::onItemActiveFocusChanged()
@@ -330,6 +417,67 @@ void QQuickMenuPrivate::onItemActiveFocusChanged()
setCurrentIndex(indexOfItem);
}
+void QQuickMenuPrivate::openSubMenu(QQuickMenuItem *item, bool activate)
+{
+ Q_Q(QQuickMenu);
+ QQuickMenu *subMenu = item ? item->subMenu() : nullptr;
+ if (!subMenu)
+ return;
+
+ if (cascade) {
+ subMenu->setParentItem(item);
+ subMenu->setClosePolicy(cascadingSubMenuClosePolicy);
+ if (popupItem->isMirrored()) {
+ subMenu->setTransformOrigin(QQuickPopup::TopRight);
+ subMenu->setPosition(QPointF(-subMenu->width() - q->leftPadding() + subMenu->overlap(), -subMenu->topPadding()));
+ } else {
+ subMenu->setTransformOrigin(QQuickPopup::TopLeft);
+ subMenu->setPosition(QPointF(item->width() + q->rightPadding() - subMenu->overlap(), -subMenu->topPadding()));
+ }
+ } else {
+ subMenu->setParentItem(parentItem);
+ subMenu->setClosePolicy(defaultMenuClosePolicy);
+ subMenu->setTransformOrigin(QQuickPopup::Center);
+ subMenu->setPosition(QPointF(q->x() + (q->width() - subMenu->width()) / 2,
+ q->y() + (q->height() - subMenu->height()) / 2));
+ }
+
+ QQuickMenuPrivate *p = QQuickMenuPrivate::get(subMenu);
+ p->parentMenu = q;
+ if (activate)
+ p->setCurrentIndex(0);
+ subMenu->setCascade(cascade);
+ subMenu->open();
+
+ // transfer focus to the sub-menu
+ if (focus)
+ subMenu->popupItem()->setFocus(true);
+
+ if (!subMenu->cascade())
+ q->close();
+}
+
+void QQuickMenuPrivate::closeSubMenu(QQuickMenu *subMenu)
+{
+ if (!subMenu || !subMenu->isVisible())
+ return;
+
+ // transfer focus back to the parent menu
+ QQuickMenu *parentMenu = QQuickMenuPrivate::get(subMenu)->parentMenu;
+ if (parentMenu && parentMenu->hasFocus()) {
+ parentMenu->popupItem()->setFocus(true);
+ if (!subMenu->cascade())
+ parentMenu->open();
+ }
+
+ // close the whole chain of sub-menus
+ while (subMenu) {
+ QPointer<QQuickMenuItem> currentSubMenuItem = QQuickMenuPrivate::get(subMenu)->currentItem;
+ subMenu->close();
+ subMenu = currentSubMenuItem ? currentSubMenuItem->subMenu() : nullptr;
+ }
+}
+
int QQuickMenuPrivate::currentIndex() const
{
QVariant index = contentItem->property("currentIndex");
@@ -355,13 +503,15 @@ void QQuickMenuPrivate::setCurrentIndex(int index)
void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj)
{
- QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data);
- QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
+ 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) {
@@ -378,27 +528,27 @@ void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObj
int QQuickMenuPrivate::contentData_count(QQmlListProperty<QObject> *prop)
{
- QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data);
- return p->contentData.count();
+ QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
+ return QQuickMenuPrivate::get(q)->contentData.count();
}
QObject *QQuickMenuPrivate::contentData_at(QQmlListProperty<QObject> *prop, int index)
{
- QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data);
- return p->contentData.value(index);
+ QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
+ return QQuickMenuPrivate::get(q)->contentData.value(index);
}
void QQuickMenuPrivate::contentData_clear(QQmlListProperty<QObject> *prop)
{
- QQuickMenuPrivate *p = static_cast<QQuickMenuPrivate *>(prop->data);
- p->contentData.clear();
+ QQuickMenu *q = static_cast<QQuickMenu *>(prop->object);
+ QQuickMenuPrivate::get(q)->contentData.clear();
}
QQuickMenu::QQuickMenu(QObject *parent)
: QQuickPopup(*(new QQuickMenuPrivate), parent)
{
setFocus(true);
- setClosePolicy(CloseOnEscape | CloseOnPressOutside | CloseOnReleaseOutside);
+ setClosePolicy(defaultMenuClosePolicy);
}
/*!
@@ -568,8 +718,7 @@ QVariant QQuickMenu::contentModel() const
*/
QQmlListProperty<QObject> QQuickMenu::contentData()
{
- Q_D(QQuickMenu);
- return QQmlListProperty<QObject>(this, d,
+ return QQmlListProperty<QObject>(this, nullptr,
QQuickMenuPrivate::contentData_append,
QQuickMenuPrivate::contentData_count,
QQuickMenuPrivate::contentData_at,
@@ -602,6 +751,64 @@ void QQuickMenu::setTitle(QString &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;
+ emit cascadeChanged();
+}
+
+/*!
+ \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 cascadeChanged();
+}
+
+/*!
+ \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
@@ -758,7 +965,7 @@ void QQuickMenu::itemChange(QQuickItem::ItemChange change, const QQuickItem::Ite
QQuickPopup::itemChange(change, data);
if (change == QQuickItem::ItemVisibleHasChanged) {
- if (!data.boolValue) {
+ if (!data.boolValue && d->cascade) {
// Ensure that when the menu isn't visible, there's no current item
// the next time it's opened.
QQuickItem *focusItem = QQuickItemPrivate::get(d->contentItem)->subFocusItem;
@@ -796,6 +1003,16 @@ void QQuickMenu::keyReleaseEvent(QKeyEvent *event)
QMetaObject::invokeMethod(d->contentItem, "incrementCurrentIndex");
break;
+ case Qt::Key_Left:
+ case Qt::Key_Right:
+ if (d->popupItem->isMirrored() == (event->key() == Qt::Key_Right)) {
+ if (d->parentMenu && d->currentItem)
+ d->closeSubMenu(this);
+ } else {
+ d->openSubMenu(d->currentItem, true);
+ }
+ return;
+
default:
break;
}
diff --git a/src/quicktemplates2/qquickmenu_p.h b/src/quicktemplates2/qquickmenu_p.h
index 04186f1a..f7f56d45 100644
--- a/src/quicktemplates2/qquickmenu_p.h
+++ b/src/quicktemplates2/qquickmenu_p.h
@@ -65,6 +65,8 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenu : public QQuickPopup
Q_PROPERTY(QVariant contentModel READ contentModel CONSTANT FINAL)
Q_PROPERTY(QQmlListProperty<QObject> contentData READ contentData FINAL)
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL)
+ Q_PROPERTY(bool cascade READ cascade WRITE setCascade NOTIFY cascadeChanged FINAL REVISION 3)
+ Q_PROPERTY(qreal overlap READ overlap WRITE setOverlap NOTIFY overlapChanged FINAL REVISION 3)
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL REVISION 3)
Q_CLASSINFO("DefaultProperty", "contentData")
@@ -85,6 +87,12 @@ public:
QString title() const;
void setTitle(QString &title);
+ bool cascade() const;
+ void setCascade(bool cascade);
+
+ qreal overlap() const;
+ void setOverlap(qreal overlap);
+
QQmlComponent *delegate() const;
void setDelegate(QQmlComponent *delegate);
@@ -98,6 +106,8 @@ protected:
Q_SIGNALS:
void titleChanged(const QString &title);
+ Q_REVISION(3) void cascadeChanged();
+ Q_REVISION(3) void overlapChanged();
Q_REVISION(3) void delegateChanged();
protected:
diff --git a/src/quicktemplates2/qquickmenu_p_p.h b/src/quicktemplates2/qquickmenu_p_p.h
index b9e0512b..9fd10779 100644
--- a/src/quicktemplates2/qquickmenu_p_p.h
+++ b/src/quicktemplates2/qquickmenu_p_p.h
@@ -51,11 +51,11 @@
#include <QtCore/qvector.h>
#include <QtCore/qpointer.h>
+#include <QtQuickTemplates2/private/qquickmenu_p.h>
#include <QtQuickTemplates2/private/qquickpopup_p_p.h>
QT_BEGIN_NAMESPACE
-class QQuickMenu;
class QQuickAction;
class QQmlComponent;
class QQmlObjectModel;
@@ -68,10 +68,20 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuPrivate : public QQuickPopupPri
public:
QQuickMenuPrivate();
+ static QQuickMenuPrivate *get(QQuickMenu *menu)
+ {
+ return menu->d_func();
+ }
+
QQuickItem *itemAt(int index) const;
void insertItem(int index, QQuickItem *item);
void moveItem(int from, int to);
void removeItem(int index, QQuickItem *item);
+
+ QQuickItem *beginCreateItem();
+ void completeCreateItem();
+
+ QQuickItem *createItem(QQuickMenu *menu);
QQuickItem *createItem(QQuickAction *action);
void resizeItem(QQuickItem *item);
@@ -83,10 +93,16 @@ public:
void itemDestroyed(QQuickItem *item) override;
void itemGeometryChanged(QQuickItem *, QQuickGeometryChange change, const QRectF &diff) override;
+ bool blockInput(QQuickItem *item, const QPointF &point) const override;
+
void onItemPressed();
void onItemHovered();
+ void onItemTriggered();
void onItemActiveFocusChanged();
+ void openSubMenu(QQuickMenuItem *item, bool activate);
+ void closeSubMenu(QQuickMenu *subMenu);
+
int currentIndex() const;
void setCurrentIndex(int index);
@@ -95,6 +111,9 @@ public:
static QObject *contentData_at(QQmlListProperty<QObject> *prop, int index);
static void contentData_clear(QQmlListProperty<QObject> *prop);
+ bool cascade;
+ qreal overlap;
+ QPointer<QQuickMenu> parentMenu;
QPointer<QQuickMenuItem> currentItem;
QQuickItem *contentItem; // TODO: cleanup
QVector<QObject *> contentData;
diff --git a/src/quicktemplates2/qquickmenuitem.cpp b/src/quicktemplates2/qquickmenuitem.cpp
index 3ef03caf..69a2b73b 100644
--- a/src/quicktemplates2/qquickmenuitem.cpp
+++ b/src/quicktemplates2/qquickmenuitem.cpp
@@ -36,6 +36,7 @@
#include "qquickmenuitem_p.h"
#include "qquickmenuitem_p_p.h"
+#include "qquickmenu_p.h"
#include <QtGui/qpa/qplatformtheme.h>
#include <QtQuick/private/qquickevents_p_p.h>
@@ -89,7 +90,9 @@ QT_BEGIN_NAMESPACE
QQuickMenuItemPrivate::QQuickMenuItemPrivate()
: highlighted(false),
- menu(nullptr)
+ arrow(nullptr),
+ menu(nullptr),
+ subMenu(nullptr)
{
}
@@ -103,6 +106,24 @@ void QQuickMenuItemPrivate::setMenu(QQuickMenu *newMenu)
emit q->menuChanged();
}
+void QQuickMenuItemPrivate::setSubMenu(QQuickMenu *newSubMenu)
+{
+ Q_Q(QQuickMenuItem);
+ if (subMenu == newSubMenu)
+ return;
+
+ if (subMenu)
+ QObject::disconnect(subMenu, &QQuickMenu::titleChanged, q, &QQuickAbstractButton::setText);
+
+ if (newSubMenu) {
+ QObject::connect(newSubMenu, &QQuickMenu::titleChanged, q, &QQuickAbstractButton::setText);
+ q->setText(newSubMenu->title());
+ }
+
+ subMenu = newSubMenu;
+ emit q->subMenuChanged();
+}
+
/*!
\qmlsignal void QtQuick.Controls::MenuItem::triggered()
@@ -142,6 +163,33 @@ void QQuickMenuItem::setHighlighted(bool highlighted)
/*!
\since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty Item QtQuick.Controls::MenuItem::arrow
+
+ This property holds the sub-menu arrow item.
+
+ \sa {Customizing MenuItem}
+*/
+QQuickItem *QQuickMenuItem::arrow() const
+{
+ Q_D(const QQuickMenuItem);
+ return d->arrow;
+}
+
+void QQuickMenuItem::setArrow(QQuickItem *arrow)
+{
+ Q_D(QQuickMenuItem);
+ if (d->arrow == arrow)
+ return;
+
+ QQuickControlPrivate::destroyDelegate(d->arrow, this);
+ d->arrow = arrow;
+ if (arrow && !arrow->parentItem())
+ arrow->setParentItem(this);
+ emit arrowChanged();
+}
+
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
\qmlproperty Menu QtQuick.Controls::MenuItem::menu
\readonly
@@ -154,6 +202,20 @@ QQuickMenu *QQuickMenuItem::menu() const
return d->menu;
}
+/*!
+ \since QtQuick.Controls 2.3 (Qt 5.10)
+ \qmlproperty Menu QtQuick.Controls::MenuItem::subMenu
+ \readonly
+
+ This property holds the sub-menu that this item presents in
+ the parent menu, or \c null if this item is not a sub-menu item.
+*/
+QQuickMenu *QQuickMenuItem::subMenu() const
+{
+ Q_D(const QQuickMenuItem);
+ return d->subMenu;
+}
+
QFont QQuickMenuItem::defaultFont() const
{
return QQuickControlPrivate::themeFont(QPlatformTheme::MenuItemFont);
diff --git a/src/quicktemplates2/qquickmenuitem_p.h b/src/quicktemplates2/qquickmenuitem_p.h
index 92b42488..7c469b6a 100644
--- a/src/quicktemplates2/qquickmenuitem_p.h
+++ b/src/quicktemplates2/qquickmenuitem_p.h
@@ -59,7 +59,9 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuItem : public QQuickAbstractBut
{
Q_OBJECT
Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL)
+ Q_PROPERTY(QQuickItem *arrow READ arrow WRITE setArrow NOTIFY arrowChanged FINAL REVISION 3)
Q_PROPERTY(QQuickMenu *menu READ menu NOTIFY menuChanged FINAL REVISION 3)
+ Q_PROPERTY(QQuickMenu *subMenu READ subMenu NOTIFY subMenuChanged FINAL REVISION 3)
public:
explicit QQuickMenuItem(QQuickItem *parent = nullptr);
@@ -67,12 +69,18 @@ public:
bool isHighlighted() const;
void setHighlighted(bool highlighted);
+ QQuickItem *arrow() const;
+ void setArrow(QQuickItem *arrow);
+
QQuickMenu *menu() const;
+ QQuickMenu *subMenu() const;
Q_SIGNALS:
void triggered();
void highlightedChanged();
+ Q_REVISION(3) void arrowChanged();
Q_REVISION(3) void menuChanged();
+ Q_REVISION(3) void subMenuChanged();
protected:
QFont defaultFont() const override;
diff --git a/src/quicktemplates2/qquickmenuitem_p_p.h b/src/quicktemplates2/qquickmenuitem_p_p.h
index f1cded91..d8b86d6e 100644
--- a/src/quicktemplates2/qquickmenuitem_p_p.h
+++ b/src/quicktemplates2/qquickmenuitem_p_p.h
@@ -68,9 +68,12 @@ public:
}
void setMenu(QQuickMenu *menu);
+ void setSubMenu(QQuickMenu *subMenu);
bool highlighted;
+ QQuickItem *arrow;
QQuickMenu *menu;
+ QQuickMenu *subMenu;
};
QT_END_NAMESPACE