← 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

 

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

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/313114

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.
=== modified file '.bazaar/plugins/makecheck_ubuntu_ui_toolkit.py'
--- .bazaar/plugins/makecheck_ubuntu_ui_toolkit.py	2014-07-14 17:34:44 +0000
+++ .bazaar/plugins/makecheck_ubuntu_ui_toolkit.py	2016-12-13 10:43:19 +0000
@@ -35,6 +35,7 @@
     if (subprocess.call("make check", shell=True) != 0):
         raise errors.BzrError("Tests failed, fix them before commit!")
 
+
 branch.Branch.hooks.install_named_hook('pre_commit',
                                        execute_makecheck,
                                        'make check pre-commit')

=== modified file '.bazaar/plugins/packaging_sorting.py'
--- .bazaar/plugins/packaging_sorting.py	2016-05-16 06:37:48 +0000
+++ .bazaar/plugins/packaging_sorting.py	2016-12-13 10:43:19 +0000
@@ -52,6 +52,7 @@
 
     subprocess.call(["rm", "-rf", "debian-packaging-wraptest-temporary"])
 
+
 branch.Branch.hooks.install_named_hook("pre_commit",
                                        pre_commit_hook,
                                        "Check packaging sorting")

=== modified file 'components.api'
--- components.api	2016-09-17 05:48:25 +0000
+++ components.api	2016-12-13 10:43:19 +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 'debian/control'
--- debian/control	2016-08-29 19:49:33 +0000
+++ debian/control	2016-12-13 10:43:19 +0000
@@ -10,6 +10,7 @@
                gdb,
                language-pack-en-base,
                libdbus-1-dev,
+               libevdev-dev,
                libfontconfig1-dev,
                libfreetype6-dev,
                libgl1-mesa-dri,
@@ -21,6 +22,7 @@
                libnih-dbus-dev,
                libqt5sql5-sqlite,
                libqt5svg5-dev,
+               libqt5systeminfo5,
                libudev-dev,
                libx11-dev[!armhf],
                libxcb1-dev[!armhf],
@@ -53,6 +55,7 @@
                qtpim5-dev,
                qtscript5-doc-html,
                qtsvg5-doc-html,
+               qtsystems5-dev,
                qttools5-dev-tools,
                qtwebkit5-doc-html,
                suru-icon-theme (>= 14.04+16.10.20160720),

=== modified file 'debian/control.gles'
--- debian/control.gles	2016-08-18 05:40:55 +0000
+++ debian/control.gles	2016-12-13 10:43:19 +0000
@@ -10,6 +10,7 @@
                gdb,
                language-pack-en-base,
                libdbus-1-dev,
+               libevdev-dev,
                libfontconfig1-dev,
                libfreetype6-dev,
                libgles2-mesa-dev,
@@ -25,6 +26,7 @@
                libqt5quickwidgets5-gles,
                libqt5sql5-sqlite,
                libqt5svg5-dev,
+               libqt5systeminfo5,
                libudev-dev,
                libx11-dev[!armhf],
                libxcb1-dev[!armhf],
@@ -58,6 +60,7 @@
                qtpim5-dev,
                qtscript5-doc-html,
                qtsvg5-doc-html,
+               qtsystems5-dev,
                qtwebkit5-doc-html,
                suru-icon-theme,
                unity-action-doc,

=== modified file 'src/UbuntuToolkit/UbuntuToolkit.pro'
--- src/UbuntuToolkit/UbuntuToolkit.pro	2016-09-12 14:56:56 +0000
+++ src/UbuntuToolkit/UbuntuToolkit.pro	2016-12-13 10:43:19 +0000
@@ -2,6 +2,9 @@
 QT = core-private gui-private qml-private quick-private testlib dbus svg organizer \
      UbuntuGestures-private UbuntuMetrics
 
