on xenial help sign in
merge lp:~mzanetti/unity8/appdrawer-improvements
commitc69314ee4b5c6e75e26677f8b84a90d0801a3803 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateThu 5 Jan 2017, 19:47 (10 years ago) parentc73be7a696c8cb
11 files changed+187−23
data/com.canonical.Unity8.gschema.xml+5 plugins/Utils/appdrawerproxymodel.cpp+1−1 qml/Launcher/Drawer.qml+89−4 qml/Launcher/DrawerGridView.qml+3−1 qml/Launcher/DrawerListView.qml+6 qml/Launcher/Launcher.qml+3−4 qml/Launcher/MoreAppsHeader.qml+2−5 tests/mocks/GSettings.1.0/fake_gsettings.cpp+14 tests/mocks/GSettings.1.0/fake_gsettings.h+7 tests/mocks/Unity/Launcher/MockAppDrawerModel.cpp+1−1 tests/qmltests/Launcher/tst_Drawer.qml+56−7
data/com.canonical.Unity8.gschema.xml+5
@@ -53,6 +53,11 @@
5353 <summary>Enable or disable the indicator pull down menus</summary>
5454 <description>Toggle the availability of the indicator pull down menus</description>
5555 </key>
56+ <key type="s" name="appstore-uri">
57+ <default>'scope://com.canonical.scopes.clickstore'</default>
58+ <summary>The uri to the app store</summary>
59+ <description>This will be used whenever the user triggers an action to open the app store.</description>
60+ </key>
5661 </schema>
5762
5863 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
plugins/Utils/appdrawerproxymodel.cpp+1−1
@@ -185,5 +185,5 @@ QString AppDrawerProxyModel::appId(int index) const
185185 return adpm->appId(sourceIndex.row());
186186 }
187187 }
188− return nullptr;
188+ return QString();
189189}
qml/Launcher/Drawer.qml+89−4
@@ -20,6 +20,7 @@ import Unity.Launcher 0.1
2020import Utils 0.1
2121import "../Components"
2222import Qt.labs.settings 1.0
23+import GSettings 1.0
2324
2425FocusScope {
2526 id: root
@@ -43,6 +44,16 @@ FocusScope {
4344 searchField.focus = true;
4445 }
4546
47+ Keys.onPressed: {
48+ if (event.text.trim() !== "") {
49+ focusInput();
50+ searchField.text = event.text;
51+ }
52+ // Catch all presses here in case the navigation lets something through
53+ // We never want to end up in the launcher with focus
54+ event.accepted = true;
55+ }
56+
4657 Settings {
4758 property alias selectedTab: sections.selectedIndex
4859 }
@@ -76,9 +87,13 @@ FocusScope {
7687
7788 TextField {
7889 id: searchField
90+ objectName: "searchField"
7991 anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
8092 placeholderText: i18n.tr("Search…")
8193 focus: true
94+
95+ KeyNavigation.down: sections
96+
8297 onAccepted: {
8398 if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
8499 root.applicationSelected(listLoader.item.getFirstAppId());
@@ -95,14 +110,21 @@ FocusScope {
95110
96111 Sections {
97112 id: sections
113+ objectName: "drawerSections"
98114 width: parent.width
115+
116+ KeyNavigation.up: searchField
117+ KeyNavigation.down: headerFocusScope
118+ KeyNavigation.backtab: searchField
119+ KeyNavigation.tab: headerFocusScope
120+
99121 actions: [
100122 Action {
101123 text: i18n.ctr("Apps sorted alphabetically", "A-Z")
102124 // TODO: Disabling this for now as we don't get the right input from u-a-l yet.
103−// },
104−// Action {
105−// text: i18n.ctr("Most used apps", "Most used")
125+ },
126+ Action {
127+ text: i18n.ctr("Most used apps", "Most used")
106128 }
107129 ]
108130
@@ -115,9 +137,45 @@ FocusScope {
115137 }
116138 }
117139
140+ FocusScope {
141+ id: headerFocusScope
142+ objectName: "headerFocusScope"
143+ KeyNavigation.up: sections
144+ KeyNavigation.down: listLoader.item
145+ KeyNavigation.backtab: sections
146+ KeyNavigation.tab: listLoader.item
147+ activeFocusOnTab: true
148+
149+ GSettings {
150+ id: settings
151+ schema.id: "com.canonical.Unity8"
152+ }
153+
154+ Keys.onPressed: {
155+ switch (event.key) {
156+ case Qt.Key_Return:
157+ case Qt.Key_Enter:
158+ case Qt.Key_Space:
159+ trigger();
160+ event.accepted = true;
161+ }
162+ }
163+
164+ function trigger() {
165+ Qt.openUrlExternally(settings.appstoreUri)
166+ }
167+ }
168+
118169 Loader {
119170 id: listLoader
171+ objectName: "drawerListLoader"
120172 anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom; leftMargin: units.gu(1); rightMargin: units.gu(1) }
173+
174+ KeyNavigation.up: headerFocusScope
175+ KeyNavigation.down: searchField
176+ KeyNavigation.backtab: headerFocusScope
177+ KeyNavigation.tab: searchField
178+
121179 sourceComponent: {
122180 switch (sections.selectedIndex) {
123181 case 0: return aToZComponent;
@@ -167,10 +225,13 @@ FocusScope {
167225 Component {
168226 id: mostUsedComponent
169227 DrawerListView {
228+ id: mostUsedListView
170229
171230 header: MoreAppsHeader {
172231 width: parent.width
173232 height: units.gu(6)
233+ highlighted: headerFocusScope.activeFocus
234+ onClicked: headerFocusScope.trigger();
174235 }
175236
176237 model: AppDrawerProxyModel {
@@ -196,6 +257,9 @@ FocusScope {
196257 bottomMargin: units.gu(1)
197258 clip: true
198259
260+ interactive: true
261+ focus: index == mostUsedListView.currentIndex
262+
199263 model: sortProxyModel
200264
201265 delegateWidth: units.gu(8)
@@ -209,10 +273,13 @@ FocusScope {
209273 Component {
210274 id: aToZComponent
211275 DrawerListView {
276+ id: aToZListView
212277
213278 header: MoreAppsHeader {
214279 width: parent.width
215280 height: units.gu(6)
281+ highlighted: headerFocusScope.activeFocus
282+ onClicked: headerFocusScope.trigger();
216283 }
217284
218285 model: AppDrawerProxyModel {
@@ -244,7 +311,11 @@ FocusScope {
244311 anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
245312 height: rows * delegateHeight
246313
247− interactive: false
314+ interactive: true
315+ focus: index == aToZListView.currentIndex
316+ onFocusChanged: print("drawer grid", index, "has focus", focus)
317+
318+ onCurrentIndexChanged: print("grid currentIndex changed", currentIndex)
248319
249320 model: AppDrawerProxyModel {
250321 id: categoryModel
@@ -263,10 +334,13 @@ FocusScope {
263334 Component {
264335 id: drawerDelegateComponent
265336 AbstractButton {
337+ id: drawerDelegate
266338 width: GridView.view.cellWidth
267339 height: units.gu(10)
268340 objectName: "drawerItem_" + model.appId
269341
342+ readonly property bool focused: index === GridView.view.currentIndex && GridView.view.activeFocus
343+
270344 onClicked: root.applicationSelected(model.appId)
271345
272346 Column {
@@ -290,6 +364,17 @@ FocusScope {
290364 source: model.icon
291365 }
292366 sourceFillMode: UbuntuShape.PreserveAspectCrop
367+
368+ Image {
369+ objectName: "focusRing"
370+ anchors.centerIn: parent
371+ height: width * 15 / 16
372+ width: parent.width + units.gu(1)
373+ source: "graphics/launcher-app-focus-ring.svg"
374+ sourceSize.width: width
375+ sourceSize.height: height
376+ visible: drawerDelegate.focused
377+ }
293378 }
294379
295380 Label {
qml/Launcher/DrawerGridView.qml+3−1
@@ -17,7 +17,7 @@
1717import QtQuick 2.4
1818import "../Components"
1919
20−Item {
20+FocusScope {
2121 id: root
2222
2323 property int delegateWidth: units.gu(10)
@@ -25,6 +25,7 @@ Item {
2525 property alias delegate: gridView.delegate
2626 property alias model: gridView.model
2727 property alias interactive: gridView.interactive
28+ property alias currentIndex: gridView.currentIndex
2829
2930 property alias header: gridView.header
3031 property alias topMargin: gridView.topMargin
@@ -37,6 +38,7 @@ Item {
3738 id: gridView
3839 anchors.fill: parent
3940 leftMargin: spacing
41+ focus: true
4042
4143 readonly property int overflow: width - (root.columns * root.delegateWidth)
4244 readonly property real spacing: overflow / (root.columns)
qml/Launcher/DrawerListView.qml+6
@@ -25,6 +25,12 @@ ListView {
2525 bottomMargin: units.gu(1)
2626 spacing: units.gu(1)
2727 clip: true
28+ focus: true
29+
30+ onActiveFocusChanged: {
31+ currentIndex = -1;
32+ currentIndex = 0;
33+ }
2834
2935 function getFirstAppId() {
3036 return model.appId(0);
qml/Launcher/Launcher.qml+3−4
@@ -74,7 +74,7 @@ FocusScope {
7474 } else {
7575 superPressTimer.stop();
7676 superLongPressTimer.stop();
77− launcher.switchToNextState("");
77+ switchToNextState("");
7878 panel.shortcutHintsShown = false;
7979 }
8080 }
@@ -469,7 +469,7 @@ FocusScope {
469469 }
470470 onPassed: {
471471 if (root.drawerEnabled) {
472− root.switchToNextState("drawer");
472+ root.openDrawer()
473473 }
474474 }
475475
@@ -545,8 +545,7 @@ FocusScope {
545545 if (!dragging) {
546546 if (distance > panel.width / 2) {
547547 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
548− root.switchToNextState("drawer");
549− root.focus = true;
548+ root.openDrawer(false)
550549 } else {
551550 root.switchToNextState("visible");
552551 }
qml/Launcher/MoreAppsHeader.qml+2−5
@@ -4,15 +4,12 @@ import Ubuntu.Components 1.3
44AbstractButton {
55 id: root
66
7− onClicked: {
8− // TODO: Make this point to the snappy store as soon as we stop landing to vivid
9− Qt.openUrlExternally("scope://com.canonical.scopes.clickstore")
10− }
7+ property bool highlighted: false
118
129 UbuntuShape {
1310 width: parent.width
1411 height: parent.height - units.gu(1)
15− color: "#20ffffff"
12+ color: root.highlighted ? "red" : "#20ffffff"
1613 aspect: UbuntuShape.Flat
1714
1815 Row {
tests/mocks/GSettings.1.0/fake_gsettings.cpp+14
@@ -29,6 +29,7 @@ GSettingsControllerQml::GSettingsControllerQml()
2929 , m_edgeDragWidth(2)
3030 , m_enableLauncher(true)
3131 , m_enableIndicatorMenu(true)
32+ , m_appstoreUri("http://uappexplorer.com")
3233{
3334}
3435
@@ -173,6 +174,11 @@ void GSettingsControllerQml::setEnableIndicatorMenu(bool enableIndicatorMenu)
173174 }
174175}
175176
177+QString GSettingsControllerQml::appstoreUri() const
178+{
179+ return m_appstoreUri;
180+}
181+
176182GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
177183}
178184
@@ -376,6 +382,14 @@ QVariant GSettingsQml::enableIndicatorMenu() const
376382 }
377383}
378384
385+QVariant GSettingsQml::appstoreUri() const
386+{
387+ if (m_valid && m_schema->id() == "com.canonical.Unity8") {
388+ return GSettingsControllerQml::instance()->appstoreUri();
389+ }
390+ return QVariant();
391+}
392+
379393void GSettingsQml::setLifecycleExemptAppids(const QVariant &appIds)
380394{
381395 if (m_valid && m_schema->id() == "com.canonical.qtmir") {
tests/mocks/GSettings.1.0/fake_gsettings.h+7
@@ -59,6 +59,7 @@ class GSettingsQml: public QObject, public QQmlParserStatus
5959 Q_PROPERTY(QVariant edgeDragWidth READ edgeDragWidth WRITE setEdgeDragWidth NOTIFY edgeDragWidthChanged)
6060 Q_PROPERTY(QVariant enableLauncher READ enableLauncher WRITE setEnableLauncher NOTIFY enableLauncherChanged)
6161 Q_PROPERTY(QVariant enableIndicatorMenu READ enableIndicatorMenu WRITE setEnableIndicatorMenu NOTIFY enableIndicatorMenuChanged)
62+ Q_PROPERTY(QVariant appstoreUri READ appstoreUri NOTIFY appstoreUriChanged)
6263
6364public:
6465 GSettingsQml(QObject *parent = nullptr);
@@ -77,6 +78,7 @@ public:
7778 QVariant edgeDragWidth() const;
7879 QVariant enableLauncher() const;
7980 QVariant enableIndicatorMenu() const;
81+ QVariant appstoreUri() const;
8082
8183 void setDisableHeight(const QVariant &val);
8284 void setPictureUri(const QVariant &str);
@@ -101,6 +103,7 @@ Q_SIGNALS:
101103 void edgeDragWidthChanged();
102104 void enableLauncherChanged();
103105 void enableIndicatorMenuChanged();
106+ void appstoreUriChanged();
104107
105108private:
106109 GSettingsSchemaQml* m_schema;
@@ -147,6 +150,8 @@ public:
147150 bool enableIndicatorMenu() const;
148151 Q_INVOKABLE void setEnableIndicatorMenu(bool enableIndicatorMenu);
149152
153+ QString appstoreUri() const;
154+
150155Q_SIGNALS:
151156 void disableHeightChanged();
152157 void pictureUriChanged(const QString&);
@@ -158,6 +163,7 @@ Q_SIGNALS:
158163 void edgeDragWidthChanged(uint edgeDragWidth);
159164 void enableLauncherChanged(bool enableLauncher);
160165 void enableIndicatorMenuChanged(bool enableIndicatorMenu);
166+ void appstoreUriChanged(const QString &appstoreUri);
161167
162168private:
163169 GSettingsControllerQml();
@@ -172,6 +178,7 @@ private:
172178 uint m_edgeDragWidth;
173179 bool m_enableLauncher;
174180 bool m_enableIndicatorMenu;
181+ QString m_appstoreUri;
175182
176183 static GSettingsControllerQml* s_controllerInstance;
177184 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/Unity/Launcher/MockAppDrawerModel.cpp+1−1
@@ -48,13 +48,13 @@ MockAppDrawerModel::MockAppDrawerModel(QObject *parent):
4848 m_list.append(item);
4949 item = new MockLauncherItem("libreoffice", "/usr/share/applications/libreoffice.desktop","Libre Office", "libreoffice", this);
5050 m_list.append(item);
51− qDebug() << "mock model created";
5251
5352 qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
5453}
5554
5655int MockAppDrawerModel::rowCount(const QModelIndex &parent) const
5756{
57+ Q_UNUSED(parent)
5858 return m_list.count();
5959}
6060
tests/qmltests/Launcher/tst_Drawer.qml+56−7
@@ -26,6 +26,7 @@ import Unity.Test 0.1
2626StyledItem {
2727 id: root
2828 theme.name: "Ubuntu.Components.Themes.SuruDark"
29+ focus: true
2930
3031 width: units.gu(140)
3132 height: units.gu(70)
@@ -108,6 +109,7 @@ StyledItem {
108109 tryCompareFunction( function(){ return drawer.x === 0; }, true );
109110 tryCompare(launcher, "state", "drawer");
110111 tryCompare(launcher, "drawerShown", true);
112+ return drawer;
111113 }
112114
113115 function revealByEdgePush() {
@@ -134,6 +136,8 @@ StyledItem {
134136 launcher.hide();
135137 var drawer = findChild(launcher, "drawer");
136138 tryCompare(drawer, "x", -drawer.width);
139+ var searchField = findChild(drawer, "searchField");
140+ searchField.text = "";
137141 }
138142
139143 function test_revealByEdgeDrag() {
@@ -168,12 +172,10 @@ StyledItem {
168172 function test_hideByDraggingDrawer(data) {
169173 launcher.lockedVisible = !data.autohide;
170174
171− dragDrawerIntoView();
175+ var drawer = dragDrawerIntoView();
172176 waitForRendering(launcher);
173177
174− var drawer = findChild(launcher, "drawer");
175−
176− mouseFlick(root, drawer.width - units.gu(1), drawer.height / 2, units.gu(2), drawer.height / 2, true, true);
178+ mouseFlick(root, drawer.width - units.gu(1), drawer.height / 2, units.gu(10), drawer.height / 2, true, true);
177179
178180 tryCompare(drawer.anchors, "rightMargin", 0);
179181 tryCompare(launcher, "state", data.endState);
@@ -181,9 +183,7 @@ StyledItem {
181183 }
182184
183185 function test_hideByClickingOutside() {
184− dragDrawerIntoView();
185−
186− var drawer = findChild(launcher, "drawer");
186+ var drawer = dragDrawerIntoView();
187187
188188 mouseClick(root, drawer.width + units.gu(1), root.height / 2);
189189
@@ -260,5 +260,54 @@ StyledItem {
260260
261261 tryCompare(launcher, "state", data.endState)
262262 }
263+
264+ function test_searchDirectly() {
265+ var drawer = dragDrawerIntoView();
266+ waitForRendering(launcher);
267+ waitUntilTransitionsEnd(launcher);
268+ tryCompare(drawer, "focus", true);
269+
270+ var searchField = findChild(drawer, "searchField");
271+ verify(searchField);
272+ typeString("cam");
273+ tryCompare(searchField, "displayText", "cam");
274+ }
275+
276+ function test_kbdNavigation() {
277+ launcher.openDrawer(true);
278+ waitForRendering(launcher);
279+ waitUntilTransitionsEnd(launcher);
280+ compare(launcher.lastSelectedApplication, "");
281+
282+ var drawer = findChild(launcher, "drawer");
283+ var searchField = findChild(drawer, "searchField");
284+ var sections = findChild(drawer, "drawerSections");
285+ var header = findChild(drawer, "headerFocusScope");
286+ var listLoader = findChild(drawer, "drawerListLoader");
287+
288+ tryCompare(searchField, "activeFocus", true);
289+ // for some reason, even when the searchField has activeFocus already,
290+ // it won't react on key down events if they're coming in too early...
291+ // let's repeat the press for a bit before giving up
292+ tryCompareFunction(function() {
293+ wait(10)
294+ keyClick(Qt.Key_Down);
295+ return sections.activeFocus;
296+ }, true)
297+
298+ keyClick(Qt.Key_Down);
299+ tryCompare(header, "activeFocus", true);
300+
301+ keyClick(Qt.Key_Down);
302+ tryCompare(listLoader, "activeFocus", true);
303+
304+ keyClick(Qt.Key_Down);
305+ keyClick(Qt.Key_Left);
306+ tryCompare(listLoader, "activeFocus", true);
307+
308+ keyClick(Qt.Key_Enter);
309+
310+ compare(launcher.lastSelectedApplication, "calendar-app");
311+ }
263312 }
264313}