on xenial help sign in
merged overview
commit38f4f43690150b547fbe8f4ba89b36fedbde9c2c authorAndrea Cimitan <andrea.cimitan@gmail.com> dateFri 8 Aug 2014, 15:56 (12 years ago) parent0946923e1e53b5
42 files changed+1874−175
data/unity8-dash.conf+1−1 plugins/Dash/CardCreator.js+3−2 plugins/Dash/ScopeStyle.qml+3−2 plugins/Dash/listviewwithpageheader.cpp+10−2 plugins/Dash/listviewwithpageheader.h+3 po/hu.po+6−5 po/sl.po+4−3 po/unity8.pot+70−34 qml/Components/PageHeader.qml+3−1 qml/Components/ResponsiveGridView.qml+3−3 qml/Dash/CardCarousel.qml+2−2 qml/Dash/CardGrid.qml+13−2 qml/Dash/CardVerticalJournal.qml+1−1 qml/Dash/Dash.qml+171−13 qml/Dash/DashBackground.qml+24 qml/Dash/DashContent.qml+10−4 qml/Dash/DashRenderer.qml+3−1 qml/Dash/GenericScopeView.qml+84−46 qml/Dash/PreviewListView.qml+1−1 qml/Dash/ScopesOverview.qml+531 qml/Dash/ScopesOverviewAll.qml+54 qml/Dash/ScopesOverviewFavorites.qml+73 qml/Dash/ScopesOverviewTab.qml+74 qml/Dash/graphics/dark_background.jpg qml/Dash/graphics/overview_hint@27.png tests/autopilot/unity8/shell/tests/test_emulators.py+1−1 tests/mocks/Unity/CMakeLists.txt+1 tests/mocks/Unity/fake_resultsmodel.cpp+2−1 tests/mocks/Unity/fake_scope.cpp+17−5 tests/mocks/Unity/fake_scope.h+6−3 tests/mocks/Unity/fake_scopes.cpp+45−6 tests/mocks/Unity/fake_scopes.h+8−1 tests/mocks/Unity/fake_scopesoverview.cpp+281 tests/mocks/Unity/fake_scopesoverview.h+104 tests/plugins/Dash/cardcreator/2.res+2−2 tests/plugins/Dash/cardcreator/5.res+6−5 tests/plugins/Dash/cardcreator/6.res+2−2 tests/plugins/Dash/cardcreator/7.res+3−3 tests/plugins/Dash/tst_ScopeStyle.qml+1−1 tests/qmltests/Dash/tst_Dash.qml+229−7 tests/qmltests/Dash/tst_DashContent.qml+7−6 tests/qmltests/Dash/tst_GenericScopeView.qml+12−9
data/unity8-dash.conf+1−1
@@ -13,7 +13,7 @@ respawn
1313pre-start script
1414 if [ -z "$UNITY_SCOPES_LIST" ]; then
1515 # FIXME: remove once we have this in dconf
16− initctl set-env UNITY_SCOPES_LIST="scopes;clickscope;musicaggregator;videoaggregator"
16+ initctl set-env UNITY_SCOPES_LIST="clickscope;musicaggregator;videoaggregator"
1717 fi
1818
1919 initctl emit scope-ui-starting
plugins/Dash/CardCreator.js+3−2
@@ -108,6 +108,7 @@ var kOverlayLoaderCode = 'Loader { \n\
108108 sourceComponent: ShaderEffect { \n\
109109 id: overlay; \n\
110110 height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2); \n\
111+ property real luminance: 0.2126 * overlayColor.r + 0.7152 * overlayColor.g + 0.0722 * overlayColor.b; \n\
111112 property color overlayColor: cardData && cardData["overlayColor"] || "#99000000"; \n\
112113 property var source: ShaderEffectSource { \n\
113114 id: shaderSource; \n\
@@ -436,14 +437,14 @@ function cardString(template, components) {
436437 mascotCode = kMascotImageCode.arg(mascotAnchors).arg(mascotImageVisible);
437438 }
438439
439− var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey")';
440+ 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")';
440441
441442 var hasTitleContainer = hasTitle && (hasEmblem || (hasMascot && (hasSubtitle || hasAttributes)));
442443 var titleSubtitleCode = '';
443444 if (hasTitle) {
444445 var titleColor;
445446 if (headerAsOverlay) {
446− titleColor = '"white"';
447+ titleColor = 'overlayLoader.item.luminance < (root.scopeStyle ? root.scopeStyle.threshold : 0.7) ? (root.scopeStyle ? root.scopeStyle.light : "white") : (root.scopeStyle ? root.scopeStyle.dark : "grey")';
447448 } else if (hasSummary) {
448449 titleColor = 'summary.color';
449450 } else if (hasBackground) {
plugins/Dash/ScopeStyle.qml+3−2
@@ -49,9 +49,10 @@ QtObject {
4949
5050 /*! \brief Luminance threshold for switching between fore and background color
5151
52− \note If background colour is not fully opaque, it's not taken into account.
52+ \note If background colour is not fully opaque, the defaultLightLuminance it's taken into account instead of it.
5353 */
54− readonly property real threshold: background.a !== 1.0 ? foregroundLuminance : (foregroundLuminance + backgroundLuminance) / 2
54+ readonly property real threshold: background.a !== 1.0 ? (foregroundLuminance + d.defaultLightLuminance) / 2
55+ : (foregroundLuminance + backgroundLuminance) / 2
5556
5657 /*! \brief The lighter of foreground and background colors
5758
plugins/Dash/listviewwithpageheader.cpp+10−2
@@ -330,6 +330,11 @@ int ListViewWithPageHeader::stickyHeaderHeight() const
330330 return m_topSectionItem ? m_topSectionItem->height() : 0;
331331}
332332
333+qreal ListViewWithPageHeader::headerItemShownHeight() const
334+{
335+ return m_headerItemShownHeight;
336+}
337+
333338void ListViewWithPageHeader::positionAtBeginning()
334339{
335340 if (m_delegateModel->count() <= 0)
@@ -396,6 +401,7 @@ void ListViewWithPageHeader::showHeader()
396401 firstItem->setY(firstItem->y() - m_headerItemShownHeight);
397402 layout();
398403 }
404+ Q_EMIT headerItemShownHeightChanged();
399405 }
400406 m_contentYAnimation->setTo(to);
401407 contentYAnimationType = ContentYAnimationShowHeader;
@@ -544,6 +550,7 @@ void ListViewWithPageHeader::adjustHeader(qreal diff)
544550 m_headerItem->setY(-m_minYExtent);
545551 }
546552 }
553+ Q_EMIT headerItemShownHeightChanged();
547554 } else {
548555 // Stick the header item to the top when dragging down
549556 m_headerItem->setY(contentY());
@@ -822,8 +829,8 @@ ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIn
822829 ListItem *nextItem = itemAtIndex(modelIndex + 1);
823830 if (nextItem) {
824831 listItem->setY(nextItem->y() - listItem->height());
825− } else if (modelIndex == 0 && m_headerItem) {
826− listItem->setY(m_headerItem->height());
832+ } else if (modelIndex == 0) {
833+ listItem->setY(m_headerItem ? m_headerItem->height() : 0);
827834 } else if (!m_visibleItems.isEmpty()) {
828835 lostItem = true;
829836 }
@@ -1096,6 +1103,7 @@ void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal ol
10961103 m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, newHeaderHeight);
10971104 updateClipItem();
10981105 adjustMinYExtent();
1106+ Q_EMIT headerItemShownHeightChanged();
10991107 } else {
11001108 if (oldHeaderY + oldHeaderHeight > contentY()) {
11011109 // If the header is shown because its position
plugins/Dash/listviewwithpageheader.h+3
@@ -53,6 +53,7 @@ class ListViewWithPageHeader : public QQuickFlickable, public QQuickItemChangeLi
5353 Q_PROPERTY(QString sectionProperty READ sectionProperty WRITE setSectionProperty NOTIFY sectionPropertyChanged)
5454 Q_PROPERTY(bool forceNoClip READ forceNoClip WRITE setForceNoClip NOTIFY forceNoClipChanged)
5555 Q_PROPERTY(int stickyHeaderHeight READ stickyHeaderHeight NOTIFY stickyHeaderHeightChanged)
56+ Q_PROPERTY(qreal headerItemShownHeight READ headerItemShownHeight NOTIFY headerItemShownHeightChanged)
5657
5758 friend class ListViewWithPageHeaderTest;
5859 friend class ListViewWithPageHeaderTestSection;
@@ -81,6 +82,7 @@ public:
8182 void setForceNoClip(bool noClip);
8283
8384 int stickyHeaderHeight() const;
85+ qreal headerItemShownHeight() const;
8486
8587 Q_INVOKABLE void positionAtBeginning();
8688 Q_INVOKABLE void showHeader();
@@ -100,6 +102,7 @@ Q_SIGNALS:
100102 void sectionPropertyChanged();
101103 void forceNoClipChanged();
102104 void stickyHeaderHeightChanged();
105+ void headerItemShownHeightChanged();
103106
104107protected:
105108 void componentComplete() override;
po/hu.po+6−5
@@ -6,16 +6,17 @@
66msgid ""
77msgstr ""
88"Project-Id-Version: unity\n"
9−"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9+"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
1111"PO-Revision-Date: 2014-08-06 07:41+0000\n"
1212"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
1313"Language-Team: Hungarian <hu@li.org>\n"
14+"Language: hu\n"
1415"MIME-Version: 1.0\n"
1516"Content-Type: text/plain; charset=UTF-8\n"
1617"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-08-08 07:07+0000\n"
18−"X-Generator: Launchpad (build 17156)\n"
18+"X-Launchpad-Export-Date: 2014-08-07 06:27+0000\n"
19+"X-Generator: Launchpad (build 17147)\n"
1920
2021#: plugins/Unity/Launcher/launcheritem.cpp:43
2122#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -73,8 +74,8 @@ msgid ""
7374"You have now mastered the edge gestures and can start using the "
7475"phone<br><br>Tap on the screen to start"
7576msgstr ""
76−"Elsajátította a gesztusokat, és megkezdheti a telefon "
77−"használatát.<br><br>Koppintson a képernyőre a kezdéshez."
77+"Elsajátította a gesztusokat, és megkezdheti a telefon használatát."
78+"<br><br>Koppintson a képernyőre a kezdéshez."
7879
7980#: qml/Components/Lockscreen.qml:214
8081msgid "Emergency Call"
po/sl.po+4−3
@@ -6,16 +6,17 @@
66msgid ""
77msgstr ""
88"Project-Id-Version: unity\n"
9−"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9+"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
1111"PO-Revision-Date: 2014-08-06 06:11+0000\n"
1212"Last-Translator: Damir Jerovšek <Unknown>\n"
1313"Language-Team: Slovenian <sl@li.org>\n"
14+"Language: sl\n"
1415"MIME-Version: 1.0\n"
1516"Content-Type: text/plain; charset=UTF-8\n"
1617"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-08-08 07:07+0000\n"
18−"X-Generator: Launchpad (build 17156)\n"
18+"X-Launchpad-Export-Date: 2014-08-07 06:27+0000\n"
19+"X-Generator: Launchpad (build 17147)\n"
1920
2021#: plugins/Unity/Launcher/launcheritem.cpp:43
2122#: plugins/Unity/Launcher/launcheritem.cpp:73
po/unity8.pot+70−34
@@ -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-08-08 11:17+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"
@@ -26,66 +26,82 @@ msgstr ""
2626msgid "Unpin shortcut"
2727msgstr ""
2828
29+#: qml/Components/Dialogs.qml:142
30+msgid "Are you sure you would like to turn power off?"
31+msgstr ""
32+
33+#: qml/Components/Dialogs.qml:144
34+msgid "Power off"
35+msgstr ""
36+
37+#: qml/Components/Dialogs.qml:153
38+msgid "Restart"
39+msgstr ""
40+
41+#: qml/Components/Dialogs.qml:162
42+msgid "Cancel"
43+msgstr ""
44+
2945#: qml/Components/EdgeDemoOverlay.qml:151
3046msgid "Skip intro"
3147msgstr ""
3248
33−#: qml/Components/EdgeDemo.qml:113
49+#: qml/Components/EdgeDemo.qml:112
3450msgid "Right edge"
3551msgstr ""
3652
37−#: qml/Components/EdgeDemo.qml:114
53+#: qml/Components/EdgeDemo.qml:113
3854msgid "Try swiping from the right edge to unlock the phone"
3955msgstr ""
4056
41−#: qml/Components/EdgeDemo.qml:145
57+#: qml/Components/EdgeDemo.qml:144
4258msgid "Top edge"
4359msgstr ""
4460
45−#: qml/Components/EdgeDemo.qml:146
61+#: qml/Components/EdgeDemo.qml:145
4662msgid "Try swiping from the top edge to access the indicators"
4763msgstr ""
4864
49−#: qml/Components/EdgeDemo.qml:171
65+#: qml/Components/EdgeDemo.qml:170
5066msgid "Close"
5167msgstr ""
5268
53−#: qml/Components/EdgeDemo.qml:172
69+#: qml/Components/EdgeDemo.qml:171
5470msgid "Swipe up again to close the settings screen"
5571msgstr ""
5672
57−#: qml/Components/EdgeDemo.qml:199
73+#: qml/Components/EdgeDemo.qml:198
5874msgid "Left edge"
5975msgstr ""
6076
61−#: qml/Components/EdgeDemo.qml:200
77+#: qml/Components/EdgeDemo.qml:199
6278msgid "Swipe from the left to reveal the launcher for quick access to apps"
6379msgstr ""
6480
65−#: qml/Components/EdgeDemo.qml:227
81+#: qml/Components/EdgeDemo.qml:226
6682msgid "Well done"
6783msgstr ""
6884
69−#: qml/Components/EdgeDemo.qml:228
85+#: qml/Components/EdgeDemo.qml:227
7086msgid ""
7187"You have now mastered the edge gestures and can start using the "
7288"phone<br><br>Tap on the screen to start"
7389msgstr ""
7490
75−#: qml/Components/Lockscreen.qml:214
91+#: qml/Components/Lockscreen.qml:220
7692msgid "Emergency Call"
7793msgstr ""
7894
79−#: qml/Components/Lockscreen.qml:231
95+#: qml/Components/Lockscreen.qml:243
8096msgid "OK"
8197msgstr ""
8298
83−#: qml/Components/PassphraseLockscreen.qml:60
99+#: qml/Components/PassphraseLockscreen.qml:62
84100#, qt-format
85101msgid "Hello %1"
86102msgstr ""
87103
88−#: qml/Components/PassphraseLockscreen.qml:60
104+#: qml/Components/PassphraseLockscreen.qml:62
89105msgid "Hello"
90106msgstr ""
91107
@@ -97,19 +113,11 @@ msgstr ""
97113msgid "DONE"
98114msgstr ""
99115
100−#: qml/Components/SeeMore.qml:35
101−msgid "See more"
102−msgstr ""
103−
104−#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
116+#: qml/Dash/GenericScopeView.qml:358
105117msgid "See less"
106118msgstr ""
107119
108−#: qml/Dash/DashApps.qml:37
109−msgid "Recent"
110−msgstr ""
111−
112−#: qml/Dash/GenericScopeView.qml:330
120+#: qml/Dash/GenericScopeView.qml:358
113121msgid "See all"
114122msgstr ""
115123
@@ -133,6 +141,26 @@ msgstr ""
133141msgid "Send"
134142msgstr ""
135143
144+#: qml/Dash/ScopesOverview.qml:200
145+msgid "Manage Dash"
146+msgstr ""
147+
148+#: qml/Dash/ScopesOverview.qml:405
149+msgid "Done"
150+msgstr ""
151+
152+#: qml/Dash/ScopesOverview.qml:431
153+msgid "Store"
154+msgstr ""
155+
156+#: qml/Dash/ScopesOverviewTab.qml:36
157+msgid "Favorites"
158+msgstr ""
159+
160+#: qml/Dash/ScopesOverviewTab.qml:54
161+msgid "All"
162+msgstr ""
163+
136164#: qml/Greeter/Greeter.qml:157
137165msgid "Swipe to unlock"
138166msgstr ""
@@ -157,11 +185,11 @@ msgstr ""
157185msgid "Speaking..."
158186msgstr ""
159187
160−#: qml/Notifications/NotificationMenuItemFactory.qml:91
188+#: qml/Notifications/NotificationMenuItemFactory.qml:100
161189msgid "Show password"
162190msgstr ""
163191
164−#: qml/Notifications/NotificationMenuItemFactory.qml:103
192+#: qml/Notifications/NotificationMenuItemFactory.qml:115
165193msgid "Please enter SIM PIN"
166194msgstr ""
167195
@@ -173,35 +201,43 @@ msgstr ""
173201msgid "Conference"
174202msgstr ""
175203
176−#: qml/Panel/Indicators/MenuItemFactory.qml:583
204+#: qml/Panel/Indicators/MenuItemFactory.qml:651
177205msgid "In queue…"
178206msgstr ""
179207
180−#: qml/Panel/Indicators/MenuItemFactory.qml:587
208+#: qml/Panel/Indicators/MenuItemFactory.qml:655
181209msgid "Downloading"
182210msgstr ""
183211
184−#: qml/Panel/Indicators/MenuItemFactory.qml:589
212+#: qml/Panel/Indicators/MenuItemFactory.qml:657
185213msgid "Paused, tap to resume"
186214msgstr ""
187215
188−#: qml/Panel/Indicators/MenuItemFactory.qml:591
216+#: qml/Panel/Indicators/MenuItemFactory.qml:659
189217msgid "Canceled"
190218msgstr ""
191219
192−#: qml/Panel/Indicators/MenuItemFactory.qml:593
220+#: qml/Panel/Indicators/MenuItemFactory.qml:661
193221msgid "Finished"
194222msgstr ""
195223
196−#: qml/Panel/Indicators/MenuItemFactory.qml:595
224+#: qml/Panel/Indicators/MenuItemFactory.qml:663
197225msgid "Failed, tap to retry"
198226msgstr ""
199227
228+#: qml/Panel/Indicators/ModemInfoItem.qml:105
229+msgid "Unlock SIM"
230+msgstr ""
231+
232+#: qml/Panel/Indicators/RoamingIndication.qml:27
233+msgid "Roaming"
234+msgstr ""
235+
200236#: qml/Panel/SearchIndicator.qml:27
201237msgid "Search"
202238msgstr ""
203239
204−#: qml/Shell.qml:359
240+#: qml/Shell.qml:256
205241#, qt-format
206242msgid "Please enter %1"
207243msgstr ""
qml/Components/PageHeader.qml+3−1
@@ -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
@@ -32,6 +32,7 @@ Item {
3232 property bool searchEntryEnabled: false
3333 property ListModel searchHistory: SearchHistoryModel
3434 property alias searchQuery: searchTextField.text
35+ property alias showSignatureLine: bottomBorder.visible
3536
3637 property alias bottomItem: bottomContainer.children
3738 property int paginationCount: 0
@@ -251,6 +252,7 @@ Item {
251252
252253 actions: [
253254 Action {
255+ objectName: "search"
254256 iconName: "search"
255257 visible: root.searchEntryEnabled
256258 onTriggered: {
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) }
5353
5454 sourceComponent: cardTool.cardComponent
5555 onLoaded: {
qml/Dash/CardGrid.qml+13−2
@@ -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,7 +73,7 @@ DashRenderer {
6273 }
6374 Connections {
6475 target: loader.item
65− onClicked: root.clicked(index, result)
76+ onClicked: root.clicked(index, result, loader.item, model)
6677 onPressAndHold: root.pressAndHold(index)
6778 }
6879 }
qml/Dash/CardVerticalJournal.qml+1−1
@@ -61,7 +61,7 @@ DashRenderer {
6161 }
6262 Connections {
6363 target: loader.item
64− onClicked: root.clicked(index, result)
64+ onClicked: root.clicked(index, result, loader.item, model)
6565 onPressAndHold: root.pressAndHold(index)
6666 }
6767 }
qml/Dash/Dash.qml+171−13
@@ -16,6 +16,7 @@
1616
1717import QtQuick 2.2
1818import Ubuntu.Components 0.1
19+import Ubuntu.Gestures 0.1
1920import Unity 0.2
2021import Utils 0.1
2122import Unity.DashCommunicator 0.1
@@ -73,26 +74,104 @@ Showable {
7374 id: scopes
7475 }
7576
76− Image {
77+ QtObject {
78+ id: overviewController
79+ objectName: "overviewController"
80+
81+ property alias enableAnimation: progressAnimation.enabled
82+ property real progress: 0
83+ Behavior on progress {
84+ id: progressAnimation
85+ UbuntuNumberAnimation { }
86+ }
87+ }
88+
89+ ScopesOverview {
90+ id: scopesOverview
91+ objectName: "scopesOverview"
7792 anchors.fill: parent
78− source: parent.width > parent.height ? "graphics/paper_landscape.png" : "graphics/paper_portrait.png"
79− fillMode: Image.PreserveAspectCrop
80− horizontalAlignment: Image.AlignRight
81− verticalAlignment: Image.AlignTop
93+ scope: scopes.overviewScope
94+ progress: overviewController.progress
95+ scopeScale: scopeItem.scope ? 0.4 : (1 - overviewController.progress * 0.6)
96+ visible: scopeScale != 1
97+ currentIndex: dashContent.currentIndex
98+ onDone: {
99+ if (currentTab == 1) {
100+ animateDashFromAll(dashContent.currentScopeId);
101+ }
102+ hide();
103+ }
104+ onFavoriteSelected: {
105+ setCurrentScope(scopeId, false, false);
106+ dashContentCache.scheduleUpdate();
107+ hide();
108+ }
109+ onAllFavoriteSelected: {
110+ setCurrentScope(scopeId, false, false);
111+ dashContentCache.scheduleUpdate();
112+ animateDashFromAll(dashContent.currentScopeId);
113+ hide();
114+ }
115+ onSearchSelected: {
116+ var scopeIndex = -1;
117+ for (var i = 0; i < scopes.count; ++i) {
118+ if (scopes.getScope(i).id == scopeId) {
119+ scopeIndex = i;
120+ break;
121+ }
122+ }
123+ if (scopeIndex >= 0) {
124+ // Is a favorite one
125+ setCurrentScope(scopeId, false, false);
126+ dashContentCache.scheduleUpdate();
127+ showDashFromPos(pos, size);
128+ hide();
129+ } else {
130+ // Is not a favorite one, activate and get openScope
131+ scope.activate(result);
132+ }
133+ }
134+ function hide() {
135+ overviewController.enableAnimation = true;
136+ overviewController.progress = 0;
137+ }
138+ onProgressChanged: {
139+ if (progress == 0) {
140+ currentTab = scopeItem.scope ? 1 : 0;
141+ }
142+ }
143+ }
144+
145+ ShaderEffectSource {
146+ id: dashContentCache
147+ parent: scopesOverview.dashItemEater
148+ z: 1
149+ sourceItem: dashContent
150+ height: sourceItem.height
151+ width: sourceItem.width
152+ opacity: 1 - overviewController.progress
153+ visible: overviewController.progress != 0 && scopeItem.scope === null
154+ live: false
82155 }
83156
84157 DashContent {
85158 id: dashContent
159+
160+ property var scopeThatOpenedScope: null
161+
86162 objectName: "dashContent"
87− width: parent.width
88− height: parent.height
163+ width: dash.width
164+ height: dash.height
89165 scopes: scopes
90− visible: x != -width
166+ visible: !scopesOverview.showingNonFavoriteScope && x != -width
91167 onGotoScope: {
92168 dash.setCurrentScope(scopeId, true, false);
93169 }
94170 onOpenScope: {
171+ scopeThatOpenedScope = currentScope;
95172 scopeItem.scope = scope;
173+ scopesOverview.currentTab = 1;
174+ scopesOverview.ensureAllScopeVisible(scope.id);
96175 x = -width;
97176 }
98177 onScopeLoaded: {
@@ -102,25 +181,63 @@ Showable {
102181 }
103182 }
104183 scale: dash.contentScale
105− clip: scale != 1.0 || scopeItem.visible
184+ clip: scale != 1.0 || scopeItem.visible || overviewController.progress != 0
106185 Behavior on x {
107186 UbuntuNumberAnimation {
187+ duration: overviewController.progress != 0 ? 0 : UbuntuAnimation.FastDuration
108188 onRunningChanged: {
109189 if (!running && dashContent.x == 0) {
110− dashContent.closeScope(scopeItem.scope);
190+ dashContent.scopeThatOpenedScope.closeScope(scopeItem.scope);
111191 scopeItem.scope = null;
192+ if (overviewController.progress == 0) {
193+ // Set tab to Favorites only if we are not showing the overview
194+ scopesOverview.currentTab = 0;
195+ }
112196 }
113197 }
114198 }
115199 }
200+
201+ enabled: overviewController.progress == 0
202+ opacity: enabled ? 1 : 0
203+ }
204+
205+ DashBackground
206+ {
207+ anchors.fill: scopeItem
208+ visible: scopeItem.visible
209+ parent: scopeItem.parent
210+ scale: scopeItem.scale
211+ opacity: scopeItem.opacity
116212 }
117213
118214 GenericScopeView {
119215 id: scopeItem
120− anchors.left: dashContent.right
216+ objectName: "dashTempScopeItem"
217+
218+ readonly property real targetOverviewScale: {
219+ if (scopesOverview.currentTab == 0) {
220+ return 0.4;
221+ } else {
222+ return scopesOverview.allCardSize.width / scopeItem.width;
223+ }
224+ }
225+ readonly property real overviewProgressScale: (1 - overviewController.progress * (1 - targetOverviewScale))
226+ readonly property var targetOverviewPosition: scope ? scopesOverview.allScopeCardPosition(scope.id) : null
227+ readonly property real overviewProgressX: scope && scopesOverview.currentTab == 1 && targetOverviewPosition ?
228+ overviewController.progress * (targetOverviewPosition.x - (width - scopesOverview.allCardSize.width) / 2)
229+ : 0
230+ readonly property real overviewProgressY: scope && scopesOverview.currentTab == 1 && targetOverviewPosition ?
231+ overviewController.progress * (targetOverviewPosition.y - (height - scopesOverview.allCardSize.height) / 2)
232+ : 0
233+
234+ x: overviewController.progress == 0 ? dashContent.x + width : overviewProgressX
235+ y: overviewController.progress == 0 ? dashContent.y : overviewProgressY
121236 width: parent.width
122237 height: parent.height
123− scale: dash.contentScale
238+ scale: dash.contentScale * overviewProgressScale
239+ enabled: opacity == 1
240+ opacity: 1 - overviewController.progress
124241 clip: scale != 1.0
125242 visible: scope != null
126243 hasBackAction: true
@@ -154,7 +271,7 @@ Showable {
154271 opacity: 0
155272 visible: opacity > 0
156273
157− readonly property bool processing: dashContent.processing || scopeItem.processing
274+ readonly property bool processing: dashContent.processing || scopeItem.processing || scopesOverview.processing
158275
159276 Behavior on opacity {
160277 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
@@ -207,4 +324,45 @@ Showable {
207324 }
208325 }
209326
327+ Image {
328+ source: "graphics/overview_hint.png"
329+ anchors.horizontalCenter: parent.horizontalCenter
330+ opacity: (scopeItem.scope ? scopeItem.pageHeaderTotallyVisible : dashContent.pageHeaderTotallyVisible) &&
331+ (overviewDragHandle.enabled || overviewController.progress != 0) ? 1 : 0
332+ Behavior on opacity {
333+ enabled: overviewController.progress == 0
334+ UbuntuNumberAnimation {}
335+ }
336+ y: parent.height - height * (1 - overviewController.progress * 4)
337+ }
338+
339+ EdgeDragArea {
340+ id: overviewDragHandle
341+ objectName: "overviewDragHandle"
342+ z: 1
343+ direction: Direction.Upwards
344+ enabled: !dashContent.previewShown &&
345+ dashContent.currentScope &&
346+ dashContent.currentScope.searchQuery == "" &&
347+ (overviewController.progress == 0 || dragging)
348+
349+ readonly property real fullMovement: units.gu(20)
350+
351+ anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
352+ height: units.gu(2)
353+
354+ onSceneDistanceChanged: {
355+ if (overviewController.enableAnimation) {
356+ dashContentCache.scheduleUpdate();
357+ }
358+ overviewController.enableAnimation = false;
359+ overviewController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
360+ }
361+
362+ onDraggingChanged: {
363+ overviewController.enableAnimation = true;
364+ overviewController.progress = (overviewController.progress > 0.7) ? 1 : 0;
365+ }
366+ }
367+
210368}
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+10−4
@@ -25,8 +25,14 @@ Item {
2525
2626 property alias scopes: dashContentList.model
2727 readonly property alias currentIndex: dashContentList.currentIndex
28+ readonly property string currentScopeId: dashContentList.currentItem ? dashContentList.currentItem.scopeId : ""
29+ readonly property var currentScope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
30+ readonly property bool previewShown: dashContentList.currentItem && dashContentList.currentItem.item ?
31+ dashContentList.currentItem.item.previewShown : false
2832 readonly property bool processing: dashContentList.currentItem && dashContentList.currentItem.item
2933 && dashContentList.currentItem.item.processing || false
34+ readonly property bool pageHeaderTotallyVisible: dashContentList.currentItem && dashContentList.currentItem.item
35+ && dashContentList.currentItem.item.pageHeaderTotallyVisible || false
3036
3137 signal scopeLoaded(string scopeId)
3238 signal gotoScope(string scopeId)
@@ -77,15 +83,15 @@ Item {
7783 }
7884 }
7985
80− function closeScope(scope) {
81− dashContentList.currentItem.theScope.closeScope(scope)
82− }
83−
8486 Item {
8587 id: dashContentListHolder
8688
8789 anchors.fill: parent
8890
91+ DashBackground {
92+ anchors.fill: parent
93+ }
94+
8995 ListView {
9096 id: dashContentList
9197 objectName: "dashContentList"
qml/Dash/DashRenderer.qml+3−1
@@ -41,7 +41,9 @@ 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
qml/Dash/GenericScopeView.qml+84−46
@@ -25,7 +25,7 @@ import "../Components/ListItems" as ListItems
2525FocusScope {
2626 id: scopeView
2727
28− readonly property alias navigationShown: dashNavigation.showList
28+ readonly property bool navigationShown: pageHeaderLoader.item ? pageHeaderLoader.item.bottomItem[0].showList : false
2929 property var scope: null
3030 property SortFilterProxyModel categories: categoryFilter
3131 property bool isCurrent: false
@@ -33,8 +33,11 @@ FocusScope {
3333 property bool hasBackAction: false
3434 property bool enableHeightBehaviorOnNextCreation: false
3535 property var categoryView: categoryView
36− property alias paginationCount: pageHeader.paginationCount
37− property alias paginationIndex: pageHeader.paginationIndex
36+ property bool showPageHeader: true
37+ readonly property alias previewShown: previewListView.open
38+ property int paginationCount: 0
39+ property int paginationIndex: 0
40+ property alias pageHeaderTotallyVisible: categoryView.pageHeaderTotallyVisible
3841
3942 property var scopeStyle: ScopeStyle {
4043 style: scope ? scope.customizations : {}
@@ -56,6 +59,34 @@ FocusScope {
5659 previewListView.open = false;
5760 }
5861
62+ function itemClicked(index, result, item, itemModel, resultsModel, limitedCategoryItemCount) {
63+ if (scope.id === "scopes" || scope.id == "clickscope") {
64+ // TODO Technically it is possible that calling activate() will make the scope emit
65+ // previewRequested so that we show a preview but there's no scope that does that yet
66+ // so it's not implemented
67+ scope.activate(result)
68+ } else {
69+ openPreview(index, resultsModel, limitedCategoryItemCount);
70+ }
71+ }
72+
73+ function itemPressedAndHeld(index, resultsModel, limitedCategoryItemCount) {
74+ openPreview(index, resultsModel, limitedCategoryItemCount);
75+ }
76+
77+ function openPreview(index, resultsModel, limitedCategoryItemCount) {
78+ if (limitedCategoryItemCount > 0) {
79+ previewLimitModel.model = resultsModel;
80+ previewLimitModel.limit = limitedCategoryItemCount;
81+ previewListView.model = previewLimitModel;
82+ } else {
83+ previewListView.model = resultsModel;
84+ }
85+ previewListView.currentIndex = -1;
86+ previewListView.currentIndex = index;
87+ previewListView.open = true;
88+ }
89+
5990 Binding {
6091 target: scope
6192 property: "isActive"
@@ -72,22 +103,24 @@ FocusScope {
72103 }
73104
74105 onIsCurrentChanged: {
75− pageHeader.resetSearch();
106+ if (showPageHeader) {
107+ pageHeaderLoader.item.resetSearch();
108+ }
76109 previewListView.open = false;
77110 }
78111
79112 Binding {
80113 target: scopeView.scope
81114 property: "searchQuery"
82− value: pageHeader.searchQuery
83− when: isCurrent
115+ value: pageHeaderLoader.item ? pageHeaderLoader.item.searchQuery : ""
116+ when: isCurrent && showPageHeader
84117 }
85118
86119 Binding {
87− target: pageHeader
120+ target: pageHeaderLoader.item
88121 property: "searchQuery"
89122 value: scopeView.scope ? scopeView.scope.searchQuery : ""
90− when: isCurrent
123+ when: isCurrent && showPageHeader
91124 }
92125
93126 Connections {
@@ -114,10 +147,13 @@ FocusScope {
114147 model: scopeView.categories
115148 forceNoClip: previewListView.open
116149 pixelAligned: true
117− interactive: !dashNavigation.showList
150+ interactive: !navigationShown
118151
119152 property string expandedCategoryId: ""
120153
154+ readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
155+ ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
156+
121157 delegate: ListItems.Base {
122158 id: baseItem
123159 objectName: "dashCategory" + category
@@ -223,28 +259,19 @@ FocusScope {
223259 Connections {
224260 target: rendererLoader.item
225261 onClicked: {
226− if (scopeView.scope.id === "scopes" || scopeView.scope.id == "clickscope") {
227− // TODO Technically it is possible that calling activate() will make the scope emit
228− // previewRequested so that we show a preview but there's no scope that does that yet
229− // so it's not implemented
230− scopeView.scope.activate(result)
231− } else {
232− openPreview(index);
233− }
262+ scopeView.itemClicked(index, result, item, itemModel, target.model, categoryItemCount());
234263 }
235− onPressAndHold: openPreview(index)
236264
237− function openPreview(index) {
265+ onPressAndHold: {
266+ scopeView.itemPressedAndHeld(index, target.model, categoryItemCount());
267+ }
268+
269+ function categoryItemCount() {
270+ var categoryItemCount = -1;
238271 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
239− previewLimitModel.model = target.model;
240− previewLimitModel.limit = target.collapsedItemCount;
241− previewListView.model = previewLimitModel;
242− } else {
243− previewListView.model = target.model;
272+ categoryItemCount = target.collapsedItemCount;
244273 }
245− previewListView.currentIndex = -1;
246− previewListView.currentIndex = index;
247− previewListView.open = true;
274+ return categoryItemCount;
248275 }
249276 }
250277 Connections {
@@ -380,26 +407,35 @@ FocusScope {
380407 }
381408 }
382409
383− pageHeader: PageHeader {
384− id: pageHeader
385− objectName: "scopePageHeader"
410+ pageHeader: scopeView.showPageHeader ? pageHeaderLoader : null
411+ Loader {
412+ id: pageHeaderLoader
386413 width: parent.width
387− title: scopeView.scope ? scopeView.scope.name : ""
388− showBackButton: scopeView.hasBackAction
389− searchEntryEnabled: true
390− scopeStyle: scopeView.scopeStyle
391−
392− bottomItem: DashNavigation {
393− id: dashNavigation
394− scope: scopeView.scope
395− width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
396− anchors.right: parent.right
397− windowHeight: scopeView.height
398− windowWidth: scopeView.width
399− scopeStyle: scopeView.scopeStyle
400− }
414+ sourceComponent: scopeView.showPageHeader ? pageHeaderComponent : undefined
415+ Component {
416+ id: pageHeaderComponent
417+ PageHeader {
418+ objectName: "scopePageHeader"
419+ width: parent.width
420+ title: scopeView.scope ? scopeView.scope.name : ""
421+ showBackButton: scopeView.hasBackAction
422+ searchEntryEnabled: true
423+ scopeStyle: scopeView.scopeStyle
424+ paginationCount: scopeView.paginationCount
425+ paginationIndex: scopeView.paginationIndex
426+
427+ bottomItem: DashNavigation {
428+ scope: scopeView.scope
429+ width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
430+ anchors.right: parent.right
431+ windowHeight: scopeView.height
432+ windowWidth: scopeView.width
433+ scopeStyle: scopeView.scopeStyle
434+ }
401435
402− onBackClicked: scopeView.backClicked()
436+ onBackClicked: scopeView.backClicked()
437+ }
438+ }
403439 }
404440 }
405441
@@ -418,7 +454,9 @@ FocusScope {
418454 anchors.left: categoryView.right
419455
420456 onOpenChanged: {
421− pageHeader.unfocus();
457+ if (showPageHeader) {
458+ pageHeaderLoader.item.unfocus();
459+ }
422460 }
423461 }
424462
qml/Dash/PreviewListView.qml+1−1
@@ -108,7 +108,7 @@ Item {
108108 anchors {
109109 left: parent.left
110110 right: parent.right
111− top: pageHeader.bottom
111+ top: header.bottom
112112 bottom: parent.bottom
113113 }
114114
qml/Dash/ScopesOverview.qmlnew file+531
@@ -0,0 +1,531 @@
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
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+ }
93+ }
94+
95+ Binding {
96+ target: scope
97+ property: "isActive"
98+ value: progress === 1
99+ }
100+
101+ function animateDashFromAll(scopeId) {
102+ var currentScopePos = allScopeCardPosition(scopeId);
103+ if (currentScopePos) {
104+ showDashFromPos(currentScopePos, allCardSize);
105+ } else {
106+ console.log("Warning: Could not find Dash OverView All card position for scope", dashContent.currentScopeId);
107+ }
108+ }
109+
110+ function showDashFromPos(itemPos, itemSize) {
111+ scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
112+ scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
113+ scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
114+ scopesOverviewXYScaler.opacity = 0;
115+ root.growingDashFromPos = true;
116+ scopesOverviewXYScaler.scale = 1;
117+ scopesOverviewXYScaler.x = 0;
118+ scopesOverviewXYScaler.y = 0;
119+ scopesOverviewXYScaler.opacity = 1;
120+ }
121+
122+ function allScopeCardPosition(scopeId) {
123+ if (middleItems.count > 1) {
124+ var loaderItem = middleItems.itemAt(1).item;
125+ if (loaderItem) {
126+ var pos = loaderItem.scopeCardPosition(scopeId);
127+ return loaderItem.mapToItem(null, pos.x, pos.y);
128+ }
129+ }
130+ }
131+
132+ function ensureAllScopeVisible(scopeId) {
133+ if (middleItems.count > 1) {
134+ var loaderItem = middleItems.itemAt(1).item;
135+ if (loaderItem) {
136+ var pos = loaderItem.scopeCardPosition(scopeId);
137+ loaderItem.contentY = Math.min(pos.y, loaderItem.contentHeight - loaderItem.height);
138+ }
139+ }
140+ }
141+
142+ onProgressChanged: {
143+ if (progress == 0) {
144+ pageHeader.resetSearch();
145+ pageHeader.unfocus(); // Shouldn't the previous call do this too?
146+ }
147+ }
148+
149+ ScopeStyle {
150+ id: overviewScopeStyle
151+ style: { "foreground-color" : "white", "background-color" : "transparent" }
152+ }
153+
154+ DashBackground {
155+ anchors.fill: parent
156+ source: "graphics/dark_background.jpg"
157+ }
158+
159+ Connections {
160+ target: pageHeader
161+ onSearchQueryChanged: {
162+ // Need this in order, otherwise something gets unhappy in rendering
163+ // of the overlay in carousels because the parent of the dash dies for
164+ // a moment, this way we make sure it's reparented first
165+ // by forceXYScalerEater making dashItemEater return scopesOverviewXYScaler
166+ // before we kill the previous parent by scope.searchQuery
167+ root.forceXYScalerEater = true;
168+ root.scope.searchQuery = pageHeader.searchQuery;
169+ root.forceXYScalerEater = false;
170+ }
171+ }
172+
173+ Binding {
174+ target: pageHeader
175+ property: "searchQuery"
176+ value: scope ? scope.searchQuery : ""
177+ }
178+
179+ Item {
180+ id: scopesOverviewContent
181+ x: previewListView.open ? -width : 0
182+ Behavior on x { UbuntuNumberAnimation { } }
183+ width: parent.width
184+ height: parent.height
185+
186+ PageHeader {
187+ id: pageHeader
188+ objectName: "scopesOverviewPageHeader"
189+
190+ readonly property real yDisplacement: pageHeader.height + tabBar.height + tabBar.anchors.margins
191+
192+ y: {
193+ if (root.progress < 0.5) {
194+ return -yDisplacement;
195+ } else {
196+ return -yDisplacement + (root.progress - 0.5) * yDisplacement * 2;
197+ }
198+ }
199+ width: parent.width
200+ clip: true
201+ title: i18n.tr("Manage Dash")
202+ scopeStyle: overviewScopeStyle
203+ showSignatureLine: false
204+ searchEntryEnabled: true
205+ }
206+
207+ ScopesOverviewTab {
208+ id: tabBar
209+ anchors {
210+ left: parent.left
211+ right: parent.right
212+ top: pageHeader.bottom
213+ margins: units.gu(2)
214+ }
215+ height: units.gu(4)
216+
217+ enabled: opacity == 1
218+ opacity: !scope || scope.searchQuery == "" ? 1 : 0
219+ Behavior on opacity { UbuntuNumberAnimation { } }
220+ }
221+
222+ Repeater {
223+ id: middleItems
224+ objectName: "scopesOverviewRepeater"
225+ property real overrideOpacity: -1
226+ model: scope && scope.searchQuery == "" ? scope.categories : null
227+ delegate: Loader {
228+ id: loader
229+ objectName: "scopesOverviewRepeaterChild" + index
230+
231+ height: {
232+ if (index == 0) {
233+ return root.height;
234+ } else {
235+ return root.height - pageHeader.height - tabBar.height - tabBar.anchors.margins - units.gu(2);
236+ }
237+ }
238+ width: {
239+ if (index == 0) {
240+ return root.width / scopeScale;
241+ } else {
242+ return root.width;
243+ }
244+ }
245+ x: {
246+ if (index == 0) {
247+ return (root.width - width) / 2;
248+ } else {
249+ return 0;
250+ }
251+ }
252+ anchors {
253+ bottom: scopesOverviewContent.bottom
254+ }
255+
256+ scale: index == 0 ? scopeScale : 1
257+
258+ opacity: {
259+ if (middleItems.overrideOpacity >= 0)
260+ return middleItems.overrideOpacity;
261+
262+ if (tabBar.currentTab != index)
263+ return 0;
264+
265+ return index == 0 ? 1 : root.progress;
266+ }
267+ Behavior on opacity {
268+ enabled: root.progress == 1
269+ UbuntuNumberAnimation { }
270+ }
271+ enabled: opacity == 1
272+
273+ clip: index == 1
274+
275+ CardTool {
276+ id: cardTool
277+ objectName: "cardTool"
278+ count: results.count
279+ template: model.renderer
280+ components: model.components
281+ viewWidth: parent.width
282+ }
283+
284+ source: {
285+ if (index == 0 && categoryId == "favorites") return "ScopesOverviewFavorites.qml";
286+ else if (index == 1 && categoryId == "all") return "ScopesOverviewAll.qml";
287+ else return "";
288+ }
289+
290+ onLoaded: {
291+ item.model = Qt.binding(function() { return results; });
292+ item.cardTool = cardTool;
293+ if (index == 0) {
294+ item.scopeWidth = Qt.binding(function() { return root.width; });
295+ item.scopeHeight = Qt.binding(function() { return root.height; });
296+ item.appliedScale = Qt.binding(function() { return loader.scale });
297+ item.currentIndex = Qt.binding(function() { return root.currentIndex });
298+ } else if (index == 1) {
299+ item.extraHeight = bottomBar.height;
300+ }
301+ }
302+
303+ Connections {
304+ target: loader.item
305+ onClicked: {
306+ if (tabBar.currentTab == 0) {
307+ root.favoriteSelected(itemModel.scopeId);
308+ } else {
309+ var favoriteScopesItem = middleItems.itemAt(0).item;
310+ var scopeIndex = favoriteScopesItem.model.scopeIndex(itemModel.scopeId);
311+ if (scopeIndex >= 0) {
312+ root.allFavoriteSelected(itemModel.scopeId);
313+ } else {
314+ // Will result in an openScope from root.scope
315+ scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
316+ scopesOverviewXYScaler.restoreSize = allCardSize;
317+ root.scope.activate(result);
318+ }
319+ }
320+ }
321+ onPressAndHold: {
322+ // Preview can call openScope so make sure restorePosition and restoreSize are set
323+ scopesOverviewXYScaler.restorePosition = undefined;
324+ scopesOverviewXYScaler.restoreSize = allCardSize;
325+
326+ previewListView.model = target.model;
327+ previewListView.currentIndex = -1;
328+ previewListView.currentIndex = index;
329+ previewListView.open = true;
330+ }
331+ }
332+ }
333+ }
334+
335+ GenericScopeView {
336+ id: searchResultsViewer
337+ objectName: "searchResultsViewer"
338+ anchors {
339+ top: pageHeader.bottom
340+ right: parent.right
341+ left: parent.left
342+ bottom: parent.bottom
343+ }
344+ scope: root.scope && root.scope.searchQuery != "" ? root.scope : null
345+ scopeStyle: overviewScopeStyle
346+ enabled: opacity == 1
347+ showPageHeader: false
348+ clip: true
349+ opacity: searchResultsViewer.scope ? 1 : 0
350+ isCurrent: true
351+ Behavior on opacity { UbuntuNumberAnimation { } }
352+
353+ function itemClicked(index, result, item, itemModel, resultsModel, limitedCategoryItemCount) {
354+ pageHeader.closePopup();
355+ if (itemModel.scopeId) {
356+ // This can end up in openScope so save restorePosition and restoreSize
357+ scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
358+ scopesOverviewXYScaler.restoreSize = Qt.size(item.width, item.height);
359+ root.searchSelected(itemModel.scopeId, result, item.mapToItem(null, 0, 0), Qt.size(item.width, item.height));
360+ } else {
361+ // Not a scope, just activate it
362+ searchResultsViewer.scope.activate(result);
363+ }
364+ }
365+
366+ function itemPressedAndHeld(index, resultsModel, limitedCategoryItemCount) {
367+ // Do nothing
368+ }
369+ }
370+
371+ Rectangle {
372+ id: bottomBar
373+ color: "black"
374+ height: units.gu(8)
375+ width: parent.width
376+ enabled: opacity == 1
377+ opacity: scope && scope.searchQuery == "" ? 1 : 0
378+ Behavior on opacity { UbuntuNumberAnimation { } }
379+ y: {
380+ if (root.progress < 0.5) {
381+ return parent.height;
382+ } else {
383+ return parent.height - (root.progress - 0.5) * height * 2;
384+ }
385+ }
386+
387+ AbstractButton {
388+ objectName: "scopesOverviewDoneButton"
389+ width: Math.max(label.width + units.gu(2), units.gu(10))
390+ height: units.gu(4)
391+ anchors {
392+ left: parent.left
393+ leftMargin: units.gu(2)
394+ verticalCenter: parent.verticalCenter
395+ }
396+ Rectangle {
397+ anchors.fill: parent
398+ border.color: "white"
399+ border.width: units.dp(1)
400+ radius: units.dp(10)
401+ color: parent.pressed ? "gray" : "transparent"
402+ }
403+ Label {
404+ id: label
405+ anchors.centerIn: parent
406+ text: i18n.tr("Done")
407+ color: parent.pressed ? "black" : "white"
408+ }
409+ onClicked: root.done();
410+ }
411+
412+ AbstractButton {
413+ objectName: "scopesOverviewStoreButton"
414+ width: Math.max(storeLabel.width, units.gu(10))
415+ height: units.gu(4)
416+ anchors {
417+ right: parent.right
418+ verticalCenter: parent.verticalCenter
419+ }
420+ Icon {
421+ id: storeImage
422+ name: "ubuntu-store-symbolic"
423+ color: "white"
424+ anchors.horizontalCenter: parent.horizontalCenter
425+ width: units.gu(2)
426+ height: units.gu(2)
427+ }
428+ Label {
429+ id: storeLabel
430+ anchors.horizontalCenter: parent.horizontalCenter
431+ anchors.top: storeImage.bottom
432+ text: i18n.tr("Store")
433+ color: "white"
434+ }
435+ onClicked: {
436+ // Just zoom from the middle
437+ scopesOverviewXYScaler.restorePosition = undefined;
438+ scopesOverviewXYScaler.restoreSize = allCardSize;
439+ scope.performQuery("scope://com.canonical.scopes.clickstore");
440+ }
441+ }
442+ }
443+ }
444+
445+ PreviewListView {
446+ id: previewListView
447+ objectName: "scopesOverviewPreviewListView"
448+ scope: root.scope
449+ scopeStyle: overviewScopeStyle
450+ visible: x != width
451+ width: parent.width
452+ height: parent.height
453+ anchors.left: scopesOverviewContent.right
454+ }
455+
456+
457+
458+ Item {
459+ id: scopesOverviewXYScaler
460+ width: parent.width
461+ height: parent.height
462+
463+ clip: scale != 1.0
464+ enabled: scale == 1
465+
466+ property bool animationsEnabled: root.showingNonFavoriteScope || root.growingDashFromPos
467+
468+ property var restorePosition
469+ property var restoreSize
470+
471+ Behavior on x {
472+ enabled: scopesOverviewXYScaler.animationsEnabled
473+ UbuntuNumberAnimation { }
474+ }
475+ Behavior on y {
476+ enabled: scopesOverviewXYScaler.animationsEnabled
477+ UbuntuNumberAnimation { }
478+ }
479+ Behavior on opacity {
480+ enabled: scopesOverviewXYScaler.animationsEnabled
481+ UbuntuNumberAnimation { }
482+ }
483+ Behavior on scale {
484+ enabled: scopesOverviewXYScaler.animationsEnabled
485+ UbuntuNumberAnimation {
486+ onRunningChanged: {
487+ if (!running) {
488+ if (root.showingNonFavoriteScope && scopesOverviewXYScaler.scale != 1) {
489+ root.scope.closeScope(tempScopeItem.scope);
490+ tempScopeItem.scope = null;
491+ } else if (root.growingDashFromPos) {
492+ root.growingDashFromPos = false;
493+ }
494+ }
495+ }
496+ }
497+ }
498+
499+ DashBackground {
500+ anchors.fill: tempScopeItem
501+ visible: tempScopeItem.visible
502+ parent: tempScopeItem.parent
503+ }
504+
505+ GenericScopeView {
506+ id: tempScopeItem
507+ objectName: "scopesOverviewTempScopeItem"
508+
509+ width: parent.width
510+ height: parent.height
511+ scale: dash.contentScale
512+ clip: scale != 1.0
513+ visible: scope != null
514+ hasBackAction: true
515+ isCurrent: visible
516+ onBackClicked: {
517+ var v = scopesOverviewXYScaler.restoreSize.width / tempScopeItem.width;
518+ scopesOverviewXYScaler.scale = v;
519+ if (scopesOverviewXYScaler.restorePosition) {
520+ scopesOverviewXYScaler.x = scopesOverviewXYScaler.restorePosition.x -(tempScopeItem.width - tempScopeItem.width * v) / 2;
521+ scopesOverviewXYScaler.y = scopesOverviewXYScaler.restorePosition.y -(tempScopeItem.height - tempScopeItem.height * v) / 2;
522+ } else {
523+ scopesOverviewXYScaler.x = 0;
524+ scopesOverviewXYScaler.y = 0;
525+ }
526+ scopesOverviewXYScaler.opacity = 0;
527+ middleItems.overrideOpacity = -1;
528+ }
529+ }
530+ }
531+}
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: 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.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/dark_background.jpgnew file

Binary file not shown.

qml/Dash/graphics/overview_hint@27.pngnew file

Binary file not shown.

tests/autopilot/unity8/shell/tests/test_emulators.py+1−1
@@ -122,7 +122,7 @@ class DashEmulatorTestCase(tests.DashBaseTestCase):
122122 self._assert_scope_is_opened(scope, scope_id)
123123
124124 def test_open_generic_scope(self):
125− scope_id = 'scopes'
125+ scope_id = 'musicaggregator'
126126 scope = self.dash.open_scope(scope_id)
127127 self._assert_scope_is_opened(scope, scope_id)
128128 self.assertIsInstance(scope, dash_emulators.GenericScopeView)
tests/mocks/Unity/CMakeLists.txt+1
@@ -22,6 +22,7 @@ add_definitions(-DQT_NO_KEYWORDS)
2222set(UnityQML_SOURCES
2323 fake_scope.cpp
2424 fake_scopes.cpp
25+ fake_scopesoverview.cpp
2526 fake_categories.cpp
2627 fake_navigation.cpp
2728 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+17−5
@@ -18,14 +18,16 @@
1818#include <QUrl>
1919
2020#include "fake_scope.h"
21+
2122#include "fake_navigation.h"
2223#include "fake_resultsmodel.h"
24+#include "fake_scopes.h"
2325
24−Scope::Scope(QObject* parent) : Scope(QString(), QString(), false, parent)
26+Scope::Scope(Scopes* parent) : Scope(QString(), QString(), false, parent)
2527{
2628}
2729
28−Scope::Scope(QString const& id, QString const& name, bool favorite, QObject* parent, int categories)
30+Scope::Scope(QString const& id, QString const& name, bool favorite, Scopes* parent, int categories)
2931 : unity::shell::scopes::ScopeInterface(parent)
3032 , m_id(id)
3133 , m_name(name)
@@ -36,6 +38,7 @@ Scope::Scope(QString const& id, QString const& name, bool favorite, QObject* par
3638 , m_currentAltNavigationId("altroot")
3739 , m_previewRendererName("preview-generic")
3840 , m_categories(new Categories(categories, this))
41+ , m_openScope(nullptr)
3942{
4043}
4144
@@ -158,7 +161,12 @@ void Scope::setNoResultsHint(const QString& str)
158161
159162void Scope::activate(QVariant const& result)
160163{
161− Q_UNUSED(result);
164+ qDebug() << "Called activate on scope" << m_id << "with result" << result;
165+ if (result.toString() == "Result.2.2") {
166+ Scopes *scopes = dynamic_cast<Scopes*>(parent());
167+ m_openScope = scopes->getScopeFromAll("MockScope9");
168+ Q_EMIT openScope(m_openScope);
169+ }
162170}
163171
164172PreviewStack* Scope::preview(QVariant const& result)
@@ -174,9 +182,13 @@ void Scope::cancelActivation()
174182{
175183}
176184
177−void Scope::closeScope(unity::shell::scopes::ScopeInterface* /*scope*/)
185+void Scope::closeScope(unity::shell::scopes::ScopeInterface* scope)
178186{
179− qFatal("Scope::closeScope is not implemented");
187+ if (scope != m_openScope) {
188+ qDebug() << scope << m_openScope;
189+ qFatal("Scope::closeScope got wrong scope in closeScope");
190+ }
191+ m_openScope = nullptr;
180192}
181193
182194QString Scope::currentNavigationId() 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 favorite, QObject* parent = 0, int categories = 20);
34+ Scope(Scopes* parent = 0);
35+ Scope(QString const& id, QString const& name, bool favorite, Scopes* parent = 0, int categories = 20);
3436
3537 /* getters */
3638 QString id() const override;
@@ -92,7 +94,8 @@ protected:
9294
9395 QString m_previewRendererName;
9496
95− Categories* m_categories;
97+ unity::shell::scopes::CategoriesInterface* m_categories;
98+ unity::shell::scopes::ScopeInterface* m_openScope;
9699};
97100
98101#endif // FAKE_SCOPE_H
tests/mocks/Unity/fake_scopes.cpp+45−6
@@ -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{
@@ -52,10 +54,17 @@ void Scopes::updateScopes()
5254 addScope(new Scope("clickscope", "Apps", true, this));
5355 addScope(new Scope("MockScope5", "Videos", true, this));
5456 addScope(new Scope("SingleCategoryScope", "Single", true, this, 1));
57+ addScope(new Scope("MockScope4", "MS4", true, this));
58+ addScope(new Scope("MockScope6", "MS6", true, this));
59+ addScope(new Scope("MockScope7", "MS7", false, this));
60+ addScope(new Scope("MockScope8", "MS8", false, this));
61+ addScope(new Scope("MockScope9", "MS9", false, this));
62+ m_scopesOverview = new ScopesOverview(this);
5563
5664 if (!m_loaded) {
5765 m_loaded = true;
5866 Q_EMIT loadedChanged();
67+ Q_EMIT overviewScopeChanged();
5968 }
6069}
6170
@@ -64,10 +73,13 @@ void Scopes::clear()
6473 timer.stop();
6574 if (m_scopes.size() > 0) {
6675 beginRemoveRows(QModelIndex(), 0, m_scopes.count()-1);
67− qDeleteAll(m_scopes);
76+ qDeleteAll(m_allScopes);
77+ m_allScopes.clear();
6878 m_scopes.clear();
6979 endRemoveRows();
7080 }
81+ delete m_scopesOverview;
82+ m_scopesOverview = nullptr;
7183
7284 if (m_loaded) {
7385 m_loaded = false;
@@ -113,8 +125,22 @@ unity::shell::scopes::ScopeInterface* Scopes::getScope(int row) const
113125 return m_scopes[row];
114126}
115127
116−unity::shell::scopes::ScopeInterface* Scopes::getScope(QString const&) const
128+unity::shell::scopes::ScopeInterface* Scopes::getScope(QString const &scope_id) const
117129{
130+ // According to mh3 Scopes::getScope should only return favorite scopes (i.e the ones in the model)
131+ for (Scope *scope : m_scopes) {
132+ if (scope->id() == scope_id)
133+ return scope;
134+ }
135+ return nullptr;
136+}
137+
138+Scope* Scopes::getScopeFromAll(const QString& scope_id) const
139+{
140+ for (Scope *scope : m_allScopes) {
141+ if (scope->id() == scope_id)
142+ return scope;
143+ }
118144 return nullptr;
119145}
120146
@@ -135,13 +161,26 @@ int Scopes::count() const
135161
136162unity::shell::scopes::ScopeInterface* Scopes::overviewScope() const
137163{
138− return nullptr;
164+ return m_scopesOverview;
165+}
166+
167+QList<Scope*> Scopes::scopes() const
168+{
169+ return m_scopes;
170+}
171+
172+QList<Scope*> Scopes::allScopes() const
173+{
174+ return m_allScopes;
139175}
140176
141177void Scopes::addScope(Scope* scope)
142178{
143179 int index = rowCount();
144− beginInsertRows(QModelIndex(), index, index);
145− m_scopes.append(scope);
146− endInsertRows();
180+ if (scope->favorite()) {
181+ beginInsertRows(QModelIndex(), index, index);
182+ m_scopes.append(scope);
183+ endInsertRows();
184+ }
185+ m_allScopes.append(scope);
147186}
tests/mocks/Unity/fake_scopes.h+8−1
@@ -53,11 +53,18 @@ public:
5353 int count() const override;
5454 unity::shell::scopes::ScopeInterface* overviewScope() const override;
5555
56+ // This is used as part of implementation of the other C++ code, not API
57+ QList<Scope*> scopes() const;
58+ QList<Scope*> allScopes() const;
59+ Scope* getScopeFromAll(const QString& scope_id) const;
60+
5661private Q_SLOTS:
5762 void updateScopes();
5863
5964private:
60− QList<Scope*> m_scopes;
65+ QList<Scope*> m_scopes; // the favorite ones
66+ QList<Scope*> m_allScopes;
67+ Scope *m_scopesOverview;
6168 bool m_loaded;
6269 QTimer timer;
6370};
tests/mocks/Unity/fake_scopesoverview.cppnew file+281
@@ -0,0 +1,281 @@
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+ QList<Scope*> scopes = index.row() == 0 ? m_scopes->scopes() : m_scopes->allScopes();
85+ resultsModel = new ScopesOverviewResultsModel(scopes, categoryId, that);
86+ m_resultsModels[index.row()] = resultsModel;
87+ }
88+ switch (role) {
89+ case RoleCategoryId:
90+ return categoryId;
91+ case RoleName:
92+ return index.row() == 0 ? "Favorites" : "All";
93+ case RoleIcon:
94+ return QVariant();
95+ case RoleRawRendererTemplate:
96+ qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
97+ return QVariant();
98+ case RoleRenderer:
99+ {
100+ QVariantMap map;
101+ map["category-layout"] = "grid";
102+ map["card-size"] = "small";
103+ map["overlay"] = true;
104+ return map;
105+ }
106+ case RoleComponents:
107+ {
108+ QVariantMap map, artMap;
109+ artMap["aspect-ratio"] = "0.5";
110+ artMap["field"] = "art";
111+ map["art"] = artMap;
112+ map["title"] = "HOLA";
113+ return map;
114+ }
115+ case RoleResults:
116+ return QVariant::fromValue(resultsModel);
117+ case RoleCount:
118+ return resultsModel->rowCount();
119+ case RoleHeaderLink:
120+ return QString();
121+ default:
122+ qFatal("Using un-implemented Categories role");
123+ return QVariant();
124+ }
125+}
126+
127+
128+
129+ScopesOverviewSearchCategories::ScopesOverviewSearchCategories(Scopes *scopes, QObject* parent)
130+ : unity::shell::scopes::CategoriesInterface(parent)
131+ , m_scopes(scopes)
132+{
133+}
134+
135+int ScopesOverviewSearchCategories::rowCount(const QModelIndex& /*parent*/) const
136+{
137+ return 2;
138+}
139+
140+void ScopesOverviewSearchCategories::addSpecialCategory(QString const&, QString const&, QString const&, QString const&, QObject*)
141+{
142+ qFatal("Using un-implemented ScopesOverviewSearchCategories::addSpecialCategory");
143+}
144+
145+bool ScopesOverviewSearchCategories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
146+{
147+ qFatal("Using un-implemented ScopesOverviewSearchCategories::overrideCategoryJson");
148+}
149+
150+QVariant
151+ScopesOverviewSearchCategories::data(const QModelIndex& index, int role) const
152+{
153+ if (!index.isValid()) {
154+ return QVariant();
155+ }
156+
157+ const QString categoryId = index.row() == 0 ? "searchA" : "searchB";
158+
159+ unity::shell::scopes::ResultsModelInterface *resultsModel = m_resultsModels[index.row()];
160+ if (!resultsModel) {
161+ QObject *that = const_cast<ScopesOverviewSearchCategories*>(this);
162+ QList<Scope *> scopes;
163+ if (index.row() == 0) {
164+ scopes << m_scopes->getScopeFromAll("clickscope") << nullptr << m_scopes->getScopeFromAll("MockScope2");
165+ } else {
166+ scopes << nullptr << m_scopes->getScopeFromAll("MockScope7") << nullptr << m_scopes->getScopeFromAll("MockScope1");
167+ }
168+ resultsModel = new ScopesOverviewResultsModel(scopes, categoryId, that);
169+ m_resultsModels[index.row()] = resultsModel;
170+ }
171+ switch (role) {
172+ case RoleCategoryId:
173+ return categoryId;
174+ case RoleName:
175+ return index.row() == 0 ? "SearchA" : "SearchB";
176+ case RoleIcon:
177+ return QVariant();
178+ case RoleRawRendererTemplate:
179+ qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
180+ return QVariant();
181+ case RoleRenderer:
182+ {
183+ QVariantMap map;
184+ map["category-layout"] = "grid";
185+ map["card-size"] = "small";
186+ map["overlay"] = true;
187+ return map;
188+ }
189+ case RoleComponents:
190+ {
191+ QVariantMap map, artMap;
192+ artMap["aspect-ratio"] = "1";
193+ artMap["field"] = "art";
194+ map["art"] = artMap;
195+ map["title"] = "HOLA";
196+ return map;
197+ }
198+ case RoleResults:
199+ return QVariant::fromValue(resultsModel);
200+ case RoleCount:
201+ return resultsModel->rowCount();
202+ case RoleHeaderLink:
203+ return QString();
204+ default:
205+ qFatal("Using un-implemented Categories role");
206+ return QVariant();
207+ }
208+}
209+
210+
211+ScopesOverviewResultsModel::ScopesOverviewResultsModel(const QList<Scope *> &scopes, const QString &categoryId, QObject* parent)
212+ : unity::shell::scopes::ResultsModelInterface(parent)
213+ , m_scopes(scopes)
214+ , m_categoryId(categoryId)
215+{
216+}
217+
218+QString ScopesOverviewResultsModel::categoryId() const
219+{
220+ return m_categoryId;
221+}
222+
223+void ScopesOverviewResultsModel::setCategoryId(QString const& /*id*/)
224+{
225+ qFatal("Calling un-implemented ScopesOverviewResultsModel::setCategoryId");
226+}
227+
228+int ScopesOverviewResultsModel::scopeIndex(QString const& id) const
229+{
230+ const int scopeCount = count();
231+ for (int i = 0; i < scopeCount; ++i) {
232+ if (m_scopes[i]->id() == id)
233+ return i;
234+ }
235+ return -1;
236+}
237+
238+QHash<int, QByteArray> ScopesOverviewResultsModel::roleNames() const
239+{
240+ QHash<int, QByteArray> roles = unity::shell::scopes::ResultsModelInterface::roleNames();
241+ roles[RoleBackground + 1] = "scopeId";
242+ return roles;
243+}
244+
245+int ScopesOverviewResultsModel::rowCount(const QModelIndex& parent) const
246+{
247+ Q_UNUSED(parent);
248+
249+ return m_scopes.count();
250+}
251+
252+int ScopesOverviewResultsModel::count() const
253+{
254+ return rowCount();
255+}
256+
257+QVariant
258+ScopesOverviewResultsModel::data(const QModelIndex& index, int role) const
259+{
260+ unity::shell::scopes::ScopeInterface *scope = m_scopes[index.row()];
261+ switch (role) {
262+ case RoleUri:
263+ case RoleCategoryId:
264+ case RoleDndUri:
265+ return QString();
266+ case RoleResult:
267+ return scope ? scope->id() : QString("Result.%1.%2").arg(categoryId()).arg(index.row());
268+ case RoleTitle:
269+ return scope ? scope->name() : QString("Title.%1.%2").arg(categoryId()).arg(index.row());
270+ case RoleArt:
271+ return qmlDirectory() + "graphics/applicationIcons/dash.png";
272+ case RoleMascot:
273+ case RoleEmblem:
274+ case RoleSummary:
275+ case RoleBackground + 1: // scopeId
276+ return scope ? scope->id() : nullptr;
277+ break;
278+ default:
279+ return QVariant();
280+ }
281+}
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<Scope *> &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<Scope *> m_scopes;
101+ QString m_categoryId;
102+};
103+
104+#endif // FAKE_SCOPESOVERVIEW_H
tests/plugins/Dash/cardcreator/2.res+2−2
@@ -94,7 +94,7 @@ Image {
9494 wrapMode: Text.Wrap;
9595 maximumLineCount: 2;
9696 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
97+ color: 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");
9898 visible: showHeader ;
9999 text: root.title;
100100 font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
@@ -112,7 +112,7 @@ Image {
112112 elide: Text.ElideRight;
113113 fontSize: "small";
114114 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
115− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
115+ color: 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");
116116 visible: titleLabel.visible && titleLabel.text;
117117 text: cardData && cardData["subtitle"] || "";
118118 font.weight: Font.Light;
tests/plugins/Dash/cardcreator/5.res+6−5
@@ -68,7 +68,8 @@ Loader {
6868 visible: showHeader && status == Loader.Ready;
6969 sourceComponent: ShaderEffect {
7070 id: overlay;
71− height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
71+ height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
72+ property real luminance: 0.2126 * overlayColor.r + 0.7152 * overlayColor.g + 0.0722 * overlayColor.b;
7273 property color overlayColor: cardData && cardData["overlayColor"] || "#99000000";
7374 property var source: ShaderEffectSource {
7475 id: shaderSource;
@@ -90,7 +91,7 @@ Loader {
9091 varying highp vec2 coord;
9192 uniform sampler2D source;
9293 uniform lowp float qt_Opacity;
93− uniform highp vec4 overlayColor;
94+ uniform highp vec4 overlayColor;
9495 void main() {
9596 lowp vec4 tex = texture2D(source, coord);
9697 gl_FragColor = vec4(overlayColor.r, overlayColor.g, overlayColor.b, 1) * qt_Opacity * overlayColor.a * tex.a;
@@ -99,7 +100,7 @@ Loader {
99100 }
100101readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
101102Label {
102− id: titleLabel;
103+ id: titleLabel;
103104 objectName: "titleLabel";
104105 anchors { right: parent.right;
105106 rightMargin: units.gu(1);
@@ -113,7 +114,7 @@ Label {
113114 wrapMode: Text.Wrap;
114115 maximumLineCount: 2;
115116 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
116− color: "white";
117+ color: overlayLoader.item.luminance < (root.scopeStyle ? root.scopeStyle.threshold : 0.7) ? (root.scopeStyle ? root.scopeStyle.light : "white") : (root.scopeStyle ? root.scopeStyle.dark : "grey");
117118 visible: showHeader && overlayLoader.active;
118119 text: root.title;
119120 font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
@@ -132,7 +133,7 @@ Label {
132133 elide: Text.ElideRight;
133134 fontSize: "small";
134135 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
135− color: "white";
136+ color: overlayLoader.item.luminance < (root.scopeStyle ? root.scopeStyle.threshold : 0.7) ? (root.scopeStyle ? root.scopeStyle.light : "white") : (root.scopeStyle ? root.scopeStyle.dark : "grey");
136137 visible: titleLabel.visible && titleLabel.text;
137138 text: cardData && cardData["subtitle"] || "";
138139 font.weight: Font.Light;
tests/plugins/Dash/cardcreator/6.res+2−2
@@ -70,7 +70,7 @@ Item {
7070 wrapMode: Text.Wrap;
7171 maximumLineCount: 2;
7272 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
73− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
73+ color: 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");
7474 visible: showHeader ;
7575 text: root.title;
7676 font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
@@ -88,7 +88,7 @@ Item {
8888 elide: Text.ElideRight;
8989 fontSize: "small";
9090 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
91+ color: 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");
9292 visible: titleLabel.visible && titleLabel.text;
9393 text: cardData && cardData["subtitle"] || "";
9494 font.weight: Font.Light;
tests/plugins/Dash/cardcreator/7.res+3−3
@@ -95,7 +95,7 @@ Image {
9595 wrapMode: Text.Wrap;
9696 maximumLineCount: 2;
9797 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
98− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
98+ color: 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");
9999 visible: showHeader ;
100100 text: root.title;
101101 font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
@@ -113,7 +113,7 @@ Image {
113113 elide: Text.ElideRight;
114114 fontSize: "small";
115115 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
116− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
116+ color: 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");
117117 visible: titleLabel.visible && titleLabel.text;
118118 text: cardData && cardData["subtitle"] || "";
119119 font.weight: Font.Light;
@@ -127,7 +127,7 @@ Image {
127127 rightMargin: units.gu(1);
128128 top: subtitleLabel.bottom;
129129 }
130− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
130+ color: 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");
131131 model: cardData && cardData["attributes"];
132132 }
133133
tests/plugins/Dash/tst_ScopeStyle.qml+1−1
@@ -101,7 +101,7 @@ Rectangle {
101101
102102 function test_threshold_data() {
103103 return [
104− { tag: "default", index: 0, threshold: 0.5020 },
104+ { tag: "default", index: 0, threshold: 0.7510 },
105105 { tag: "red on black", index: 1, threshold: 0.1063 },
106106 { tag: "green on white", index: 2, threshold: 0.6795 },
107107 { tag: "blue on darkgrey", index: 3, threshold: 0.3675 },
tests/qmltests/Dash/tst_Dash.qml+229−7
@@ -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)
@@ -61,9 +59,9 @@ Item {
6159 function get_scope_data() {
6260 return [
6361 { tag: "MockScope1", visualIndex: 0 },
64− { tag: "MockScope2", visualIndex: 1 },
65− { tag: "clickscope", visualIndex: 2 },
66− { tag: "MockScope5", visualIndex: 3 },
62+ { tag: "MockScope2", visualIndex: -1 },
63+ { tag: "clickscope", visualIndex: 1 },
64+ { tag: "MockScope5", visualIndex: 2 },
6765 ]
6866 }
6967
@@ -79,10 +77,234 @@ Item {
7977 tryCompare(dashContentList, "count", 0);
8078 scopes.load();
8179 tryCompare(scopes, "loaded", true);
82− tryCompare(dashContentList, "count", 5);
80+ tryCompare(dashContentList, "count", 6);
8381
8482 verify(dashContentList != undefined);
85− tryCompare(dashContentList, "currentIndex", data.visualIndex);
83+ if (data.visualIndex == -1) {
84+ tryCompare(dashContentList, "currentIndex", 0);
85+ expectFail(data.tag, "non favorite scopes should not be visble in the scopes model");
86+ compare(dashContentList.currentItem.scopeId, data.tag); // this should fail
87+ } else {
88+ tryCompare(dashContentList, "currentIndex", data.visualIndex);
89+ compare(dashContentList.currentItem.scopeId, data.tag);
90+ }
91+ }
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 categoryListView = findChild(dashContentList.currentItem, "categoryListView");
235+ var dashCategory2 = findChild(categoryListView, "dashCategory2");
236+ tryCompareFunction(function() {
237+ var d = findChild(dashCategory2, "delegate2");
238+ if (d) return true;
239+ categoryListView.contentY += 100;
240+ return false;
241+ }, true);
242+ var card2 = findChild(dashCategory2, "delegate2");
243+ waitForRendering(card2);
244+ mouseClick(card2, card2.width / 2, card2.height / 2);
245+
246+ // Wait for temp scope to be there
247+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
248+ tryCompare(dashTempScopeItem, "x", 0);
249+
250+ // Show the overview
251+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
252+ var overviewController = findInvisibleChild(dash, "overviewController");
253+ tryCompare(overviewController, "progress", 1);
254+
255+ // Make sure tab is where it should
256+ var scopesOverview = findChild(dash, "scopesOverview");
257+ compare(scopesOverview.currentTab, 1);
258+
259+ // Do a search
260+ var scopesOverviewPageHeader = findChild(scopesOverview, "scopesOverviewPageHeader");
261+ var searchButton = findChild(scopesOverviewPageHeader, "search_header_button");
262+ mouseClick(searchButton, 0, 0);
263+
264+ // Type something
265+ keyClick(Qt.Key_H);
266+
267+ // Check results grid is there and the other lists are not
268+ var searchResultsViewer = findChild(scopesOverview, "searchResultsViewer");
269+ var scopesOverviewRepeater = findChild(dash, "scopesOverviewRepeater");
270+ tryCompare(searchResultsViewer, "opacity", 1);
271+ tryCompare(scopesOverviewRepeater, "count", 0);
272+
273+ // Click on a temp scope in the search
274+ var dashCategorysearchA = findChild(searchResultsViewer, "dashCategorysearchA");
275+ var cardTempScope = findChild(dashCategorysearchA, "delegate2");
276+ waitForRendering(cardTempScope);
277+ mouseClick(cardTempScope, cardTempScope.width / 2, cardTempScope.height / 2);
278+
279+ // Check the bottom edge (overview) is disabled from temp scope
280+ var overviewDragHandle = findChild(dash, "overviewDragHandle");
281+ compare(overviewDragHandle.enabled, false);
282+
283+ // Check temp scope is there
284+ var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
285+ tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope != null; }, true);
286+ tryCompare(scopesOverviewTempScopeItem, "enabled", true);
287+
288+ // Go back
289+ var scopesOverviewTempScopeItemHeader = findChild(scopesOverviewTempScopeItem, "scopePageHeader");
290+ var backButton = findChild(findChild(scopesOverviewTempScopeItemHeader, "innerPageHeader"), "backButton");
291+ mouseClick(backButton, 0, 0);
292+
293+ // Check temp scope is gone
294+ var scopesOverviewTempScopeItem = findChild(dash, "scopesOverviewTempScopeItem");
295+ tryCompareFunction( function() { return scopesOverviewTempScopeItem.scope == null; }, true);
296+ tryCompare(scopesOverviewTempScopeItem, "enabled", false);
297+
298+ // Press on a favorite
299+ var dashCategorysearchB = findChild(searchResultsViewer, "dashCategorysearchB");
300+ var cardFavSearch = findChild(dashCategorysearchB, "delegate3");
301+ mouseClick(cardFavSearch, 0, 0);
302+
303+ // Check the dash overview is gone
304+ tryCompare(overviewController, "progress", 0);
305+
306+ // Original list went to the favorite
307+ compare(dashContentList.currentIndex, 0);
86308 }
87309
88310 function test_setCurrentScope() {
tests/qmltests/Dash/tst_DashContent.qml+7−6
@@ -70,7 +70,7 @@ Item {
7070 function loadScopes() {
7171 scopeLoadedSpy.clear();
7272 scopesModel.load();
73− tryCompare(scopeLoadedSpy, "count", 4);
73+ tryCompare(scopeLoadedSpy, "count", 6);
7474 }
7575
7676 function init() {
@@ -114,7 +114,8 @@ Item {
114114
115115 loadScopes();
116116
117− verify(dashContentList.currentIndex >= 0 && dashContentList.currentIndex < 5);
117+ compare(dashContentList.count, 6);
118+ verify(dashContentList.currentIndex >= 0 && dashContentList.currentIndex < dashContentList.count);
118119 }
119120
120121 function test_show_header_on_list_movement() {
@@ -170,7 +171,7 @@ Item {
170171
171172 // test greater than scope count.
172173 var currentScopeIndex = dashContent.currentIndex;
173− dashContent.setCurrentScopeAtIndex(8, true, false);
174+ dashContent.setCurrentScopeAtIndex(18, true, false);
174175 compare(dashContent.currentIndex, currentScopeIndex, "Scope should not change if changing to greater index than count");
175176 }
176177
@@ -189,9 +190,9 @@ Item {
189190 function test_scope_mapping_data() {
190191 return [
191192 {tag: "index0", index: 0, objectName: "MockScope1"},
192− {tag: "index1", index: 1, objectName: "MockScope2"},
193− {tag: "index2", index: 2, objectName: "clickscope"},
194− {tag: "index3", index: 3, objectName: "MockScope5"}
193+ {tag: "index1", index: 1, objectName: "clickscope"},
194+ {tag: "index2", index: 2, objectName: "MockScope5"},
195+ {tag: "index3", index: 3, objectName: "SingleCategoryScope"}
195196 ]
196197 }
197198
tests/qmltests/Dash/tst_GenericScopeView.qml+12−9
@@ -61,8 +61,8 @@ Item {
6161 property Item header: findChild(genericScopeView, "scopePageHeader")
6262
6363 function init() {
64− genericScopeView.scope = scopes.getScope(1)
65− shell.width = units.gu(120)
64+ genericScopeView.scope = scopes.getScope(2);
65+ shell.width = units.gu(120);
6666 genericScopeView.categoryView.positionAtBeginning();
6767 waitForRendering(genericScopeView.categoryView);
6868 }
@@ -88,13 +88,13 @@ Item {
8888
8989 function test_showDash() {
9090 testCase.previewListView.open = true;
91− scopes.getScope(1).showDash();
91+ genericScopeView.scope.showDash();
9292 tryCompare(testCase.previewListView, "open", false);
9393 }
9494
9595 function test_hideDash() {
9696 testCase.previewListView.open = true;
97− scopes.getScope(1).hideDash();
97+ genericScopeView.scope.hideDash();
9898 tryCompare(testCase.previewListView, "open", false);
9999 }
100100
@@ -111,8 +111,9 @@ Item {
111111
112112 function test_changeScope() {
113113 genericScopeView.scope.searchQuery = "test"
114− genericScopeView.scope = scopes.getScope(2)
115− genericScopeView.scope = scopes.getScope(1)
114+ var originalScopeId = genericScopeView.scope.id;
115+ genericScopeView.scope = scopes.getScope(originalScopeId + 1)
116+ genericScopeView.scope = scopes.getScope(originalScopeId)
116117 tryCompare(genericScopeView.scope, "searchQuery", "test")
117118 }
118119
@@ -198,6 +199,8 @@ Item {
198199 openPreview(4, 0);
199200
200201 compare(testCase.previewListView.count, 12, "There should only be 12 items in preview.");
202+
203+ closePreview();
201204 }
202205
203206 function test_narrow_delegate_ranges_expand() {
@@ -232,7 +235,7 @@ Item {
232235 }
233236
234237 function test_single_category_expansion() {
235− genericScopeView.scope = scopes.getScope(4);
238+ genericScopeView.scope = scopes.getScope(3);
236239
237240 tryCompareFunction(function() { return findChild(genericScopeView, "dashCategory0") != undefined; }, true);
238241 var category = findChild(genericScopeView, "dashCategory0")
@@ -329,8 +332,8 @@ Item {
329332 function test_header_style_data() {
330333 return [
331334 { tag: "Default", index: 0, foreground: "grey", background: "", logo: "" },
332− { tag: "Foreground", index: 2, foreground: "yellow", background: "", logo: "" },
333− { tag: "Logo+Background", index: 3, foreground: "grey", background: "gradient:///lightgrey/grey",
335+ { tag: "Foreground", index: 1, foreground: "yellow", background: "", logo: "" },
336+ { tag: "Logo+Background", index: 2, foreground: "grey", background: "gradient:///lightgrey/grey",
334337 logo: Qt.resolvedUrl("../Components/tst_PageHeader/logo-ubuntu-orange.svg") },
335338 ];
336339 }