on xenial help sign in
Implement a page for choosing HW keyboard layout in OOBE wizard

Approved by: Albert Astals Cid

commit23c80b9580f923ac5b580b713607b83b55078361 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateThu 11 Aug 2016, 06:25 (10 years ago) parent926c951eae1155
15 files changed+596−12
CMakeLists.txt+1 debian/control+1 plugins/AccountsService/AccountsService.cpp+18 plugins/AccountsService/AccountsService.h+2−3 plugins/Wizard/CMakeLists.txt+6−3 plugins/Wizard/keyboardLayoutsModel.cpp+163 plugins/Wizard/keyboardLayoutsModel.h+66 plugins/Wizard/plugin.cpp+2 plugins/Wizard/qmldir−1 qml/Wizard/Pages/20-keyboard.qml+160 qml/Wizard/WizardItemSelector.qml+128 tests/mocks/AccountsService/AccountsService.h+1−1 tests/mocks/Wizard/CMakeLists.txt+6−3 tests/mocks/Wizard/mockplugin.cpp+2 tests/qmltests/Wizard/tst_Wizard.qml+40−1
CMakeLists.txt+1
@@ -62,6 +62,7 @@ pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
6262pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
6363pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
6464pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
65+pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
6566
6667pkg_check_modules(UBUNTUGESTURES REQUIRED UbuntuGestures)
6768
debian/control+1
@@ -17,6 +17,7 @@ Build-Depends: cmake,
1717 libconnectivity-qt1-dev (>= 0.7.1),
1818 libevdev-dev,
1919 libgeonames-dev (>= 0.2),
20+ libgnome-desktop-3-dev,
2021 libgl1-mesa-dev[!arm64 !armhf] | libgl-dev[!arm64 !armhf],
2122 libgl1-mesa-dri,
2223 libgles2-mesa-dev[arm64 armhf],
plugins/AccountsService/AccountsService.cpp+18
@@ -284,6 +284,24 @@ QStringList AccountsService::keymaps() const
284284 return {QStringLiteral("us")};
285285}
286286
287+void AccountsService::setKeymaps(const QStringList &keymaps)
288+{
289+ if (keymaps.isEmpty()) {
290+ qWarning() << "Setting empty keymaps is not supported";
291+ return;
292+ }
293+
294+ StringMapList result;
295+ Q_FOREACH(const QString &keymap, keymaps) {
296+ StringMap map;
297+ map.insert(QStringLiteral("xkb"), keymap);
298+ result.append(map);
299+ }
300+
301+ setProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QVariant::fromValue(result));
302+ Q_EMIT keymapsChanged();
303+}
304+
287305uint AccountsService::failedFingerprintLogins() const
288306{
289307 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS).toUInt();
plugins/AccountsService/AccountsService.h+2−3
@@ -79,9 +79,7 @@ class AccountsService: public QObject
7979 NOTIFY hereLicensePathChanged)
8080 Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
8181 Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
82− Q_PROPERTY(QStringList keymaps
83− READ keymaps
84− NOTIFY keymapsChanged)
82+ Q_PROPERTY(QStringList keymaps READ keymaps WRITE setKeymaps NOTIFY keymapsChanged)
8583
8684public:
8785 enum PasswordDisplayHint {
@@ -117,6 +115,7 @@ public:
117115 QString email() const;
118116 void setEmail(const QString &email);
119117 QStringList keymaps() const;
118+ void setKeymaps(const QStringList &keymaps);
120119
121120Q_SIGNALS:
122121 void userChanged();
plugins/Wizard/CMakeLists.txt+6−3
@@ -1,4 +1,6 @@
1−include_directories(${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS})
1+pkg_search_module(GD3 REQUIRED gnome-desktop-3.0)
2+
3+include_directories(SYSTEM ${GD3_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS})
24
35add_library(Wizard-qml MODULE
46 plugin.cpp
@@ -7,10 +9,11 @@ add_library(Wizard-qml MODULE
79 LocalePlugin.cpp
810 timezonemodel.cpp
911 Status.cpp
12+ keyboardLayoutsModel.cpp
1013)
1114
12−qt5_use_modules(Wizard-qml DBus Qml Concurrent)
13−target_link_libraries(Wizard-qml ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
15+qt5_use_modules(Wizard-qml DBus Qml)
16+target_link_libraries(Wizard-qml ${GD3_LDFLAGS} ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
1417add_unity8_plugin(Wizard 0.1 Wizard TARGETS Wizard-qml)
1518
1619set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
plugins/Wizard/keyboardLayoutsModel.cppnew file+163
@@ -0,0 +1,163 @@
1+/*
2+ * Copyright (C) 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDBusMetaType>
18+#include <QDebug>
19+
20+#define GNOME_DESKTOP_USE_UNSTABLE_API
21+#include <libgnome-desktop/gnome-xkb-info.h>
22+
23+#include "keyboardLayoutsModel.h"
24+
25+typedef QList<QMap<QString, QString>> StringMapList;
26+Q_DECLARE_METATYPE(StringMapList)
27+
28+KeyboardLayoutsModel::KeyboardLayoutsModel(QObject *parent)
29+ : QAbstractListModel(parent)
30+{
31+ m_roleNames = {
32+ {LayoutIdRole, "layoutId"},
33+ {DisplayNameRole, "displayName"},
34+ {LanguageRole, "language"}
35+ };
36+
37+ qDBusRegisterMetaType<StringMapList>();
38+
39+ buildModel();
40+ connect(this, &KeyboardLayoutsModel::languageChanged, this, &KeyboardLayoutsModel::updateModel);
41+}
42+
43+QString KeyboardLayoutsModel::language() const
44+{
45+ return m_language;
46+}
47+
48+void KeyboardLayoutsModel::setLanguage(const QString &language)
49+{
50+ if (m_language == language)
51+ return;
52+
53+ m_language = language;
54+ Q_EMIT languageChanged(language);
55+}
56+
57+static bool compareLayouts(const KeyboardLayoutInfo &layout0, const KeyboardLayoutInfo &layout1)
58+{
59+ QString name0(layout0.id);
60+ QString name1(layout1.id);
61+
62+ if (name0 == name1) {
63+ name0 = layout0.displayName;
64+ name1 = layout1.displayName;
65+
66+ if (name0 == name1) {
67+ name0 = layout0.language;
68+ name1 = layout1.language;
69+ }
70+ }
71+
72+ return QString::localeAwareCompare(name0, name1) < 0;
73+}
74+
75+void KeyboardLayoutsModel::buildModel()
76+{
77+ GList *sources, *tmp;
78+ const gchar *display_name;
79+ const gchar *short_name;
80+ const gchar *xkb_layout;
81+ const gchar *xkb_variant;
82+
83+ GnomeXkbInfo * xkbInfo(gnome_xkb_info_new());
84+
85+ sources = gnome_xkb_info_get_all_layouts(xkbInfo);
86+
87+ for (tmp = sources; tmp != NULL; tmp = tmp->next) {
88+ gboolean result = gnome_xkb_info_get_layout_info(xkbInfo, (const gchar *)tmp->data,
89+ &display_name, &short_name, &xkb_layout, &xkb_variant);
90+ if (!result) {
91+ qWarning() << "Skipping invalid layout";
92+ continue;
93+ }
94+
95+ KeyboardLayoutInfo layout;
96+ layout.id = QString::fromUtf8((const gchar *)tmp->data);
97+ layout.language = QString::fromUtf8(short_name);
98+ layout.displayName = QString::fromUtf8(display_name);
99+
100+ m_db.append(layout);
101+ }
102+ g_list_free(sources);
103+ g_object_unref(xkbInfo);
104+
105+ std::sort(m_db.begin(), m_db.end(), compareLayouts);
106+}
107+
108+void KeyboardLayoutsModel::updateModel()
109+{
110+ beginResetModel();
111+ m_layouts.clear();
112+
113+ const QString lang = m_language.section("_", 0, 0);
114+ const QString country = m_language.section("_", 1, 1).toLower();
115+
116+ Q_FOREACH(const KeyboardLayoutInfo & info, m_db) {
117+ const QString kbdCountry = info.id.section("+", 0, 0);
118+ if (info.language == lang && // filter by language
119+ (kbdCountry.startsWith(country) || kbdCountry.length() > 2)) { // and by known country, also insert anything that doesn't match the country
120+ m_layouts.append(info);
121+ }
122+ }
123+
124+ std::sort(m_layouts.begin(), m_layouts.end(), compareLayouts);
125+ endResetModel();
126+}
127+
128+int KeyboardLayoutsModel::rowCount(const QModelIndex &parent) const
129+{
130+ Q_UNUSED(parent)
131+ return m_layouts.count();
132+}
133+
134+QVariant KeyboardLayoutsModel::data(const QModelIndex &index, int role) const
135+{
136+ const int row = index.row();
137+
138+ if (row >= m_layouts.count()) {
139+ qWarning() << Q_FUNC_INFO << "index out of bounds";
140+ return QVariant();
141+ }
142+
143+ const KeyboardLayoutInfo &layout = m_layouts.at(row);
144+
145+ switch (role) {
146+ case Qt::DisplayRole:
147+ case DisplayNameRole:
148+ return layout.displayName;
149+ case LayoutIdRole:
150+ return layout.id;
151+ case LanguageRole:
152+ return layout.language;
153+ default: {
154+ qWarning() << Q_FUNC_INFO << "unsupported data role";
155+ return QVariant();
156+ }
157+ }
158+}
159+
160+QHash<int, QByteArray> KeyboardLayoutsModel::roleNames() const
161+{
162+ return m_roleNames;
163+}
plugins/Wizard/keyboardLayoutsModel.hnew file+66
@@ -0,0 +1,66 @@
1+/*
2+ * Copyright (C) 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef KEYBOARDLAYOUTSMODEL_H
18+#define KEYBOARDLAYOUTSMODEL_H
19+
20+#include <QAbstractListModel>
21+
22+struct KeyboardLayoutInfo {
23+ QString id;
24+ QString displayName;
25+ QString language;
26+};
27+
28+class KeyboardLayoutsModel: public QAbstractListModel
29+{
30+ Q_OBJECT
31+
32+ Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged)
33+
34+public:
35+ explicit KeyboardLayoutsModel(QObject * parent = nullptr);
36+ ~KeyboardLayoutsModel() = default;
37+
38+ enum Roles {
39+ LayoutIdRole = Qt::UserRole + 1,
40+ DisplayNameRole,
41+ LanguageRole
42+ };
43+
44+ QString language() const;
45+ void setLanguage(const QString &language);
46+
47+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
48+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
49+ QHash<int, QByteArray> roleNames() const override;
50+
51+Q_SIGNALS:
52+ void languageChanged(const QString &language);
53+
54+private Q_SLOTS:
55+ void updateModel();
56+
57+private:
58+ void buildModel();
59+
60+ QString m_language;
61+ QHash<int, QByteArray> m_roleNames;
62+ QVector<KeyboardLayoutInfo> m_layouts;
63+ QVector<KeyboardLayoutInfo> m_db;
64+};
65+
66+#endif
plugins/Wizard/plugin.cpp+2
@@ -20,6 +20,7 @@
2020#include "timezonemodel.h"
2121#include "LocalePlugin.h"
2222#include "Status.h"
23+#include "keyboardLayoutsModel.h"
2324
2425#include <QtQml/qqml.h>
2526
@@ -30,5 +31,6 @@ void WizardPlugin::registerTypes(const char *uri)
3031 qmlRegisterSingletonType<System>(uri, 0, 1, "System", [](QQmlEngine*, QJSEngine*) -> QObject* { return new System; });
3132 qmlRegisterSingletonType<Status>(uri, 0, 1, "Status", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Status; });
3233 qmlRegisterType<TimeZoneLocationModel>(uri, 0, 1, "TimeZoneModel");
34+ qmlRegisterType<KeyboardLayoutsModel>(uri, 0, 1, "KeyboardLayoutsModel");
3335 qmlRegisterType<LocalePlugin>(uri, 0, 1, "LocalePlugin");
3436}
plugins/Wizard/qmldir−1
@@ -1,3 +1,2 @@
11module Wizard
22plugin Wizard-qml
3−typeinfo Wizard.qmltypes
qml/Wizard/Pages/20-keyboard.qmlnew file+160
@@ -0,0 +1,160 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3
20+import Ubuntu.SystemSettings.LanguagePlugin 1.0
21+import Wizard 0.1
22+import AccountsService 0.1
23+import Unity.InputInfo 0.1
24+import Unity.Application 0.1
25+import ".." as LocalComponents
26+
27+LocalComponents.Page {
28+ objectName: "keyboardPage"
29+
30+ title: i18n.tr("Select Keyboard")
31+ forwardButtonSourceComponent: forwardButton
32+
33+ skip: keyboardsModel.count == 0
34+ skipValid: false
35+
36+ UbuntuLanguagePlugin {
37+ id: langPlugin
38+ }
39+
40+ KeyboardLayoutsModel {
41+ id: layoutsModel
42+ language: selectedLanguage
43+ }
44+
45+ InputDeviceModel {
46+ id: keyboardsModel
47+ deviceFilter: InputInfo.Keyboard
48+ Component.onCompleted: skipValid = true;
49+ }
50+
51+ readonly property string selectedLanguage: langPlugin.languageCodes[langSelector.selectedIndex].split(".")[0] // chop off the codeset (.UTF-8)
52+
53+ property string selectedKeymap: ""
54+
55+ Column {
56+ id: column
57+ spacing: units.gu(2)
58+
59+ anchors {
60+ fill: content
61+ leftMargin: wideMode ? parent.leftMargin : staticMargin
62+ rightMargin: wideMode ? parent.rightMargin : staticMargin
63+ topMargin: staticMargin
64+ }
65+
66+ Label {
67+ id: label1
68+ anchors.left: parent.left
69+ anchors.right: parent.right
70+ text: i18n.tr("Keyboard language")
71+ font.weight: Font.Normal
72+ color: textColor
73+ }
74+
75+ LocalComponents.WizardItemSelector {
76+ id: langSelector
77+ objectName: "langSelector"
78+ anchors.left: parent.left
79+ anchors.right: parent.right
80+ model: langPlugin.languageNames
81+ selectedIndex: langPlugin.languageCodes.indexOf(i18n.language)
82+ onSelectedIndexChanged: {
83+ keyboardListView.currentIndex = -1;
84+ selectedKeymap = "";
85+ }
86+ }
87+
88+ Label {
89+ id: label2
90+ anchors.left: parent.left
91+ anchors.right: parent.right
92+ text: i18n.tr("Keyboard layout")
93+ font.weight: Font.Normal
94+ color: textColor
95+ }
96+
97+ ListView {
98+ id: keyboardListView
99+ clip: true
100+ anchors.left: parent.left
101+ anchors.right: parent.right
102+ snapMode: ListView.SnapToItem
103+ model: layoutsModel
104+ currentIndex: -1
105+ opacity: langSelector.expanded ? 0.5 : 1
106+ height: column.height - label1.height - langSelector.height - label2.height - column.spacing * 3
107+ enabled: !langSelector.expanded
108+ Behavior on opacity {
109+ UbuntuNumberAnimation {}
110+ }
111+
112+ delegate: ListItem {
113+ id: itemDelegate
114+ objectName: "kbdDelegate" + index
115+ height: layout.height + (divider.visible ? divider.height : 0)
116+ readonly property bool isCurrent: index === ListView.view.currentIndex
117+ highlightColor: backgroundColor
118+ divider.colorFrom: dividerColor
119+ divider.colorTo: backgroundColor
120+
121+ ListItemLayout {
122+ id: layout
123+ title.text: displayName
124+ title.color: textColor
125+ subtitle.text: layoutId
126+ subtitle.color: textColor
127+ padding.leading: -units.gu(1)
128+ padding.trailing: -units.gu(1)
129+ Image {
130+ SlotsLayout.position: SlotsLayout.Trailing
131+ SlotsLayout.overrideVerticalPositioning: true
132+ fillMode: Image.PreserveAspectFit
133+ anchors.verticalCenter: parent.verticalCenter
134+ height: units.gu(1.5)
135+ source: "data/Tick@30.png"
136+ visible: itemDelegate.isCurrent
137+ }
138+ }
139+
140+ onClicked: {
141+ keyboardListView.currentIndex = index;
142+ selectedKeymap = layoutId;
143+ }
144+ }
145+ }
146+ }
147+
148+ Component {
149+ id: forwardButton
150+ LocalComponents.StackButton {
151+ text: keyboardListView.currentIndex != -1 ? i18n.tr("Next") : i18n.tr("Skip")
152+ onClicked: {
153+ if (keyboardListView.currentIndex != -1) {
154+ AccountsService.keymaps = selectedKeymap;
155+ }
156+ pageStack.next();
157+ }
158+ }
159+ }
160+}
qml/Wizard/WizardItemSelector.qmlnew file+128
@@ -0,0 +1,128 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3 as ListItem
20+
21+Rectangle {
22+ id: optionToggle
23+
24+ property bool expanded
25+ property var model
26+ property int selectedIndex: -1
27+ readonly property double itemHeight: units.gu(4)
28+ readonly property int maxVisibleItems: 6
29+
30+ color: theme.palette.normal.foreground
31+ height: expanded ? maxVisibleItems * itemHeight : itemHeight
32+ Behavior on height {
33+ UbuntuNumberAnimation { id: heightAnimation }
34+ }
35+
36+ width: parent.width
37+ radius: units.gu(0.6)
38+ clip: true
39+ border.width: units.dp(1)
40+ border.color: theme.palette.normal.base
41+
42+ Flickable {
43+ id: flickable
44+ interactive: expanded
45+ flickableDirection: Flickable.VerticalFlick
46+ width: parent.width
47+ height: parent.height
48+ contentHeight: optionToggleRepeater.count * itemHeight
49+
50+ Column {
51+ id: optionToggleContent
52+ width: parent.width
53+
54+ Repeater {
55+ id: optionToggleRepeater
56+ model: optionToggle.model
57+
58+ delegate: Loader {
59+ asynchronous: true
60+ visible: status === Loader.Ready
61+
62+ Component {
63+ id: optionToggleEntry
64+
65+ AbstractButton {
66+ width: optionToggleContent.width
67+ height: optionToggle.itemHeight
68+ onClicked: {
69+ if (expanded) {
70+ selectedIndex = index;
71+ }
72+ expanded = !expanded
73+ }
74+
75+ ListItem.ThinDivider {
76+ visible: expanded && index != 0
77+ }
78+
79+ Label {
80+ id: delegateLabel
81+ anchors {
82+ left: parent.left
83+ leftMargin: units.gu(1)
84+ right: parent.right
85+ rightMargin: units.gu(3)
86+ verticalCenter: parent.verticalCenter
87+ }
88+
89+ width: parent.width
90+ text: expanded ? modelData : optionToggle.model[selectedIndex]
91+ color: textColor
92+ font.weight: Font.Light
93+ maximumLineCount: 1
94+ elide: Text.ElideRight
95+ }
96+
97+ Icon {
98+ anchors {
99+ right: parent.right
100+ rightMargin: units.gu(1)
101+ verticalCenter: parent.verticalCenter
102+ }
103+
104+ visible: (index == 0 || !expanded) && !heightAnimation.running
105+ name: expanded ? "up" : "down"
106+ width: units.gu(1.5)
107+ height: width
108+ }
109+
110+ Image {
111+ anchors {
112+ right: parent.right
113+ rightMargin: units.gu(1)
114+ verticalCenter: parent.verticalCenter
115+ }
116+ visible: expanded && index == optionToggle.selectedIndex && index != 0
117+ height: units.gu(1.5)
118+ fillMode: Image.PreserveAspectFit
119+ source: Qt.resolvedUrl("Pages/data/Tick@30.png")
120+ }
121+ }
122+ }
123+ sourceComponent: optionToggleEntry
124+ }
125+ }
126+ }
127+ }
128+}
tests/mocks/AccountsService/AccountsService.h+1−1
@@ -84,7 +84,7 @@ class AccountsService: public QObject
8484 Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
8585 Q_PROPERTY(QStringList keymaps
8686 READ keymaps
87− WRITE setKeymaps // only in mock
87+ WRITE setKeymaps
8888 NOTIFY keymapsChanged)
8989
9090public:
tests/mocks/Wizard/CMakeLists.txt+6−3
@@ -1,5 +1,7 @@
1+pkg_search_module(GD3 REQUIRED gnome-desktop-3.0)
2+
13include_directories(
2− ${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS}
4+ ${GD3_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS}
35 ${CMAKE_SOURCE_DIR}/plugins/Wizard
46)
57
@@ -10,8 +12,9 @@ add_library(MockWizard-qml MODULE
1012 ${CMAKE_SOURCE_DIR}/plugins/Wizard/timezonemodel.cpp
1113 ${CMAKE_SOURCE_DIR}/plugins/Wizard/LocalePlugin.cpp
1214 ${CMAKE_SOURCE_DIR}/plugins/Wizard/Status.cpp
15+ ${CMAKE_SOURCE_DIR}/plugins/Wizard/keyboardLayoutsModel.cpp
1316)
1417
15−qt5_use_modules(MockWizard-qml DBus Qml Concurrent)
16−target_link_libraries(MockWizard-qml ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
18+qt5_use_modules(MockWizard-qml DBus Qml)
19+target_link_libraries(MockWizard-qml ${GD3_LDFLAGS} ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
1720add_unity8_mock(Wizard 0.1 Wizard TARGETS MockWizard-qml)
tests/mocks/Wizard/mockplugin.cpp+2
@@ -20,6 +20,7 @@
2020#include "timezonemodel.h"
2121#include "LocalePlugin.h"
2222#include "Status.h"
23+#include "keyboardLayoutsModel.h"
2324
2425#include <QtQml/qqml.h>
2526
@@ -30,5 +31,6 @@ void MockWizardPlugin::registerTypes(const char *uri)
3031 qmlRegisterSingletonType<MockSystem>(uri, 0, 1, "System", [](QQmlEngine*, QJSEngine*) -> QObject* { return new MockSystem; });
3132 qmlRegisterSingletonType<Status>(uri, 0, 1, "Status", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Status; });
3233 qmlRegisterType<TimeZoneLocationModel>(uri, 0, 1, "TimeZoneModel");
34+ qmlRegisterType<KeyboardLayoutsModel>(uri, 0, 1, "KeyboardLayoutsModel");
3335 qmlRegisterType<LocalePlugin>(uri, 0, 1, "LocalePlugin");
3436}
tests/qmltests/Wizard/tst_Wizard.qml+40−1
@@ -24,6 +24,7 @@ import Ubuntu.SystemSettings.SecurityPrivacy 1.0
2424import Ubuntu.SystemSettings.TimeDate 1.1
2525import Unity.Test 0.1 as UT
2626import Wizard 0.1
27+import Unity.InputInfo 0.1
2728import "../../../qml/Wizard"
2829
2930Item {
@@ -41,7 +42,12 @@ Item {
4142 id: wizard
4243 anchors.fill: parent
4344
45+ Component.onCompleted: {
46+ MockInputDeviceBackend.addMockDevice("/test", InputInfo.Keyboard);
47+ }
48+
4449 Component.onDestruction: {
50+ MockInputDeviceBackend.removeDevice("/test");
4551 wizardLoader.itemDestroyed = true;
4652 }
4753 }
@@ -74,6 +80,12 @@ Item {
7480 signalName: "timeZoneChangedCalled"
7581 }
7682
83+ SignalSpy {
84+ id: kbdLayoutSpy
85+ target: AccountsService
86+ signalName: "keymapsChanged"
87+ }
88+
7789 function setup() {
7890 AccountsService.hereEnabled = false;
7991 AccountsService.hereLicensePath = Qt.resolvedUrl("licenses");
@@ -88,6 +100,7 @@ Item {
88100 activateLocationSpy.clear();
89101 activateGPSSpy.clear();
90102 timezoneSpy.clear();
103+ kbdLayoutSpy.clear();
91104
92105 ActionData.data = {
93106 "location-detection-enabled": {
@@ -176,6 +189,10 @@ Item {
176189 tap(findChild(page, "forwardButton"));
177190 }
178191
192+ page = waitForPage("keyboardPage");
193+ if (name === page.objectName) return page;
194+ tap(findChild(page, "forwardButton"));
195+
179196 page = waitForPage("wifiPage");
180197 if (name === page.objectName) return page;
181198 tap(findChild(page, "forwardButton"));
@@ -281,7 +298,7 @@ Item {
281298
282299 // Make sure that moving from sim page lands on wifi page
283300 tap(findChild(page, "forwardButton"));
284− waitForPage("wifiPage"); // thus skipping passwdPage
301+ waitForPage("keyboardPage"); // thus skipping passwdPage
285302 }
286303
287304 function verifyAnimationsNotRunning(page) {
@@ -486,5 +503,27 @@ Item {
486503 tap(findChild(page, "nameInput"));
487504 typeString("foobar");
488505 }
506+
507+ function test_keyboardPage() {
508+ var page = goToPage("keyboardPage");
509+ var forwardButton = findChild(page, "forwardButton");
510+
511+ // change language
512+ var langSelector = findChild(page, "langSelector");
513+ verify(langSelector);
514+ langSelector.selectedIndex = 1; // should be fr_FR
515+ print("Selected language:", page.selectedLanguage);
516+
517+ // pick some layout
518+ var kbdDelegate = findChild(page, "kbdDelegate1");
519+ verify(kbdDelegate);
520+ mouseClick(kbdDelegate);
521+ verify(kbdDelegate.isCurrent);
522+ print("Selected keymap:", page.selectedKeymap);
523+
524+ // verify the keymapsChanged signal got fired
525+ tap(findChild(page, "forwardButton"));
526+ tryCompare(kbdLayoutSpy, "count", 1);
527+ }
489528 }
490529}