+#Qt SystemInfo
+QT *= systeminfo systeminfo-private
+
 unix {
     CONFIG += link_pkgconfig
     PKGCONFIG += gio-2.0 dbus-1 libnih libnih-dbus

=== modified file 'src/UbuntuToolkit/quickutils.cpp'
--- src/UbuntuToolkit/quickutils.cpp	2016-09-21 08:47:38 +0000
+++ src/UbuntuToolkit/quickutils.cpp	2016-12-13 10:43:19 +0000
@@ -27,6 +27,7 @@
 #include <QtQuick/QQuickItem>
 #include <QtQuick/private/qquicktextinput_p.h>
 #include <QtQuick/private/qquicktextedit_p.h>
+#include <QtSystemInfo/QInputInfoManager>
 
 UT_NAMESPACE_BEGIN
 
@@ -37,10 +38,100 @@
     m_rootWindow(0),
     m_rootView(0),
     m_mouseAttached(false),
-    m_keyboardAttached(false)
+    m_keyboardAttached(false),
+    m_explicitMouseAttached(false),
+    m_explicitKeyboardAttached(false)
 {
     QGuiApplication::instance()->installEventFilter(this);
     m_omitIM << QStringLiteral("ibus") << QStringLiteral("none") << QStringLiteral("compose");
+
+    m_inputInfo = new QInputInfoManager(this);
+    connect(m_inputInfo, &QInputInfoManager::ready,
+            this, &QuickUtils::onInputInfoReady);
+    // the default values of mouse and keyboard attachment depend on the input info
+    connect(m_inputInfo, &QInputInfoManager::deviceAdded,
+            this, &QuickUtils::onDeviceAdded);
+    connect(m_inputInfo, &QInputInfoManager::deviceRemoved,
+            this, &QuickUtils::onDeviceRemoved);
+}
+
+void QuickUtils::onInputInfoReady()
+{
+    QMapIterator<QString, QInputDevice*> i(m_inputInfo->deviceMap());
+    while (i.hasNext()) {
+        i.next();
+        registerDevice(i.value(), i.key());
+    }
+
+    m_inputInfo->setFilter(QInputDevice::Mouse | QInputDevice::TouchPad | QInputDevice::Keyboard);
+}
+
+void QuickUtils::onDeviceAdded(QInputDevice *device)
+{
+    QMapIterator<QString, QInputDevice*> i(m_inputInfo->deviceMap());
+    while (i.hasNext()) {
+        i.next();
+        if (i.value() == device) {
+            registerDevice(device, i.key());
+            break;
+        }
+    }
+}
+
+void QuickUtils::onDeviceRemoved(const QString deviceId)
+{
+    // the device info is removed by now, so we must look at the internal cache
+    if (m_keyboards.remove(deviceId)) {
+        if (!m_explicitKeyboardAttached && !m_keyboards.size()) {
+            m_keyboardAttached = false;
+            Q_EMIT keyboardAttachedChanged();
+        }
+    }
+    if (m_mice.remove(deviceId)) {
+        if (!m_explicitMouseAttached && !m_mice.size()) {
+            m_mouseAttached = false;
+            Q_EMIT mouseAttachedChanged();
+        }
+    }
+}
+
+void QuickUtils::registerDevice(QInputDevice *device, const QString &deviceId)
+{
+    if (device->types().testFlag(QInputDevice::Keyboard)) {
+        m_keyboards.insert(deviceId);
+        if (!m_explicitKeyboardAttached && !m_keyboardAttached) {
+            m_keyboardAttached = true;
+            Q_EMIT keyboardAttachedChanged();
+        }
+    }
+    if (device->types().testFlag(QInputDevice::Mouse)
+            || device->types().testFlag(QInputDevice::TouchPad)) {
+        m_mice.insert(deviceId);
+        if (!m_explicitMouseAttached && !m_mouseAttached) {
+            m_mouseAttached = true;
+            Q_EMIT mouseAttachedChanged();
+        }
+    }
+}
+
+void QuickUtils::setMouseAttached(bool set)
+{
+    m_explicitMouseAttached = true;
+    if (set == m_mouseAttached) {
+        return;
+    }
+    m_mouseAttached = set;
+    Q_EMIT mouseAttachedChanged();
+}
+
+void QuickUtils::setKeyboardAttached(bool set)
+{
+    m_explicitKeyboardAttached = true;
+    if (set == m_keyboardAttached) {
+        return;
+    }
+    m_keyboardAttached = true;
+    Q_EMIT keyboardAttachedChanged();
 }
 
 /*!

=== modified file 'src/UbuntuToolkit/quickutils_p.h'
--- src/UbuntuToolkit/quickutils_p.h	2016-09-19 20:40:13 +0000
+++ src/UbuntuToolkit/quickutils_p.h	2016-12-13 10:43:19 +0000
@@ -26,6 +26,8 @@
 class QQuickItem;
 class QQmlEngine;
 class QQmlComponent;
+class QInputInfoManager;
+class QInputDevice;
 
 UT_NAMESPACE_BEGIN
 
@@ -35,8 +37,8 @@
     Q_PROPERTY(QQuickItem *rootObject READ rootObject NOTIFY rootObjectChanged)
     Q_PROPERTY(QString inputMethodProvider READ inputMethodProvider)
     Q_PROPERTY(bool touchScreenAvailable READ touchScreenAvailable NOTIFY touchScreenAvailableChanged)
-    Q_PROPERTY(bool mouseAttached MEMBER m_mouseAttached NOTIFY mouseAttachedChanged)
-    Q_PROPERTY(bool keyboardAttached MEMBER m_keyboardAttached NOTIFY keyboardAttachedChanged)
+    Q_PROPERTY(bool mouseAttached MEMBER m_mouseAttached WRITE setMouseAttached NOTIFY mouseAttachedChanged)
+    Q_PROPERTY(bool keyboardAttached MEMBER m_keyboardAttached WRITE setKeyboardAttached NOTIFY keyboardAttachedChanged)
 public:
     static QuickUtils *instance(QObject *parent = Q_NULLPTR)
     {
@@ -87,13 +89,25 @@
     explicit QuickUtils(QObject *parent = 0);
     QPointer<QQuickWindow> m_rootWindow;
     QPointer<QQuickView> m_rootView;
+    QInputInfoManager *m_inputInfo;
     QStringList m_omitIM;
-    bool m_mouseAttached;
-    bool m_keyboardAttached;
+    QSet<QString> m_mice;
+    QSet<QString> m_keyboards;
+    bool m_mouseAttached:1;
+    bool m_keyboardAttached:1;
+    bool m_explicitMouseAttached:1;
+    bool m_explicitKeyboardAttached:1;
 
     static QuickUtils *m_instance;
 
     void lookupQuickView();
+    void registerDevice(QInputDevice *device, const QString &deviceId);
+    void setMouseAttached(bool set);
+    void setKeyboardAttached(bool set);
+private Q_SLOTS:
+    void onInputInfoReady();
+    void onDeviceAdded(QInputDevice *device);
+    void onDeviceRemoved(const QString deviceId);
 };
 
 #define UC_QML_DEPRECATION_WARNING(msg) \

=== 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:43:19 +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/ubuntutoolkitmodule.h'
--- src/UbuntuToolkit/ubuntutoolkitmodule.h	2016-09-09 17:49:07 +0000
+++ src/UbuntuToolkit/ubuntutoolkitmodule.h	2016-12-13 10:43:19 +0000
@@ -61,7 +61,7 @@
     // use this API only in tests!
     static void initializeContextProperties(QQmlEngine*);
 private:
-    explicit UbuntuToolkitModule(QObject *parent = 0);
+    explicit UbuntuToolkitModule(QObject *parent = Q_NULLPTR);
     static UbuntuToolkitModule* create(QQmlEngine *engine, const QUrl &baseUrl);
     void registerWindowContextProperty();
     Q_SLOT void setWindowContextProperty(QWindow* focusWindow);

=== 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:43:19 +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:43:19 +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:43:19 +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:43:19 +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:43:19 +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 'src/UbuntuToolkit/ucthemingextension.cpp'
--- src/UbuntuToolkit/ucthemingextension.cpp	2016-09-12 14:07:38 +0000
+++ src/UbuntuToolkit/ucthemingextension.cpp	2016-12-13 10:43:19 +0000
@@ -95,7 +95,25 @@
 
 UCItemAttached::~UCItemAttached()
 {
-    QQuickItemPrivate::get(m_item)->removeItemChangeListener(this, QQuickItemPrivate::Parent);
+    /*
+     * Apparent problem: the UCItemAttached for a given item may be
+     * destructed along with the user data for that item, which
+     * occurs after the QQuickItemPrivate destructor has run, in the
+     * destructor for the base class QObjectPrivate of
+     * QQuickItemPrivate. At that point, the destructors of the
+     * members of QQuickItemPrivate have already been called, so
+     * accessing those members via a member function is invalid.
+     * In particular, the changeListeners list of the
+     * QQuickItemPrivate has already been destructed, so it does
+     * not have much of a chance of removing a listener. We
+     * must detect this and avoid the call to it.  Fortunately
+     * the base class QObjectPrivate has a flag wasDeleted that
+     * should still be valid at this point.
+     */
+    QQuickItemPrivate* itemPriv = QQuickItemPrivate::get(m_item);
+    if (!(itemPriv->wasDeleted)) {
+        itemPriv->removeItemChangeListener(this, QQuickItemPrivate::Parent);
+    }
 }
 
 bool UCThemingExtension::isThemed(QQuickItem *item)

