summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel de Dietrich <gabriel.dedietrich@digia.com>2013-09-05 17:29:10 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-13 14:56:38 +0200
commit4eb3400843cafcedffa47fb8a272704bca4e9473 (patch)
tree6541694bca4cca47ea8fa8b4cc8db38fb3ead11a
parent82081e737038262c82c2e408d412fc80a607bf9f (diff)
downloadqtquickcontrols-4eb3400843cafcedffa47fb8a272704bca4e9473.tar.gz
Menu: Enable mnemonic menu navigation
We also added a new mnemonic specific shortcut context matcher. This prevents two menu items with the same mnemonic but within different menus to be reported as ambiguous. Task-number: QTBUG-33030 ChangeLog: Added mnemonic navigation for menus Change-Id: I192c9aacba4d15851fe65bf9201251962fe976d5 Reviewed-by: J-P Nurmi <jpnurmi@digia.com>
-rw-r--r--examples/quick/controls/gallery/main.qml18
-rw-r--r--src/controls/ApplicationWindow.qml2
-rw-r--r--src/controls/Menu.qml51
-rw-r--r--src/controls/MenuBar.qml62
-rw-r--r--src/controls/qquickaction.cpp37
-rw-r--r--src/controls/qquickmenu.cpp4
-rw-r--r--src/controls/qquickmenu_p.h4
-rw-r--r--src/controls/qquickmenuitem.cpp7
-rw-r--r--src/controls/qquickmenuitem_p.h3
-rw-r--r--src/private/qquickstyleitem.cpp3
-rw-r--r--src/styles/Desktop/MenuBarStyle.qml7
-rw-r--r--src/styles/Desktop/MenuStyle.qml7
12 files changed, 176 insertions, 29 deletions
diff --git a/examples/quick/controls/gallery/main.qml b/examples/quick/controls/gallery/main.qml
index ec00627b..2896c356 100644
--- a/examples/quick/controls/gallery/main.qml
+++ b/examples/quick/controls/gallery/main.qml
@@ -111,20 +111,20 @@ ApplicationWindow {
Action {
id: a1
- text: "Align Left"
+ text: "Align &Left"
checkable: true
Component.onCompleted: checked = true
}
Action {
id: a2
- text: "Center"
+ text: "&Center"
checkable: true
}
Action {
id: a3
- text: "Align Right"
+ text: "Align &Right"
checkable: true
}
}
@@ -138,18 +138,18 @@ ApplicationWindow {
MenuItem { action: pasteAction }
MenuSeparator {}
Menu {
- title: "Text Format"
+ title: "Text &Format"
MenuItem { action: a1 }
MenuItem { action: a2 }
MenuItem { action: a3 }
MenuSeparator { }
- MenuItem { text: "Allow Hyphenation"; checkable: true }
+ MenuItem { text: "Allow &Hyphenation"; checkable: true }
}
Menu {
- title: "Font Style"
- MenuItem { text: "Bold"; checkable: true }
- MenuItem { text: "Italic"; checkable: true }
- MenuItem { text: "Underline"; checkable: true }
+ title: "Font &Style"
+ MenuItem { text: "&Bold"; checkable: true }
+ MenuItem { text: "&Italic"; checkable: true }
+ MenuItem { text: "&Underline"; checkable: true }
}
}
diff --git a/src/controls/ApplicationWindow.qml b/src/controls/ApplicationWindow.qml
index 364b2b11..8967a8a1 100644
--- a/src/controls/ApplicationWindow.qml
+++ b/src/controls/ApplicationWindow.qml
@@ -133,6 +133,8 @@ Window {
id: backgroundItem
anchors.fill: parent
+ Keys.forwardTo: [menuBar.__contentItem]
+
Item {
id: contentArea
anchors.top: toolBarArea.bottom
diff --git a/src/controls/Menu.qml b/src/controls/Menu.qml
index 6c9f8fd2..525b0737 100644
--- a/src/controls/Menu.qml
+++ b/src/controls/Menu.qml
@@ -133,7 +133,7 @@ MenuPrivate {
property Component style: Qt.createComponent(Settings.style + "/MenuStyle.qml", root)
/*! \internal */
- property var __menuBar: null
+ property var __parentContentItem: __parentMenu.__contentItem
/*! \internal */
property int __currentIndex: -1
/*! \internal */
@@ -144,6 +144,8 @@ MenuPrivate {
sourceComponent: __menuComponent
active: !root.__isNative && root.__popupVisible
focus: true
+ Keys.forwardTo: item ? [item, root.__parentContentItem] : []
+ property bool altPressed: root.__parentContentItem ? root.__parentContentItem.altPressed : false
}
/*! \internal */
@@ -174,14 +176,30 @@ MenuPrivate {
}
focus: true
- Keys.forwardTo: __menuBar ? [__menuBar] : []
+ property var mnemonicsMap: ({})
+
+ Keys.onPressed: {
+ var item = null
+ if (!(event.modifiers & Qt.AltModifier)
+ && (item = mnemonicsMap[event.text.toUpperCase()])) {
+ if (item.isSubmenu) {
+ root.__currentIndex = item.menuItemIndex
+ item.showSubMenu(true)
+ item.menuItem.__currentIndex = 0
+ } else {
+ triggerAndDismiss(item)
+ }
+ event.accepted = true
+ } else {
+ event.accepted = false
+ }
+ }
+
Keys.onEscapePressed: root.__dismissMenu()
Keys.onDownPressed: {
- if (root.__currentIndex < 0) {
- root.__currentIndex = 0
- return
- }
+ if (root.__currentIndex < 0)
+ root.__currentIndex = -1
for (var i = root.__currentIndex + 1;
i < root.items.length && !canBeHovered(i); i++)
@@ -204,13 +222,13 @@ MenuPrivate {
}
Keys.onLeftPressed: {
- if (root.__parentMenu)
+ if ((event.accepted = root.__parentMenu.hasOwnProperty("title")))
__closeMenu()
}
Keys.onRightPressed: {
var item = itemsRepeater.itemAt(root.__currentIndex)
- if (item && item.isSubmenu) {
+ if ((event.accepted = (item && item.isSubmenu))) {
item.showSubMenu(true)
item.menuItem.__currentIndex = 0
}
@@ -220,8 +238,9 @@ MenuPrivate {
Keys.onReturnPressed: menuFrameLoader.triggerAndDismiss()
Keys.onEnterPressed: menuFrameLoader.triggerAndDismiss()
- function triggerAndDismiss() {
- var item = itemsRepeater.itemAt(root.__currentIndex)
+ function triggerAndDismiss(item) {
+ if (!item)
+ item = itemsRepeater.itemAt(root.__currentIndex)
if (item && !item.isSeparator) {
root.__dismissMenu()
if (!item.isSubmenu)
@@ -261,7 +280,7 @@ MenuPrivate {
var itemUnderMouse = column.childAt(pos.x, pos.y)
if (itemUnderMouse) {
currentItem = itemUnderMouse
- } else {
+ } else if (currentItem) {
var itemItem = currentItem.item
if (!itemItem.contains(itemItem.mapFromItem(column, pos)))
currentItem = null
@@ -294,6 +313,7 @@ MenuPrivate {
readonly property bool isSubmenu: !!menuItem && menuItem.type === MenuItemType.Menu
property bool selected: !isSeparator && root.__currentIndex === index
property string text: isSubmenu ? menuItem.title : !isSeparator ? menuItem.text : ""
+ property bool showUnderlined: __contentItem.altPressed
property int menuItemIndex: index
@@ -328,7 +348,14 @@ MenuPrivate {
}
}
- Component.onCompleted: menuItem.__visualItem = menuItemLoader
+ Component.onCompleted: {
+ menuItem.__visualItem = menuItemLoader
+
+ var title = text
+ var ampersandPos = title.indexOf("&")
+ if (ampersandPos !== -1)
+ menuFrameLoader.mnemonicsMap[title[ampersandPos + 1].toUpperCase()] = menuItemLoader
+ }
}
}
diff --git a/src/controls/MenuBar.qml b/src/controls/MenuBar.qml
index eabb4d48..a126c7c6 100644
--- a/src/controls/MenuBar.qml
+++ b/src/controls/MenuBar.qml
@@ -82,6 +82,8 @@ MenuBarPrivate {
sourceComponent: __menuBarComponent
active: !root.__isNative
focus: true
+ Keys.forwardTo: [item]
+ property bool altPressed: item ? item.altPressed : false
}
/*! \internal */
@@ -118,7 +120,51 @@ MenuBarPrivate {
value: menuMouseArea.z - 1
}
- focus: openedMenuIndex !== -1
+ focus: true
+
+ property bool altPressed: false
+ property bool altPressedAgain: false
+ property var mnemonicsMap: ({})
+
+ Keys.onPressed: {
+ var action = null
+ if (event.key === Qt.Key_Alt) {
+ if (!altPressed)
+ altPressed = true
+ else
+ altPressedAgain = true
+ } else if (altPressed && (action = mnemonicsMap[event.text.toUpperCase()])) {
+ preselectMenuItem = true
+ action.trigger()
+ event.accepted = true
+ }
+ }
+
+ function dismissActiveFocus(event, reason) {
+ if (reason) {
+ altPressedAgain = false
+ altPressed = false
+ openedMenuIndex = -1
+ root.__contentItem.parent.forceActiveFocus()
+ } else {
+ event.accepted = false
+ }
+ }
+
+ Keys.onReleased: dismissActiveFocus(event, altPressedAgain && openedMenuIndex === -1)
+ Keys.onEscapePressed: dismissActiveFocus(event, openedMenuIndex === -1)
+
+ function maybeOpenFirstMenu(event) {
+ if (altPressed && openedMenuIndex === -1) {
+ preselectMenuItem = true
+ openedMenuIndex = 0
+ } else {
+ event.accepted = false
+ }
+ }
+
+ Keys.onUpPressed: maybeOpenFirstMenu(event)
+ Keys.onDownPressed: maybeOpenFirstMenu(event)
Keys.onLeftPressed: {
if (openedMenuIndex > 0) {
@@ -128,7 +174,7 @@ MenuBarPrivate {
}
Keys.onRightPressed: {
- if (openedMenuIndex < root.menus.length - 1) {
+ if (openedMenuIndex !== -1 && openedMenuIndex < root.menus.length - 1) {
preselectMenuItem = true
openedMenuIndex++
}
@@ -177,6 +223,7 @@ MenuBarPrivate {
property var menuItem: modelData
property bool selected: menuMouseArea.hoveredItem === menuItemLoader
property bool sunken: menuItem.__popupVisible || menuBarLoader.openedMenuIndex === index
+ property bool showUnderlined: menuBarLoader.altPressed
sourceComponent: menuBarLoader.menuItemStyle
property int menuItemIndex: index
@@ -203,9 +250,18 @@ MenuBarPrivate {
}
}
+ Connections {
+ target: menuItem.__action
+ onTriggered: menuBarLoader.openedMenuIndex = menuItemIndex
+ }
+
Component.onCompleted: {
menuItem.__visualItem = menuItemLoader
- menuItem.__menuBar = menuBarLoader
+
+ var title = menuItem.title
+ var ampersandPos = title.indexOf("&")
+ if (ampersandPos !== -1)
+ menuBarLoader.mnemonicsMap[title[ampersandPos + 1].toUpperCase()] = menuItem.__action
}
}
}
diff --git a/src/controls/qquickaction.cpp b/src/controls/qquickaction.cpp
index e240d111..6a031404 100644
--- a/src/controls/qquickaction.cpp
+++ b/src/controls/qquickaction.cpp
@@ -41,6 +41,7 @@
#include "qquickaction_p.h"
#include "qquickexclusivegroup_p.h"
+#include "qquickmenuitem_p.h"
#include <QtGui/qguiapplication.h>
#include <QtQuick/qquickitem.h>
@@ -203,6 +204,8 @@ void QQuickAction::setText(const QString &text)
emit textChanged();
}
+namespace {
+
bool qShortcutContextMatcher(QObject *o, Qt::ShortcutContext context)
{
switch (context) {
@@ -226,6 +229,38 @@ bool qShortcutContextMatcher(QObject *o, Qt::ShortcutContext context)
return false;
}
+bool qMnemonicContextMatcher(QObject *o, Qt::ShortcutContext context)
+{
+ switch (context) {
+ case Qt::ApplicationShortcut:
+ return true;
+ case Qt::WindowShortcut: {
+ QObject *w = o;
+ while (w && !w->isWindowType()) {
+ w = w->parent();
+ if (QQuickItem * item = qobject_cast<QQuickItem*>(w))
+ w = item->window();
+ else if (QQuickMenuBase *mb = qobject_cast<QQuickMenuBase *>(w)) {
+ QQuickItem *vi = mb->visualItem();
+ if (vi && vi->isVisible())
+ w = vi->window();
+ else
+ break; // Non visible menu objects don't get mnemonic match
+ }
+ }
+ if (w && w == QGuiApplication::focusWindow())
+ return true;
+ }
+ case Qt::WidgetShortcut:
+ case Qt::WidgetWithChildrenShortcut:
+ break;
+ }
+
+ return false;
+}
+
+} // namespace
+
QString QQuickAction::shortcut() const
{
return m_shortcut.toString(QKeySequence::NativeText);
@@ -262,7 +297,7 @@ void QQuickAction::setMnemonicFromText(const QString &text)
if (!m_mnemonic.isEmpty()) {
Qt::ShortcutContext context = Qt::WindowShortcut;
- QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, m_mnemonic, context, qShortcutContextMatcher);
+ QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, m_mnemonic, context, qMnemonicContextMatcher);
}
}
diff --git a/src/controls/qquickmenu.cpp b/src/controls/qquickmenu.cpp
index 2dbaf952..f65d7044 100644
--- a/src/controls/qquickmenu.cpp
+++ b/src/controls/qquickmenu.cpp
@@ -401,8 +401,10 @@ void QQuickMenu::__popup(qreal x, qreal y, int atItemIndex)
void QQuickMenu::setMenuContentItem(QQuickItem *item)
{
- if (m_menuContentItem != item)
+ if (m_menuContentItem != item) {
m_menuContentItem = item;
+ emit menuContentItemChanged();
+ }
}
void QQuickMenu::setPopupVisible(bool v)
diff --git a/src/controls/qquickmenu_p.h b/src/controls/qquickmenu_p.h
index 54f7451b..f15e6caa 100644
--- a/src/controls/qquickmenu_p.h
+++ b/src/controls/qquickmenu_p.h
@@ -68,11 +68,12 @@ class QQuickMenu : public QQuickMenuText
Q_PROPERTY(int __selectedIndex READ selectedIndex WRITE setSelectedIndex NOTIFY __selectedIndexChanged)
Q_PROPERTY(bool __popupVisible READ popupVisible NOTIFY popupVisibleChanged)
- Q_PROPERTY(QQuickItem *__contentItem READ menuContentItem WRITE setMenuContentItem)
+ Q_PROPERTY(QQuickItem *__contentItem READ menuContentItem WRITE setMenuContentItem NOTIFY menuContentItemChanged)
Q_PROPERTY(int __minimumWidth READ minimumWidth WRITE setMinimumWidth)
Q_PROPERTY(QFont __font READ font WRITE setFont)
Q_PROPERTY(qreal __xOffset READ xOffset WRITE setXOffset)
Q_PROPERTY(qreal __yOffset READ yOffset WRITE setYOffset)
+ Q_PROPERTY(QQuickAction *__action READ action CONSTANT)
public:
Q_INVOKABLE void popup();
@@ -98,6 +99,7 @@ Q_SIGNALS:
void __selectedIndexChanged();
void __menuClosed();
void popupVisibleChanged();
+ void menuContentItemChanged();
public:
QQuickMenu(QObject *parent = 0);
diff --git a/src/controls/qquickmenuitem.cpp b/src/controls/qquickmenuitem.cpp
index 2fda7e00..4d276ac1 100644
--- a/src/controls/qquickmenuitem.cpp
+++ b/src/controls/qquickmenuitem.cpp
@@ -84,6 +84,13 @@ void QQuickMenuBase::setVisible(bool v)
}
}
+QObject *QQuickMenuBase::parentMenuOrMenuBar() const
+{
+ if (!m_parentMenu)
+ return parent();
+ return m_parentMenu;
+}
+
QQuickMenu *QQuickMenuBase::parentMenu() const
{
return m_parentMenu;
diff --git a/src/controls/qquickmenuitem_p.h b/src/controls/qquickmenuitem_p.h
index 02e7f5e3..791a0edb 100644
--- a/src/controls/qquickmenuitem_p.h
+++ b/src/controls/qquickmenuitem_p.h
@@ -78,7 +78,7 @@ class QQuickMenuBase: public QObject
Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)
Q_PROPERTY(QQuickMenuItemType::MenuItemType type READ type CONSTANT)
- Q_PROPERTY(QQuickMenu *__parentMenu READ parentMenu CONSTANT)
+ Q_PROPERTY(QObject *__parentMenu READ parentMenuOrMenuBar CONSTANT)
Q_PROPERTY(bool __isNative READ isNative CONSTANT)
Q_PROPERTY(QQuickItem *__visualItem READ visualItem WRITE setVisualItem)
@@ -93,6 +93,7 @@ public:
virtual void setVisible(bool);
QQuickMenu *parentMenu() const;
+ QObject *parentMenuOrMenuBar() const;
virtual void setParentMenu(QQuickMenu *parentMenu);
QQuickMenuItemContainer *container() const;
diff --git a/src/private/qquickstyleitem.cpp b/src/private/qquickstyleitem.cpp
index b8edbd2d..d0f3e26e 100644
--- a/src/private/qquickstyleitem.cpp
+++ b/src/private/qquickstyleitem.cpp
@@ -318,7 +318,6 @@ void QQuickStyleItem::initStyleOption()
if (!m_styleoption)
m_styleoption = new QStyleOptionTab();
-
QStyleOptionTab *opt = qstyleoption_cast<QStyleOptionTab*>(m_styleoption);
opt->text = text();
@@ -396,6 +395,7 @@ void QQuickStyleItem::initStyleOption()
QStyleOptionMenuItem *opt = qstyleoption_cast<QStyleOptionMenuItem*>(m_styleoption);
opt->text = text();
opt->menuItemType = QStyleOptionMenuItem::Normal;
+ setProperty("_q_showUnderlined", m_hints["showUnderlined"].toBool());
if (const QFont *font = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::MenuBarFont)) {
opt->font = *font;
@@ -443,6 +443,7 @@ void QQuickStyleItem::initStyleOption()
}
if (m_properties["icon"].canConvert<QIcon>())
opt->icon = m_properties["icon"].value<QIcon>();
+ setProperty("_q_showUnderlined", m_hints["showUnderlined"].toBool());
if (const QFont *font = QGuiApplicationPrivate::platformTheme()->font(m_itemType == ComboBoxItem ? QPlatformTheme::ComboMenuItemFont : QPlatformTheme::MenuFont)) {
opt->font = *font;
diff --git a/src/styles/Desktop/MenuBarStyle.qml b/src/styles/Desktop/MenuBarStyle.qml
index 291df0fb..f1d7b353 100644
--- a/src/styles/Desktop/MenuBarStyle.qml
+++ b/src/styles/Desktop/MenuBarStyle.qml
@@ -51,6 +51,8 @@ Style {
width: implicitWidth + 2 * (pixelMetric("menubarhmargin") + pixelMetric("menubarpanelwidth"))
height: implicitHeight + 2 * (pixelMetric("menubarvmargin") + pixelMetric("menubarpanelwidth"))
+ pixelMetric("spacebelowmenubar")
+
+ Accessible.role: Accessible.MenuBar
}
property Component menuItem: StyleItem {
@@ -66,5 +68,10 @@ Style {
enabled: menuItem.enabled
selected: (parent && parent.selected) || sunken
sunken: parent && parent.sunken
+
+ hints: { "showUnderlined": showUnderlined }
+
+ Accessible.role: Accessible.MenuItem
+ Accessible.name: StyleHelpers.removeMnemonics(text)
}
}
diff --git a/src/styles/Desktop/MenuStyle.qml b/src/styles/Desktop/MenuStyle.qml
index 960a163d..d659c25d 100644
--- a/src/styles/Desktop/MenuStyle.qml
+++ b/src/styles/Desktop/MenuStyle.qml
@@ -65,6 +65,8 @@ Style {
}
color: __syspal.window
}
+
+ Accessible.role: Accessible.PopupMenu
}
property Component menuItem: StyleItem {
@@ -81,6 +83,8 @@ Style {
selected: !!parent && parent.selected
on: !!menuItem && !!menuItem["checkable"] && menuItem.checked
+ hints: { "showUnderlined": showUnderlined }
+
properties: {
"checkable": !!menuItem && !!menuItem["checkable"],
"exclusive": !!menuItem && !!menuItem["exclusiveGroup"],
@@ -88,5 +92,8 @@ Style {
"isSubmenu": isSubmenu,
"icon": !!menuItem && menuItem.__icon
}
+
+ Accessible.role: Accessible.MenuItem
+ Accessible.name: StyleHelpers.removeMnemonics(text)
}
}