← Back to team overview

ubuntu-sdk-team team mailing list archive

[Merge] lp:~nick-dedekind/ubuntu-ui-toolkit/actionItem-mnemonics into lp:ubuntu-ui-toolkit/staging

 

Nick Dedekind has proposed merging lp:~nick-dedekind/ubuntu-ui-toolkit/actionItem-mnemonics into lp:ubuntu-ui-toolkit/staging.

Commit message:
Moved Action mnemonic logic to the ActionItem.

Requested reviews:
  Ubuntu SDK team (ubuntu-sdk-team)

For more details, see:
https://code.launchpad.net/~nick-dedekind/ubuntu-ui-toolkit/actionItem-mnemonics/+merge/313115

Moved Action mnemonic logic to the ActionItem.
Added ActionItem.mnemonic for altering properties of the mnemonic logic.
-- 
Your team Ubuntu SDK team is requested to review the proposed merge of lp:~nick-dedekind/ubuntu-ui-toolkit/actionItem-mnemonics into lp:ubuntu-ui-toolkit/staging.
=== modified file 'components.api'
--- components.api	2016-09-17 05:48:25 +0000
+++ components.api	2016-12-13 10:46:54 +0000
@@ -45,13 +45,14 @@
     property bool active
     function addAction(Action action)
     function removeAction(Action action)
-Ubuntu.Components.ActionItem 1.0 0.1 UCActionItem: StyledItem
+Ubuntu.Components.ActionItem 1.3 1.0 0.1 UCActionItem: StyledItem
     property Action action
     property string iconName
     property url iconSource
     signal triggered(var value)
     function trigger(var value)
     function trigger()
+    readonly property ActionMnemonic mnemonic 1.3
     property string text
 Ubuntu.Components.Styles.ActionItemProperties 1.3: QtObject
     property color backgroundColor
@@ -79,6 +80,10 @@
     function removeAction(Action action)
     function addLocalContext(ActionContext context)
     function removeLocalContext(ActionContext context)
+Ubuntu.Components.ActionMnemonic 1.3: QtObject
+    property int modifier
+    readonly property StandardKey sequence
+    property bool visible
 Ubuntu.Components.Popups.ActionSelectionPopover 1.0 0.1: Popover
     property var actions
     property Component delegate
@@ -1597,6 +1602,7 @@
     signal triggered(var value)
     function trigger(var value)
     function trigger()
+    readonly property ActionMnemonic mnemonic
     property string text
 Ubuntu.Components.ToolbarButton 1.3: StyledItem
     property Action action
@@ -1605,6 +1611,7 @@
     signal triggered(var value)
     function trigger(var value)
     function trigger()
+    readonly property ActionMnemonic mnemonic
     property string text
 Ubuntu.Components.ToolbarItems 1.0 0.1: Item
     property Item back

=== modified file 'src/UbuntuToolkit/ubuntutoolkitmodule.cpp'
--- src/UbuntuToolkit/ubuntutoolkitmodule.cpp	2016-09-29 10:19:06 +0000
+++ src/UbuntuToolkit/ubuntutoolkitmodule.cpp	2016-12-13 10:46:54 +0000
@@ -398,6 +398,7 @@
     qmlRegisterType<UCMainViewBase>(uri, 1, 3, "MainViewBase");
     qmlRegisterType<ActionList>(uri, 1, 3, "ActionList");
     qmlRegisterType<ExclusiveGroup>(uri, 1, 3, "ExclusiveGroup");
+    qmlRegisterType<UCActionItem, 1>(uri, 1, 3, "ActionItem");
 }
 
 void UbuntuToolkitModule::undefineModule()

=== modified file 'src/UbuntuToolkit/ucaction.cpp'
--- src/UbuntuToolkit/ucaction.cpp	2016-09-20 08:23:40 +0000
+++ src/UbuntuToolkit/ucaction.cpp	2016-12-13 10:46:54 +0000
@@ -138,20 +138,6 @@
  *
  * Examples: See \l Page
  *
