on xenial help sign in
Replace Scopes Overview by the new Manage Dash Fixes: #1368670, #1386698

Approved by: Nick Dedekind, Benjamin Keyser

commit4246cafee2339c85a5a7c9309a89ebfc2debe1ba authorAlbert Astals <albert.astals@canonical.com> dateFri 5 Dec 2014, 10:55 (12 years ago) parente4af96154c78e7
20 files changed+778−1102
debian/control+1−1 po/unity8.pot+28−32 qml/Dash/Dash.qml+90−128 qml/Dash/DashContent.qml+11−1 qml/Dash/GenericScopeView.qml+1−1 qml/Dash/PageHeader.qml+11−1 qml/Dash/ScopesList.qml+127 qml/Dash/ScopesListCategory.qml+176 qml/Dash/ScopesListCategoryItem.qml+104 qml/Dash/ScopesOverview.qml−575 qml/Dash/ScopesOverviewAll.qml−54 qml/Dash/ScopesOverviewFavorites.qml−73 qml/Dash/ScopesOverviewTab.qml−74 tests/mocks/Unity/CMakeLists.txt+1−1 tests/mocks/Unity/fake_scopes.cpp+57−3 tests/mocks/Unity/fake_scopes.h+8−3 tests/mocks/Unity/fake_scopesoverview.cpp+77−11 tests/mocks/Unity/fake_scopesoverview.h+18−3 tests/qmltests/Dash/tst_Dash.qml+68−140 tests/qmltests/Dash/tst_GenericScopeView.qml−1
debian/control+1−1
@@ -25,7 +25,7 @@ Build-Depends: cmake,
2525 libqmenumodel-dev (>= 0.2.8),
2626 libqt5xmlpatterns5-dev,
2727 libsystemsettings-dev,
28− libunity-api-dev (>= 7.93),
28+ libunity-api-dev (>= 7.94),
2929 libusermetricsoutput1-dev,
3030 libxcb1-dev,
3131 pkg-config,
po/unity8.pot+28−32
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2014-11-07 10:59+0100\n"
11+"POT-Creation-Date: 2014-11-20 12:00+0100\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -172,20 +172,24 @@ msgstr ""
172172msgid "See all"
173173msgstr ""
174174
175−#: qml/Dash/GenericScopeView.qml:496 qml/Dash/PageHeader.qml:258
175+#: qml/Dash/GenericScopeView.qml:496 qml/Dash/PageHeader.qml:267
176176#: qml/Panel/SearchIndicator.qml:27
177177msgid "Search"
178178msgstr ""
179179
180−#: qml/Dash/PageHeader.qml:268
180+#: qml/Dash/PageHeader.qml:260
181+msgid "Store"
182+msgstr ""
183+
184+#: qml/Dash/PageHeader.qml:277
181185msgid "Settings"
182186msgstr ""
183187
184−#: qml/Dash/PageHeader.qml:275
188+#: qml/Dash/PageHeader.qml:284
185189msgid "Remove from Favorites"
186190msgstr ""
187191
188−#: qml/Dash/PageHeader.qml:275
192+#: qml/Dash/PageHeader.qml:284
189193msgid "Add to Favorites"
190194msgstr ""
191195
@@ -217,31 +221,23 @@ msgstr ""
217221msgid "Release to refresh…"
218222msgstr ""
219223
220−#: qml/Dash/ScopesOverview.qml:215
221−msgid "Manage Scopes"
224+#: qml/Dash/ScopesList.qml:63
225+msgid "Manage"
222226msgstr ""
223227
224−#: qml/Dash/ScopesOverview.qml:437
225−msgid "Done"
226−msgstr ""
227−
228−#: qml/Dash/ScopesOverview.qml:463
229−msgid "Store"
228+#: qml/Dash/ScopesList.qml:105
229+msgid "Home"
230230msgstr ""
231231
232−#: qml/Dash/ScopesOverviewTab.qml:36
233−msgid "Favorites"
234−msgstr ""
235−
236−#: qml/Dash/ScopesOverviewTab.qml:54
237−msgid "All"
232+#: qml/Dash/ScopesList.qml:106
233+msgid "Also installed"
238234msgstr ""
239235
240236#: qml/Greeter/Greeter.qml:174
241237msgid "Unlock"
242238msgstr ""
243239
244−#: qml/Notifications/NotificationMenuItemFactory.qml:116
240+#: qml/Notifications/NotificationMenuItemFactory.qml:128
245241msgid "Show password"
246242msgstr ""
247243
@@ -289,55 +285,55 @@ msgstr ""
289285msgid "Roaming"
290286msgstr ""
291287
292−#: qml/Shell.qml:391
288+#: qml/Shell.qml:390
293289msgid "Enter passphrase"
294290msgstr ""
295291
296−#: qml/Shell.qml:392
292+#: qml/Shell.qml:391
297293msgid "Sorry, incorrect passphrase"
298294msgstr ""
299295
300−#: qml/Shell.qml:393
296+#: qml/Shell.qml:392
301297msgid "Please re-enter"
302298msgstr ""
303299
304−#: qml/Shell.qml:395
300+#: qml/Shell.qml:394
305301msgid "Enter passcode"
306302msgstr ""
307303
308−#: qml/Shell.qml:396
304+#: qml/Shell.qml:395
309305msgid "Sorry, incorrect passcode"
310306msgstr ""
311307
312−#: qml/Shell.qml:399
308+#: qml/Shell.qml:398
313309#, qt-format
314310msgid "Enter %1"
315311msgstr ""
316312
317−#: qml/Shell.qml:400
313+#: qml/Shell.qml:399
318314#, qt-format
319315msgid "Sorry, incorrect %1"
320316msgstr ""
321317
322−#: qml/Shell.qml:441
318+#: qml/Shell.qml:440
323319msgid "Sorry, incorrect passphrase."
324320msgstr ""
325321
326−#: qml/Shell.qml:442
322+#: qml/Shell.qml:441
327323msgid "Sorry, incorrect passcode."
328324msgstr ""
329325
330−#: qml/Shell.qml:443
326+#: qml/Shell.qml:442
331327msgid "This will be your last attempt."
332328msgstr ""
333329
334−#: qml/Shell.qml:445
330+#: qml/Shell.qml:444
335331msgid ""
336332"If passphrase is entered incorrectly, your phone will conduct a factory "
337333"reset and all personal data will be deleted."
338334msgstr ""
339335
340−#: qml/Shell.qml:446
336+#: qml/Shell.qml:445
341337msgid ""
342338"If passcode is entered incorrectly, your phone will conduct a factory reset "
343339"and all personal data will be deleted."
qml/Dash/Dash.qml+90−128
@@ -31,16 +31,13 @@ Showable {
3131 DashCommunicatorService {
3232 objectName: "dashCommunicatorService"
3333 onSetCurrentScopeRequested: {
34− if (!isSwipe || !window.active || overviewController.progress != 0 || scopeItem.scope || dashContent.subPageShown) {
35− if (overviewController.progress != 0 && window.active) animate = false;
34+ if (!isSwipe || !window.active || bottomEdgeController.progress != 0 || scopeItem.scope || dashContent.subPageShown) {
35+ if (bottomEdgeController.progress != 0 && window.active) animate = false;
3636 dashContent.setCurrentScopeAtIndex(index, animate, isSwipe)
3737 // Close dash overview and nested temp scopes in it
38− if (overviewController.progress != 0) {
39− if (window.active) {
40− dashContentCache.scheduleUpdate();
41− }
42− overviewController.enableAnimation = window.active && !scopesOverview.showingNonFavoriteScope;
43− overviewController.progress = 0;
38+ if (bottomEdgeController.progress != 0) {
39+ bottomEdgeController.enableAnimation = window.active && !scopesOverview.showingNonFavoriteScope;
40+ bottomEdgeController.progress = 0;
4441 scopesOverview.closeTempScope();
4542 }
4643 // Close normal temp scopes (e.g. App Store)
@@ -92,8 +89,8 @@ Showable {
9289 }
9390
9491 QtObject {
95− id: overviewController
96− objectName: "overviewController"
92+ id: bottomEdgeController
93+ objectName: "bottomEdgeController"
9794
9895 property alias enableAnimation: progressAnimation.enabled
9996 property real progress: 0
@@ -101,108 +98,40 @@ Showable {
10198 id: progressAnimation
10299 UbuntuNumberAnimation { }
103100 }
104− }
105101
106− ScopesOverview {
107− id: scopesOverview
108− objectName: "scopesOverview"
109− anchors.fill: parent
110− scope: scopes.overviewScope
111− progress: overviewController.progress
112− scopeScale: scopeItem.scope ? 0.4 : (1 - overviewController.progress * 0.6)
113− visible: scopeScale != 1
114− currentIndex: dashContent.currentIndex
115− onDone: {
116− if (currentTab == 1) {
117− animateDashFromAll(dashContent.currentScopeId);
118− }
119− hide();
120− }
121− onFavoriteSelected: {
122− setCurrentScope(scopeId, false, false);
123− dashContentCache.scheduleUpdate();
124− hide();
125− }
126− onAllFavoriteSelected: {
127− setCurrentScope(scopeId, false, false);
128− dashContentCache.scheduleUpdate();
129− animateDashFromAll(dashContent.currentScopeId);
130− hide();
131− }
132− onSearchSelected: {
133− var scopeIndex = -1;
134− for (var i = 0; i < scopes.count; ++i) {
135− if (scopes.getScope(i).id == scopeId) {
136− scopeIndex = i;
137− break;
138− }
139− }
140− if (scopeIndex >= 0) {
141− // Is a favorite one
142− setCurrentScope(scopeId, false, false);
143− dashContentCache.scheduleUpdate();
144− showDashFromPos(pos, size);
145− hide();
146− } else {
147− // Is not a favorite one, activate and get openScope
148− scope.activate(result);
149− }
150− }
151− function hide() {
152− overviewController.enableAnimation = true;
153− overviewController.progress = 0;
154− }
155102 onProgressChanged: {
156− if (progress == 0) {
157− currentTab = scopeItem.scope ? 1 : 0;
103+ // FIXME This is to workaround a Qt bug with the model moving the current item
104+ // when the list is ListView.SnapOneItem and ListView.StrictlyEnforceRange
105+ // together with the code in DashContent.qml
106+ if (dashContent.workaroundRestoreIndex != -1) {
107+ dashContent.currentIndex = dashContent.workaroundRestoreIndex;
108+ dashContent.workaroundRestoreIndex = -1;
158109 }
159110 }
160111 }
161112
162− ShaderEffectSource {
163− id: dashContentCache
164− parent: scopesOverview.dashItemEater
165− z: 1
166− sourceItem: dashContent
167− height: sourceItem.height
168− width: sourceItem.width
169− opacity: 1 - overviewController.progress
170− visible: overviewController.progress != 0 && scopeItem.scope === null
171− live: false
172− }
173−
174113 DashContent {
175114 id: dashContent
176115
177− property var scopeThatOpenedScope: null
178−
179116 objectName: "dashContent"
180117 width: dash.width
181118 height: dash.height
182119 scopes: scopes
183− visible: !scopesOverview.showingNonFavoriteScope && x != -width
120+ visible: x != -width
184121 onGotoScope: {
185122 dash.setCurrentScope(scopeId, true, false);
186123 }
187124 onOpenScope: {
188− scopeThatOpenedScope = currentScope;
125+ scopeItem.scopeThatOpenedScope = currentScope;
189126 scopeItem.scope = scope;
190− scopesOverview.currentTab = 1;
191− scopesOverview.ensureAllScopeVisible(scope.id);
192127 x = -width;
193128 }
194− clip: scale != 1.0 || scopeItem.visible || overviewController.progress != 0
195129 Behavior on x {
196130 UbuntuNumberAnimation {
197− duration: overviewController.progress != 0 ? 0 : UbuntuAnimation.FastDuration
198131 onRunningChanged: {
199132 if (!running && dashContent.x == 0) {
200− dashContent.scopeThatOpenedScope.closeScope(scopeItem.scope);
133+ scopeItem.scopeThatOpenedScope.closeScope(scopeItem.scope);
201134 scopeItem.scope = null;
202− if (overviewController.progress == 0) {
203− // Set tab to Favorites only if we are not showing the overview
204− scopesOverview.currentTab = 0;
205− }
206135 }
207136 }
208137 }
@@ -214,47 +143,78 @@ Showable {
214143 // about whether that touch was indeed performing a directional drag gesture.
215144 forceNonInteractive: overviewDragHandle.status != DirectionalDragArea.WaitingForTouch
216145
217− enabled: overviewController.progress == 0
218− opacity: enabled ? 1 : 0
146+ enabled: bottomEdgeController.progress == 0
147+ }
148+
149+ Rectangle {
150+ color: "black"
151+ opacity: bottomEdgeController.progress
152+ anchors.fill: dashContent
153+ }
154+
155+ ScopesList {
156+ id: scopesList
157+ objectName: "scopesList"
158+ width: dash.width
159+ height: dash.height
160+ scope: scopes.overviewScope
161+ y: dash.height * (1 - bottomEdgeController.progress)
162+ visible: bottomEdgeController.progress != 0
163+ onBackClicked: {
164+ bottomEdgeController.enableAnimation = true;
165+ bottomEdgeController.progress = 0;
166+ }
167+ onStoreClicked: {
168+ bottomEdgeController.enableAnimation = true;
169+ bottomEdgeController.progress = 0;
170+ dashContent.currentScope.performQuery("scope://com.canonical.scopes.clickstore");
171+ }
172+ onRequestFavorite: {
173+ scopes.setFavorite(scopeId, favorite);
174+ }
175+ onRequestFavoriteMoveTo: {
176+ scopes.moveFavoriteTo(scopeId, index);
177+ }
178+
179+ Binding {
180+ target: scopesList.scope
181+ property: "isActive"
182+ value: bottomEdgeController.progress === 1
183+ }
184+
185+ Connections {
186+ target: scopesList.scope
187+ onOpenScope: {
188+ bottomEdgeController.enableAnimation = true;
189+ bottomEdgeController.progress = 0;
190+ scopeItem.scopeThatOpenedScope = scopesList.scope;
191+ scopeItem.scope = scope;
192+ dashContent.x = -dashContent.width;
193+ }
194+ onGotoScope: {
195+ bottomEdgeController.enableAnimation = true;
196+ bottomEdgeController.progress = 0;
197+ dashContent.gotoScope(scopeId);
198+ }
199+ }
219200 }
220201
221202 DashBackground
222203 {
223204 anchors.fill: scopeItem
224205 visible: scopeItem.visible
225− parent: scopeItem.parent
226− scale: scopeItem.scale
227− opacity: scopeItem.opacity
228206 }
229207
230208 GenericScopeView {
231209 id: scopeItem
232210 objectName: "dashTempScopeItem"
233211
234− readonly property real targetOverviewScale: {
235− if (scopesOverview.currentTab == 0) {
236− return 0.4;
237− } else {
238− return scopesOverview.allCardSize.width / scopeItem.width;
239− }
240− }
241− readonly property real overviewProgressScale: (1 - overviewController.progress * (1 - targetOverviewScale))
242− readonly property var targetOverviewPosition: scope ? scopesOverview.allScopeCardPosition(scope.id) : null
243− readonly property real overviewProgressX: scope && scopesOverview.currentTab == 1 && targetOverviewPosition ?
244− overviewController.progress * (targetOverviewPosition.x - (width - scopesOverview.allCardSize.width) / 2)
245− : 0
246− readonly property real overviewProgressY: scope && scopesOverview.currentTab == 1 && targetOverviewPosition ?
247− overviewController.progress * (targetOverviewPosition.y - (height - scopesOverview.allCardSize.height) / 2)
248− : 0
249−
250− x: overviewController.progress == 0 ? dashContent.x + width : overviewProgressX
251− y: overviewController.progress == 0 ? dashContent.y : overviewProgressY
212+ property var scopeThatOpenedScope: null
213+
214+ x: dashContent.x + width
215+ y: dashContent.y
252216 width: parent.width
253217 height: parent.height
254− scale: overviewProgressScale
255− enabled: opacity == 1
256− opacity: 1 - overviewController.progress
257− clip: scale != 1.0
258218 visible: scope != null
259219 hasBackAction: true
260220 isCurrent: visible
@@ -288,7 +248,7 @@ Showable {
288248 opacity: 0
289249 visible: opacity > 0
290250
291− readonly property bool processing: dashContent.processing || scopeItem.processing || scopesOverview.processing
251+ readonly property bool processing: dashContent.processing || scopeItem.processing || scopesList.processing
292252
293253 Behavior on opacity {
294254 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
@@ -345,12 +305,17 @@ Showable {
345305 source: "graphics/overview_hint.png"
346306 anchors.horizontalCenter: parent.horizontalCenter
347307 opacity: (scopeItem.scope ? scopeItem.pageHeaderTotallyVisible : dashContent.pageHeaderTotallyVisible) &&
348− (overviewDragHandle.enabled || overviewController.progress != 0) ? 1 : 0
308+ (overviewDragHandle.enabled || bottomEdgeController.progress != 0) ? 1 : 0
349309 Behavior on opacity {
350− enabled: overviewController.progress == 0
310+ enabled: bottomEdgeController.progress == 0
351311 UbuntuNumberAnimation {}
352312 }
353− y: parent.height - height * (1 - overviewController.progress * 4)
313+ y: parent.height - height * (1 - bottomEdgeController.progress * 4)
314+ MouseArea {
315+ // Eat direct presses on the overview hint so that they do not end up in the card below
316+ anchors.fill: parent
317+ enabled: parent.opacity != 0
318+ }
354319 }
355320
356321 EdgeDragArea {
@@ -361,22 +326,19 @@ Showable {
361326 enabled: !dashContent.subPageShown &&
362327 dashContent.currentScope &&
363328 dashContent.currentScope.searchQuery == "" &&
364− !scopeItem.subPageShown &&
365− (overviewController.progress == 0 || dragging)
329+ !scopeItem.scope &&
330+ (bottomEdgeController.progress == 0 || dragging)
366331
367− readonly property real fullMovement: units.gu(20)
332+ readonly property real fullMovement: dash.height
368333
369334 anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
370335 height: units.gu(2)
371336
372337 onSceneDistanceChanged: {
373338 if (status == DirectionalDragArea.Recognized && initialSceneDistance != -1) {
374− if (overviewController.enableAnimation) {
375− dashContentCache.scheduleUpdate();
376− }
377− overviewController.enableAnimation = false;
339+ bottomEdgeController.enableAnimation = false;
378340 var deltaDistance = sceneDistance - initialSceneDistance;
379− overviewController.progress = Math.max(0, Math.min(1, deltaDistance / fullMovement));
341+ bottomEdgeController.progress = Math.max(0, Math.min(1, deltaDistance / fullMovement));
380342 }
381343 }
382344
@@ -392,8 +354,8 @@ Showable {
392354 initialSceneDistance = sceneDistance;
393355 } else if (status == DirectionalDragArea.WaitingForTouch &&
394356 previousStatus == DirectionalDragArea.Recognized) {
395− overviewController.enableAnimation = true;
396− overviewController.progress = (overviewController.progress > 0.7) ? 1 : 0;
357+ bottomEdgeController.enableAnimation = true;
358+ bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
397359 initialSceneDistance = -1;
398360 }
399361 }
qml/Dash/DashContent.qml+11−1
@@ -25,7 +25,8 @@ Item {
2525
2626 property bool forceNonInteractive: false
2727 property alias scopes: dashContentList.model
28− readonly property alias currentIndex: dashContentList.currentIndex
28+ property alias currentIndex: dashContentList.currentIndex
29+ property int workaroundRestoreIndex: -1
2930 readonly property string currentScopeId: dashContentList.currentItem ? dashContentList.currentItem.scopeId : ""
3031 readonly property var currentScope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
3132 readonly property bool subPageShown: dashContentList.currentItem && dashContentList.currentItem.item ?
@@ -51,6 +52,15 @@ Item {
5152 set_current_index = undefined;
5253 }
5354 }
55+ onRowsMoved: {
56+ // FIXME This is to workaround a Qt bug with the model moving the current item
57+ // when the list is ListView.SnapOneItem and ListView.StrictlyEnforceRange
58+ // together with the code in Dash.qml
59+ if (row == dashContentList.currentIndex || start == dashContentList.currentIndex) {
60+ dashContent.workaroundRestoreIndex = dashContentList.currentIndex;
61+ dashContentList.currentIndex = -1;
62+ }
63+ }
5464 }
5565
5666 Connections {
qml/Dash/GenericScopeView.qml+1−1
@@ -494,7 +494,7 @@ FocusScope {
494494 showBackButton: scopeView.hasBackAction
495495 searchEntryEnabled: true
496496 settingsEnabled: scopeView.scope && scopeView.scope.settings && scopeView.scope.settings.count > 0 || false
497− favoriteEnabled: scopeView.scope && scopeView.scope.id !== "clickscope"
497+ favoriteEnabled: scopeView.scope
498498 favorite: scopeView.scope && scopeView.scope.favorite
499499 scopeStyle: scopeView.scopeStyle
500500 paginationCount: scopeView.paginationCount
qml/Dash/PageHeader.qml+11−1
@@ -28,8 +28,10 @@ Item {
2828 implicitHeight: headerContainer.height + bottomContainer.height + (showSignatureLine ? units.gu(2) : 0)
2929
3030 property bool showBackButton: false
31+ property bool backIsClose: false
3132 property string title
3233
34+ property bool storeEntryEnabled: false
3335 property bool searchEntryEnabled: false
3436 property bool settingsEnabled: false
3537 property bool favoriteEnabled: false
@@ -46,6 +48,7 @@ Item {
4648 property var scopeStyle: null
4749
4850 signal backClicked()
51+ signal storeClicked()
4952 signal settingsClicked()
5053 signal favoriteClicked()
5154
@@ -246,12 +249,19 @@ Item {
246249 property var config: PageHeadConfiguration {
247250 foregroundColor: root.scopeStyle ? root.scopeStyle.headerForeground : Theme.palette.normal.baseText
248251 backAction: Action {
249− iconName: "back"
252+ iconName: backIsClose ? "close" : "back"
250253 visible: root.showBackButton
251254 onTriggered: root.backClicked()
252255 }
253256
254257 actions: [
258+ Action {
259+ objectName: "store"
260+ text: i18n.tr("Store")
261+ iconName: "ubuntu-store-symbolic"
262+ visible: root.storeEntryEnabled
263+ onTriggered: root.storeClicked();
264+ },
255265 Action {
256266 objectName: "search"
257267 text: i18n.tr("Search")
qml/Dash/ScopesList.qmlnew file+127
@@ -0,0 +1,127 @@
1+/*
2+ * Copyright (C) 2014 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.3
18+import Dash 0.1
19+
20+Item {
21+ id: root
22+
23+ // Properties set by parent
24+ property var scope: null
25+
26+ // Properties used by parent
27+ readonly property bool processing: scope ? scope.searchInProgress : false
28+
29+ // Signals
30+ signal backClicked()
31+ signal storeClicked()
32+ signal requestFavorite(string scopeId, bool favorite)
33+ signal requestFavoriteMoveTo(string scopeId, int index)
34+
35+ state: "browse"
36+
37+ onStateChanged: {
38+ if (state == "edit") {
39+ // As per design entering edit mode clears the possible existing search
40+ header.resetSearch(false /* false == unfocus */);
41+ }
42+ }
43+
44+ DashBackground {
45+ anchors.fill: parent
46+ }
47+
48+ Binding {
49+ target: root.scope
50+ property: "searchQuery"
51+ value: header.searchQuery
52+ }
53+
54+ Binding {
55+ target: header
56+ property: "searchQuery"
57+ value: root.scope ? root.scope.searchQuery : ""
58+ }
59+
60+ PageHeader {
61+ id: header
62+ objectName: "pageHeader"
63+ title: i18n.tr("Manage")
64+ width: parent.width
65+ showBackButton: true
66+ backIsClose: root.state == "edit"
67+ storeEntryEnabled: root.state == "browse"
68+ searchEntryEnabled: false // Disable search for now
69+ onBackClicked: {
70+ if (backIsClose) {
71+ root.state = "browse"
72+ } else {
73+ root.backClicked()
74+ }
75+ }
76+ onStoreClicked: root.storeClicked();
77+ z: 1
78+ }
79+
80+ Flickable {
81+ anchors {
82+ top: header.bottom
83+ bottom: parent.bottom
84+ left: parent.left
85+ right: parent.right
86+ }
87+ clip: true
88+ contentWidth: root.width
89+ contentHeight: column.height
90+ onContentHeightChanged: returnToBounds();
91+ Column {
92+ id: column
93+ Repeater {
94+ model: scope ? scope.categories : null
95+
96+ delegate: Loader {
97+ asynchronous: true
98+ width: root.width
99+ active: results.count > 0
100+ visible: active
101+ sourceComponent: ScopesListCategory {
102+ objectName: "scopesListCategory" + categoryId
103+
104+ model: results
105+
106+ title: {
107+ if (isFavoritesFeed) return i18n.tr("Home");
108+ else if (isAlsoInstalled) return i18n.tr("Also installed");
109+ else return name;
110+ }
111+
112+ editMode: root.state == "edit"
113+
114+ scopeStyle: root.scope.scopeStyle
115+ isFavoritesFeed: categoryId == "favorites"
116+ isAlsoInstalled: categoryId == "other"
117+
118+ onRequestFavorite: root.requestFavorite(scopeId, favorite);
119+ onRequestEditMode: root.state = "edit";
120+ onRequestScopeMoveTo: root.requestFavoriteMoveTo(scopeId, index);
121+ onRequestActivate: root.scope.activate(result);
122+ }
123+ }
124+ }
125+ }
126+ }
127+}
qml/Dash/ScopesListCategory.qmlnew file+176
@@ -0,0 +1,176 @@
1+/*
2+ * Copyright (C) 2014 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.3
18+import Ubuntu.Components 1.1
19+import Dash 0.1
20+import "../Components/ListItems" as ListItems
21+
22+Item {
23+ id: root
24+
25+ property alias model: list.model
26+ property alias title: header.text
27+ property var scopeStyle
28+ property bool editMode: false
29+ property bool isFavoritesFeed: false
30+ property bool isAlsoInstalled: false
31+
32+ visible: !editMode || isFavoritesFeed
33+
34+ signal requestFavorite(string scopeId, bool favorite)
35+ signal requestEditMode()
36+ signal requestScopeMoveTo(string scopeId, int index)
37+ signal requestActivate(var result)
38+
39+ implicitHeight: visible ? childrenRect.height : 0
40+
41+ ListItems.Header {
42+ id: header
43+ width: root.width
44+ height: units.gu(5)
45+ color: scopeStyle ? scopeStyle.foreground : Theme.palette.normal.baseText
46+ }
47+
48+ readonly property double listItemHeight: units.gu(6)
49+
50+ ListView {
51+ id: list
52+ objectName: "scopesListCategoryInnerList"
53+
54+ readonly property double targetHeight: model.count * listItemHeight
55+ clip: height != targetHeight
56+ height: targetHeight
57+ Behavior on height { enabled: visible; UbuntuNumberAnimation { } }
58+ width: parent.width
59+ interactive: false
60+
61+ anchors.top: header.bottom
62+ delegate: Loader {
63+ id: loader
64+ readonly property bool addDropHint: {
65+ if (dragMarker.visible) {
66+ if (dragItem.originalIndex > index) {
67+ return dragMarker.index == index;
68+ } else {
69+ return dragMarker.index == index - 1;
70+ }
71+ } else {
72+ return false;
73+ }
74+ }
75+ asynchronous: true
76+ width: root.width
77+ height: listItemHeight + (addDropHint ? units.gu(2) : 0)
78+ clip: height < listItemHeight
79+ Behavior on height { enabled: visible; UbuntuNumberAnimation { } }
80+ sourceComponent: ScopesListCategoryItem {
81+ objectName: "delegate" + index
82+
83+ width: root.width
84+ topMargin: height > listItemHeight ? height - listItemHeight : 0
85+
86+ icon: model.art || model.mascot || ""
87+ text: model.title || ""
88+ subtext: model.subtitle || ""
89+ showStar: root.isFavoritesFeed || root.isAlsoInstalled
90+ isFavorite: root.isFavoritesFeed
91+
92+ hideChildren: dragItem.loaderToShrink == loader
93+
94+ onClicked: {
95+ if (!editMode) {
96+ root.requestActivate(result);
97+ }
98+ }
99+ onPressAndHold: {
100+ if (!editMode) {
101+ root.requestEditMode();
102+ }
103+ }
104+ onRequestFavorite: root.requestFavorite(model.scopeId, favorite);
105+ onHandlePressed: {
106+ if (editMode) {
107+ handle.drag.target = dragItem;
108+ handle.drag.maximumX = units.gu(1);
109+ handle.drag.minimumX = units.gu(1);
110+ handle.drag.minimumY = list.y - dragItem.height / 2;
111+ handle.drag.maximumY = list.y + list.height - dragItem.height / 2
112+ dragItem.icon = icon;
113+ dragItem.text = text;
114+ dragItem.subtext = subtext;
115+ dragItem.originalY = mapToItem(root, 0, 0).y;
116+ dragItem.originalIndex = index;
117+ dragItem.y = dragItem.originalY;
118+ dragItem.x = units.gu(1);
119+ dragItem.visible = true;
120+ dragItem.loaderToShrink = loader;
121+ }
122+ }
123+ onHandleReleased: {
124+ if (dragItem.visible) {
125+ handle.drag.target = undefined;
126+ dragItem.visible = false;
127+ if (dragMarker.visible && dragMarker.index != index) {
128+ root.requestScopeMoveTo(model.scopeId, dragMarker.index);
129+ }
130+ dragMarker.visible = false;
131+ dragItem.loaderToShrink.height = listItemHeight;
132+ dragItem.loaderToShrink = null;
133+ }
134+ }
135+ }
136+ }
137+ }
138+
139+ ListItems.ThinDivider {
140+ id: dragMarker
141+ visible: false
142+ anchors {
143+ leftMargin: units.gu(1)
144+ rightMargin: units.gu(1)
145+ }
146+ property int index: {
147+ var i = Math.round((dragItem.y - list.y + dragItem.height/2) / listItemHeight);
148+ if (i < 0) i = 0;
149+ if (i >= model.count - 1) i = model.count - 1;
150+ return i;
151+ }
152+ y: list.y + index * listItemHeight + units.gu(1)
153+ }
154+
155+ ScopesListCategoryItem {
156+ id: dragItem
157+
158+ property real originalY
159+ property int originalIndex
160+ property var loaderToShrink: null
161+
162+ objectName: "dragItem"
163+ visible: false
164+ showStar: false
165+ width: root.width
166+ height: listItemHeight
167+ opacity: 0.9
168+
169+ onYChanged: {
170+ if (!dragMarker.visible && Math.abs(y - originalY) > height / 2) {
171+ dragMarker.visible = true;
172+ loaderToShrink.height = 0;
173+ }
174+ }
175+ }
176+}
qml/Dash/ScopesListCategoryItem.qmlnew file+104
@@ -0,0 +1,104 @@
1+/*
2+ * Copyright (C) 2014 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.3
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.1
20+
21+MouseArea {
22+ id: root
23+
24+ signal requestFavorite(string scopeId, bool favorite)
25+ signal handlePressed(var handle)
26+ signal handleReleased(var handle)
27+
28+ property real topMargin: 0
29+ property alias icon: shapeImage.source
30+ property alias text: titleLabel.text
31+ property alias subtext: subtitleLabel.text
32+ property alias showStar: star.visible
33+
34+ property bool isFavorite: false
35+ property bool hideChildren: false
36+
37+ Item {
38+ id: holder
39+ anchors.fill: parent
40+ anchors.topMargin: root.topMargin
41+
42+ UbuntuShape {
43+ id: shape
44+ anchors {
45+ left: parent.left
46+ leftMargin: units.gu(1)
47+ verticalCenter: parent.verticalCenter
48+ }
49+ width: units.gu(5)
50+ height: units.gu(5)
51+ visible: !hideChildren
52+ image: Image {
53+ id: shapeImage
54+ cache: true
55+ fillMode: Image.PreserveAspectCrop
56+ }
57+ }
58+
59+ ColumnLayout {
60+ visible: !hideChildren
61+ anchors {
62+ left: shape.right
63+ leftMargin: units.gu(1)
64+ right: starArea.right
65+ rightMargin: units.gu(1)
66+ verticalCenter: parent.verticalCenter
67+ }
68+ Label {
69+ id: titleLabel
70+ elide: Text.ElideRight
71+ wrapMode: Text.Wrap
72+ maximumLineCount: 1
73+ verticalAlignment: Text.AlignHCenter
74+ }
75+ Label {
76+ id: subtitleLabel
77+ elide: Text.ElideRight
78+ fontSize: "xx-small"
79+ wrapMode: Text.Wrap
80+ maximumLineCount: 1
81+ verticalAlignment: Text.AlignHCenter
82+ visible: text != ""
83+ }
84+ }
85+ MouseArea {
86+ id: starArea
87+ height: parent.height
88+ width: height
89+ anchors.right: parent.right
90+ onClicked: if (!editMode) root.requestFavorite(model.scopeId, !isFavorite);
91+ onPressed: if (editMode) root.handlePressed(starArea);
92+ onReleased: if (editMode) root.handleReleased(starArea);
93+ Icon {
94+ id: star
95+ anchors.centerIn: parent
96+ height: units.gu(2)
97+ width: units.gu(2)
98+ visible: !hideChildren
99+ // TODO is view-grid-symbolic what we really want here? Looks good but seems semantically wrong
100+ source: editMode ? "image://theme/view-grid-symbolic" : isFavorite ? "image://theme/starred" : "image://theme/non-starred"
101+ }
102+ }
103+ }
104+}
qml/Dash/ScopesOverview.qmldeleted−575
@@ -1,575 +0,0 @@
1−/*
2− * Copyright (C) 2014 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.0
18−import Dash 0.1
19−import Ubuntu.Components 0.1
20−import "../Components"
21−
22−Item {
23− id: root
24−
25− // Properties set by parent
26− property real progress: 0
27− property var scope: null
28− property int currentIndex: 0
29− property real scopeScale: 1
30−
31− // Properties set and used by parent
32− property alias currentTab: tabBar.currentTab
33−
34− // Properties used by parent
35− readonly property bool processing: searchResultsViewer.processing || tempScopeItem.processing || previewListView.processing
36− property bool growingDashFromPos: false
37− readonly property bool searching: scope && scope.searchQuery == ""
38− readonly property bool showingNonFavoriteScope: tempScopeItem.scope != null
39− readonly property var dashItemEater: {
40− if (!forceXYScalerEater && tabBar.currentTab == 0 && middleItems.count > 0) {
41− var loaderItem = middleItems.itemAt(0).item;
42− return loaderItem && loaderItem.currentItem ? loaderItem.currentItem : null;
43− }
44− return scopesOverviewXYScaler;
45− }
46− readonly property size allCardSize: {
47− if (middleItems.count > 1) {
48− var loaderItem = middleItems.itemAt(1).item;
49− if (loaderItem) {
50− var cardTool = loaderItem.cardTool;
51− return Qt.size(cardTool.cardWidth, cardTool.cardHeight);
52− }
53− }
54− return Qt.size(0, 0);
55− }
56−
57− // Internal properties
58− property bool forceXYScalerEater: false
59−
60− signal done()
61− signal favoriteSelected(var scopeId)
62− signal allFavoriteSelected(var scopeId)
63− signal searchSelected(var scopeId, var result, var pos, var size)
64−
65− Connections {
66− target: scope
67− onOpenScope: {
68− var itemPos = scopesOverviewXYScaler.restorePosition;
69− var itemSize = scopesOverviewXYScaler.restoreSize;
70− scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
71− if (itemPos) {
72− scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
73− scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
74− } else {
75− scopesOverviewXYScaler.x = 0;
76− scopesOverviewXYScaler.y = 0;
77− }
78− scopesOverviewXYScaler.opacity = 0;
79− tempScopeItem.scope = scope;
80− middleItems.overrideOpacity = 0;
81− scopesOverviewXYScaler.scale = 1;
82− scopesOverviewXYScaler.x = 0;
83− scopesOverviewXYScaler.y = 0;
84− scopesOverviewXYScaler.opacity = 1;
85− }
86− onGotoScope: {
87− if (tabBar.currentTab == 0) {
88− root.favoriteSelected(scopeId);
89− } else {
90− root.allFavoriteSelected(scopeId);
91− }
92− previewListView.open = false;
93− }
94− }
95−
96− Binding {
97− target: scope
98− property: "isActive"
99− value: progress === 1
100− }
101−
102− function closeTempScope() {
103− if (tempScopeItem.scope) {
104− root.scope.closeScope(tempScopeItem.scope);
105− tempScopeItem.scope = null;
106− tempScopeItem.backClicked()
107− }
108− }
109−
110− function animateDashFromAll(scopeId) {
111− var currentScopePos = allScopeCardPosition(scopeId);
112− if (currentScopePos) {
113− showDashFromPos(currentScopePos, allCardSize);
114− } else {
115− console.log("Warning: Could not find Dash OverView All card position for scope", dashContent.currentScopeId);
116− }
117− }
118−
119− function showDashFromPos(itemPos, itemSize) {
120− scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
121− scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
122− scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
123− scopesOverviewXYScaler.opacity = 0;
124− root.growingDashFromPos = true;
125− scopesOverviewXYScaler.scale = 1;
126− scopesOverviewXYScaler.x = 0;
127− scopesOverviewXYScaler.y = 0;
128− scopesOverviewXYScaler.opacity = 1;
129− }
130−
131− function allScopeCardPosition(scopeId) {
132− if (middleItems.count > 1) {
133− var loaderItem = middleItems.itemAt(1).item;
134− if (loaderItem) {
135− var pos = loaderItem.scopeCardPosition(scopeId);
136− return loaderItem.mapToItem(null, pos.x, pos.y);
137− }
138− }
139− }
140−
141− function ensureAllScopeVisible(scopeId) {
142− if (middleItems.count > 1) {
143− var loaderItem = middleItems.itemAt(1).item;
144− if (loaderItem) {
145− var pos = loaderItem.scopeCardPosition(scopeId);
146− loaderItem.contentY = Math.min(pos.y, loaderItem.contentHeight - loaderItem.height);
147− }
148− }
149− }
150−
151− onProgressChanged: {
152− if (progress == 0) {
153− pageHeader.resetSearch();
154− pageHeader.unfocus(); // Shouldn't the previous call do this too?
155− }
156− }
157−
158− ScopeStyle {
159− id: overviewScopeStyle
160− style: { "foreground-color" : "white",
161− "background-color" : "transparent",
162− "page-header": {
163− "background": "color:///transparent"
164− }
165− }
166− }
167−
168− DashBackground {
169− anchors.fill: parent
170− source: "graphics/dark_background.jpg"
171− }
172−
173− Connections {
174− target: pageHeader
175− onSearchQueryChanged: {
176− // Need this in order, otherwise something gets unhappy in rendering
177− // of the overlay in carousels because the parent of the dash dies for
178− // a moment, this way we make sure it's reparented first
179− // by forceXYScalerEater making dashItemEater return scopesOverviewXYScaler
180− // before we kill the previous parent by scope.searchQuery
181− root.forceXYScalerEater = true;
182− root.scope.searchQuery = pageHeader.searchQuery;
183− root.forceXYScalerEater = false;
184− }
185− }
186−
187− Binding {
188− target: pageHeader
189− property: "searchQuery"
190− value: scope ? scope.searchQuery : ""
191− }
192−
193− Item {
194− id: scopesOverviewContent
195− x: previewListView.open ? -width : 0
196− Behavior on x { UbuntuNumberAnimation { } }
197− width: parent.width
198− height: parent.height
199−
200− PageHeader {
201− id: pageHeader
202− objectName: "scopesOverviewPageHeader"
203−
204− readonly property real yDisplacement: pageHeader.height + tabBar.height + tabBar.anchors.margins
205−
206− y: {
207− if (root.progress < 0.5) {
208− return -yDisplacement;
209− } else {
210− return -yDisplacement + (root.progress - 0.5) * yDisplacement * 2;
211− }
212− }
213− width: parent.width
214− clip: true
215− title: i18n.tr("Manage Scopes")
216− scopeStyle: overviewScopeStyle
217− showSignatureLine: false
218− searchEntryEnabled: true
219− }
220−
221− ScopesOverviewTab {
222− id: tabBar
223− anchors {
224− left: parent.left
225− right: parent.right
226− top: pageHeader.bottom
227− margins: units.gu(2)
228− }
229− height: units.gu(4)
230−
231− enabled: opacity == 1
232− opacity: !scope || scope.searchQuery == "" ? 1 : 0
233− Behavior on opacity { UbuntuNumberAnimation { } }
234− }
235−
236− Repeater {
237− id: middleItems
238− objectName: "scopesOverviewRepeater"
239− property real overrideOpacity: -1
240− model: scope && scope.searchQuery == "" ? scope.categories : null
241− delegate: Loader {
242− id: loader
243− objectName: "scopesOverviewRepeaterChild" + index
244−
245− height: {
246− if (index == 0) {
247− return root.height;
248− } else {
249− return root.height - pageHeader.height - tabBar.height - tabBar.anchors.margins - units.gu(2);
250− }
251− }
252− width: {
253− if (index == 0) {
254− return root.width / scopeScale;
255− } else {
256− return root.width;
257− }
258− }
259− x: {
260− if (index == 0) {
261− return (root.width - width) / 2;
262− } else {
263− return 0;
264− }
265− }
266− anchors {
267− bottom: scopesOverviewContent.bottom
268− }
269−
270− scale: index == 0 ? scopeScale : 1
271−
272− opacity: {
273− if (middleItems.overrideOpacity >= 0)
274− return middleItems.overrideOpacity;
275−
276− if (tabBar.currentTab != index)
277− return 0;
278−
279− return index == 0 ? 1 : root.progress;
280− }
281− Behavior on opacity {
282− enabled: root.progress == 1
283− UbuntuNumberAnimation { }
284− }
285− enabled: opacity == 1
286−
287− clip: index == 1
288−
289− CardTool {
290− id: cardTool
291− objectName: "cardTool"
292− count: results.count
293− template: model.renderer
294− components: model.components
295− viewWidth: parent.width
296− }
297−
298− source: {
299− if (index == 0 && categoryId == "favorites") return "ScopesOverviewFavorites.qml";
300− else if (index == 1 && categoryId == "all") return "ScopesOverviewAll.qml";
301− else return "";
302− }
303−
304− onLoaded: {
305− item.model = Qt.binding(function() { return results; });
306− item.cardTool = cardTool;
307− if (index == 0) {
308− item.scopeWidth = Qt.binding(function() { return root.width; });
309− item.scopeHeight = Qt.binding(function() { return root.height; });
310− item.appliedScale = Qt.binding(function() { return loader.scale });
311− item.currentIndex = Qt.binding(function() { return root.currentIndex });
312− } else if (index == 1) {
313− item.extraHeight = bottomBar.height;
314− }
315− }
316−
317− Connections {
318− target: loader.item
319− onClicked: {
320− pageHeader.unfocus();
321− if (tabBar.currentTab == 0) {
322− root.favoriteSelected(itemModel.scopeId);
323− } else {
324− var favoriteScopesItem = middleItems.itemAt(0).item;
325− var scopeIndex = favoriteScopesItem.model.scopeIndex(itemModel.scopeId);
326− if (scopeIndex >= 0) {
327− root.allFavoriteSelected(itemModel.scopeId);
328− } else {
329− // Will result in an openScope from root.scope
330− scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
331− scopesOverviewXYScaler.restoreSize = allCardSize;
332− root.scope.activate(result);
333− }
334− }
335− }
336− onPressAndHold: {
337− // Preview can call openScope so make sure restorePosition and restoreSize are set
338− scopesOverviewXYScaler.restorePosition = undefined;
339− scopesOverviewXYScaler.restoreSize = allCardSize;
340−
341− previewListView.model = target.model;
342− previewListView.currentIndex = -1;
343− previewListView.currentIndex = index;
344− previewListView.open = true;
345− }
346− }
347− }
348− }
349−
350− GenericScopeView {
351− id: searchResultsViewer
352− objectName: "searchResultsViewer"
353− anchors {
354− top: pageHeader.bottom
355− right: parent.right
356− left: parent.left
357− bottom: parent.bottom
358− }
359− scope: root.scope && root.scope.searchQuery != "" ? root.scope : null
360− scopeStyle: overviewScopeStyle
361− enabled: opacity == 1
362− showPageHeader: false
363− clip: true
364− opacity: searchResultsViewer.scope ? 1 : 0
365− isCurrent: true
366− Behavior on opacity { UbuntuNumberAnimation { } }
367−
368− function itemClicked(index, result, item, itemModel, resultsModel, limitedCategoryItemCount) {
369− pageHeader.unfocus();
370− pageHeader.closePopup();
371− if (itemModel.scopeId) {
372− // This can end up in openScope so save restorePosition and restoreSize
373− scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
374− scopesOverviewXYScaler.restoreSize = Qt.size(item.width, item.height);
375− root.searchSelected(itemModel.scopeId, result, item.mapToItem(null, 0, 0), Qt.size(item.width, item.height));
376− } else {
377− // Not a scope, just activate it
378− searchResultsViewer.scope.activate(result);
379− }
380− }
381−
382− function itemPressedAndHeld(index, itemModel, resultsModel, limitedCategoryItemCount) {
383− if (itemModel.uri.indexOf("scope://") === 0) {
384− // Preview can call openScope so make sure restorePosition and restoreSize are set
385− scopesOverviewXYScaler.restorePosition = undefined;
386− scopesOverviewXYScaler.restoreSize = allCardSize;
387−
388− previewListView.model = resultsModel;
389− previewListView.currentIndex = -1;
390− previewListView.currentIndex = index;
391− previewListView.open = true;
392− }
393− }
394− }
395−
396− Rectangle {
397− id: bottomBar
398− objectName: "bottomBar"
399− color: "black"
400− height: units.gu(8)
401− width: parent.width
402− enabled: opacity == 1
403− opacity: scope && scope.searchQuery == "" ? 1 : 0
404− Behavior on opacity { UbuntuNumberAnimation { } }
405− y: {
406− if (root.progress < 0.5) {
407− return parent.height;
408− } else {
409− return parent.height - (root.progress - 0.5) * height * 2;
410− }
411− }
412−
413− MouseArea {
414− // Just eat any other press since this parent is black opaque
415− anchors.fill: parent
416− }
417−
418− AbstractButton {
419− objectName: "scopesOverviewDoneButton"
420− width: Math.max(label.width + units.gu(2), units.gu(10))
421− height: units.gu(4)
422− anchors {
423− left: parent.left
424− leftMargin: units.gu(2)
425− verticalCenter: parent.verticalCenter
426− }
427− Rectangle {
428− anchors.fill: parent
429− border.color: "white"
430− border.width: units.dp(1)
431− radius: units.dp(10)
432− color: parent.pressed ? Theme.palette.normal.baseText : "transparent"
433− }
434− Label {
435− id: label
436− anchors.centerIn: parent
437− text: i18n.tr("Done")
438− color: parent.pressed ? "black" : "white"
439− }
440− onClicked: root.done();
441− }
442−
443− AbstractButton {
444− objectName: "scopesOverviewStoreButton"
445− width: Math.max(storeLabel.width, units.gu(10))
446− height: units.gu(4)
447− anchors {
448− right: parent.right
449− verticalCenter: parent.verticalCenter
450− }
451− Icon {
452− id: storeImage
453− name: "ubuntu-store-symbolic"
454− color: "white"
455− anchors.horizontalCenter: parent.horizontalCenter
456− width: units.gu(2)
457− height: units.gu(2)
458− }
459− Label {
460− id: storeLabel
461− anchors.horizontalCenter: parent.horizontalCenter
462− anchors.top: storeImage.bottom
463− text: i18n.tr("Store")
464− color: "white"
465− }
466− onClicked: {
467− // Just zoom from the middle
468− scopesOverviewXYScaler.restorePosition = undefined;
469− scopesOverviewXYScaler.restoreSize = allCardSize;
470− scope.performQuery("scope://com.canonical.scopes.clickstore");
471− }
472− }
473− }
474− }
475−
476− PreviewListView {
477− id: previewListView
478− objectName: "scopesOverviewPreviewListView"
479− scope: root.scope
480− scopeStyle: overviewScopeStyle
481− showSignatureLine: false
482− visible: x != width
483− width: parent.width
484− height: parent.height
485− anchors.left: scopesOverviewContent.right
486−
487− onBackClicked: open = false
488− }
489−
490− Item {
491− id: scopesOverviewXYScaler
492− width: parent.width
493− height: parent.height
494−
495− clip: scale != 1.0
496− enabled: scale == 1
497−
498− property bool animationsEnabled: root.showingNonFavoriteScope || root.growingDashFromPos
499−
500− property var restorePosition
501− property var restoreSize
502−
503− Behavior on x {
504− enabled: scopesOverviewXYScaler.animationsEnabled
505− UbuntuNumberAnimation { }
506− }
507− Behavior on y {
508− enabled: scopesOverviewXYScaler.animationsEnabled
509− UbuntuNumberAnimation { }
510− }
511− Behavior on opacity {
512− enabled: scopesOverviewXYScaler.animationsEnabled
513− UbuntuNumberAnimation { }
514− }
515− Behavior on scale {
516− enabled: scopesOverviewXYScaler.animationsEnabled
517− UbuntuNumberAnimation {
518− onRunningChanged: {
519− if (!running) {
520− if (root.showingNonFavoriteScope && scopesOverviewXYScaler.scale != 1) {
521− root.scope.closeScope(tempScopeItem.scope);
522− tempScopeItem.scope = null;
523− } else if (root.growingDashFromPos) {
524− root.growingDashFromPos = false;
525− }
526− }
527− }
528− }
529− }
530−
531− DashBackground {
532− anchors.fill: tempScopeItem
533− visible: tempScopeItem.visible
534− parent: tempScopeItem.parent
535− }
536−
537− GenericScopeView {
538− id: tempScopeItem
539− objectName: "scopesOverviewTempScopeItem"
540−
541− width: parent.width
542− height: parent.height
543− clip: scale != 1.0
544− visible: scope != null
545− hasBackAction: true
546− isCurrent: visible
547− onBackClicked: {
548− var v = scopesOverviewXYScaler.restoreSize.width / tempScopeItem.width;
549− scopesOverviewXYScaler.scale = v;
550− if (scopesOverviewXYScaler.restorePosition) {
551− scopesOverviewXYScaler.x = scopesOverviewXYScaler.restorePosition.x -(tempScopeItem.width - tempScopeItem.width * v) / 2;
552− scopesOverviewXYScaler.y = scopesOverviewXYScaler.restorePosition.y -(tempScopeItem.height - tempScopeItem.height * v) / 2;
553− } else {
554− scopesOverviewXYScaler.x = 0;
555− scopesOverviewXYScaler.y = 0;
556− }
557− scopesOverviewXYScaler.opacity = 0;
558− middleItems.overrideOpacity = -1;
559− }
560− // TODO Add tests for these connections
561− Connections {
562− target: tempScopeItem.scope
563− onOpenScope: {
564− // TODO Animate the newly opened scope into the foreground (stacked on top of the current scope)
565− tempScopeItem.scope = scope;
566− }
567− onGotoScope: {
568− tempScopeItem.backClicked();
569− root.currentTab = 0;
570− root.scope.gotoScope(scopeId);
571− }
572− }
573− }
574− }
575−}
qml/Dash/ScopesOverviewAll.qmldeleted−54
@@ -1,54 +0,0 @@
1−/*
2− * Copyright (C) 2014 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.0
18−import Ubuntu.Components 0.1
19−
20−Flickable {
21− id: root
22−
23− property alias model: cardGrid.model
24− property alias cardTool: cardGrid.cardTool
25−
26− property real extraHeight: 0
27−
28− signal clicked(int index, var result, var item, var itemModel)
29− signal pressAndHold(int index)
30−
31− contentHeight: cardGrid.expandedHeight + extraHeight
32− contentWidth: cardGrid.width
33− flickableDirection: Flickable.VerticalFlick
34−
35− function scopeCardPosition(scopeId) {
36− var index = model.scopeIndex(scopeId);
37− var pos = cardGrid.cardPosition(index);
38− pos.y = pos.y - root.contentY;
39− return pos;
40− }
41−
42− CardGrid {
43− id: cardGrid
44− width: root.width
45− height: parent.height
46−
47− onClicked: {
48− root.clicked(index, result, item, itemModel);
49− }
50− onPressAndHold: {
51− root.pressAndHold(index);
52− }
53− }
54−}
qml/Dash/ScopesOverviewFavorites.qmldeleted−73
@@ -1,73 +0,0 @@
1−/*
2− * Copyright (C) 2014 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.0
18−
19−Flickable {
20− id: root
21−
22− signal clicked(int index, var result, var itemModel)
23− signal pressAndHold(int index)
24−
25− property var cardTool: null
26− property real scopeHeight: 0
27− property real scopeWidth: 0
28− property real appliedScale: 1
29− property int currentIndex: -1
30− property var currentItem: currentIndex < repeater.count ? repeater.itemAt(currentIndex) : null
31−
32− property alias model: repeater.model
33−
34− contentHeight: height
35− contentWidth: repeater.count * root.scopeWidth + units.gu(2) / appliedScale * (repeater.count - 1)
36−
37− contentX: {
38− var indexX = currentIndex * scopeWidth + units.gu(2) / appliedScale * currentIndex;
39− var newContentX = indexX - (width - scopeWidth) / 2;
40− newContentX = Math.min(Math.max(newContentX, 0), contentWidth - width);
41− return newContentX;
42− }
43−
44− Repeater {
45− id: repeater
46− objectName: "scopesOverviewFavoritesRepeater"
47−
48− delegate: Loader {
49− id: loader
50−
51− x: index * root.scopeWidth + units.gu(2) / appliedScale * index
52− asynchronous: true
53−
54− sourceComponent: cardTool.cardComponent
55− onLoaded: {
56− item.fixedArtShapeSize = Qt.binding(function() { return Qt.size(root.scopeWidth, root.scopeHeight); });
57− item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight / appliedScale; });
58− item.fontScale = Qt.binding(function() { return 1 / appliedScale; });
59− item.height = Qt.binding(function() { return root.scopeHeight; });
60− item.width = Qt.binding(function() { return root.scopeWidth; });
61− item.cardData = Qt.binding(function() { return model; });
62− item.template = Qt.binding(function() { return cardTool.template; });
63− item.components = Qt.binding(function() { return cardTool.components; });
64− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
65− }
66−
67− Connections {
68− target: loader.item
69− onClicked: root.clicked(index, result, model)
70− }
71− }
72− }
73−}
qml/Dash/ScopesOverviewTab.qmldeleted−74
@@ -1,74 +0,0 @@
1−/*
2− * Copyright (C) 2014 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.3
18−import Ubuntu.Components 0.1
19−
20−Item {
21− id: root
22−
23− property int currentTab: 0
24−
25− AbstractButton {
26− id: tab1
27− height: parent.height
28− width: parent.width / 2
29− Rectangle {
30− anchors.fill: parent
31− color: root.currentTab == 0 && root.enabled ? "white" : "transparent"
32− radius: units.dp(10)
33− }
34− Label {
35− anchors.centerIn: parent
36− text: i18n.tr("Favorites")
37− color: root.currentTab == 0 && root.enabled ? "black" : "white"
38− }
39− onClicked: root.currentTab = 0
40− }
41− AbstractButton {
42− id: tab2
43− objectName: "scopesOverviewAllTabButton"
44− x: width
45− height: parent.height
46− width: parent.width / 2
47− Rectangle {
48− anchors.fill: parent
49− color: root.currentTab == 1 && root.enabled ? "white" : "transparent"
50− radius: units.dp(10)
51− }
52− Label {
53− anchors.centerIn: parent
54− text: i18n.tr("All")
55− color: root.currentTab == 1 && root.enabled ? "black" : "white"
56− }
57− onClicked: root.currentTab = 1
58− }
59− Rectangle {
60− id: centerPiece
61− width: root.enabled ? units.dp(10) : units.dp(1)
62− height: parent.height
63− color: "white"
64− x: root.currentTab == 1 ? tab2.x : tab2.x - width
65− }
66− Rectangle {
67− id: border
68− anchors.fill: parent
69− radius: units.dp(10)
70− color: "transparent"
71− border.color: centerPiece.color
72− border.width: units.dp(1)
73− }
74−}
tests/mocks/Unity/CMakeLists.txt+1−1
@@ -6,7 +6,7 @@ add_subdirectory(Notifications)
66add_subdirectory(DashCommunicator)
77
88pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
9−pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=4)
9+pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=5)
1010
1111include_directories(
1212 ${CMAKE_CURRENT_BINARY_DIR}
tests/mocks/Unity/fake_scopes.cpp+57−3
@@ -165,14 +165,68 @@ unity::shell::scopes::ScopeInterface* Scopes::overviewScope() const
165165 return m_scopesOverview;
166166}
167167
168−QList<Scope*> Scopes::scopes() const
168+void Scopes::setFavorite(const QString& scopeId, bool favorite)
169+{
170+ if (favorite) {
171+ for (Scope *scope : m_scopes) {
172+ // Check it's not already there
173+ Q_ASSERT(scope->id() != scopeId);
174+ }
175+ for (Scope *scope : m_allScopes) {
176+ if (scope->id() == scopeId) {
177+ const int index = rowCount();
178+ beginInsertRows(QModelIndex(), index, index);
179+ m_scopes << scope;
180+ endInsertRows();
181+ m_scopesOverview->setFavorite(scope, true);
182+ return;
183+ }
184+ }
185+ Q_ASSERT(false && "Unknown scopeId");
186+ } else {
187+ for (Scope *scope : m_scopes) {
188+ if (scope->id() == scopeId) {
189+ const int index = m_scopes.indexOf(scope);
190+ beginRemoveRows(QModelIndex(), index, index);
191+ m_scopes.removeAt(index);
192+ endRemoveRows();
193+ m_scopesOverview->setFavorite(scope, false);
194+ return;
195+ }
196+ }
197+ Q_ASSERT(false && "Unknown scopeId");
198+ }
199+}
200+
201+void Scopes::moveFavoriteTo(const QString& scopeId, int to)
202+{
203+ int from = -1;
204+ for (int i = 0; i < m_scopes.count(); ++i) {
205+ if (m_scopes[i]->id() == scopeId) {
206+ from = i;
207+ break;
208+ }
209+ }
210+ Q_ASSERT(from != -1);
211+ beginMoveRows(QModelIndex(), from, from, QModelIndex(), to + (to > from ? 1 : 0));
212+ m_scopes.move(from, to);
213+ endMoveRows();
214+ m_scopesOverview->moveFavoriteTo(m_scopes[to], to);
215+}
216+
217+QList<Scope*> Scopes::favScopes() const
169218{
170219 return m_scopes;
171220}
172221
173−QList<Scope*> Scopes::allScopes() const
222+QList<Scope*> Scopes::nonFavScopes() const
174223{
175− return m_allScopes;
224+ QList<Scope*> res;
225+ for (Scope *scope : m_allScopes) {
226+ if (!m_scopes.contains(scope))
227+ res << scope;
228+ }
229+ return res;
176230}
177231
178232void Scopes::addScope(Scope* scope)
tests/mocks/Unity/fake_scopes.h+8−3
@@ -28,6 +28,8 @@
2828#include <QList>
2929#include <QTimer>
3030
31+class ScopesOverview;
32+
3133class Scopes : public unity::shell::scopes::ScopesInterface
3234{
3335 Q_OBJECT
@@ -53,9 +55,12 @@ public:
5355 int count() const override;
5456 unity::shell::scopes::ScopeInterface* overviewScope() const override;
5557
58+ Q_INVOKABLE void setFavorite(const QString& scopeId, bool favorite) override;
59+ Q_INVOKABLE void moveFavoriteTo(const QString& scopeId, int index) override;
60+
5661 // This is used as part of implementation of the other C++ code, not API
57− QList<Scope*> scopes() const;
58− QList<Scope*> allScopes() const;
62+ QList<Scope*> favScopes() const;
63+ QList<Scope*> nonFavScopes() const;
5964 Q_INVOKABLE Scope* getScopeFromAll(const QString& scope_id) const;
6065
6166private Q_SLOTS:
@@ -64,7 +69,7 @@ private Q_SLOTS:
6469private:
6570 QList<Scope*> m_scopes; // the favorite ones
6671 QList<Scope*> m_allScopes;
67− Scope *m_scopesOverview;
72+ ScopesOverview *m_scopesOverview;
6873 bool m_loaded;
6974 QTimer timer;
7075};
tests/mocks/Unity/fake_scopesoverview.cpp+77−11
@@ -44,8 +44,22 @@ void ScopesOverview::setSearchQuery(const QString& search_query)
4444Q_INVOKABLE void ScopesOverview::activate(QVariant const& result)
4545{
4646 Scopes *scopes = dynamic_cast<Scopes*>(parent());
47− m_openScope = scopes->getScopeFromAll(result.toString());
48− Q_EMIT openScope(m_openScope);
47+ if (scopes->getScope(result.toString())) {
48+ Q_EMIT gotoScope(result.toString());
49+ } else {
50+ m_openScope = scopes->getScopeFromAll(result.toString());
51+ Q_EMIT openScope(m_openScope);
52+ }
53+}
54+
55+void ScopesOverview::setFavorite(Scope *scope, bool favorite)
56+{
57+ m_scopesOverviewCategories->setFavorite(scope, favorite);
58+}
59+
60+void ScopesOverview::moveFavoriteTo(Scope *scope, int index)
61+{
62+ m_scopesOverviewCategories->moveFavoriteTo(scope, index);
4963}
5064
5165ScopesOverviewCategories::ScopesOverviewCategories(Scopes *scopes, QObject* parent)
@@ -69,19 +83,41 @@ bool ScopesOverviewCategories::overrideCategoryJson(QString const& /* categoryId
6983 qFatal("Using un-implemented ScopesOverviewCategories::overrideCategoryJson");
7084}
7185
72−QVariant
73−ScopesOverviewCategories::data(const QModelIndex& index, int role) const
86+void ScopesOverviewCategories::setFavorite(Scope *scope, bool favorite)
87+{
88+ if (m_resultsModels.value(0)) {
89+ if (favorite) {
90+ m_resultsModels[0]->appendScope(scope);
91+ } else {
92+ m_resultsModels[0]->removeScope(scope);
93+ }
94+ }
95+ if (m_resultsModels.value(1)) {
96+ if (favorite) {
97+ m_resultsModels[1]->removeScope(scope);
98+ } else {
99+ m_resultsModels[1]->appendScope(scope);
100+ }
101+ }
102+}
103+
104+void ScopesOverviewCategories::moveFavoriteTo(Scope *scope, int index)
105+{
106+ m_resultsModels[0]->moveScopeTo(scope, index);
107+}
108+
109+QVariant ScopesOverviewCategories::data(const QModelIndex& index, int role) const
74110{
75111 if (!index.isValid()) {
76112 return QVariant();
77113 }
78114
79− const QString categoryId = index.row() == 0 ? "favorites" : "all";
115+ const QString categoryId = index.row() == 0 ? "favorites" : "other";
80116
81− unity::shell::scopes::ResultsModelInterface *resultsModel = m_resultsModels[index.row()];
117+ ScopesOverviewResultsModel *resultsModel = m_resultsModels[index.row()];
82118 if (!resultsModel) {
83119 QObject *that = const_cast<ScopesOverviewCategories*>(this);
84− QList<Scope*> scopes = index.row() == 0 ? m_scopes->scopes() : m_scopes->allScopes();
120+ QList<Scope*> scopes = index.row() == 0 ? m_scopes->favScopes() : m_scopes->nonFavScopes();
85121 resultsModel = new ScopesOverviewResultsModel(scopes, categoryId, that);
86122 m_resultsModels[index.row()] = resultsModel;
87123 }
@@ -89,7 +125,7 @@ ScopesOverviewCategories::data(const QModelIndex& index, int role) const
89125 case RoleCategoryId:
90126 return categoryId;
91127 case RoleName:
92− return index.row() == 0 ? "Favorites" : "All";
128+ return index.row() == 0 ? "Favorites" : "Non Favorites";
93129 case RoleIcon:
94130 return QVariant();
95131 case RoleRawRendererTemplate:
@@ -157,7 +193,7 @@ ScopesOverviewSearchCategories::data(const QModelIndex& index, int role) const
157193
158194 const QString categoryId = index.row() == 0 ? "searchA" : "searchB";
159195
160− unity::shell::scopes::ResultsModelInterface *resultsModel = m_resultsModels[index.row()];
196+ ScopesOverviewResultsModel *resultsModel = m_resultsModels[index.row()];
161197 if (!resultsModel) {
162198 QObject *that = const_cast<ScopesOverviewSearchCategories*>(this);
163199 QList<Scope *> scopes;
@@ -256,8 +292,7 @@ int ScopesOverviewResultsModel::count() const
256292 return rowCount();
257293}
258294
259−QVariant
260−ScopesOverviewResultsModel::data(const QModelIndex& index, int role) const
295+QVariant ScopesOverviewResultsModel::data(const QModelIndex& index, int role) const
261296{
262297 unity::shell::scopes::ScopeInterface *scope = m_scopes[index.row()];
263298 switch (role) {
@@ -269,6 +304,8 @@ ScopesOverviewResultsModel::data(const QModelIndex& index, int role) const
269304 return scope ? scope->id() : QString("Result.%1.%2").arg(categoryId()).arg(index.row());
270305 case RoleTitle:
271306 return scope ? scope->name() : QString("Title.%1.%2").arg(categoryId()).arg(index.row());
307+ case RoleSubtitle:
308+ return scope && scope->name() == "Videos" ? "tube, movies, cinema" : QString();
272309 case RoleArt:
273310 return qmlDirectory() + "graphics/applicationIcons/dash.png";
274311 case RoleMascot:
@@ -281,3 +318,32 @@ ScopesOverviewResultsModel::data(const QModelIndex& index, int role) const
281318 return QVariant();
282319 }
283320}
321+
322+void ScopesOverviewResultsModel::appendScope(Scope *scope)
323+{
324+ Q_ASSERT(!m_scopes.contains(scope));
325+ const int index = rowCount();
326+ beginInsertRows(QModelIndex(), index, index);
327+ m_scopes << scope;
328+ endInsertRows();
329+ Q_EMIT countChanged();
330+}
331+
332+void ScopesOverviewResultsModel::removeScope(Scope *scope)
333+{
334+ const int index = m_scopes.indexOf(scope);
335+ Q_ASSERT(index != -1);
336+ beginRemoveRows(QModelIndex(), index, index);
337+ m_scopes.removeAt(index);
338+ endRemoveRows();
339+ Q_EMIT countChanged();
340+}
341+
342+void ScopesOverviewResultsModel::moveScopeTo(Scope *scope, int to)
343+{
344+ const int from = m_scopes.indexOf(scope);
345+ Q_ASSERT(from!= -1);
346+ beginMoveRows(QModelIndex(), from, from, QModelIndex(), to + (to > from ? 1 : 0));
347+ m_scopes.move(from, to);
348+ endMoveRows();
349+}
tests/mocks/Unity/fake_scopesoverview.h+18−3
@@ -21,6 +21,8 @@
2121#include <unity/shell/scopes/ResultsModelInterface.h>
2222
2323class Scopes;
24+class ScopesOverviewCategories;
25+class ScopesOverviewResultsModel;
2426
2527class ScopesOverview : public Scope
2628{
@@ -32,8 +34,12 @@ public:
3234 void setSearchQuery(const QString& search_query) override;
3335 Q_INVOKABLE void activate(QVariant const& result) override;
3436
37+ // This is implementation detail
38+ void setFavorite(Scope *scope, bool favorite);
39+ void moveFavoriteTo(Scope *scope, int index);
40+
3541private:
36− unity::shell::scopes::CategoriesInterface *m_scopesOverviewCategories;
42+ ScopesOverviewCategories *m_scopesOverviewCategories;
3743 unity::shell::scopes::CategoriesInterface *m_searchCategories;
3844};
3945
@@ -50,8 +56,12 @@ public:
5056 Q_INVOKABLE void addSpecialCategory(QString const& categoryId, QString const& name, QString const& icon, QString const& rawTemplate, QObject* countObject) override;
5157 Q_INVOKABLE bool overrideCategoryJson(QString const& categoryId, QString const& json) override;
5258
59+ // This is implementation detail
60+ void setFavorite(Scope *scope, bool favorite);
61+ void moveFavoriteTo(Scope *scope, int index);
62+
5363private:
54− mutable QHash<int, unity::shell::scopes::ResultsModelInterface*> m_resultsModels;
64+ mutable QHash<int, ScopesOverviewResultsModel*> m_resultsModels;
5565
5666 Scopes *m_scopes;
5767};
@@ -70,7 +80,7 @@ public:
7080 Q_INVOKABLE bool overrideCategoryJson(QString const& categoryId, QString const& json) override;
7181
7282private:
73− mutable QHash<int, unity::shell::scopes::ResultsModelInterface*> m_resultsModels;
83+ mutable QHash<int, ScopesOverviewResultsModel*> m_resultsModels;
7484
7585 Scopes *m_scopes;
7686};
@@ -96,6 +106,11 @@ public:
96106 Q_INVOKABLE int scopeIndex(QString const& id) const;
97107 QHash<int, QByteArray> roleNames() const override;
98108
109+ // This is implementation detail
110+ void appendScope(Scope *scope);
111+ void removeScope(Scope *scope);
112+ void moveScopeTo(Scope *scope, int index);
113+
99114private:
100115 QList<Scope *> m_scopes;
101116 QString m_categoryId;
tests/qmltests/Dash/tst_Dash.qml+68−140
@@ -79,175 +79,108 @@ Item {
7979 return get_scope_data()
8080 }
8181
82− function test_dash_overview_show_select_same_favorite() {
83− // Show the overview
84− touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
85− var overviewController = findInvisibleChild(dash, "overviewController");
86− tryCompare(overviewController, "progress", 1);
87−
88− // Make sure tab is where it should
89− var scopesOverview = findChild(dash, "scopesOverview");
90− compare(scopesOverview.currentTab, 0);
82+ function test_manage_dash_select_same_favorite() {
83+ // Show the manage dash
84+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
85+ var bottomEdgeController = findInvisibleChild(dash, "bottomEdgeController");
86+ tryCompare(bottomEdgeController, "progress", 1);
9187
9288 // Make sure stuff is loaded
93− var scopesOverviewFavoritesRepeater = findChild(dash, "scopesOverviewFavoritesRepeater");
94− tryCompare(scopesOverviewFavoritesRepeater, "count", 6);
95− tryCompareFunction(function() { return scopesOverviewFavoritesRepeater.itemAt(0).item != null; }, true);
96− waitForRendering(scopesOverviewFavoritesRepeater.itemAt(0).item);
89+ var favScopesListCategory = findChild(dash, "scopesListCategoryfavorites");
90+ var favScopesListCategoryList = findChild(favScopesListCategory, "scopesListCategoryInnerList");
91+ tryCompare(favScopesListCategoryList, "currentIndex", 0);
9792
9893 // Click in first item
99− mouseClick(scopesOverviewFavoritesRepeater.itemAt(0).item, 0, 0);
94+ mouseClick(favScopesListCategoryList.currentItem, 0, 0);
10095
10196 // Make sure animation went back
102− tryCompare(overviewController, "progress", 0);
97+ tryCompare(bottomEdgeController, "progress", 0);
10398
10499 var dashContentList = findChild(dash, "dashContentList");
105100 compare(dashContentList.currentIndex, 0);
106101 }
107102
108− function test_dash_overview_show_select_different_favorite() {
109− // Show the overview
110− touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
111− var overviewController = findInvisibleChild(dash, "overviewController");
112− tryCompare(overviewController, "progress", 1);
113−
114− // Make sure tab is where it should
115− var scopesOverview = findChild(dash, "scopesOverview");
116− compare(scopesOverview.currentTab, 0);
103+ function test_manage_dash_select_different_favorite() {
104+ // Show the manage dash
105+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
106+ var bottomEdgeController = findInvisibleChild(dash, "bottomEdgeController");
107+ tryCompare(bottomEdgeController, "progress", 1);
117108
118109 // Make sure stuff is loaded
119− var scopesOverviewFavoritesRepeater = findChild(dash, "scopesOverviewFavoritesRepeater");
120− tryCompare(scopesOverviewFavoritesRepeater, "count", 6);
121− tryCompareFunction(function() { return scopesOverviewFavoritesRepeater.itemAt(0).item != null; }, true);
122− waitForRendering(scopesOverviewFavoritesRepeater.itemAt(1).item);
110+ var favScopesListCategory = findChild(dash, "scopesListCategoryfavorites");
111+ var favScopesListCategoryList = findChild(favScopesListCategory, "scopesListCategoryInnerList");
112+ tryCompare(favScopesListCategoryList, "currentIndex", 0);
123113
124− // Click in first item
125− mouseClick(scopesOverviewFavoritesRepeater.itemAt(1).item, 0, 0);
114+ // Click in second item
115+ favScopesListCategoryList.currentIndex = 1;
116+ mouseClick(favScopesListCategoryList.currentItem, 0, 0);
126117
127118 // Make sure animation went back
128− tryCompare(overviewController, "progress", 0);
119+ tryCompare(bottomEdgeController, "progress", 0);
129120 var dashContentList = findChild(dash, "dashContentList");
130121 compare(dashContentList.currentIndex, 1);
131122 }
132123
133− function test_dash_overview_all_temp_scope_done_from_all() {
134− // Show the overview
135− touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
136− var overviewController = findInvisibleChild(dash, "overviewController");
137− tryCompare(overviewController, "progress", 1);
138−
139− // Make sure tab is where it should
140− var scopesOverview = findChild(dash, "scopesOverview");
141− compare(scopesOverview.currentTab, 0);
124+ function test_manage_dash_select_non_favorite() {
125+ // Show the manage dash
126+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
127+ var bottomEdgeController = findInvisibleChild(dash, "bottomEdgeController");
128+ tryCompare(bottomEdgeController, "progress", 1);
142129
143130 // Make sure stuff is loaded
144− var scopesOverviewFavoritesRepeater = findChild(dash, "scopesOverviewFavoritesRepeater");
145− tryCompare(scopesOverviewFavoritesRepeater, "count", 6);
146− tryCompareFunction(function() { return scopesOverviewFavoritesRepeater.itemAt(0).item != null; }, true);
147− waitForRendering(scopesOverviewFavoritesRepeater.itemAt(1).item);
148−
149− // Click on the all tab
150− var scopesOverviewAllTabButton = findChild(dash, "scopesOverviewAllTabButton");
151− mouseClick(scopesOverviewAllTabButton, 0, 0);
152−
153− // Wait for all tab to be enabled (animation finish)
154− var scopesOverviewAllView = findChild(dash, "scopesOverviewRepeaterChild1");
155− tryCompare(scopesOverviewAllView, "enabled", true);
156−
157− // Click in the middle of the black bar (nothing happens)
158− var bottomBar = findChild(scopesOverview, "bottomBar");
159− mouseClick(bottomBar, bottomBar.width / 2, bottomBar.height / 2);
160− // Check temp scope is not there
161− var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
162− expectFailContinue("", "Clicking in the middle of bottom bar should not open a temp scope");
163− tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope != null; }, true);
164−
165− // Click on a temp scope
166− var tempScopeCard = findChild(scopesOverviewAllView, "delegate1");
167− mouseClick(tempScopeCard, 0, 0);
131+ var nonfavScopesListCategory = findChild(dash, "scopesListCategoryother");
132+ var nonfavScopesListCategoryList = findChild(nonfavScopesListCategory, "scopesListCategoryInnerList");
133+ tryCompare(nonfavScopesListCategoryList, "currentIndex", 0);
168134
169− // Check the bottom edge (overview) is disabled from temp scope
135+ // Click on a non favorite scope
136+ mouseClick(nonfavScopesListCategoryList.currentItem, 0, 0);
137+
138+ // Check the bottom edge (manage dash) is disabled from temp scope
170139 var overviewDragHandle = findChild(dash, "overviewDragHandle");
171140 compare(overviewDragHandle.enabled, false);
172141
173142 // Check temp scope is there
174− tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope != null; }, true);
175− tryCompare(scopesOverviewTempScopeItem, "enabled", true);
143+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
144+ tryCompare(dashTempScopeItem, "x", 0);
145+ tryCompare(dashTempScopeItem, "visible", true);
146+
147+ // Check the manage dash is gone
148+ tryCompare(bottomEdgeController, "progress", 0);
176149
177150 // Go back
178− var scopesOverviewTempScopeItemHeader = findChild(scopesOverviewTempScopeItem, "scopePageHeader");
179− var backButton = findChild(findChild(scopesOverviewTempScopeItemHeader, "innerPageHeader"), "backButton");
151+ var dashTempScopeItemHeader = findChild(dashTempScopeItem, "scopePageHeader");
152+ var backButton = findChild(findChild(dashTempScopeItemHeader, "innerPageHeader"), "backButton");
180153 mouseClick(backButton, 0, 0);
181154
182155 // Check temp scope is gone
183− var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
184− tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope == null; }, true);
185− tryCompare(scopesOverviewTempScopeItem, "enabled", false);
186−
187− // Press on done
188− var scopesOverviewDoneButton = findChild(scopesOverview, "scopesOverviewDoneButton");
189− mouseClick(scopesOverviewDoneButton, 0, 0);
190−
191− // Check the dash overview is gone
192− tryCompare(overviewController, "progress", 0);
156+ tryCompare(dashTempScopeItem, "x", dash.width);
157+ tryCompare(dashTempScopeItem, "visible", false);
193158
194159 // Original list is still on 0
195160 var dashContentList = findChild(dash, "dashContentList");
196161 compare(dashContentList.currentIndex, 0);
197162 }
198163
199− function test_temp_scope_dash_overview_all_search_temp_scope_favorite_from_all() {
200− // Swipe right to Apps scope
201− var dashContentList = findChild(dash, "dashContentList");
202− touchFlick(dash, dash.width - 1, units.gu(1), dash.width - units.gu(10), units.gu(1));
203− tryCompare(dashContentList, "contentX", dashContentList.width);
204− tryCompare(dashContentList, "currentIndex", 1);
205−
206− // Click on card that opens temp scope
207− var categoryListView = findChild(dashContentList.currentItem, "categoryListView");
208− var dashCategory2 = findChild(categoryListView, "dashCategory2");
209− tryCompareFunction(function() {
210− if (dashCategory2.y < 200) return true;
211− categoryListView.contentY += 100;
212− return false;
213− }, true);
214− var card2 = findChild(dashCategory2, "delegate2");
215− waitForRendering(card2);
216− mouseClick(card2, card2.width / 2, card2.height / 2);
217−
218− // Wait for temp scope to be there
219− var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
220− tryCompare(dashTempScopeItem, "x", 0);
221−
222− // Show the overview
223− touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
224− var overviewController = findInvisibleChild(dash, "overviewController");
225− tryCompare(overviewController, "progress", 1);
226−
227− // Make sure tab is where it should
228− var scopesOverview = findChild(dash, "scopesOverview");
229− compare(scopesOverview.currentTab, 1);
164+ function test_manage_dash_search_temp_scope() {
165+ // Show the manage dash
166+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
167+ var bottomEdgeController = findInvisibleChild(dash, "bottomEdgeController");
168+ tryCompare(bottomEdgeController, "progress", 1);
230169
231170 // Do a search
232− var scopesOverviewPageHeader = findChild(scopesOverview, "scopesOverviewPageHeader");
233− var searchButton = findChild(scopesOverviewPageHeader, "search_header_button");
171+ var scopesList = findChild(dash, "scopesList");
172+ var scopesListPageHeader = findChild(scopesList, "pageHeader");
173+ var searchButton = findChild(scopesListPageHeader, "search_header_button");
234174 mouseClick(searchButton, 0, 0);
235175
236176 // Type something
237177 keyClick(Qt.Key_H);
238178
239− // Check results grid is there and the other lists are not
240− var searchResultsViewer = findChild(scopesOverview, "searchResultsViewer");
241− var scopesOverviewRepeater = findChild(dash, "scopesOverviewRepeater");
242− tryCompare(searchResultsViewer, "opacity", 1);
243− tryCompare(scopesOverviewRepeater, "count", 0);
244−
245179 // Click on a temp scope in the search
246− tryCompareFunction( function() {
247− return findChild(findChild(searchResultsViewer, "dashCategorysearchA"), "delegate2") != null;
248− }, true);
249− var cardTempScope = findChild(findChild(searchResultsViewer, "dashCategorysearchA"), "delegate2");
250− verify(cardTempScope, "Could not find delegate2");
180+ tryCompareFunction( function() { return findChild(scopesList, "scopesListCategorysearchA") != null; }, true);
181+ var dashCategorysearchA = findChild(scopesList, "scopesListCategorysearchA");
182+ tryCompareFunction( function() { return findChild(dashCategorysearchA, "delegate2") != null; }, true);
183+ var cardTempScope = findChild(dashCategorysearchA, "delegate2");
251184
252185 waitForRendering(cardTempScope);
253186 mouseClick(cardTempScope, cardTempScope.width / 2, cardTempScope.height / 2);
@@ -257,29 +190,24 @@ Item {
257190 compare(overviewDragHandle.enabled, false);
258191
259192 // Check temp scope is there
260− var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
261− tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope != null; }, true);
262− tryCompare(scopesOverviewTempScopeItem, "enabled", true);
193+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
194+ tryCompare(dashTempScopeItem, "x", 0);
195+ tryCompare(dashTempScopeItem, "visible", true);
196+
197+ // Check the manage dash is gone
198+ tryCompare(bottomEdgeController, "progress", 0);
263199
264200 // Go back
265− var scopesOverviewTempScopeItemHeader = findChild(scopesOverviewTempScopeItem, "scopePageHeader");
266− var backButton = findChild(findChild(scopesOverviewTempScopeItemHeader, "innerPageHeader"), "backButton");
201+ var dashTempScopeItemHeader = findChild(dashTempScopeItem, "scopePageHeader");
202+ var backButton = findChild(findChild(dashTempScopeItemHeader, "innerPageHeader"), "backButton");
267203 mouseClick(backButton, 0, 0);
268204
269205 // Check temp scope is gone
270− var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
271− tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope == null; }, true);
272− tryCompare(scopesOverviewTempScopeItem, "enabled", false);
273−
274− // Press on a favorite
275− var dashCategorysearchB = findChild(searchResultsViewer, "dashCategorysearchB");
276− var cardFavSearch = findChild(dashCategorysearchB, "delegate3");
277− mouseClick(cardFavSearch, 0, 0);
206+ tryCompare(dashTempScopeItem, "x", dash.width);
207+ tryCompare(dashTempScopeItem, "visible", false);
278208
279− // Check the dash overview is gone
280− tryCompare(overviewController, "progress", 0);
281−
282− // Original list went to the favorite
209+ // Original list is still on 0
210+ var dashContentList = findChild(dash, "dashContentList");
283211 compare(dashContentList.currentIndex, 0);
284212 }
285213
tests/qmltests/Dash/tst_GenericScopeView.qml−1
@@ -574,7 +574,6 @@ Item {
574574 var innerHeader = findChild(header, "innerPageHeader");
575575 verify(innerHeader, "Could not find the inner header");
576576
577− expectFail("Apps", "Click scope should not have a favorite button");
578577 var favoriteAction = findChild(innerHeader, "favorite_header_button");
579578 verify(favoriteAction, "Could not find the favorite action.");
580579 mouseClick(favoriteAction, favoriteAction.width / 2, favoriteAction.height / 2);