=== modified file 'src/imports/Components/1.3/TextArea.qml'
--- src/imports/Components/1.3/TextArea.qml	2016-09-19 07:24:45 +0000
+++ src/imports/Components/1.3/TextArea.qml	2016-12-13 10:43:19 +0000
@@ -767,7 +767,12 @@
     QtObject {
         id: internal
         // public property locals enabling aliasing
-        property string displayText: editor.getText(0, editor.length)
+        property string displayText: {
+            var plainText = editor.getText(0, editor.length);
+            if (editor.hasOwnProperty('preeditText'))
+                plainText += editor.preeditText;
+            return plainText;
+        }
         property real frameSpacing: control.__styleInstance.frameSpacing
         property real minimumSize: units.gu(4)
         property real scrollbarSpacing: rightScrollbar.__interactive ? units.gu(2) : 0

=== modified file 'src/imports/Components/Themes/Ambiance/1.2/SliderStyle.qml'
--- src/imports/Components/Themes/Ambiance/1.2/SliderStyle.qml	2015-04-24 14:43:08 +0000
+++ src/imports/Components/Themes/Ambiance/1.2/SliderStyle.qml	2016-12-13 10:43:19 +0000
@@ -48,7 +48,7 @@
         overlayColor: sliderStyle.foregroundColor
         overlayRect: Qt.application.layoutDirection == Qt.LeftToRight ?
             Qt.rect(0.0, 0.0, thumb.x / thumb.barMinusThumbWidth, 1.0) :
-            Qt.rect(1.0 - (thumb.x / thumb.barMinusThumbWidth), 0.0, 1.0, 1.0)
+            Qt.rect(thumb.x / thumb.barMinusThumbWidth, 0.0, 1 - (thumb.x / thumb.barMinusThumbWidth), 1.0)
     }
 
     UbuntuShape {

=== modified file 'src/imports/Components/Themes/Ambiance/1.3/AmbianceNormal.qml'
--- src/imports/Components/Themes/Ambiance/1.3/AmbianceNormal.qml	2016-05-17 13:00:51 +0000
+++ src/imports/Components/Themes/Ambiance/1.3/AmbianceNormal.qml	2016-12-13 10:43:19 +0000
@@ -21,10 +21,10 @@
 PaletteValues {
     background: "#FFFFFF"
     backgroundText: UbuntuColors.jet
-    backgroundSecondaryText: UbuntuColors.slate
-    backgroundTertiaryText: UbuntuColors.ash
+    backgroundSecondaryText: UbuntuColors.inkstone
+    backgroundTertiaryText: UbuntuColors.graphite
     base: UbuntuColors.silk
-    baseText: UbuntuColors.graphite
+    baseText: UbuntuColors.inkstone
     foreground: UbuntuColors.porcelain
     foregroundText: UbuntuColors.jet
     raised: "#FFFFFF"
@@ -35,7 +35,7 @@
     overlaySecondaryText: UbuntuColors.silk
     field: "#FFFFFF"
     fieldText: UbuntuColors.jet
-    focus: UbuntuColors.orange
+    focus: UbuntuColors.blue
     focusText: "#FFFFFF"
     selection: Qt.rgba(UbuntuColors.blue.r, UbuntuColors.blue.g, UbuntuColors.blue.b, 0.2)
     selectionText: UbuntuColors.jet

=== modified file 'src/imports/Components/Themes/Ambiance/1.3/AmbianceSelected.qml'
--- src/imports/Components/Themes/Ambiance/1.3/AmbianceSelected.qml	2016-04-29 12:22:02 +0000
+++ src/imports/Components/Themes/Ambiance/1.3/AmbianceSelected.qml	2016-12-13 10:43:19 +0000
@@ -20,7 +20,7 @@
 AmbianceNormal {
     background: UbuntuColors.porcelain
     base: UbuntuColors.ash
-    baseText: UbuntuColors.inkstone
+    baseText: UbuntuColors.jet
     foreground: UbuntuColors.ash
     overlay: UbuntuColors.porcelain
 }

=== modified file 'src/imports/Components/Themes/Ambiance/1.3/Palette.qml'
--- src/imports/Components/Themes/Ambiance/1.3/Palette.qml	2016-05-17 13:00:51 +0000
+++ src/imports/Components/Themes/Ambiance/1.3/Palette.qml	2016-12-13 10:43:19 +0000
@@ -25,19 +25,19 @@
         Component.onCompleted: {
             // specific disabled colors
             var diff = {
-                field: UbuntuColors.porcelain,
                 selection: Qt.rgba(UbuntuColors.blue.r, UbuntuColors.blue.g, UbuntuColors.blue.b, 0.1),
                 positiveText: "#FFFFFF",
                 negativeText: "#FFFFFF",
                 activityText: "#FFFFFF",
-                focusText: "#FFFFFF"
+                focusText: "#FFFFFF",
+                position: "#00000000"
             };
             for (var p in normal) {
                 // skip objectName and all change signals
                 if (p === "objectName" || p.indexOf("Changed") > 0) continue;
                 disabled[p] = diff[p] || (
-                    // if not specific, colors are 30% opaque normal
-                    Qt.rgba(normal[p].r, normal[p].g, normal[p].b, normal[p].a * 0.5)
+                    // if not specific, colors are 40% opaque normal
+                    Qt.rgba(normal[p].r, normal[p].g, normal[p].b, normal[p].a * 0.4)
                 );
             }
         }
@@ -51,21 +51,22 @@
         Component.onCompleted: {
             // specific selected-disabled colors
             var diff = {
-                background: UbuntuColors.porcelain,
-                base: UbuntuColors.porcelain,
                 foreground: UbuntuColors.porcelain,
                 selection: Qt.rgba(UbuntuColors.blue.r, UbuntuColors.blue.g, UbuntuColors.blue.b, 0.1),
                 positiveText: "#FFFFFF",
                 negativeText: "#FFFFFF",
                 activityText: "#FFFFFF",
-                focusText: "#FFFFFF"
+                focus: UbuntuColors.blue,
+                focusText: "#FFFFFF",
+                field: "#FFFFFF",
+                position: "#00000000"
             };
             for (var p in selected) {
                 // skip objectName and all change signals
                 if (p === "objectName" || p.indexOf("Changed") > 0) continue;
                 selectedDisabled[p] = diff[p] || (
-                    // if not specific, colors are 30% opaque normal
-                    Qt.rgba(selected[p].r, selected[p].g, selected[p].b, normal[p].a * 0.5)
+                    // if not specific, colors are 40% opaque normal
+                    Qt.rgba(selected[p].r, selected[p].g, selected[p].b, normal[p].a * 0.4)
                 );
             }
         }
@@ -74,7 +75,7 @@
     highlighted: AmbianceNormal {
         background: UbuntuColors.silk
         base: UbuntuColors.ash
-        baseText: UbuntuColors.inkstone
+        baseText: UbuntuColors.jet
         foreground: UbuntuColors.silk
         raised: UbuntuColors.silk
         raisedText: UbuntuColors.inkstone

=== modified file 'src/imports/Components/Themes/Ambiance/1.3/SliderStyle.qml'
--- src/imports/Components/Themes/Ambiance/1.3/SliderStyle.qml	2016-02-10 17:46:21 +0000
+++ src/imports/Components/Themes/Ambiance/1.3/SliderStyle.qml	2016-12-13 10:43:19 +0000
@@ -49,7 +49,7 @@
         overlayColor: sliderStyle.foregroundColor
         overlayRect: Qt.application.layoutDirection == Qt.LeftToRight ?
             Qt.rect(0.0, 0.0, thumb.x / thumb.barMinusThumbWidth, 1.0) :
-            Qt.rect(1.0 - (thumb.x / thumb.barMinusThumbWidth), 0.0, 1.0, 1.0)
+            Qt.rect(thumb.x / thumb.barMinusThumbWidth, 0.0, 1 - (thumb.x / thumb.barMinusThumbWidth), 1.0)
     }
 
     UbuntuShape {

=== modified file 'src/imports/Components/Themes/SuruDark/1.3/Palette.qml'
--- src/imports/Components/Themes/SuruDark/1.3/Palette.qml	2016-05-17 13:00:51 +0000
+++ src/imports/Components/Themes/SuruDark/1.3/Palette.qml	2016-12-13 10:43:19 +0000
@@ -25,19 +25,19 @@
         Component.onCompleted: {
             // specific disabled colors
             var diff = {
-                foreground: UbuntuColors.inkstone,
-                field: UbuntuColors.inkstone,
+                raised: "#FFFFFF",
                 positiveText: UbuntuColors.porcelain,
                 negativeText: UbuntuColors.porcelain,
                 activityText: UbuntuColors.porcelain,
-                focusText: UbuntuColors.porcelain
+                focusText: "#FFFFFF",
+                selectionText: "#FFFFFF"
             };
             for (var p in normal) {
                 // skip objectName and all change signals
                 if (p == "objectName"|| p.indexOf("Changed") > 0) continue;
                 disabled[p] = diff[p] || (
-                    // if not specific, colors are 30% opaque normal
-                    Qt.rgba(normal[p].r, normal[p].g, normal[p].b, normal[p].a * 0.5)
+                    // if not specific, colors are 40% opaque normal
+                    Qt.rgba(normal[p].r, normal[p].g, normal[p].b, normal[p].a * 0.4)
                 );
             }
         }
@@ -51,12 +51,17 @@
         Component.onCompleted: {
             var diff = {
                 background: UbuntuColors.inkstone,
-                base: UbuntuColors.inkstone,
-                foreground: UbuntuColors.inkstone,
+                backgroundText: "#FFFFFF",
+                raised: "#FFFFFF",
+                foregroundText: "#FFFFFF",
+                overlayText: "#FFFFFF",
+                fieldText: "#FFFFFF",
+                selectionText: "#FFFFFF",
                 positiveText: UbuntuColors.porcelain,
                 negativeText: UbuntuColors.porcelain,
-                activityText: UbuntuColors.porcelain,
-                focusText: UbuntuColors.porcelain
+                activityText: UbuntuColors.porcelain,                
+                focusText: UbuntuColors.porcelain,
+                position: "#00000000"
             };
             for (var p in selected) {
                 // skip objectName and all change signals

=== modified file 'src/imports/Components/Themes/SuruDark/1.3/SuruDarkNormal.qml'
--- src/imports/Components/Themes/SuruDark/1.3/SuruDarkNormal.qml	2016-05-17 13:00:51 +0000
+++ src/imports/Components/Themes/SuruDark/1.3/SuruDarkNormal.qml	2016-12-13 10:43:19 +0000
@@ -24,7 +24,7 @@
     backgroundSecondaryText: UbuntuColors.silk
     backgroundTertiaryText: UbuntuColors.ash
     base: UbuntuColors.graphite
-    baseText: UbuntuColors.silk
+    baseText: UbuntuColors.porcelain
     foreground: UbuntuColors.inkstone
     foregroundText: "#FFFFFF"
     raised: "#FFFFFF"
@@ -35,7 +35,7 @@
     overlaySecondaryText: UbuntuColors.slate
     field: UbuntuColors.jet
     fieldText: "#FFFFFF"
-    focus: UbuntuColors.orange
+    focus: UbuntuColors.blue
     focusText: "#FFFFFF"
     selection: Qt.rgba(UbuntuColors.blue.r, UbuntuColors.blue.g, UbuntuColors.blue.b, 0.4)
     selectionText: "#FFFFFF"

=== modified file 'src/imports/Components/Themes/SuruDark/1.3/SuruDarkSelected.qml'
--- src/imports/Components/Themes/SuruDark/1.3/SuruDarkSelected.qml	2016-04-29 12:22:02 +0000
+++ src/imports/Components/Themes/SuruDark/1.3/SuruDarkSelected.qml	2016-12-13 10:43:19 +0000
@@ -20,8 +20,8 @@
 
 SuruDarkNormal {
     background: UbuntuColors.inkstone
-    base: UbuntuColors.inkstone
-    baseText: UbuntuColors.ash
+    base: UbuntuColors.slate
+    baseText: UbuntuColors.silk
     foreground: UbuntuColors.slate
     overlay: UbuntuColors.slate
 }

=== modified file 'tests/unit/test-include-x11.pri'
--- tests/unit/test-include-x11.pri	2016-07-07 15:33:34 +0000
+++ tests/unit/test-include-x11.pri	2016-12-13 10:43:19 +0000
@@ -3,5 +3,5 @@
 include( add_makecheck_x11.pri )
 
 TEMPLATE = app
-QT += testlib qml quick UbuntuToolkit UbuntuToolkit-private
+QT += testlib qml quick systeminfo UbuntuToolkit UbuntuToolkit-private
 CONFIG += no_keywords c++11

=== modified file 'tests/unit/test-include.pri'
--- tests/unit/test-include.pri	2016-09-09 17:49:07 +0000
+++ tests/unit/test-include.pri	2016-12-13 10:43:19 +0000
@@ -2,5 +2,5 @@
 include( add_makecheck.pri )
 
 TEMPLATE = app
-QT += testlib qml quick UbuntuToolkit-private
+QT += testlib qml quick systeminfo UbuntuToolkit-private
 CONFIG += no_keywords c++11

=== 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:43:19 +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() {

=== removed file 'tests/unit/visual/tst_pickerpanel.11.qml'
--- tests/unit/visual/tst_pickerpanel.11.qml	2016-06-15 13:46:51 +0000
+++ tests/unit/visual/tst_pickerpanel.11.qml	1970-01-01 00:00:00 +0000
@@ -1,249 +0,0 @@
-/*
- * Copyright 2013 Canonical Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-import QtQuick 2.0
-import QtTest 1.0
-import Ubuntu.Test 1.0
-import Ubuntu.Components 1.1
-import Ubuntu.Components.Pickers 1.0
-
-Item {
-    id: testSuite
-    width: units.gu(40)
-    height: units.gu(71)
-
-    Flow {
-        anchors {
-            fill: parent
-            // give a margin so we can dismiss panels
-            topMargin: units.gu(4)
-        }
-
-        Button {
-            id: defaultMode
-            text: "defaultMode"
-            property date buttonDate: new Date()
-            property Item panel
-            onClicked: panel = PickerPanel.openDatePicker(defaultMode, "buttonDate")
-        }
-        Button {
-            id: modeSet
-            text: "modeSet"
-            property string mode
-            property date buttonDate: new Date()
-            property Item panel
-            onClicked: panel = PickerPanel.openDatePicker(modeSet, "buttonDate", mode)
-        }
-    }
-
-    SignalSpy {
-        id: closeSpy
-        signalName: "closed"
-    }
-
-    UbuntuTestCase {
-        name: "PickerPanelAPI"
-        when: windowShown
-
-        function initTestCase() {
-            waitForRendering(testSuite);
-        }
-
-        function test_0_clickOndefaultMode() {
-            mouseClick(defaultMode, units.gu(1), units.gu(1));
-            verify(defaultMode.panel !== null, "the picker is not opened");
-            verify(defaultMode.panel.picker !== null, "the DatePicker is not defined");
-            compare(defaultMode.panel.pickerMode, "Years|Months|Days", "the mode from the picker is not the default");
-            compare(defaultMode.panel.date, defaultMode.buttonDate, "the date from the picker differs from the button's");
-            compare(defaultMode.panel.caller, defaultMode, "wrong caller");
-            compare(defaultMode.panel.callerProperty, "buttonDate", "wrong callerProperty");
-            verify(defaultMode.panel.hasOwnProperty("closed"), "the object has no closed signal");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = defaultMode.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_1_modeSet_YM() {
-            modeSet.mode = "Years|Months" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            compare(picker.children.length, 3, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_1_modeSet_YD() {
-            ignoreWarning("Invalid DatePicker mode: Years|Days")
-            modeSet.mode = "Years|Days" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            expectFailContinue("", "this mode is invalid");
-            compare(picker.children.length, 2, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_1_modeSet_HMS() {
-            modeSet.mode = "Hours|Minutes|Seconds" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            compare(picker.children.length, 4, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_1_modeSet_HS() {
-            ignoreWarning("Invalid DatePicker mode: Hours|Seconds")
-            modeSet.mode = "Hours|Seconds" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            expectFailContinue("", "this mode is invalid");
-            compare(picker.children.length, 2, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        // forced panel tests
-        // these should be executed as last ones
-        function test_2_clickOndefaultMode() {
-            // force panel - this is private specific!!!
-            var privates = findChild(PickerPanel, "PickerPanel_Internals");
-            privates.isPhone = true;
-
-            mouseClick(defaultMode, units.gu(1), units.gu(1));
-            verify(defaultMode.panel !== null, "the picker is not opened");
-            verify(defaultMode.panel.picker !== null, "the DatePicker is not defined");
-            compare(defaultMode.panel.pickerMode, "Years|Months|Days", "the mode from the picker is not the default");
-            compare(defaultMode.panel.date, defaultMode.buttonDate, "the date from the picker differs from the button's");
-            compare(defaultMode.panel.caller, defaultMode, "wrong caller");
-            compare(defaultMode.panel.callerProperty, "buttonDate", "wrong callerProperty");
-            verify(defaultMode.panel.hasOwnProperty("closed"), "the object has no closed signal");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = defaultMode.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_3_modeSet_YM() {
-            modeSet.mode = "Years|Months" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            compare(picker.children.length, 3, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_3_modeSet_YD() {
-            ignoreWarning("Invalid DatePicker mode: Years|Days")
-            modeSet.mode = "Years|Days" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            expectFailContinue("", "this mode is invalid");
-            compare(picker.children.length, 2, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_3_modeSet_HMS() {
-            modeSet.mode = "Hours|Minutes|Seconds" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            compare(picker.children.length, 4, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-
-        function test_3_modeSet_HS() {
-            ignoreWarning("Invalid DatePicker mode: Hours|Seconds")
-            modeSet.mode = "Hours|Seconds" ;
-            mouseClick(modeSet, units.gu(1), units.gu(1));
-            verify(modeSet.panel !== null, "the picker is opened");
-            compare(modeSet.panel.date, modeSet.buttonDate, "the date from the picker differs from the button's");
-            compare(modeSet.panel.pickerMode, modeSet.mode, "the mode from the picker differs from the button's");
-            // check the number of pickers
-            var picker = findChild(modeSet.panel.picker, "PickerRow_Positioner");
-            expectFailContinue("", "this mode is invalid");
-            compare(picker.children.length, 2, "there is not enough pickers in the panel");
-
-            // dismiss
-            closeSpy.clear();
-            closeSpy.target = modeSet.panel;
-            mouseClick(testSuite, units.gu(1), units.gu(1));
-            closeSpy.wait();
-        }
-    }
-}

=== modified file 'tests/unit/visual/tst_slider.13.qml'
--- tests/unit/visual/tst_slider.13.qml	2016-06-15 13:46:51 +0000
+++ tests/unit/visual/tst_slider.13.qml	2016-12-13 10:43:19 +0000
@@ -89,6 +89,14 @@
                 }
             }
         }
+        Slider {
+           property bool enableRTL: false
+           LayoutMirroring.enabled: enableRTL
+           LayoutMirroring.childrenInherit: enableRTL
+
+           id: slider
+           readonly property rect bar: slider.__styleInstance && slider.__styleInstance.bar ? slider.__styleInstance.bar.overlayRect : null
+        }
     }
 
     UbuntuTestCase {
@@ -107,6 +115,7 @@
         function cleanup() {
             flickSpy.target = null;
             flickSpy.clear();
+            slider.enableRTL = false;
         }
 
         function test_slider_blocks_flickable_data() {
@@ -160,5 +169,21 @@
                 valueSpy.wait();
             compare(slider.value, to, "Slider has the wrong value!");
         }
+
+        function test_bar_rtl() {
+            verify(slider.bar);
+            slider.minimumValue = 0.0;
+            slider.maximumValue = 1.0;
+            slider.value = 0.3;
+
+            // check for LTR
+            print("Slider width:", slider.bar.width)
+            verify(slider.value >= slider.bar.width)
+
+            // check for RTL
+            slider.enableRTL = true;
+            print("Slider RTL width:", slider.bar.width)
+            verify(slider.value >= slider.bar.width)
+        }
     }
 }

=== renamed file 'tests/unit/visual/FIXME-QT56_textarea.11.qml' => 'tests/unit/visual/tst_textarea.11.qml'
--- tests/unit/visual/FIXME-QT56_textarea.11.qml	2016-09-16 11:20:57 +0000
+++ tests/unit/visual/tst_textarea.11.qml	2016-12-13 10:43:19 +0000
@@ -137,11 +137,6 @@
             compare(textArea.canUndo,textEdit.canUndo,"TextArea.canUndo is same as TextEdit.canUndo")
         }
 
-        // FIXME: Fails with Qt 5.6. See bug #1624342.
-        function test_0_color() {
-            compare(textArea.color,textEdit.color,"TextArea.color is same as TextEdit.canUndo")
-        }
-
         function test_0_cursorDelegate() {
             verify(textArea.cursorDelegate === null, "TextArea.cursorDelegate is not null")
         }
@@ -220,14 +215,6 @@
             compare(textArea.selectedText,textEdit.selectedText,"TextArea.selectedText is same as TextEdit.selectedText")
         }
 
-        function test_0_selectedTextColor() {
-            compare(textArea.selectedTextColor,textEdit.selectedTextColor,"TextArea.selectedTextColor is same as TextEdit.selectedTextColor")
-        }
-
-        function test_0_selectionColor() {
-            compare(textArea.selectionColor,textEdit.selectionColor,"TextArea.selectionColor is same as TextEdit.selectionColor")
-        }
-
         function test_0_selectionEnd() {
             compare(textArea.selectionEnd,textEdit.selectionEnd,"TextArea.selectionEnd is same as TextEdit.selectionEnd")
         }