- * \section2 Mnemonics
- * Since Ubuntu.Components 1.3 Action supports mnemonics. Mnemonics are shortcuts
- * defined in the \l text property, prefixed the shortcut letter with \&. For instance
- * \c "\&Call" will bint the \c "Alt-C" shortcut to the action. When a mnemonic
- * is detected on the Action and a keyboard is attached to the device, the \l text
- * property will provide a formatted text having the mnemonic letter underscored.
- * \qml
- * Action {
- *     id: call
- *     iconName: "call"
- *     text: "&Call"
- * }
- * \endqml
- *
  * \section2 Checkable property
  * Since Ubuntu.Components 1.3 Action supports the checkable/checked properties.
  * \qml
@@ -191,40 +177,10 @@
 /*!
  * \qmlproperty string Action::text
  * The user visible primary label of the action.
- *
- * Mnemonics are shortcuts prefixed in the text with \&. If the text has multiple
- * occurences of the \& character, the first one will be considered for the shortcut.
- * However \&\& can be used for a single \& in the text, not as a mnemonic.
- * The \& character cannot be used as shortcut.
  */
 QString UCAction::text()
 {
-    QString displayText(m_text);
-    // if we have a mnemonic, underscore it
-    if (!m_mnemonic.isEmpty()) {
-
-        QString mnemonic = "&" + m_mnemonic.toString().remove(QStringLiteral("Alt+"));
-        // patch special cases
-        mnemonic.replace(QStringLiteral("Space"), QStringLiteral(" "));
-        int mnemonicIndex = m_text.indexOf(mnemonic);
-        if (mnemonicIndex < 0) {
-            // try lower case
-            mnemonic = mnemonic.toLower();
-            mnemonicIndex = m_text.indexOf(mnemonic);
-        }
-        ACT_TRACE("MNEM" << mnemonic);
-        // FIXME: we need QInputDeviceInfo to detect the keyboard attechment
-        // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1276808
-        if (QuickUtils::instance()->keyboardAttached()) {
-            // underscore the character
-            displayText.replace(mnemonicIndex, mnemonic.length(), "<u>" + mnemonic[1] + "</u>");
-        } else {
-            displayText.remove(mnemonicIndex, 1);
-        }
-    }
-    // Escape ampersands
-    displayText.replace(QStringLiteral("&&"), QStringLiteral("&amp;"));
-    return displayText;
+    return m_text;
 }
 void UCAction::setText(const QString &text)
 {
@@ -232,7 +188,6 @@
         return;
     }
     m_text = text;
-    setMnemonicFromText(m_text);
     Q_EMIT textChanged();
 }
 void UCAction::resetText()
@@ -240,25 +195,6 @@
     setText(QString());
 }
 
