on xenial help sign in
Merge lp:~aacid/unity8/scopeActivatePreview
commit4b76dda98185c3b23787a5bc315b51f09404c226 authorAlbert Astals <albert.astals@canonical.com> dateTue 29 Jul 2014, 13:26 (12 years ago) parentf77f486b20f7f3
37 files changed+1774−110
data/unity8.conf+1−1 debian/control+1−1 plugins/Dash/CardCreator.js+2−1 plugins/Dash/ScopeStyle.qml+2−2 plugins/Dash/listviewwithpageheader.cpp+2−2 po/unity8.pot+44−12 qml/Components/PageHeader.qml+26−8 qml/Components/ResponsiveGridView.qml+3−3 qml/Dash/CardCarousel.qml+2−2 qml/Dash/CardGrid.qml+14−3 qml/Dash/CardVerticalJournal.qml+2−2 qml/Dash/Dash.qml+138−7 qml/Dash/DashBackground.qml+24 qml/Dash/DashContent.qml+7−4 qml/Dash/DashRenderer.qml+5−2 qml/Dash/GenericScopeView.qml+57−27 qml/Dash/PreviewListView.qml+2−2 qml/Dash/ScopesOverview.qml+518 qml/Dash/ScopesOverviewAll.qml+54 qml/Dash/ScopesOverviewFavorites.qml+73 qml/Dash/ScopesOverviewTab.qml+74 qml/Dash/graphics/apps-scope.png qml/Dash/graphics/dark_background.jpg qml/Shell.qml+2−8 tests/autopilot/unity8/shell/emulators/dash.py+9 tests/mocks/Unity/CMakeLists.txt+2−1 tests/mocks/Unity/fake_resultsmodel.cpp+2−1 tests/mocks/Unity/fake_scope.cpp+18−5 tests/mocks/Unity/fake_scope.h+6−3 tests/mocks/Unity/fake_scopes.cpp+42−1 tests/mocks/Unity/fake_scopes.h+6 tests/mocks/Unity/fake_scopesoverview.cpp+280 tests/mocks/Unity/fake_scopesoverview.h+104 tests/plugins/Dash/cardcreator/5.res+1 tests/qmltests/Components/tst_PageHeader.qml+36−6 tests/qmltests/Dash/tst_Dash.qml+211−3 tests/qmltests/Dash/tst_DashContent.qml+4−3
data/unity8.conf+1−1
@@ -36,7 +36,7 @@ pre-start script
3636
3737 if [ -z "$UNITY_SCOPES_LIST" ]; then
3838 # FIXME: remove once we have this in dconf
39− initctl set-env UNITY_SCOPES_LIST="scopes;clickscope;musicaggregator;videoaggregator"
39+ initctl set-env UNITY_SCOPES_LIST="clickscope;musicaggregator;videoaggregator"
4040 fi
4141
4242 # Remove the socket if still there
debian/control+1−1
@@ -23,7 +23,7 @@ Build-Depends: cmake,
2323 libpulse-dev,
2424 libqmenumodel-dev (>= 0.2.8),
2525 libqt5xmlpatterns5-dev,
26− libunity-api-dev (>= 7.85),
26+ libunity-api-dev (>= 7.87),
2727 libunity-mir-dev,
2828 libusermetricsoutput1-dev,
2929 libxcb1-dev,
plugins/Dash/CardCreator.js+2−1
@@ -408,7 +408,7 @@ function cardString(template, components) {
408408 mascotCode = kMascotImageCode.arg(anchors).arg(mascotImageVisible);
409409 }
410410
411− var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey")';
411+ var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < (root.scopeStyle ? root.scopeStyle.threshold : 0.7) ? (root.scopeStyle ? root.scopeStyle.light : "white") : (root.scopeStyle ? root.scopeStyle.dark : "grey")';
412412
413413 var titleSubtitleCode = "";
414414 if (hasTitle) {
@@ -438,6 +438,7 @@ function cardString(template, components) {
438438 titleAnchors = 'left: parent.left; \n\
439439 leftMargin: units.gu(1); \n\
440440 right: parent.right; \n\
441+ rightMargin: units.gu(1); \n\
441442 top: overlayLoader.top; \n\
442443 topMargin: units.gu(1);\n';
443444 } else {
plugins/Dash/ScopeStyle.qml+2−2
@@ -43,9 +43,9 @@ QtObject {
4343
4444 /*! \brief Luminance threshold for switching between fore and background color
4545
46− \note If background colour is not fully opaque, it's not taken into account.
46+ \note If background colour is not fully opaque, the defaultLightLuminance it's taken into account instead of it.
4747 */
48− readonly property real threshold: background.a !== 1.0 ? d.foregroundLuminance : (d.foregroundLuminance + d.backgroundLuminance) / 2
48+ readonly property real threshold: background.a !== 1.0 ? (d.foregroundLuminance + d.defaultLightLuminance) / 2: (d.foregroundLuminance + d.backgroundLuminance) / 2
4949
5050 /*! \brief The lighter of foreground and background colors
5151
plugins/Dash/listviewwithpageheader.cpp+2−2
@@ -822,8 +822,8 @@ ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIn
822822 ListItem *nextItem = itemAtIndex(modelIndex + 1);
823823 if (nextItem) {
824824 listItem->setY(nextItem->y() - listItem->height());
825− } else if (modelIndex == 0 && m_headerItem) {
826− listItem->setY(m_headerItem->height());
825+ } else if (modelIndex == 0) {
826+ listItem->setY(m_headerItem ? m_headerItem->height() : 0);
827827 } else if (!m_visibleItems.isEmpty()) {
828828 lostItem = true;
829829 }
po/unity8.pot+44−12
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11+"POT-Creation-Date: 2014-07-21 15:41+0200\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"
@@ -101,7 +101,7 @@ msgstr ""
101101msgid "See more"
102102msgstr ""
103103
104−#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
104+#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:356
105105msgid "See less"
106106msgstr ""
107107
@@ -109,7 +109,7 @@ msgstr ""
109109msgid "Recent"
110110msgstr ""
111111
112−#: qml/Dash/GenericScopeView.qml:330
112+#: qml/Dash/GenericScopeView.qml:356
113113msgid "See all"
114114msgstr ""
115115
@@ -133,6 +133,22 @@ msgstr ""
133133msgid "Send"
134134msgstr ""
135135
136+#: qml/Dash/ScopesOverview.qml:185
137+msgid "Manage Dash"
138+msgstr ""
139+
140+#: qml/Dash/ScopesOverview.qml:390
141+msgid "Done"
142+msgstr ""
143+
144+#: qml/Dash/ScopesOverviewTab.qml:36
145+msgid "Favorites"
146+msgstr ""
147+
148+#: qml/Dash/ScopesOverviewTab.qml:54
149+msgid "All"
150+msgstr ""
151+
136152#: qml/Greeter/Greeter.qml:157
137153msgid "Swipe to unlock"
138154msgstr ""
@@ -157,11 +173,11 @@ msgstr ""
157173msgid "Speaking..."
158174msgstr ""
159175
160−#: qml/Notifications/NotificationMenuItemFactory.qml:91
176+#: qml/Notifications/NotificationMenuItemFactory.qml:97
161177msgid "Show password"
162178msgstr ""
163179
164−#: qml/Notifications/NotificationMenuItemFactory.qml:103
180+#: qml/Notifications/NotificationMenuItemFactory.qml:112
165181msgid "Please enter SIM PIN"
166182msgstr ""
167183
@@ -173,27 +189,27 @@ msgstr ""
173189msgid "Conference"
174190msgstr ""
175191
176−#: qml/Panel/Indicators/MenuItemFactory.qml:583
192+#: qml/Panel/Indicators/MenuItemFactory.qml:577
177193msgid "In queue…"
178194msgstr ""
179195
180−#: qml/Panel/Indicators/MenuItemFactory.qml:587
196+#: qml/Panel/Indicators/MenuItemFactory.qml:581
181197msgid "Downloading"
182198msgstr ""
183199
184−#: qml/Panel/Indicators/MenuItemFactory.qml:589
200+#: qml/Panel/Indicators/MenuItemFactory.qml:583
185201msgid "Paused, tap to resume"
186202msgstr ""
187203
188−#: qml/Panel/Indicators/MenuItemFactory.qml:591
204+#: qml/Panel/Indicators/MenuItemFactory.qml:585
189205msgid "Canceled"
190206msgstr ""
191207
192−#: qml/Panel/Indicators/MenuItemFactory.qml:593
208+#: qml/Panel/Indicators/MenuItemFactory.qml:587
193209msgid "Finished"
194210msgstr ""
195211
196−#: qml/Panel/Indicators/MenuItemFactory.qml:595
212+#: qml/Panel/Indicators/MenuItemFactory.qml:589
197213msgid "Failed, tap to retry"
198214msgstr ""
199215
@@ -201,7 +217,23 @@ msgstr ""
201217msgid "Search"
202218msgstr ""
203219
204−#: qml/Shell.qml:359
220+#: qml/Shell.qml:347
221+msgid "Are you sure you would like to turn power off?"
222+msgstr ""
223+
224+#: qml/Shell.qml:349
225+msgid "Power off"
226+msgstr ""
227+
228+#: qml/Shell.qml:360
229+msgid "Restart"
230+msgstr ""
231+
232+#: qml/Shell.qml:369
233+msgid "Cancel"
234+msgstr ""
235+
236+#: qml/Shell.qml:496
205237#, qt-format
206238msgid "Please enter %1"
207239msgstr ""
qml/Components/PageHeader.qml+26−8
@@ -24,7 +24,7 @@ import "SearchHistoryModel"
2424Item {
2525 id: root
2626 objectName: "pageHeader"
27− implicitHeight: headerContainer.height + units.gu(2) + bottomContainer.height
27+ implicitHeight: headerContainer.height + bottomContainer.height + (showSignatureLine ? units.gu(2) : 0)
2828
2929 property bool showBackButton: false
3030 property string title
@@ -34,6 +34,7 @@ Item {
3434 property alias searchQuery: searchTextField.text
3535 property alias searchHint: searchTextField.placeholderText
3636 property bool searchInProgress: false
37+ property alias showSignatureLine: bottomBorder.visible
3738
3839 property alias bottomItem: bottomContainer.children
3940
@@ -45,6 +46,12 @@ Item {
4546 signal backClicked()
4647
4748 onScopeStyleChanged: refreshLogo()
49+ onSearchQueryChanged: {
50+ // Make sure we are at the search page if the search query changes behind our feet
51+ if (searchQuery) {
52+ headerContainer.showSearch = true;
53+ }
54+ }
4855
4956 function triggerSearch() {
5057 if (searchEntryEnabled) {
@@ -53,6 +60,12 @@ Item {
5360 }
5461 }
5562
63+ function closePopup() {
64+ if (headerContainer.popover != null) {
65+ PopupUtils.close(headerContainer.popover);
66+ }
67+ }
68+
5669 function resetSearch(keepFocus) {
5770 if (searchHistory) {
5871 searchHistory.addQuery(searchTextField.text);
@@ -61,9 +74,7 @@ Item {
6174 unfocus();
6275 }
6376 searchTextField.text = "";
64− if (headerContainer.popover != null) {
65− PopupUtils.close(headerContainer.popover);
66− }
77+ closePopup();
6778 }
6879
6980 function unfocus() {
@@ -104,9 +115,7 @@ Item {
104115 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
105116 visible: headerContainer.showSearch
106117 onPressed: {
107− if (headerContainer.popover) {
108− PopupUtils.close(headerContainer.popover);
109− }
118+ closePopup();
110119 if (!searchTextField.text) {
111120 headerContainer.showSearch = false;
112121 }
@@ -221,6 +230,12 @@ Item {
221230 root.openSearchHistory();
222231 }
223232 }
233+
234+ onTextChanged: {
235+ if (text != "") {
236+ closePopup();
237+ }
238+ }
224239 }
225240 }
226241
@@ -245,6 +260,7 @@ Item {
245260
246261 actions: [
247262 Action {
263+ objectName: "search"
248264 iconName: "search"
249265 visible: root.searchEntryEnabled
250266 onTriggered: {
@@ -297,6 +313,7 @@ Item {
297313
298314 Repeater {
299315 id: recentSearches
316+ objectName: "recentSearches"
300317 model: searchHistory
301318
302319 delegate: Standard {
@@ -305,7 +322,8 @@ Item {
305322 onClicked: {
306323 searchHistory.addQuery(text);
307324 searchTextField.text = text;
308− PopupUtils.close(popover);
325+ closePopup();
326+ unfocus();
309327 }
310328 }
311329 }
qml/Components/ResponsiveGridView.qml+3−3
@@ -34,7 +34,7 @@ Item {
3434 readonly property int cellWidth: gridView.cellWidth
3535 readonly property int cellHeight: gridView.cellHeight
3636 readonly property int totalContentHeight: {
37− return contentHeightForRows(Math.ceil(gridView.model.count / columns))
37+ return contentHeightForRows(Math.ceil(gridView.model.count / columns), cellHeight)
3838 }
3939 property alias interactive: gridView.interactive
4040 readonly property alias flicking: gridView.flicking
@@ -47,8 +47,8 @@ Item {
4747 property alias cacheBuffer: gridView.cacheBuffer
4848 readonly property alias currentItem: gridView.currentItem
4949
50− function contentHeightForRows(rows) {
51− return rows * cellHeight;
50+ function contentHeightForRows(rows, height) {
51+ return rows * height
5252 }
5353
5454 GridView {
qml/Dash/CardCarousel.qml+2−2
@@ -48,8 +48,8 @@ DashRenderer {
4848
4949 objectName: "carouselDelegate" + index
5050
51− function clicked() { cardCarousel.clicked(index, model.result) }
52− function pressAndHold() { cardCarousel.pressAndHold(index, model.result) }
51+ function clicked() { cardCarousel.clicked(index, model.result, loader.item, model) }
52+ function pressAndHold() { cardCarousel.pressAndHold(index, model) }
5353
5454 sourceComponent: cardTool.cardComponent
5555 onLoaded: {
qml/Dash/CardGrid.qml+14−3
@@ -26,10 +26,20 @@ DashRenderer {
2626 }
2727
2828 expandedHeight: grid.totalContentHeight
29− collapsedHeight: Math.min(grid.contentHeightForRows(collapsedRows), expandedHeight)
29+ collapsedHeight: Math.min(grid.contentHeightForRows(collapsedRows, grid.cellHeight), expandedHeight)
3030 collapsedItemCount: collapsedRows * grid.columns
3131 originY: grid.originY
3232
33+ function cardPosition(index) {
34+ var pos = {};
35+ var row = Math.floor(index / grid.columns);
36+ var column = index % grid.columns;
37+ // Bit sad this is not symmetrical
38+ pos.x = column * grid.cellWidth + grid.margins;
39+ pos.y = row * grid.cellHeight;
40+ return pos;
41+ }
42+
3343 ResponsiveGridView {
3444 id: grid
3545 anchors.fill: parent
@@ -53,6 +63,7 @@ DashRenderer {
5363 item.objectName = "delegate" + index;
5464 item.width = Qt.binding(function() { return cardTool.cardWidth; });
5565 item.height = Qt.binding(function() { return cardTool.cardHeight; });
66+ item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
5667 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
5768 item.cardData = Qt.binding(function() { return model; });
5869 item.template = Qt.binding(function() { return cardTool.template; });
@@ -62,8 +73,8 @@ DashRenderer {
6273 }
6374 Connections {
6475 target: loader.item
65− onClicked: root.clicked(index, result)
66− onPressAndHold: root.pressAndHold(index)
76+ onClicked: root.clicked(index, result, loader.item, model)
77+ onPressAndHold: root.pressAndHold(index, model)
6778 }
6879 }
6980 }
qml/Dash/CardVerticalJournal.qml+2−2
@@ -61,8 +61,8 @@ DashRenderer {
6161 }
6262 Connections {
6363 target: loader.item
64− onClicked: root.clicked(index, result)
65− onPressAndHold: root.pressAndHold(index)
64+ onClicked: root.clicked(index, result, loader.item, model)
65+ onPressAndHold: root.pressAndHold(index, model)
6666 }
6767 }
6868 }
qml/Dash/Dash.qml+138−7
@@ -16,6 +16,7 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import Ubuntu.Gestures 0.1
1920import Unity 0.2
2021import Utils 0.1
2122import "../Components"
@@ -29,6 +30,8 @@ Showable {
2930 property string showScopeOnLoaded: "clickscope"
3031 property real contentScale: 1.0
3132
33+ property alias overviewHandleHeight: overviewDragHandle.height
34+
3235 function setCurrentScope(scopeId, animate, reset) {
3336 var scopeIndex = filteredScopes.findFirst(Scopes.RoleId, scopeId)
3437
@@ -66,19 +69,85 @@ Showable {
6669 filterRegExp: RegExp("^true$")
6770 }
6871
72+ QtObject {
73+ id: overviewController
74+ objectName: "overviewController"
75+
76+ property alias enableAnimation: progressAnimation.enabled
77+ property real progress: 0
78+ Behavior on progress {
79+ id: progressAnimation
80+ UbuntuNumberAnimation { }
81+ }
82+ }
83+
84+ ScopesOverview {
85+ id: scopesOverview
86+ objectName: "scopesOverview"
87+ anchors.fill: parent
88+ scope: scopes.overviewScope
89+ progress: overviewController.progress
90+ scopeScale: scopeItem.scope ? 0.4 : (1 - overviewController.progress * 0.6)
91+ visible: scopeScale != 1
92+ currentIndex: dashContent.currentIndex
93+ onDone: {
94+ if (currentTab == 1) {
95+ animateDashFromAll(dashContent.currentScopeId);
96+ }
97+ hide();
98+ }
99+ onFavoriteSelected: {
100+ setCurrentScope(scopeId, false, false);
101+ hide();
102+ }
103+ onAllFavoriteSelected: {
104+ setCurrentScope(scopeId, false, false);
105+ animateDashFromAll(dashContent.currentScopeId);
106+ hide();
107+ }
108+ onSearchSelected: {
109+ var scopeIndex = filteredScopes.findFirst(Scopes.RoleId, scopeId);
110+ if (scopeIndex >= 0) {
111+ // Is a favorite one
112+ setCurrentScope(scopeId, false, false);
113+ showDashFromPos(pos, size);
114+ hide();
115+ } else {
116+ // Is not a favorite one, activate and get openScope
117+ scope.activate(result);
118+ }
119+ }
120+ function hide() {
121+ overviewController.enableAnimation = true;
122+ overviewController.progress = 0;
123+ }
124+ onProgressChanged: {
125+ if (progress == 0) {
126+ currentTab = scopeItem.scope ? 1 : 0;
127+ }
128+ }
129+ }
130+
69131 DashContent {
70132 id: dashContent
133+
134+ property var scopeThatOpenedScope: null
135+
136+ parent: overviewController.progress == 0 ? dash : scopesOverview.dashItemEater
71137 objectName: "dashContent"
72− width: parent.width
73− height: parent.height
138+ width: dash.width
139+ height: dash.height
74140 model: filteredScopes
75141 scopes: scopes
76− visible: x != -width
142+ visible: !scopesOverview.showingNonFavoriteScope && x != -width
77143 onGotoScope: {
78144 dash.setCurrentScope(scopeId, true, false);
79145 }
80146 onOpenScope: {
147+ scopeThatOpenedScope = currentScope;
81148 scopeItem.scope = scope;
149+ scopesOverview.currentTab = 1;
150+ scopesOverview.ensureAllScopeVisible(scope.id);
82151 x = -width;
83152 }
84153 onScopeLoaded: {
@@ -88,25 +157,63 @@ Showable {
88157 }
89158 }
90159 scale: dash.contentScale
91− clip: scale != 1.0 || scopeItem.visible
160+ clip: scale != 1.0 || scopeItem.visible || overviewController.progress != 0
92161 Behavior on x {
93162 UbuntuNumberAnimation {
163+ duration: overviewController.progress != 0 ? 0 : UbuntuAnimation.FastDuration
94164 onRunningChanged: {
95165 if (!running && dashContent.x == 0) {
96− dashContent.closeScope(scopeItem.scope);
166+ dashContent.scopeThatOpenedScope.closeScope(scopeItem.scope);
97167 scopeItem.scope = null;
168+ if (overviewController.progress == 0) {
169+ // Set tab to Favorites only if we are not showing the overview
170+ scopesOverview.currentTab = 0;
171+ }
98172 }
99173 }
100174 }
101175 }
176+
177+ enabled: opacity == 1
178+ opacity: scopesOverview.growingDashFromPos ? 1 : 1 - overviewController.progress
179+ }
180+
181+ DashBackground
182+ {
183+ anchors.fill: scopeItem
184+ visible: scopeItem.visible
185+ parent: scopeItem.parent
186+ scale: scopeItem.scale
187+ opacity: scopeItem.opacity
102188 }
103189
104190 GenericScopeView {
105191 id: scopeItem
106− anchors.left: dashContent.right
192+ objectName: "dashTempScopeItem"
193+
194+ readonly property real targetOverviewScale: {
195+ if (scopesOverview.currentTab == 0) {
196+ return 0.4;
197+ } else {
198+ return scopesOverview.allCardSize.width / scopeItem.width;
199+ }
200+ }
201+ readonly property real overviewProgressScale: (1 - overviewController.progress * (1 - targetOverviewScale))
202+ readonly property var targetOverviewPosition: scope ? scopesOverview.allScopeCardPosition(scope.id) : null
203+ readonly property real overviewProgressX: scope && scopesOverview.currentTab == 1 && targetOverviewPosition ?
204+ overviewController.progress * (targetOverviewPosition.x - (width - scopesOverview.allCardSize.width) / 2)
205+ : 0
206+ readonly property real overviewProgressY: scope && scopesOverview.currentTab == 1 && targetOverviewPosition ?
207+ overviewController.progress * (targetOverviewPosition.y - (height - scopesOverview.allCardSize.height) / 2)
208+ : 0
209+
210+ x: overviewController.progress == 0 ? dashContent.x + width : overviewProgressX
211+ y: overviewController.progress == 0 ? dashContent.y : overviewProgressY
107212 width: parent.width
108213 height: parent.height
109− scale: dash.contentScale
214+ scale: dash.contentScale * overviewProgressScale
215+ enabled: opacity == 1
216+ opacity: 1 - overviewController.progress
110217 clip: scale != 1.0
111218 visible: scope != null
112219 hasBackAction: true
@@ -126,4 +233,28 @@ Showable {
126233 }
127234 }
128235 }
236+
237+ EdgeDragArea {
238+ id: overviewDragHandle
239+ objectName: "overviewDragHandle"
240+ z: 1
241+ direction: Direction.Upwards
242+ enabled: !dashContent.previewShown && dashContent.currentScope && dashContent.currentScope.searchQuery == "" && (overviewController.progress == 0 || dragging)
243+
244+ readonly property real fullMovement: units.gu(20)
245+
246+ anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
247+ height: units.gu(2)
248+
249+ onSceneDistanceChanged: {
250+ overviewController.enableAnimation = false;
251+ overviewController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
252+ }
253+
254+ onDraggingChanged: {
255+ overviewController.enableAnimation = true;
256+ overviewController.progress = (overviewController.progress > 0.7) ? 1 : 0;
257+ }
258+ }
259+
129260}
qml/Dash/DashBackground.qmlnew file+24
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (C) 2013, 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+Image {
20+ source: anchors.fill.width > anchors.fill.height ? "graphics/paper_landscape.png" : "graphics/paper_portrait.png"
21+ fillMode: Image.PreserveAspectCrop
22+ horizontalAlignment: Image.AlignRight
23+ verticalAlignment: Image.AlignTop
24+}
qml/Dash/DashContent.qml+7−4
@@ -26,6 +26,9 @@ Item {
2626 property var model: null
2727 property var scopes: null
2828 readonly property alias currentIndex: dashContentList.currentIndex
29+ readonly property string currentScopeId: dashContentList.currentItem ? dashContentList.currentItem.scopeId : ""
30+ readonly property var currentScope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
31+ readonly property bool previewShown: dashContentList.currentItem && dashContentList.currentItem.item ? dashContentList.currentItem.item.previewShown : false
2932
3033 signal scopeLoaded(string scopeId)
3134 signal gotoScope(string scopeId)
@@ -76,15 +79,15 @@ Item {
7679 }
7780 }
7881
79− function closeScope(scope) {
80− dashContentList.currentItem.theScope.closeScope(scope)
81− }
82−
8382 Item {
8483 id: dashContentListHolder
8584
8685 anchors.fill: parent
8786
87+ DashBackground {
88+ anchors.fill: parent
89+ }
90+
8891 ListView {
8992 id: dashContentList
9093 objectName: "dashContentList"
qml/Dash/DashRenderer.qml+5−2
@@ -41,9 +41,12 @@ Item {
4141 /// Emitted when the user clicked on an item
4242 /// @param index is the index of the clicked item
4343 /// @param result result model of the cliked item, used for activation
44− signal clicked(int index, var result)
44+ /// @param item item that has been clicked
45+ /// @param itemModel model of the item
46+ signal clicked(int index, var result, var item, var itemModel)
4547
4648 /// Emitted when the user pressed and held on an item
4749 /// @param index is the index of the held item
48− signal pressAndHold(int index)
50+ /// @param itemModel model of the item
51+ signal pressAndHold(int index, var itemModel)
4952}
qml/Dash/GenericScopeView.qml+57−27
@@ -33,6 +33,10 @@ FocusScope {
3333 property bool hasBackAction: false
3434 property bool enableHeightBehaviorOnNextCreation: false
3535 property var categoryView: categoryView
36+ property bool showPageHeader: true
37+ property var clickOverride: null
38+ property var pressAndHoldOverride: null
39+ readonly property alias previewShown: previewListView.open
3640
3741 property var scopeStyle: ScopeStyle {
3842 style: scope ? scope.customizations : {}
@@ -78,22 +82,24 @@ FocusScope {
7882 }
7983
8084 onIsCurrentChanged: {
81− pageHeader.resetSearch();
85+ if (showPageHeader) {
86+ pageHeaderLoader.item.resetSearch();
87+ }
8288 previewListView.open = false;
8389 }
8490
8591 Binding {
8692 target: scopeView.scope
8793 property: "searchQuery"
88− value: pageHeader.searchQuery
89− when: isCurrent
94+ value: pageHeaderLoader.item ? pageHeaderLoader.item.searchQuery : ""
95+ when: isCurrent && showPageHeader
9096 }
9197
9298 Binding {
93− target: pageHeader
99+ target: pageHeaderLoader.item
94100 property: "searchQuery"
95101 value: scopeView.scope ? scopeView.scope.searchQuery : ""
96− when: isCurrent
102+ when: isCurrent && showPageHeader
97103 }
98104
99105 Connections {
@@ -232,7 +238,12 @@ FocusScope {
232238 Connections {
233239 target: rendererLoader.item
234240 onClicked: {
235− if (scopeView.scope.id === "scopes" || scopeView.scope.id == "clickscope") {
241+ if (scopeView.clickOverride) {
242+ scopeView.clickOverride(index, result, item, itemModel);
243+ return;
244+ }
245+
246+ if (itemModel.uri.indexOf("scope://") === 0 || scopeView.scope.id === "clickscope") {
236247 // TODO Technically it is possible that calling activate() will make the scope emit
237248 // previewRequested so that we show a preview but there's no scope that does that yet
238249 // so it's not implemented
@@ -241,7 +252,16 @@ FocusScope {
241252 openPreview(index);
242253 }
243254 }
244− onPressAndHold: openPreview(index)
255+
256+ onPressAndHold: {
257+ if (scopeView.pressAndHoldOverride) {
258+ scopeView.pressAndHoldOverride(index);
259+ } else {
260+ if (itemModel.uri.indexOf("scope://") !== 0) {
261+ openPreview(index)
262+ }
263+ }
264+ }
245265
246266 function openPreview(index) {
247267 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
@@ -389,27 +409,35 @@ FocusScope {
389409 }
390410 }
391411
392− pageHeader: PageHeader {
393− id: pageHeader
394− objectName: "scopePageHeader"
412+ pageHeader: scopeView.showPageHeader ? pageHeaderLoader : null
413+ Loader {
414+ id: pageHeaderLoader
395415 width: parent.width
396− title: scopeView.scope ? scopeView.scope.name : ""
397− searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.tr("Search")
398− showBackButton: scopeView.hasBackAction
399− searchEntryEnabled: true
400− searchInProgress: scopeView.scope ? scopeView.scope.searchInProgress : false
401− scopeStyle: scopeView.scopeStyle
402−
403− bottomItem: DashDepartments {
404− scope: scopeView.scope
405− width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
406− anchors.right: parent.right
407− windowHeight: scopeView.height
408− windowWidth: scopeView.width
409− scopeStyle: scopeView.scopeStyle
410− }
416+ sourceComponent: scopeView.showPageHeader ? pageHeaderComponent : undefined
417+ Component {
418+ id: pageHeaderComponent
419+ PageHeader {
420+ objectName: "scopePageHeader"
421+ width: parent.width
422+ title: scopeView.scope ? scopeView.scope.name : ""
423+ searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.tr("Search")
424+ showBackButton: scopeView.hasBackAction
425+ searchEntryEnabled: true
426+ searchInProgress: scopeView.scope ? scopeView.scope.searchInProgress : false
427+ scopeStyle: scopeView.scopeStyle
428+
429+ bottomItem: DashDepartments {
430+ scope: scopeView.scope
431+ width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
432+ anchors.right: parent.right
433+ windowHeight: scopeView.height
434+ windowWidth: scopeView.width
435+ scopeStyle: scopeView.scopeStyle
436+ }
411437
412− onBackClicked: scopeView.backClicked()
438+ onBackClicked: scopeView.backClicked()
439+ }
440+ }
413441 }
414442 }
415443
@@ -428,7 +456,9 @@ FocusScope {
428456 anchors.left: categoryView.right
429457
430458 onOpenChanged: {
431− pageHeader.unfocus();
459+ if (showPageHeader) {
460+ pageHeaderLoader.item.unfocus();
461+ }
432462 }
433463 }
434464
qml/Dash/PreviewListView.qml+2−2
@@ -88,7 +88,7 @@ Item {
8888 height: previewListView.height
8989 width: previewListView.width
9090
91− readonly property bool ready: preview.previewModel.loaded
91+ readonly property bool ready: preview.previewModel && preview.previewModel.loaded
9292
9393 Previews.Preview {
9494 id: preview
@@ -108,7 +108,7 @@ Item {
108108 id: processingMouseArea
109109 objectName: "processingMouseArea"
110110 anchors.fill: parent
111− enabled: !preview.previewModel.loaded || preview.previewModel.processingAction
111+ enabled: preview.previewModel && (!preview.previewModel.loaded || preview.previewModel.processingAction)
112112
113113 ActivityIndicator {
114114 anchors.centerIn: parent
qml/Dash/ScopesOverview.qmlnew file+518
@@ -0,0 +1,518 @@
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+ property bool growingDashFromPos: false
36+ readonly property bool searching: scope && scope.searchQuery == ""
37+ readonly property bool showingNonFavoriteScope: tempScopeItem.scope != null
38+ readonly property var dashItemEater: {
39+ if (!forceXYScalerEater && tabBar.currentTab == 0 && middleItems.count > 0) {
40+ var loaderItem = middleItems.itemAt(0).item;
41+ return loaderItem && loaderItem.currentItem ? loaderItem.currentItem : null;
42+ }
43+ return scopesOverviewXYScaler;
44+ }
45+ readonly property size allCardSize: {
46+ if (middleItems.count > 1) {
47+ var loaderItem = middleItems.itemAt(1).item;
48+ if (loaderItem) {
49+ var cardTool = loaderItem.cardTool;
50+ return Qt.size(cardTool.cardWidth, cardTool.cardHeight);
51+ }
52+ }
53+ return Qt.size(0, 0);
54+ }
55+
56+ // Internal properties
57+ property bool forceXYScalerEater: false
58+
59+ signal done()
60+ signal favoriteSelected(var scopeId)
61+ signal allFavoriteSelected(var scopeId)
62+ signal searchSelected(var scopeId, var result, var pos, var size)
63+
64+ Connections {
65+ target: scope
66+ onOpenScope: {
67+ var itemPos = scopesOverviewXYScaler.restorePosition;
68+ var itemSize = scopesOverviewXYScaler.restoreSize;
69+ scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
70+ if (itemPos) {
71+ scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
72+ scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
73+ } else {
74+ scopesOverviewXYScaler.x = 0;
75+ scopesOverviewXYScaler.y = 0;
76+ }
77+ scopesOverviewXYScaler.opacity = 0;
78+ tempScopeItem.scope = scope;
79+ middleItems.overrideOpacity = 0;
80+ scopesOverviewXYScaler.scale = 1;
81+ scopesOverviewXYScaler.x = 0;
82+ scopesOverviewXYScaler.y = 0;
83+ scopesOverviewXYScaler.opacity = 1;
84+ }
85+ }
86+
87+ function animateDashFromAll(scopeId) {
88+ var currentScopePos = allScopeCardPosition(scopeId);
89+ if (currentScopePos) {
90+ showDashFromPos(currentScopePos, allCardSize);
91+ } else {
92+ console.log("Warning: Could not find Dash OverView All card position for scope", dashContent.currentScopeId);
93+ }
94+ }
95+
96+ function showDashFromPos(itemPos, itemSize) {
97+ scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
98+ scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
99+ scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
100+ scopesOverviewXYScaler.opacity = 0;
101+ root.growingDashFromPos = true;
102+ scopesOverviewXYScaler.scale = 1;
103+ scopesOverviewXYScaler.x = 0;
104+ scopesOverviewXYScaler.y = 0;
105+ scopesOverviewXYScaler.opacity = 1;
106+ }
107+
108+ function allScopeCardPosition(scopeId) {
109+ if (middleItems.count > 1) {
110+ var loaderItem = middleItems.itemAt(1).item;
111+ if (loaderItem) {
112+ var pos = loaderItem.scopeCardPosition(scopeId);
113+ return loaderItem.mapToItem(null, pos.x, pos.y);
114+ }
115+ }
116+ }
117+
118+ function ensureAllScopeVisible(scopeId) {
119+ if (middleItems.count > 1) {
120+ var loaderItem = middleItems.itemAt(1).item;
121+ if (loaderItem) {
122+ var pos = loaderItem.scopeCardPosition(scopeId);
123+ loaderItem.contentY = Math.min(pos.y, loaderItem.contentHeight - loaderItem.height);
124+ }
125+ }
126+ }
127+
128+ onProgressChanged: {
129+ if (progress == 0) {
130+ pageHeader.resetSearch();
131+ pageHeader.unfocus(); // Shouldn't the previous call do this too?
132+ }
133+ }
134+
135+ ScopeStyle {
136+ id: overviewScopeStyle
137+ style: { "foreground-color" : "white", "background-color" : "transparent" }
138+ }
139+
140+ DashBackground {
141+ anchors.fill: parent
142+ source: "graphics/dark_background.jpg"
143+ }
144+
145+ Connections {
146+ target: pageHeader
147+ onSearchQueryChanged: {
148+ // Need this in order, otherwise something gets unhappy in rendering
149+ // of the overlay in carousels because the parent of the dash dies for
150+ // a moment, this way we make sure it's reparented first
151+ // by forceXYScalerEater making dashItemEater return scopesOverviewXYScaler
152+ // before we kill the previous parent by scope.searchQuery
153+ root.forceXYScalerEater = true;
154+ root.scope.searchQuery = pageHeader.searchQuery;
155+ root.forceXYScalerEater = false;
156+ }
157+ }
158+
159+ Binding {
160+ target: pageHeader
161+ property: "searchQuery"
162+ value: scope ? scope.searchQuery : ""
163+ }
164+
165+ Item {
166+ id: scopesOverviewContent
167+ x: previewListView.open ? -width : 0
168+ Behavior on x { UbuntuNumberAnimation { } }
169+ width: parent.width
170+ height: parent.height
171+
172+ PageHeader {
173+ id: pageHeader
174+ objectName: "scopesOverviewPageHeader"
175+
176+ readonly property real yDisplacement: pageHeader.height + tabBar.height + tabBar.anchors.margins
177+
178+ y: {
179+ if (root.progress < 0.5) {
180+ return -yDisplacement;
181+ } else {
182+ return -yDisplacement + (root.progress - 0.5) * yDisplacement * 2;
183+ }
184+ }
185+ width: parent.width
186+ clip: true
187+ title: i18n.tr("Manage Dash")
188+ scopeStyle: overviewScopeStyle
189+ showSignatureLine: false
190+ searchEntryEnabled: true
191+ searchInProgress: root.scope ? root.scope.searchInProgress : false
192+ }
193+
194+ ScopesOverviewTab {
195+ id: tabBar
196+ anchors {
197+ left: parent.left
198+ right: parent.right
199+ top: pageHeader.bottom
200+ margins: units.gu(2)
201+ }
202+ height: units.gu(4)
203+
204+ enabled: opacity == 1
205+ opacity: !scope || scope.searchQuery == "" ? 1 : 0
206+ Behavior on opacity { UbuntuNumberAnimation { } }
207+ }
208+
209+ Repeater {
210+ id: middleItems
211+ objectName: "scopesOverviewRepeater"
212+ property real overrideOpacity: -1
213+ model: scope && scope.searchQuery == "" ? scope.categories : null
214+ delegate: Loader {
215+ id: loader
216+ objectName: "scopesOverviewRepeaterChild" + index
217+
218+ height: {
219+ if (index == 0) {
220+ return root.height;
221+ } else {
222+ return root.height - pageHeader.height - tabBar.height - tabBar.anchors.margins - units.gu(2)
223+ }
224+ }
225+ width: {
226+ if (index == 0) {
227+ return root.width / scopeScale
228+ } else {
229+ return root.width
230+ }
231+ }
232+ x: {
233+ if (index == 0) {
234+ return (root.width - width) / 2;
235+ } else {
236+ return 0;
237+ }
238+ }
239+ anchors {
240+ bottom: scopesOverviewContent.bottom
241+ }
242+
243+ scale: index == 0 ? scopeScale : 1
244+
245+ opacity: {
246+ if (middleItems.overrideOpacity >= 0)
247+ return middleItems.overrideOpacity;
248+
249+ if (tabBar.currentTab != index)
250+ return 0;
251+
252+ return index == 0 ? 1 : root.progress;
253+ }
254+ Behavior on opacity {
255+ enabled: root.progress == 1
256+ UbuntuNumberAnimation { }
257+ }
258+ enabled: opacity == 1
259+
260+ clip: index == 1
261+
262+ CardTool {
263+ id: cardTool
264+ objectName: "cardTool"
265+ count: results.count
266+ template: model.renderer
267+ components: model.components
268+ viewWidth: parent.width
269+ }
270+
271+ source: {
272+ if (index == 0 && categoryId == "favorites") return "ScopesOverviewFavorites.qml";
273+ else if (index == 1 && categoryId == "all") return "ScopesOverviewAll.qml";
274+ else return "";
275+ }
276+
277+ onLoaded: {
278+ item.model = Qt.binding(function() { return results })
279+ item.cardTool = cardTool;
280+ if (index == 0) {
281+ item.scopeWidth = Qt.binding(function() { return root.width; });
282+ item.scopeHeight = Qt.binding(function() { return root.height; });
283+ item.appliedScale = Qt.binding(function() { return loader.scale });
284+ item.currentIndex = Qt.binding(function() { return root.currentIndex });
285+ } else if (index == 1) {
286+ item.extraHeight = bottomBar.height;
287+ }
288+ }
289+
290+ Connections {
291+ target: loader.item
292+ onClicked: {
293+ if (tabBar.currentTab == 0) {
294+ root.favoriteSelected(itemModel.scopeId)
295+ } else {
296+ var favoriteScopesItem = middleItems.itemAt(0).item;
297+ var scopeIndex = favoriteScopesItem.model.scopeIndex(itemModel.scopeId);
298+ if (scopeIndex >= 0) {
299+ root.allFavoriteSelected(itemModel.scopeId);
300+ } else {
301+ // Will result in an openScope from root.scope
302+ scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
303+ scopesOverviewXYScaler.restoreSize = allCardSize;
304+ root.scope.activate(result);
305+ }
306+ }
307+ }
308+ onPressAndHold: {
309+ // Preview can call openScope so make sure restorePosition and restoreSize are set
310+ scopesOverviewXYScaler.restorePosition = undefined;
311+ scopesOverviewXYScaler.restoreSize = allCardSize;
312+
313+ previewListView.model = target.model;
314+ previewListView.currentIndex = -1
315+ previewListView.currentIndex = index;
316+ previewListView.open = true
317+ }
318+ }
319+ }
320+ }
321+
322+ GenericScopeView {
323+ id: searchResultsViewer
324+ objectName: "searchResultsViewer"
325+ anchors {
326+ top: pageHeader.bottom
327+ right: parent.right
328+ left: parent.left
329+ bottom: parent.bottom
330+ }
331+ scope: root.scope && root.scope.searchQuery != "" ? root.scope : null
332+ scopeStyle: overviewScopeStyle
333+ enabled: opacity == 1
334+ showPageHeader: false
335+ clip: true
336+ opacity: searchResultsViewer.scope ? 1 : 0
337+ Behavior on opacity { UbuntuNumberAnimation { } }
338+
339+ clickOverride: function (index, result, item, itemModel) {
340+ pageHeader.closePopup();
341+ if (itemModel.scopeId) {
342+ // This can end up in openScope so save restorePosition and restoreSize
343+ scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
344+ scopesOverviewXYScaler.restoreSize = Qt.size(item.width, item.height);
345+ root.searchSelected(itemModel.scopeId, result, item.mapToItem(null, 0, 0), Qt.size(item.width, item.height));
346+ } else {
347+ // Not a scope, just activate it
348+ searchResultsViewer.scope.activate(result);
349+ }
350+ }
351+
352+ pressAndHoldOverride: function (index) {
353+ // Do nothing
354+ }
355+ }
356+
357+ Rectangle {
358+ id: bottomBar
359+ color: "black"
360+ height: units.gu(6)
361+ width: parent.width
362+ enabled: opacity == 0.4
363+ opacity: scope && scope.searchQuery == "" ? 0.4 : 0
364+ Behavior on opacity { UbuntuNumberAnimation { } }
365+ y: {
366+ if (root.progress < 0.5) {
367+ return parent.height;
368+ } else {
369+ return parent.height - (root.progress - 0.5) * height * 2;
370+ }
371+ }
372+
373+ AbstractButton {
374+ objectName: "scopesOverviewDoneButton"
375+ width: Math.max(label.width + units.gu(2), units.gu(10))
376+ height: units.gu(4)
377+ anchors {
378+ left: parent.left
379+ leftMargin: units.gu(2)
380+ verticalCenter: parent.verticalCenter
381+ }
382+ Rectangle {
383+ anchors.fill: parent
384+ border.color: "white"
385+ border.width: units.dp(1)
386+ radius: units.dp(10)
387+ color: parent.pressed ? "gray" : "transparent"
388+ }
389+ Label {
390+ id: label
391+ anchors.centerIn: parent
392+ text: i18n.tr("Done")
393+ color: parent.pressed ? "black" : "white"
394+ }
395+ onClicked: root.done();
396+ }
397+
398+ AbstractButton {
399+ objectName: "scopesOverviewStoreButton"
400+ width: Math.max(storeLabel.width, units.gu(10))
401+ height: units.gu(4)
402+ anchors {
403+ right: parent.right
404+ verticalCenter: parent.verticalCenter
405+ }
406+ Image {
407+ id: storeImage
408+ source: "graphics/apps-scope.png"
409+ anchors.horizontalCenter: parent.horizontalCenter
410+ width: units.gu(2)
411+ height: units.gu(2)
412+ fillMode: Image.Stretch
413+ }
414+ Label {
415+ id: storeLabel
416+ anchors.horizontalCenter: parent.horizontalCenter
417+ anchors.top: storeImage.bottom
418+ text: i18n.tr("Store")
419+ color: "white"
420+ }
421+ onClicked: {
422+ // Just zoom from the middle
423+ scopesOverviewXYScaler.restorePosition = undefined;
424+ scopesOverviewXYScaler.restoreSize = allCardSize;
425+ scope.performQuery("scope://com.canonical.scopes.clickstore");
426+ }
427+ }
428+ }
429+ }
430+
431+ PreviewListView {
432+ id: previewListView
433+ objectName: "scopesOverviewPreviewListView"
434+ scope: root.scope
435+ scopeStyle: overviewScopeStyle
436+ visible: x != width
437+ width: parent.width
438+ height: parent.height
439+ anchors.left: scopesOverviewContent.right
440+ }
441+
442+
443+
444+ Item {
445+ id: scopesOverviewXYScaler
446+ width: parent.width
447+ height: parent.height
448+
449+ clip: scale != 1.0
450+ enabled: scale == 1
451+
452+ property bool animationsEnabled: root.showingNonFavoriteScope || root.growingDashFromPos
453+
454+ property var restorePosition
455+ property var restoreSize
456+
457+ Behavior on x {
458+ enabled: scopesOverviewXYScaler.animationsEnabled
459+ UbuntuNumberAnimation { }
460+ }
461+ Behavior on y {
462+ enabled: scopesOverviewXYScaler.animationsEnabled
463+ UbuntuNumberAnimation { }
464+ }
465+ Behavior on opacity {
466+ enabled: scopesOverviewXYScaler.animationsEnabled
467+ UbuntuNumberAnimation { }
468+ }
469+ Behavior on scale {
470+ enabled: scopesOverviewXYScaler.animationsEnabled
471+ UbuntuNumberAnimation {
472+ onRunningChanged: {
473+ if (!running) {
474+ if (root.showingNonFavoriteScope && scopesOverviewXYScaler.scale != 1) {
475+ root.scope.closeScope(tempScopeItem.scope);
476+ tempScopeItem.scope = null;
477+ } else if (root.growingDashFromPos) {
478+ root.growingDashFromPos = false;
479+ }
480+ }
481+ }
482+ }
483+ }
484+
485+ DashBackground
486+ {
487+ anchors.fill: tempScopeItem
488+ visible: tempScopeItem.visible
489+ parent: tempScopeItem.parent
490+ }
491+
492+ GenericScopeView {
493+ id: tempScopeItem
494+ objectName: "scopesOverviewTempScopeItem"
495+
496+ width: parent.width
497+ height: parent.height
498+ scale: dash.contentScale
499+ clip: scale != 1.0
500+ visible: scope != null
501+ hasBackAction: true
502+ isCurrent: visible
503+ onBackClicked: {
504+ var v = scopesOverviewXYScaler.restoreSize.width / tempScopeItem.width;
505+ scopesOverviewXYScaler.scale = v;
506+ if (scopesOverviewXYScaler.restorePosition) {
507+ scopesOverviewXYScaler.x = scopesOverviewXYScaler.restorePosition.x -(tempScopeItem.width - tempScopeItem.width * v) / 2;
508+ scopesOverviewXYScaler.y = scopesOverviewXYScaler.restorePosition.y -(tempScopeItem.height - tempScopeItem.height * v) / 2;
509+ } else {
510+ scopesOverviewXYScaler.x = 0;
511+ scopesOverviewXYScaler.y = 0;
512+ }
513+ scopesOverviewXYScaler.opacity = 0;
514+ middleItems.overrideOpacity = -1;
515+ }
516+ }
517+ }
518+}
qml/Dash/ScopesOverviewAll.qmlnew file+54
@@ -0,0 +1,54 @@
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.qmlnew file+73
@@ -0,0 +1,73 @@
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: repeater.itemAt(currentIndex);
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.headerAlignment = Qt.binding(function() { return cardTool.headerAlignment; });
65+ }
66+
67+ Connections {
68+ target: loader.item
69+ onClicked: root.clicked(index, result, model)
70+ }
71+ }
72+ }
73+}
qml/Dash/ScopesOverviewTab.qmlnew file+74
@@ -0,0 +1,74 @@
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+}
qml/Dash/graphics/apps-scope.pngnew file

Binary file not shown.

qml/Dash/graphics/dark_background.jpgnew file

Binary file not shown.

qml/Shell.qml+2−8
@@ -125,14 +125,6 @@ FocusScope {
125125 opacity: dash.disappearingAnimationProgress
126126 }
127127
128− Image {
129− anchors.fill: dash
130− source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
131− fillMode: Image.PreserveAspectCrop
132− horizontalAlignment: Image.AlignRight
133− verticalAlignment: Image.AlignTop
134− }
135−
136128 Dash {
137129 id: dash
138130 objectName: "dash"
@@ -142,6 +134,8 @@ FocusScope {
142134 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
143135 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
144136
137+ overviewHandleHeight: shell.edgeSize
138+
145139 anchors {
146140 fill: parent
147141 topMargin: panel.panelHeight
tests/autopilot/unity8/shell/emulators/dash.py+9
@@ -18,6 +18,7 @@
1818#
1919
2020import logging
21+import time
2122import ubuntuuitoolkit
2223
2324from unity8.shell import emulators
@@ -68,7 +69,9 @@ class Dash(emulators.UnityEmulatorBase):
6869
6970 """
7071 scope_loader = self._get_scope_loader(scope_id)
72+ print ("open_scope", scope_id, scope_loader)
7173 if scope_loader.isCurrent:
74+ print ("scope_loader.isCurrent")
7275 logger.info('The scope is already open.')
7376 return self._get_scope_from_loader(scope_loader)
7477 else:
@@ -83,6 +86,12 @@ class Dash(emulators.UnityEmulatorBase):
8386 'No scope found with id {0}'.format(scope_id))
8487
8588 def _get_scope_from_loader(self, loader):
89+ print ("_get_scope_from_loader", loader, loader.isCurrent, loader.isLoaded, loader.get_children())
90+ i = 0
91+ time.sleep(1)
92+ while len(loader.get_children()) < 1 and i < 5:
93+ time.sleep(1)
94+ i = i + 1
8695 return loader.get_children()[0]
8796
8897 def _open_scope_scrolling(self, scope_loader):
tests/mocks/Unity/CMakeLists.txt+2−1
@@ -6,7 +6,7 @@ add_subdirectory(Notifications)
66pkg_search_module(DEE dee-1.0 REQUIRED)
77pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
88pkg_search_module(DEEQT libdee-qt5 REQUIRED)
9−pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=2)
9+pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=3)
1010
1111include_directories(
1212 ${CMAKE_CURRENT_BINARY_DIR}
@@ -21,6 +21,7 @@ add_definitions(-DQT_NO_KEYWORDS)
2121set(UnityQML_SOURCES
2222 fake_scope.cpp
2323 fake_scopes.cpp
24+ fake_scopesoverview.cpp
2425 fake_categories.cpp
2526 fake_department.cpp
2627 fake_resultsmodel.cpp
tests/mocks/Unity/fake_resultsmodel.cpp+2−1
@@ -58,8 +58,9 @@ ResultsModel::data(const QModelIndex& index, int role) const
5858 case RoleUri:
5959 case RoleCategoryId:
6060 case RoleDndUri:
61− case RoleResult:
6261 return QString();
62+ case RoleResult:
63+ return QString("Result.%1.%2").arg(m_categoryId).arg(index.row());
6364 case RoleTitle:
6465 return QString("Title.%1.%2").arg(m_categoryId).arg(index.row());
6566 case RoleArt:
tests/mocks/Unity/fake_scope.cpp+18−5
@@ -14,17 +14,20 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17+#include <QDebug>
1718#include <QUrl>
1819
1920#include "fake_scope.h"
21+
2022#include "fake_department.h"
2123#include "fake_resultsmodel.h"
24+#include "fake_scopes.h"
2225
23−Scope::Scope(QObject* parent) : Scope(QString(), QString(), false, parent)
26+Scope::Scope(Scopes* parent) : Scope(QString(), QString(), false, parent)
2427{
2528}
2629
27−Scope::Scope(QString const& id, QString const& name, bool visible, QObject* parent, int categories)
30+Scope::Scope(QString const& id, QString const& name, bool visible, Scopes* parent, int categories)
2831 : unity::shell::scopes::ScopeInterface(parent)
2932 , m_id(id)
3033 , m_name(name)
@@ -34,6 +37,7 @@ Scope::Scope(QString const& id, QString const& name, bool visible, QObject* pare
3437 , m_currentDeparment("root")
3538 , m_previewRendererName("preview-generic")
3639 , m_categories(new Categories(categories, this))
40+ , m_openScope(nullptr)
3741{
3842}
3943
@@ -149,7 +153,12 @@ void Scope::setNoResultsHint(const QString& str)
149153
150154void Scope::activate(QVariant const& result)
151155{
152− Q_UNUSED(result);
156+ qDebug() << "Called activate on scope" << m_id << "with result" << result;
157+ if (result.toString() == "Result.2.2") {
158+ Scopes *scopes = dynamic_cast<Scopes*>(parent());
159+ m_openScope = scopes->getScopeFromAll("MockScope9");
160+ Q_EMIT openScope(m_openScope);
161+ }
153162}
154163
155164PreviewStack* Scope::preview(QVariant const& result)
@@ -165,9 +174,13 @@ void Scope::cancelActivation()
165174{
166175}
167176
168−void Scope::closeScope(unity::shell::scopes::ScopeInterface* /*scope*/)
177+void Scope::closeScope(unity::shell::scopes::ScopeInterface* scope)
169178{
170− qFatal("Scope::closeScope is not implemented");
179+ if (scope != m_openScope) {
180+ qDebug() << scope << m_openScope;
181+ qFatal("Scope::closeScope got wrong scope in closeScope");
182+ }
183+ m_openScope = nullptr;
171184}
172185
173186QString Scope::currentDepartmentId() const
tests/mocks/Unity/fake_scope.h+6−3
@@ -24,13 +24,15 @@
2424
2525#include <QTimer>
2626
27+class Scopes;
28+
2729class Scope : public unity::shell::scopes::ScopeInterface
2830{
2931 Q_OBJECT
3032
3133public:
32− Scope(QObject* parent = 0);
33− Scope(QString const& id, QString const& name, bool visible, QObject* parent = 0, int categories = 20);
34+ Scope(Scopes* parent = 0);
35+ Scope(QString const& id, QString const& name, bool visible, Scopes* parent = 0, int categories = 20);
3436
3537 /* getters */
3638 QString id() const override;
@@ -84,7 +86,8 @@ protected:
8486
8587 QString m_previewRendererName;
8688
87− Categories* m_categories;
89+ unity::shell::scopes::CategoriesInterface* m_categories;
90+ unity::shell::scopes::ScopeInterface* m_openScope;
8891};
8992
9093#endif // FAKE_SCOPE_H
tests/mocks/Unity/fake_scopes.cpp+42−1
@@ -18,6 +18,7 @@
1818
1919// Self
2020#include "fake_scopes.h"
21+#include "fake_scopesoverview.h"
2122
2223// TODO: Implement remaining pieces, like Categories (i.e. LensView now gives warnings)
2324
@@ -26,6 +27,7 @@
2627
2728Scopes::Scopes(QObject *parent)
2829 : unity::shell::scopes::ScopesInterface(parent)
30+ , m_scopesOverview(nullptr)
2931 , m_loaded(false)
3032 , timer(this)
3133{
@@ -47,10 +49,17 @@ void Scopes::updateScopes()
4749 addScope(new Scope("clickscope", "Apps", true, this));
4850 addScope(new Scope("MockScope5", "Videos", true, this));
4951 addScope(new Scope("SingleCategoryScope", "Single", true, this, 1));
52+ addScope(new Scope("MockScope4", "MS4", true, this));
53+ addScope(new Scope("MockScope6", "MS6", true, this));
54+ addScope(new Scope("MockScope7", "MS7", false, this));
55+ addScope(new Scope("MockScope8", "MS8", false, this));
56+ addScope(new Scope("MockScope9", "MS9", false, this));
57+ m_scopesOverview = new ScopesOverview(this);
5058
5159 if (!m_loaded) {
5260 m_loaded = true;
5361 Q_EMIT loadedChanged();
62+ Q_EMIT overviewScopeChanged();
5463 }
5564}
5665
@@ -63,6 +72,8 @@ void Scopes::clear()
6372 m_scopes.clear();
6473 endRemoveRows();
6574 }
75+ delete m_scopesOverview;
76+ m_scopesOverview = nullptr;
6677
6778 if (m_loaded) {
6879 m_loaded = false;
@@ -110,8 +121,22 @@ unity::shell::scopes::ScopeInterface* Scopes::getScope(int row) const
110121 return m_scopes[row];
111122}
112123
113−unity::shell::scopes::ScopeInterface* Scopes::getScope(QString const&) const
124+unity::shell::scopes::ScopeInterface* Scopes::getScope(QString const &scope_id) const
114125{
126+ for (Scope *scope : m_scopes) {
127+ // According to mh3 Scopes::getScope should only return non null for visible scopes
128+ if (scope->id() == scope_id && scope->visible())
129+ return scope;
130+ }
131+ return nullptr;
132+}
133+
134+unity::shell::scopes::ScopeInterface* Scopes::getScopeFromAll(const QString& scope_id) const
135+{
136+ for (Scope *scope : m_scopes) {
137+ if (scope->id() == scope_id)
138+ return scope;
139+ }
115140 return nullptr;
116141}
117142
@@ -125,6 +150,22 @@ bool Scopes::loaded() const
125150 return m_loaded;
126151}
127152
153+unity::shell::scopes::ScopeInterface* Scopes::overviewScope() const
154+{
155+ return m_scopesOverview;
156+}
157+
158+QList<unity::shell::scopes::ScopeInterface *> Scopes::scopes(bool onlyVisible) const
159+{
160+ QList<unity::shell::scopes::ScopeInterface *> res;
161+ for (Scope *scope : m_scopes) {
162+ if (!onlyVisible || scope->visible()) {
163+ res << scope;
164+ }
165+ }
166+ return res;
167+}
168+
128169void Scopes::addScope(Scope* scope)
129170{
130171 int index = rowCount();
tests/mocks/Unity/fake_scopes.h+6
@@ -50,12 +50,18 @@ public:
5050 QModelIndex parent ( const QModelIndex & index ) const;
5151
5252 bool loaded() const override;
53+ unity::shell::scopes::ScopeInterface* overviewScope() const override;
54+
55+ // This is used as part of implementation of the other C++ code, not API
56+ QList<unity::shell::scopes::ScopeInterface *> scopes(bool onlyVisible) const;
57+ unity::shell::scopes::ScopeInterface* getScopeFromAll(const QString& scope_id) const;
5358
5459private Q_SLOTS:
5560 void updateScopes();
5661
5762private:
5863 QList<Scope*> m_scopes;
64+ Scope *m_scopesOverview;
5965 bool m_loaded;
6066 QTimer timer;
6167};
tests/mocks/Unity/fake_scopesoverview.cppnew file+280
@@ -0,0 +1,280 @@
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+#include "fake_scopesoverview.h"
18+
19+#include "fake_scopes.h"
20+
21+#include <paths.h>
22+
23+ScopesOverview::ScopesOverview(Scopes* parent)
24+ : Scope("scopesOverview", "Scopes Overview", false, parent)
25+{
26+ delete m_categories; // delete the usual categories, we're not going to use it
27+ m_scopesOverviewCategories = new ScopesOverviewCategories(parent, this);
28+ m_searchCategories = new ScopesOverviewSearchCategories(parent, this);
29+ m_categories = m_scopesOverviewCategories;
30+}
31+
32+void ScopesOverview::setSearchQuery(const QString& search_query)
33+{
34+ Scope::setSearchQuery(search_query);
35+
36+ auto origCategories = m_categories;
37+ if (search_query.isEmpty()) m_categories = m_scopesOverviewCategories;
38+ else m_categories = m_searchCategories;
39+
40+ if (m_categories != origCategories)
41+ Q_EMIT categoriesChanged();
42+}
43+
44+Q_INVOKABLE void ScopesOverview::activate(QVariant const& result)
45+{
46+ Scopes *scopes = dynamic_cast<Scopes*>(parent());
47+ m_openScope = scopes->getScopeFromAll(result.toString());
48+ Q_EMIT openScope(m_openScope);
49+}
50+
51+ScopesOverviewCategories::ScopesOverviewCategories(Scopes *scopes, QObject* parent)
52+ : unity::shell::scopes::CategoriesInterface(parent)
53+ , m_scopes(scopes)
54+{
55+}
56+
57+int ScopesOverviewCategories::rowCount(const QModelIndex& /*parent*/) const
58+{
59+ return 2;
60+}
61+
62+void ScopesOverviewCategories::addSpecialCategory(QString const&, QString const&, QString const&, QString const&, QObject*)
63+{
64+ qFatal("Using un-implemented ScopesOverviewCategories::addSpecialCategory");
65+}
66+
67+bool ScopesOverviewCategories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
68+{
69+ qFatal("Using un-implemented ScopesOverviewCategories::overrideCategoryJson");
70+}
71+
72+QVariant
73+ScopesOverviewCategories::data(const QModelIndex& index, int role) const
74+{
75+ if (!index.isValid()) {
76+ return QVariant();
77+ }
78+
79+ const QString categoryId = index.row() == 0 ? "favorites" : "all";
80+
81+ unity::shell::scopes::ResultsModelInterface *resultsModel = m_resultsModels[index.row()];
82+ if (!resultsModel) {
83+ QObject *that = const_cast<ScopesOverviewCategories*>(this);
84+ resultsModel = new ScopesOverviewResultsModel(m_scopes->scopes(index.row() == 0), categoryId, that);
85+ m_resultsModels[index.row()] = resultsModel;
86+ }
87+ switch (role) {
88+ case RoleCategoryId:
89+ return categoryId;
90+ case RoleName:
91+ return index.row() == 0 ? "Favorites" : "All";
92+ case RoleIcon:
93+ return QVariant();
94+ case RoleRawRendererTemplate:
95+ qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
96+ return QVariant();
97+ case RoleRenderer:
98+ {
99+ QVariantMap map;
100+ map["category-layout"] = "grid";
101+ map["card-size"] = "small";
102+ map["overlay"] = true;
103+ return map;
104+ }
105+ case RoleComponents:
106+ {
107+ QVariantMap map, artMap;
108+ artMap["aspect-ratio"] = "0.5";
109+ artMap["field"] = "art";
110+ map["art"] = artMap;
111+ map["title"] = "HOLA";
112+ return map;
113+ }
114+ case RoleResults:
115+ return QVariant::fromValue(resultsModel);
116+ case RoleCount:
117+ return resultsModel->rowCount();
118+ case RoleHeaderLink:
119+ return QString();
120+ default:
121+ qFatal("Using un-implemented Categories role");
122+ return QVariant();
123+ }
124+}
125+
126+
127+
128+ScopesOverviewSearchCategories::ScopesOverviewSearchCategories(Scopes *scopes, QObject* parent)
129+ : unity::shell::scopes::CategoriesInterface(parent)
130+ , m_scopes(scopes)
131+{
132+}
133+
134+int ScopesOverviewSearchCategories::rowCount(const QModelIndex& /*parent*/) const
135+{
136+ return 2;
137+}
138+
139+void ScopesOverviewSearchCategories::addSpecialCategory(QString const&, QString const&, QString const&, QString const&, QObject*)
140+{
141+ qFatal("Using un-implemented ScopesOverviewSearchCategories::addSpecialCategory");
142+}
143+
144+bool ScopesOverviewSearchCategories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
145+{
146+ qFatal("Using un-implemented ScopesOverviewSearchCategories::overrideCategoryJson");
147+}
148+
149+QVariant
150+ScopesOverviewSearchCategories::data(const QModelIndex& index, int role) const
151+{
152+ if (!index.isValid()) {
153+ return QVariant();
154+ }
155+
156+ const QString categoryId = index.row() == 0 ? "searchA" : "searchB";
157+
158+ unity::shell::scopes::ResultsModelInterface *resultsModel = m_resultsModels[index.row()];
159+ if (!resultsModel) {
160+ QObject *that = const_cast<ScopesOverviewSearchCategories*>(this);
161+ QList<unity::shell::scopes::ScopeInterface *> scopes;
162+ if (index.row() == 0) {
163+ scopes << m_scopes->getScopeFromAll("clickscope") << nullptr << m_scopes->getScopeFromAll("MockScope2");
164+ } else {
165+ scopes << nullptr << m_scopes->getScopeFromAll("MockScope7") << nullptr << m_scopes->getScopeFromAll("MockScope1");
166+ }
167+ resultsModel = new ScopesOverviewResultsModel(scopes, categoryId, that);
168+ m_resultsModels[index.row()] = resultsModel;
169+ }
170+ switch (role) {
171+ case RoleCategoryId:
172+ return categoryId;
173+ case RoleName:
174+ return index.row() == 0 ? "SearchA" : "SearchB";
175+ case RoleIcon:
176+ return QVariant();
177+ case RoleRawRendererTemplate:
178+ qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
179+ return QVariant();
180+ case RoleRenderer:
181+ {
182+ QVariantMap map;
183+ map["category-layout"] = "grid";
184+ map["card-size"] = "small";
185+ map["overlay"] = true;
186+ return map;
187+ }
188+ case RoleComponents:
189+ {
190+ QVariantMap map, artMap;
191+ artMap["aspect-ratio"] = "1";
192+ artMap["field"] = "art";
193+ map["art"] = artMap;
194+ map["title"] = "HOLA";
195+ return map;
196+ }
197+ case RoleResults:
198+ return QVariant::fromValue(resultsModel);
199+ case RoleCount:
200+ return resultsModel->rowCount();
201+ case RoleHeaderLink:
202+ return QString();
203+ default:
204+ qFatal("Using un-implemented Categories role");
205+ return QVariant();
206+ }
207+}
208+
209+
210+ScopesOverviewResultsModel::ScopesOverviewResultsModel(const QList<unity::shell::scopes::ScopeInterface *> &scopes, const QString &categoryId, QObject* parent)
211+ : unity::shell::scopes::ResultsModelInterface(parent)
212+ , m_scopes(scopes)
213+ , m_categoryId(categoryId)
214+{
215+}
216+
217+QString ScopesOverviewResultsModel::categoryId() const
218+{
219+ return m_categoryId;
220+}
221+
222+void ScopesOverviewResultsModel::setCategoryId(QString const& /*id*/)
223+{
224+ qFatal("Calling un-implemented ScopesOverviewResultsModel::setCategoryId");
225+}
226+
227+int ScopesOverviewResultsModel::scopeIndex(QString const& id) const
228+{
229+ const int scopeCount = count();
230+ for (int i = 0; i < scopeCount; ++i) {
231+ if (m_scopes[i]->id() == id)
232+ return i;
233+ }
234+ return -1;
235+}
236+
237+QHash<int, QByteArray> ScopesOverviewResultsModel::roleNames() const
238+{
239+ QHash<int, QByteArray> roles = unity::shell::scopes::ResultsModelInterface::roleNames();
240+ roles[RoleBackground + 1] = "scopeId";
241+ return roles;
242+}
243+
244+int ScopesOverviewResultsModel::rowCount(const QModelIndex& parent) const
245+{
246+ Q_UNUSED(parent);
247+
248+ return m_scopes.count();
249+}
250+
251+int ScopesOverviewResultsModel::count() const
252+{
253+ return rowCount();
254+}
255+
256+QVariant
257+ScopesOverviewResultsModel::data(const QModelIndex& index, int role) const
258+{
259+ unity::shell::scopes::ScopeInterface *scope = m_scopes[index.row()];
260+ switch (role) {
261+ case RoleUri:
262+ case RoleCategoryId:
263+ case RoleDndUri:
264+ return QString();
265+ case RoleResult:
266+ return scope ? scope->id() : QString("Result.%1.%2").arg(categoryId()).arg(index.row());
267+ case RoleTitle:
268+ return scope ? scope->name() : QString("Title.%1.%2").arg(categoryId()).arg(index.row());
269+ case RoleArt:
270+ return qmlDirectory() + "graphics/applicationIcons/dash.png";
271+ case RoleMascot:
272+ case RoleEmblem:
273+ case RoleSummary:
274+ case RoleBackground + 1: // scopeId
275+ return scope ? scope->id() : nullptr;
276+ break;
277+ default:
278+ return QVariant();
279+ }
280+}
tests/mocks/Unity/fake_scopesoverview.hnew 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+#ifndef FAKE_SCOPESOVERVIEW_H
18+#define FAKE_SCOPESOVERVIEW_H
19+
20+#include "fake_scope.h"
21+#include <unity/shell/scopes/ResultsModelInterface.h>
22+
23+class Scopes;
24+
25+class ScopesOverview : public Scope
26+{
27+ Q_OBJECT
28+
29+public:
30+ ScopesOverview(Scopes* parent = 0);
31+
32+ void setSearchQuery(const QString& search_query) override;
33+ Q_INVOKABLE void activate(QVariant const& result) override;
34+
35+private:
36+ unity::shell::scopes::CategoriesInterface *m_scopesOverviewCategories;
37+ unity::shell::scopes::CategoriesInterface *m_searchCategories;
38+};
39+
40+class ScopesOverviewCategories : public unity::shell::scopes::CategoriesInterface
41+{
42+ Q_OBJECT
43+
44+public:
45+ ScopesOverviewCategories(Scopes *scopes, QObject* parent = 0);
46+
47+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
48+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
49+
50+ Q_INVOKABLE void addSpecialCategory(QString const& categoryId, QString const& name, QString const& icon, QString const& rawTemplate, QObject* countObject) override;
51+ Q_INVOKABLE bool overrideCategoryJson(QString const& categoryId, QString const& json) override;
52+
53+private:
54+ mutable QHash<int, unity::shell::scopes::ResultsModelInterface*> m_resultsModels;
55+
56+ Scopes *m_scopes;
57+};
58+
59+class ScopesOverviewSearchCategories : public unity::shell::scopes::CategoriesInterface
60+{
61+ Q_OBJECT
62+
63+public:
64+ ScopesOverviewSearchCategories(Scopes *scopes, QObject* parent = 0);
65+
66+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
67+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
68+
69+ Q_INVOKABLE void addSpecialCategory(QString const& categoryId, QString const& name, QString const& icon, QString const& rawTemplate, QObject* countObject) override;
70+ Q_INVOKABLE bool overrideCategoryJson(QString const& categoryId, QString const& json) override;
71+
72+private:
73+ mutable QHash<int, unity::shell::scopes::ResultsModelInterface*> m_resultsModels;
74+
75+ Scopes *m_scopes;
76+};
77+
78+class ScopesOverviewResultsModel : public unity::shell::scopes::ResultsModelInterface
79+{
80+ Q_OBJECT
81+
82+public:
83+ explicit ScopesOverviewResultsModel(const QList<unity::shell::scopes::ScopeInterface *> &scopes, const QString &categoryId, QObject* parent = 0);
84+
85+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
86+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
87+
88+ /* getters */
89+ QString categoryId() const override;
90+ int count() const override;
91+
92+ /* setters */
93+ void setCategoryId(QString const& id) override;
94+
95+ /* Special API */
96+ Q_INVOKABLE int scopeIndex(QString const& id) const;
97+ QHash<int, QByteArray> roleNames() const override;
98+
99+private:
100+ QList<unity::shell::scopes::ScopeInterface *> m_scopes;
101+ QString m_categoryId;
102+};
103+
104+#endif // FAKE_SCOPESOVERVIEW_H
tests/plugins/Dash/cardcreator/5.res+1
@@ -109,6 +109,7 @@ Label {
109109 anchors { left: parent.left;
110110 leftMargin: units.gu(1);
111111 right: parent.right;
112+ rightMargin: units.gu(1);
112113 top: overlayLoader.top;
113114 topMargin: units.gu(1);
114115}
tests/qmltests/Components/tst_PageHeader.qml+36−6
@@ -36,6 +36,16 @@ Item {
3636
3737 function init() {
3838 searchEnabled = true;
39+
40+ // Reset to initial state
41+ pageHeader.searchQuery = "";
42+ pageHeader.closePopup();
43+ pageHeader.searchHistory.clear();
44+
45+ // Check initial state
46+ var headerContainer = findChild(pageHeader, "headerContainer");
47+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
48+ compare(pageHeader.searchHistory.count, 0);
3949 }
4050
4151 function test_search_disabled() {
@@ -80,9 +90,6 @@ Item {
8090 }
8191
8292 function test_history() {
83− pageHeader.searchHistory.clear()
84− compare(pageHeader.searchHistory.count, 0)
85−
8693 pageHeader.triggerSearch()
8794 typeString("humppa1")
8895 pageHeader.resetSearch()
@@ -166,16 +173,26 @@ Item {
166173 tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
167174
168175 tryCompare(headerContainer.popover, "visible", true);
176+
177+ var searchTextField = findChild(pageHeader, "searchTextField");
178+ compare(searchTextField.focus, true);
179+
180+ var recentSearches = findChild(headerContainer.popover, "recentSearches");
181+ mouseClick(recentSearches.itemAt(0), 0, 0);
182+
183+ compare(pageHeader.searchQuery, "Search2");
184+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
185+ compare(searchTextField.focus, false);
169186 }
170187
171− function test_tap_outside_closes_popup_data() {
188+ function test_popup_closing_data() {
172189 return [
173190 { tag: "with search text", searchText: "foobar", hideSearch: false },
174191 { tag: "without search text", searchText: "", hideSearch: true }
175192 ];
176193 }
177194
178− function test_tap_outside_closes_popup(data) {
195+ function test_popup_closing(data) {
179196 searchEnabled = true;
180197 pageHeader.searchHistory.clear();
181198
@@ -191,13 +208,26 @@ Item {
191208
192209 pageHeader.searchQuery = data.searchText;
193210
194− mouseClick(root, root.width / 2, root.height - 1);
211+ if (data.searchText == "") {
212+ // When the text is empty the user can also close the
213+ // popup by clicking outside the header instead of by starting a search
214+ mouseClick(root, root.width / 2, root.height - 1);
215+ }
195216
196217 tryCompare(headerContainer, "showSearch", !data.hideSearch);
197218 tryCompareFunction(function() { return headerContainer.popover === null; }, true);
198219
199220 pageHeader.resetSearch();
200221 }
222+
223+ function test_search_change_shows_search() {
224+ var headerContainer = findChild(pageHeader, "headerContainer");
225+ compare(headerContainer.showSearch, false);
226+ compare(searchQuery, "");
227+
228+ searchQuery = "H";
229+ compare(headerContainer.showSearch, true);
230+ }
201231 }
202232
203233 Column {
tests/qmltests/Dash/tst_Dash.qml+211−3
@@ -20,8 +20,6 @@ import "../../../qml/Dash"
2020import Ubuntu.Components 0.1
2121import Unity.Test 0.1 as UT
2222
23−// TODO We don't have any tests for the overlay scope functionality.
24−
2523Item {
2624 id: shell
2725 width: units.gu(40)
@@ -86,10 +84,220 @@ Item {
8684 tryCompare(dashContentList, "count", 0);
8785 scopes.load();
8886 tryCompare(scopes, "loaded", true);
89− tryCompare(dashContentList, "count", 4);
87+ tryCompare(dashContentList, "count", 6);
9088
9189 verify(dashContentList != undefined);
9290 tryCompare(dashContentList, "currentIndex", data.visualIndex);
9391 }
92+
93+ function test_dash_overview_show_select_same_favorite() {
94+ // Wait for stuff to be loaded
95+ tryCompare(scopes, "loaded", true);
96+ var dashContentList = findChild(dash, "dashContentList");
97+ tryCompare(dashContentList, "count", 6);
98+ var mockScope1Loader = findChild(dash, "MockScope1 loader");
99+ tryCompareFunction(function() { return mockScope1Loader.item != null; }, true);
100+
101+ // Show the overview
102+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
103+ var overviewController = findInvisibleChild(dash, "overviewController");
104+ tryCompare(overviewController, "progress", 1);
105+
106+ // Make sure tab is where it should
107+ var scopesOverview = findChild(dash, "scopesOverview");
108+ compare(scopesOverview.currentTab, 0);
109+
110+ // Make sure stuff is loaded
111+ var scopesOverviewFavoritesRepeater = findChild(dash, "scopesOverviewFavoritesRepeater");
112+ tryCompare(scopesOverviewFavoritesRepeater, "count", 6);
113+ tryCompareFunction(function() { return scopesOverviewFavoritesRepeater.itemAt(0).item != null; }, true);
114+ waitForRendering(scopesOverviewFavoritesRepeater.itemAt(0).item);
115+
116+ // Click in first item
117+ mouseClick(scopesOverviewFavoritesRepeater.itemAt(0).item, 0, 0);
118+
119+ // Make sure animation went back
120+ tryCompare(overviewController, "progress", 0);
121+ compare(dashContentList.currentIndex, 0);
122+ }
123+
124+ function test_dash_overview_show_select_different_favorite() {
125+ // Wait for stuff to be loaded
126+ tryCompare(scopes, "loaded", true);
127+ var dashContentList = findChild(dash, "dashContentList");
128+ tryCompare(dashContentList, "count", 6);
129+ var mockScope1Loader = findChild(dash, "MockScope1 loader");
130+ tryCompareFunction(function() { return mockScope1Loader.item != null; }, true);
131+
132+ // Show the overview
133+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
134+ var overviewController = findInvisibleChild(dash, "overviewController");
135+ tryCompare(overviewController, "progress", 1);
136+
137+ // Make sure tab is where it should
138+ var scopesOverview = findChild(dash, "scopesOverview");
139+ compare(scopesOverview.currentTab, 0);
140+
141+ // Make sure stuff is loaded
142+ var scopesOverviewFavoritesRepeater = findChild(dash, "scopesOverviewFavoritesRepeater");
143+ tryCompare(scopesOverviewFavoritesRepeater, "count", 6);
144+ tryCompareFunction(function() { return scopesOverviewFavoritesRepeater.itemAt(0).item != null; }, true);
145+ waitForRendering(scopesOverviewFavoritesRepeater.itemAt(1).item);
146+
147+ // Click in first item
148+ mouseClick(scopesOverviewFavoritesRepeater.itemAt(1).item, 0, 0);
149+
150+ // Make sure animation went back
151+ tryCompare(overviewController, "progress", 0);
152+ compare(dashContentList.currentIndex, 1);
153+ }
154+
155+ function test_dash_overview_all_temp_scope_done_from_all() {
156+ // Wait for stuff to be loaded
157+ tryCompare(scopes, "loaded", true);
158+ var dashContentList = findChild(dash, "dashContentList");
159+ tryCompare(dashContentList, "count", 6);
160+ var mockScope1Loader = findChild(dash, "MockScope1 loader");
161+ tryCompareFunction(function() { return mockScope1Loader.item != null; }, true);
162+
163+ // Show the overview
164+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
165+ var overviewController = findInvisibleChild(dash, "overviewController");
166+ tryCompare(overviewController, "progress", 1);
167+
168+ // Make sure tab is where it should
169+ var scopesOverview = findChild(dash, "scopesOverview");
170+ compare(scopesOverview.currentTab, 0);
171+
172+ // Make sure stuff is loaded
173+ var scopesOverviewFavoritesRepeater = findChild(dash, "scopesOverviewFavoritesRepeater");
174+ tryCompare(scopesOverviewFavoritesRepeater, "count", 6);
175+ tryCompareFunction(function() { return scopesOverviewFavoritesRepeater.itemAt(0).item != null; }, true);
176+ waitForRendering(scopesOverviewFavoritesRepeater.itemAt(1).item);
177+
178+ // Click on the all tab
179+ var scopesOverviewAllTabButton = findChild(dash, "scopesOverviewAllTabButton");
180+ mouseClick(scopesOverviewAllTabButton, 0, 0);
181+
182+ // Wait for all tab to be enabled (animation finish)
183+ var scopesOverviewAllView = findChild(dash, "scopesOverviewRepeaterChild1");
184+ tryCompare(scopesOverviewAllView, "enabled", true);
185+
186+ // Click on a temp scope
187+ var tempScopeCard = findChild(scopesOverviewAllView, "delegate1");
188+ mouseClick(tempScopeCard, 0, 0);
189+
190+ // Check the bottom edge (overview) is disabled from temp scope
191+ var overviewDragHandle = findChild(dash, "overviewDragHandle");
192+ compare(overviewDragHandle.enabled, false);
193+
194+ // Check temp scope is there
195+ var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
196+ tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope != null; }, true);
197+ tryCompare(scopesOverviewTempScopeItem, "enabled", true);
198+
199+ // Go back
200+ var scopesOverviewTempScopeItemHeader = findChild(scopesOverviewTempScopeItem, "scopePageHeader");
201+ var backButton = findChild(findChild(scopesOverviewTempScopeItemHeader, "innerPageHeader"), "backButton");
202+ mouseClick(backButton, 0, 0);
203+
204+ // Check temp scope is gone
205+ var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
206+ tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope == null; }, true);
207+ tryCompare(scopesOverviewTempScopeItem, "enabled", false);
208+
209+ // Press on done
210+ var scopesOverviewDoneButton = findChild(scopesOverview, "scopesOverviewDoneButton");
211+ mouseClick(scopesOverviewDoneButton, 0, 0);
212+
213+ // Check the dash overview is gone
214+ tryCompare(overviewController, "progress", 0);
215+
216+ // Original list is still on 0
217+ compare(dashContentList.currentIndex, 0);
218+ }
219+
220+ function test_temp_scope_dash_overview_all_search_temp_scope_favorite_from_all() {
221+ // Wait for stuff to be loaded
222+ tryCompare(scopes, "loaded", true);
223+ var dashContentList = findChild(dash, "dashContentList");
224+ tryCompare(dashContentList, "count", 6);
225+ var mockScope1Loader = findChild(dash, "MockScope1 loader");
226+ tryCompareFunction(function() { return mockScope1Loader.item != null; }, true);
227+
228+ // Swipe right to Apps scope
229+ touchFlick(dash, dash.width - 1, units.gu(1), dash.width - units.gu(10), units.gu(1));
230+ tryCompare(dashContentList, "contentX", dashContentList.width);
231+ tryCompare(dashContentList, "currentIndex", 1);
232+
233+ // Click on card that opens temp scope
234+ var dashCategory2 = findChild(dashContentList.currentItem, "dashCategory2");
235+ var card2 = findChild(dashCategory2, "delegate2");
236+ waitForRendering(card2);
237+ mouseClick(card2, card2.width / 2, card2.height / 2);
238+
239+ // Wait for temp scope to be there
240+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
241+ tryCompare(dashTempScopeItem, "x", 0);
242+
243+ // Show the overview
244+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
245+ var overviewController = findInvisibleChild(dash, "overviewController");
246+ tryCompare(overviewController, "progress", 1);
247+
248+ // Make sure tab is where it should
249+ var scopesOverview = findChild(dash, "scopesOverview");
250+ compare(scopesOverview.currentTab, 1);
251+
252+ // Do a search
253+ var scopesOverviewPageHeader = findChild(scopesOverview, "scopesOverviewPageHeader");
254+ var searchButton = findChild(scopesOverviewPageHeader, "search_header_button");
255+ mouseClick(searchButton, 0, 0);
256+
257+ // Type something
258+ keyClick(Qt.Key_H);
259+
260+ // Check results grid is there and the other lists are not
261+ var searchResultsViewer = findChild(scopesOverview, "searchResultsViewer");
262+ var scopesOverviewRepeater = findChild(dash, "scopesOverviewRepeater");
263+ tryCompare(searchResultsViewer, "opacity", 1);
264+ tryCompare(scopesOverviewRepeater, "count", 0);
265+
266+ // Click on a temp scope in the search
267+ var dashCategorysearchA = findChild(searchResultsViewer, "dashCategorysearchA");
268+ var cardTempScope = findChild(dashCategorysearchA, "delegate2");
269+ waitForRendering(cardTempScope);
270+ mouseClick(cardTempScope, cardTempScope.width / 2, cardTempScope.height / 2);
271+
272+ // Check the bottom edge (overview) is disabled from temp scope
273+ var overviewDragHandle = findChild(dash, "overviewDragHandle");
274+ compare(overviewDragHandle.enabled, false);
275+
276+ // Check temp scope is there
277+ var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
278+ tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope != null; }, true);
279+ tryCompare(scopesOverviewTempScopeItem, "enabled", true);
280+
281+ // Go back
282+ var scopesOverviewTempScopeItemHeader = findChild(scopesOverviewTempScopeItem, "scopePageHeader");
283+ var backButton = findChild(findChild(scopesOverviewTempScopeItemHeader, "innerPageHeader"), "backButton");
284+ mouseClick(backButton, 0, 0);
285+
286+ // Check temp scope is gone
287+ var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
288+ tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope == null; }, true);
289+ tryCompare(scopesOverviewTempScopeItem, "enabled", false);
290+
291+ // Press on a favorite
292+ var dashCategorysearchB = findChild(searchResultsViewer, "dashCategorysearchB");
293+ var cardFavSearch = findChild(dashCategorysearchB, "delegate3");
294+ mouseClick(cardFavSearch, 0, 0);
295+
296+ // Check the dash overview is gone
297+ tryCompare(overviewController, "progress", 0);
298+
299+ // Original list went to the favorite
300+ compare(dashContentList.currentIndex, 0);
301+ }
94302 }
95303}
tests/qmltests/Dash/tst_DashContent.qml+4−3
@@ -73,7 +73,7 @@ Item {
7373 function loadScopes() {
7474 scopeLoadedSpy.clear();
7575 scopesModel.load();
76− tryCompare(scopeLoadedSpy, "count", 4);
76+ tryCompare(scopeLoadedSpy, "count", 10);
7777 }
7878
7979 function init() {
@@ -117,7 +117,8 @@ Item {
117117
118118 loadScopes();
119119
120− verify(dashContentList.currentIndex >= 0 && dashContentList.currentIndex < 5);
120+ compare(dashContentList.count, 10);
121+ verify(dashContentList.currentIndex >= 0 && dashContentList.currentIndex < dashContentList.count);
121122 }
122123
123124 function test_show_header_on_list_movement() {
@@ -173,7 +174,7 @@ Item {
173174
174175 // test greater than scope count.
175176 var currentScopeIndex = dashContent.currentIndex;
176− dashContent.setCurrentScopeAtIndex(8, true, false);
177+ dashContent.setCurrentScopeAtIndex(18, true, false);
177178 compare(dashContent.currentIndex, currentScopeIndex, "Scope should not change if changing to greater index than count");
178179 }
179180