-void UCAction::setMnemonicFromText(const QString &text)
-{
-    QKeySequence sequence = QKeySequence::mnemonic(text);
-    if (sequence == m_mnemonic) {
-        return;
-    }
-    if (!m_mnemonic.isEmpty()) {
-        QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(0, this, m_mnemonic);
-    }
-
-    m_mnemonic = sequence;
-
-    if (!m_mnemonic.isEmpty()) {
-        ACT_TRACE("MNEMONIC SET" << m_mnemonic.toString());
-        Qt::ShortcutContext context = Qt::WindowShortcut;
-        QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, m_mnemonic, context, shortcutContextMatcher);
-    }
-}
-
 /*!
  * \qmlproperty string Action::keywords
  * Additional user visible keywords for the action.
@@ -287,10 +223,6 @@
     , m_checked(false)
 {
     generateName();
-    // FIXME: we need QInputDeviceInfo to detect the keyboard attechment
-    // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1276808
-    connect(QuickUtils::instance(), &QuickUtils::keyboardAttachedChanged,
-            this, &UCAction::onKeyboardAttached);
 }
 
 UCAction::~UCAction()
@@ -609,14 +541,6 @@
     return true;
 }
 
-// trigger text changes whenever HW keyboad is attached/detached
-void UCAction::onKeyboardAttached()
-{
-    if (!m_mnemonic.isEmpty()) {
-        Q_EMIT textChanged();
-    }
-}
-
 /*!
  * \qmlmethod Action::trigger(var value)
  * Checks the \c value against the action \l parameterType and triggers the action.

=== modified file 'src/UbuntuToolkit/ucaction_p.h'
--- src/UbuntuToolkit/ucaction_p.h	2016-09-09 17:49:07 +0000
+++ src/UbuntuToolkit/ucaction_p.h	2016-12-13 10:46:54 +0000
@@ -32,6 +32,8 @@
 
 UT_NAMESPACE_BEGIN
 
+Q_DECLARE_LOGGING_CATEGORY(ucAction)
+
 // the function detects whether QML has an overridden trigger() slot available
 // and invokes the one with the appropriate signature
 template<class T>
@@ -185,7 +187,6 @@
     void generateName();
     void setMnemonicFromText(const QString &text);
     bool event(QEvent *event) override;
-    void onKeyboardAttached();
 };
 
 UT_NAMESPACE_END

=== modified file 'src/UbuntuToolkit/ucactionitem.cpp'
--- src/UbuntuToolkit/ucactionitem.cpp	2016-09-12 09:03:50 +0000
+++ src/UbuntuToolkit/ucactionitem.cpp	2016-12-13 10:46:54 +0000
@@ -15,16 +15,65 @@
  */
 
 #include "ucactionitem_p_p.h"
-
-#define foreach Q_FOREACH
-#include <QtQml/private/qqmlbinding_p.h>
-#undef foreach
-
 #include "ucaction_p.h"
+#include "ucactioncontext_p.h"
 #include "ucstyleditembase_p_p.h"
+#include "quickutils_p.h"
+
+#include <QtQuick/qquickwindow.h>
+#include <private/qguiapplication_p.h>
 
 UT_NAMESPACE_BEGIN
 
+#define ACT_TRACE(params) qCDebug(ucAction) << params
+
+bool itemShortcutContextMatcher(QObject* object, Qt::ShortcutContext context)
+{
+    UCActionItem* actionItem = static_cast<UCActionItem*>(object);
+    UCAction* action = actionItem->action();
+    if (!action || !action->isEnabled()) {
+        return false;
+    }
+
+    switch (context) {
+    case Qt::ApplicationShortcut:
+        return true;
+    case Qt::WindowShortcut: {
+        QObject* window = actionItem->window();
+        bool activatable = window && window == QGuiApplication::focusWindow();
+
+        if (activatable) {
+            QQuickItem *pl = actionItem;
+            activatable = false;
+            while (pl) {
+                UCActionContextAttached *attached = static_cast<UCActionContextAttached*>(
+                            qmlAttachedPropertiesObject<UCActionContext>(pl, false));
+                if (attached) {
+                    activatable = attached->context()->active();
+                    if (!activatable) {
+                        ACT_TRACE(action << "Inactive context found" << attached->context());
+                        break;
+                    }
+                }
+                pl = pl->parentItem();
+            }
+            if (!activatable) {
+                // check if the action is in an active context
+                UCActionContext *context = qobject_cast<UCActionContext*>(action->parent());
+                activatable = context && context->active();
+            }
+        }
+        if (activatable) {
+            ACT_TRACE("SELECTED ACTION" << action);
+        }
+
+        return activatable;
+    }
+    default: break;
+    }
+    return false;
+}
+
 UCActionItemPrivate::UCActionItemPrivate()
     : action(Q_NULLPTR)
     , flags(0)
@@ -36,6 +85,9 @@
     Q_Q(UCActionItem);
     QObject::connect(q, &UCActionItem::enabledChanged, q, &UCActionItem::enabledChanged2);
     QObject::connect(q, &UCActionItem::visibleChanged, q, &UCActionItem::visibleChanged2);
+
+    QObject::connect(&mnemonic, SIGNAL(visibleChanged()), q, SLOT(_q_textBinding()));
+    QObject::connect(&mnemonic, SIGNAL(modifierChanged()), q, SLOT(_q_updateMnemonic()));
 }
 
 /*!
@@ -50,6 +102,20 @@
  * If \l action is set, the values of the other properties will by default
  * be identical to the \l Action's property values. Setting the other properties
  * will override the properties copied from the \l Action.
+ *
+ * \section2 Mnemonics
+ * Since Ubuntu.Components 1.3 ActionItem supports mnemonics. Mnemonics are shortcuts
+ * defined in the \l text property, prefixed the shortcut letter with \&. For instance
+ * \c "\&Call" will bint the \c "Alt-C" shortcut to the action. When a mnemonic
+ * is detected on the ActionItem and a keyboard is attached to the device, the \l text
+ * property will provide a formatted text having the mnemonic letter underscored.
+ * \qml
+ * ActionItem {
+ *     id: call
+ *     iconName: "call"
+ *     text: "&Call"
+ * }
+ * \endqml
  */
 
 /*!
@@ -60,6 +126,11 @@
     : UCStyledItemBase(*(new UCActionItemPrivate), parent)
 {
     d_func()->init();
+
+    // FIXME: we need QInputDeviceInfo to detect the keyboard attechment
+    // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1276808
+    connect(QuickUtils::instance(), SIGNAL(keyboardAttachedChanged()),
+            this, SLOT(_q_onKeyboardAttached()), Qt::DirectConnection);
 }
 
 UCActionItem::UCActionItem(UCActionItemPrivate &dd, QQuickItem *parent)
@@ -68,6 +139,29 @@
     d_func()->init();
 }
 
+bool UCActionItem::event(QEvent *e)
+{
+    Q_D(UCActionItem);
+    if (e->type() == QEvent::Shortcut) {
+        if (!d->action) {
+            return false;
+        }
+
+        // when we reach this point, we can be sure the Action is used
+        // by a component belonging to an active ActionContext.
+        QShortcutEvent *shortcut_event(static_cast<QShortcutEvent*>(e));
+        if (shortcut_event->isAmbiguous()) {
+            qmlInfo(this) << "Ambiguous shortcut: " << shortcut_event->key().toString();
+            return false;
+        }
+
+        // do not call trigger() directly but invoke, as it may get overridden in QML
+        invokeTrigger<UCAction>(d->action, QVariant());
+        return true;
+    }
+    return UCStyledItemBase::event(e);
+}
+
 bool UCActionItemPrivate::hasBindingOnProperty(const QString &name)
 {
     Q_Q(UCActionItem);
@@ -109,6 +203,52 @@
     invokeTrigger<UCAction>(action, value);
 }
 
+// update the text property
+void UCActionItemPrivate::_q_textBinding()
+{
+    if (flags & CustomText) {
+        return;
+    }
+    _q_updateMnemonic();
+    Q_EMIT q_func()->textChanged();
+}
+
+// trigger text changes whenever HW keyboad is attached/detached
+void UCActionItemPrivate::_q_onKeyboardAttached()
+{
+    if (!mnemonic.sequence().isEmpty()) {
+        Q_EMIT q_func()->textChanged();
+    }
+}
+
+void UCActionItemPrivate::_q_updateMnemonic()
+{
+    Q_Q(UCActionItem);
+    if (!action) return;
+
+    const QString displayText = action ? action->text() : QString();
+
+    QKeySequence sequence = QKeySequence::mnemonic(displayText);
+    if (!sequence.isEmpty()) {
+        sequence = sequence[0] & ~Qt::ALT;
+        sequence = sequence[0] | mnemonic.modifier();
+    }
+
+    if (sequence == mnemonic.sequence()) {
+        return;
+    }
+    if (!mnemonic.sequence().isEmpty()) {
+        QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(0, q, mnemonic.sequence());
+    }
+
+    mnemonic.setSequence(sequence);
+
+    if (!sequence.isEmpty()) {
+        Qt::ShortcutContext context = Qt::WindowShortcut;
+        QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(q, sequence, context, itemShortcutContextMatcher);
+    }
+}
+
 // setter called when bindings from QML set the value. Internal functions will
 // all use the setVisible setter, so initialization and (re)parenting related
 // visible alteration won't set the custom flag
@@ -124,12 +264,15 @@
     setEnabled(enabled);
 }
 
+UCActionMnemonic *UCActionItem::mnemonic()
+{
+    Q_D(UCActionItem);
+    return &d->mnemonic;
+}
+
 void UCActionItemPrivate::updateProperties()
 {
     Q_Q(UCActionItem);
-    if (!(flags & CustomText)) {
-        Q_EMIT q->textChanged();
-    }
     if (!(flags & CustomIconSource)) {
         Q_EMIT q->iconSourceChanged();
     }
@@ -154,8 +297,8 @@
                     q, SLOT(_q_enabledBinding()), Qt::DirectConnection);
         }
         if (!(flags & CustomText)) {
-            QObject::connect(action, &UCAction::textChanged,
-                    q, &UCActionItem::textChanged, Qt::DirectConnection);
+            QObject::connect(action, SIGNAL(textChanged()),
+                    q, SLOT(_q_textBinding()), Qt::DirectConnection);
         }
         if (!(flags & CustomIconSource)) {
             QObject::connect(action, &UCAction::iconSourceChanged,
@@ -178,8 +321,8 @@
                        q, SLOT(_q_enabledBinding()));
         }
         if (!(flags & CustomText)) {
-            QObject::disconnect(action, &UCAction::textChanged,
-                       q, &UCActionItem::textChanged);
+            QObject::disconnect(action, SIGNAL(textChanged()),
+                       q, SLOT(_q_textBinding()));
         }
         if (!(flags & CustomIconSource)) {
             QObject::disconnect(action, &UCAction::iconSourceChanged,
@@ -189,6 +332,11 @@
             QObject::disconnect(action, &UCAction::iconNameChanged,
                        q, &UCActionItem::iconNameChanged);
         }
+
+        if (!mnemonic.sequence().isEmpty()) {
+            QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(0, q, mnemonic.sequence());
+            mnemonic.setSequence(QKeySequence());
+        }
     }
 }
 
@@ -220,20 +368,59 @@
     }
     d->_q_visibleBinding();
     d->_q_enabledBinding();
+    d->_q_textBinding();
     d->updateProperties();
 }
 
 /*!
  * \qmlproperty string ActionItem::text
  * The title of the actionItem. Defaults to the \c action.text.
- */
+ *
+ * Mnemonics are shortcuts prefixed in the text with \&. If the text has multiple
+ * occurences of the \& character, the first one will be considered for the shortcut.
+ * However \&\& can be used for a single \& in the text, not as a mnemonic.
+ * The \& character cannot be used as shortcut.
+ */ */
 QString UCActionItem::text()
 {
     Q_D(UCActionItem);
     if (d->flags & UCActionItemPrivate::CustomText) {
         return d->text;
     }
-    return d->action ? d->action->text() : QString();
+
+    if (!d->action) {
+        return QString();
+    }
+
+    QString displayText(d->action->text());
+
+    // if we have a mnemonic, underscore it
+    if (!d->mnemonic.sequence().isEmpty()) {
+        const QString modifier = QKeySequence(d->mnemonic.modifier()).toString();
+
+        QString mnemonic = "&" + d->mnemonic.sequence().toString().remove(modifier);
+        // patch special cases
+        mnemonic.replace(QStringLiteral("Space"), QStringLiteral(" "));
+        int mnemonicIndex = displayText.indexOf(mnemonic);
+        if (mnemonicIndex < 0) {
+            // try lower case
+            mnemonic = mnemonic.toLower();
+            mnemonicIndex = displayText.indexOf(mnemonic);
+        }
+
+        // FIXME: we need QInputDeviceInfo to detect the keyboard attechment
+        // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1276808
+        if (d->mnemonic.visible() && QuickUtils::instance()->keyboardAttached()) {
+            // underscore the character
+            displayText.replace(mnemonicIndex, mnemonic.length(), "<u>" + mnemonic[1] + "</u>");
+        } else {
+            displayText.remove(mnemonicIndex, 1);
+        }
+    }
+
+    // Escape ampersands
+    displayText.replace(QStringLiteral("&&"), QStringLiteral("&amp;"));
+    return displayText;
 }
 void UCActionItem::setText(const QString &text)
 {
@@ -241,8 +428,8 @@
 
     if (d->action && !(d->flags & UCActionItemPrivate::CustomText)) {
         // disconnect change signal from Action
-        disconnect(d->action, &UCAction::textChanged,
-                   this, &UCActionItem::textChanged);
+        disconnect(d->action, SIGNAL(textChanged()),
+                   this, SLOT(_q_textBinding()));
     }
     d->flags |= UCActionItemPrivate::CustomText;
 
@@ -259,8 +446,8 @@
     d->flags &= ~UCActionItemPrivate::CustomText;
     if (d->action) {
         // re-connect change signal from Action
-        connect(d->action, &UCAction::textChanged,
-                this, &UCActionItem::textChanged, Qt::DirectConnection);
+        connect(d->action, SIGNAL(textChanged()),
+                this, SLOT(_q_textBinding()), Qt::DirectConnection);
     }
     Q_EMIT textChanged();
 }
@@ -382,6 +569,52 @@
     }
 }
 
+UCActionMnemonic::UCActionMnemonic(QObject *parent)
+    : QObject(parent)
+    , m_visible(true)
+    , m_modifier(Qt::ALT)
+{
+}
+
+bool UCActionMnemonic::visible() const
+{
+    return m_visible;
+}
+
+void UCActionMnemonic::setVisible(bool visible)
+{
+    if (visible != m_visible) {
+        m_visible = visible;
+        Q_EMIT visibleChanged();
+    }
+}
+
+int UCActionMnemonic::modifier() const
+{
+    return m_modifier;
+}
+
+void UCActionMnemonic::setModifier(int modifier)
+{
+    if (modifier != m_modifier) {
+        m_modifier = modifier;
+        Q_EMIT modifierChanged();
+    }
+}
+
+const QKeySequence& UCActionMnemonic::sequence() const
+{
+    return m_sequence;
+}
+
+void UCActionMnemonic::setSequence(const QKeySequence &sequence)
+{
+    if (m_sequence != sequence) {
+        m_sequence = sequence;
+        Q_EMIT sequenceChanged();
+    }
+}
+
 UT_NAMESPACE_END
 
 #include "moc_ucactionitem_p.cpp"

=== modified file 'src/UbuntuToolkit/ucactionitem_p.h'
--- src/UbuntuToolkit/ucactionitem_p.h	2016-09-09 17:49:07 +0000
+++ src/UbuntuToolkit/ucactionitem_p.h	2016-12-13 10:46:54 +0000
@@ -23,6 +23,7 @@
 
 class UCAction;
 class UCActionItemPrivate;
+class UCActionMnemonic;
 class UBUNTUTOOLKIT_EXPORT UCActionItem : public UCStyledItemBase
 {
     Q_OBJECT
@@ -35,6 +36,13 @@
     Q_PROPERTY(QUrl iconSource READ iconSource WRITE setIconSource RESET resetIconSource NOTIFY iconSourceChanged)
     Q_PROPERTY(QString iconName READ iconName WRITE setIconName RESET resetIconName NOTIFY iconNameChanged)
 
+    // 1.3
+#ifndef Q_QDOC
+    Q_PROPERTY(UT_PREPEND_NAMESPACE(UCActionMnemonic) *mnemonic READ mnemonic CONSTANT FINAL REVISION 1)
+#else
+    Q_PROPERTY(UCActionMnemonic *mnemonic READ mnemonic CONSTANT FINAL REVISION 1)
+#endif
+
     // overrides
     Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled2 NOTIFY enabledChanged2)
     Q_PROPERTY(bool visible READ isVisible WRITE setVisible2 NOTIFY visibleChanged2 FINAL)
@@ -56,6 +64,8 @@
     void setVisible2(bool visible);
     void setEnabled2(bool enabled);
 
+    UCActionMnemonic* mnemonic();
+
 Q_SIGNALS:
     void actionChanged();
     void textChanged();
@@ -72,10 +82,44 @@
 protected:
     UCActionItem(UCActionItemPrivate &, QQuickItem *parent);
 
+    bool event(QEvent *event) override;
+
     Q_DECLARE_PRIVATE(UCActionItem)
     Q_PRIVATE_SLOT(d_func(), void _q_visibleBinding())
     Q_PRIVATE_SLOT(d_func(), void _q_enabledBinding())
     Q_PRIVATE_SLOT(d_func(), void _q_invokeActionTrigger(const QVariant &value))
+    Q_PRIVATE_SLOT(d_func(), void _q_textBinding())
+    Q_PRIVATE_SLOT(d_func(), void _q_onKeyboardAttached())
+    Q_PRIVATE_SLOT(d_func(), void _q_updateMnemonic())
+};
+
+class UBUNTUTOOLKIT_EXPORT UCActionMnemonic : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)
+    Q_PROPERTY(int modifier READ modifier WRITE setModifier NOTIFY modifierChanged)
+    Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged)
+public:
+    UCActionMnemonic(QObject* parent = 0);
+
+    bool visible() const;
+    void setVisible(bool visible);
+
+    int modifier() const;
+    void setModifier(int modifier);
+
+    const QKeySequence& sequence() const;
+    void setSequence(const QKeySequence& sequence);
+
+Q_SIGNALS:
+    void visibleChanged();
+    void modifierChanged();
+    void sequenceChanged();
+
+private:
+    bool m_visible;
+    int m_modifier;
+    QKeySequence m_sequence;
 };
 
 UT_NAMESPACE_END

=== modified file 'src/UbuntuToolkit/ucactionitem_p_p.h'
--- src/UbuntuToolkit/ucactionitem_p_p.h	2016-09-09 17:49:07 +0000
+++ src/UbuntuToolkit/ucactionitem_p_p.h	2016-12-13 10:46:54 +0000
@@ -45,6 +45,9 @@
     void _q_visibleBinding();
     void _q_enabledBinding();
     void _q_invokeActionTrigger(const QVariant &value);
+    void _q_textBinding();
+    void _q_onKeyboardAttached();
+    void _q_updateMnemonic();
 
     enum {
         CustomText = 0x01,
@@ -58,6 +61,7 @@
     QUrl iconSource;
     UCAction *action;
     quint8 flags;
+    UCActionMnemonic mnemonic;
 };
 
 UT_NAMESPACE_END

=== modified file 'tests/unit/visual/tst_actionitem.11.qml'
--- tests/unit/visual/tst_actionitem.11.qml	2016-06-15 13:46:51 +0000
+++ tests/unit/visual/tst_actionitem.11.qml	2016-12-13 10:46:54 +0000
@@ -16,7 +16,7 @@
 
 import QtQuick 2.0
 import QtTest 1.0
-import Ubuntu.Components 1.1
+import Ubuntu.Components 1.3
 
 Item {
     id: main
@@ -58,6 +58,7 @@
     Action {
         id: action2
         objectName: "action2"
+        text: "&mnemonicActionText"
     }
 
     Loader {
@@ -86,6 +87,7 @@
         function cleanup() {
             loader.sourceComponent = null;
             item1.action = null;
+            item1.text = undefined;
             action1.visible = true;
             action1.enabled = true;
             action2.visible = true;
@@ -116,6 +118,12 @@
             compare(item1.text, "", "text can be unset")
         }
 
+        function test_action_mnemonic() {
+            QuickUtils.keyboardAttached = true;
+            item1.action = action2;
+            compare(item1.text, "<u>m</u>nemonicActionText", "Text uses action text mnemonics");
+        }
+
         // NOTE: This test must be run AFTER test_action(), otherwise setting the action will
         // will not update the iconSource
         function test_iconSource() {


Follow ups