@@ -0,0 +1,518 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Dash 0.1
19+import Ubuntu.Components 0.1
20+import "../Components"
21+
22+Item {
23+ id: root
24+
25+ // Properties set by parent
26+ property real progress: 0
27+ property var scope: null
28+ property int currentIndex: 0
29+ property real scopeScale: 1
30+
31+ // Properties set and used by parent
32+ property alias currentTab: tabBar.currentTab
33+
34+ // Properties used by parent
35+ property bool growingDashFromPos: false
36+ readonly property bool searching: scope && scope.searchQuery == ""
37+ readonly property bool showingNonFavoriteScope: tempScopeItem.scope != null
38+ readonly property var dashItemEater: {
39+ if (!forceXYScalerEater && tabBar.currentTab == 0 && middleItems.count > 0) {
40+ var loaderItem = middleItems.itemAt(0).item;
41+ return loaderItem && loaderItem.currentItem ? loaderItem.currentItem : null;
42+ }
43+ return scopesOverviewXYScaler;
44+ }
45+ readonly property size allCardSize: {
46+ if (middleItems.count > 1) {
47+ var loaderItem = middleItems.itemAt(1).item;
48+ if (loaderItem) {
49+ var cardTool = loaderItem.cardTool;
50+ return Qt.size(cardTool.cardWidth, cardTool.cardHeight);
51+ }
52+ }
53+ return Qt.size(0, 0);
54+ }
55+
56+ // Internal properties
57+ property bool forceXYScalerEater: false
58+
59+ signal done()
60+ signal favoriteSelected(var scopeId)
61+ signal allFavoriteSelected(var scopeId)
62+ signal searchSelected(var scopeId, var result, var pos, var size)
63+
64+ Connections {
65+ target: scope
66+ onOpenScope: {
67+ var itemPos = scopesOverviewXYScaler.restorePosition;
68+ var itemSize = scopesOverviewXYScaler.restoreSize;
69+ scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
70+ if (itemPos) {
71+ scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
72+ scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
73+ } else {
74+ scopesOverviewXYScaler.x = 0;
75+ scopesOverviewXYScaler.y = 0;
76+ }
77+ scopesOverviewXYScaler.opacity = 0;
78+ tempScopeItem.scope = scope;
79+ middleItems.overrideOpacity = 0;
80+ scopesOverviewXYScaler.scale = 1;
81+ scopesOverviewXYScaler.x = 0;
82+ scopesOverviewXYScaler.y = 0;
83+ scopesOverviewXYScaler.opacity = 1;
84+ }
85+ }
86+
87+ function animateDashFromAll(scopeId) {
88+ var currentScopePos = allScopeCardPosition(scopeId);
89+ if (currentScopePos) {
90+ showDashFromPos(currentScopePos, allCardSize);
91+ } else {
92+ console.log("Warning: Could not find Dash OverView All card position for scope", dashContent.currentScopeId);
93+ }
94+ }
95+
96+ function showDashFromPos(itemPos, itemSize) {
97+ scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
98+ scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
99+ scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
100+ scopesOverviewXYScaler.opacity = 0;
101+ root.growingDashFromPos = true;
102+ scopesOverviewXYScaler.scale = 1;
103+ scopesOverviewXYScaler.x = 0;
104+ scopesOverviewXYScaler.y = 0;
105+ scopesOverviewXYScaler.opacity = 1;
106+ }
107+
108+ function allScopeCardPosition(scopeId) {
109+ if (middleItems.count > 1) {
110+ var loaderItem = middleItems.itemAt(1).item;
111+ if (loaderItem) {
112+ var pos = loaderItem.scopeCardPosition(scopeId);
113+ return loaderItem.mapToItem(null, pos.x, pos.y);
114+ }
115+ }
116+ }
117+
118+ function ensureAllScopeVisible(scopeId) {
119+ if (middleItems.count > 1) {
120+ var loaderItem = middleItems.itemAt(1).item;
121+ if (loaderItem) {
122+ var pos = loaderItem.scopeCardPosition(scopeId);
123+ loaderItem.contentY = Math.min(pos.y, loaderItem.contentHeight - loaderItem.height);
124+ }
125+ }
126+ }
127+
128+ onProgressChanged: {
129+ if (progress == 0) {
130+ pageHeader.resetSearch();
131+ pageHeader.unfocus(); // Shouldn't the previous call do this too?
132+ }
133+ }
134+
135+ ScopeStyle {
136+ id: overviewScopeStyle
137+ style: { "foreground-color" : "white", "background-color" : "transparent" }
138+ }
139+
140+ DashBackground {
141+ anchors.fill: parent
142+ source: "graphics/dark_background.jpg"
143+ }
144+
145+ Connections {
146+ target: pageHeader
147+ onSearchQueryChanged: {
148+ // Need this in order, otherwise something gets unhappy in rendering
149+ // of the overlay in carousels because the parent of the dash dies for
150+ // a moment, this way we make sure it's reparented first
151+ // by forceXYScalerEater making dashItemEater return scopesOverviewXYScaler
152+ // before we kill the previous parent by scope.searchQuery
153+ root.forceXYScalerEater = true;
154+ root.scope.searchQuery = pageHeader.searchQuery;
155+ root.forceXYScalerEater = false;
156+ }
157+ }
158+
159+ Binding {
160+ target: pageHeader
161+ property: "searchQuery"
162+ value: scope ? scope.searchQuery : ""
163+ }
164+
165+ Item {
166+ id: scopesOverviewContent
167+ x: previewListView.open ? -width : 0
168+ Behavior on x { UbuntuNumberAnimation { } }
169+ width: parent.width
170+ height: parent.height
171+
172+ PageHeader {
173+ id: pageHeader
174+ objectName: "scopesOverviewPageHeader"
175+
176+ readonly property real yDisplacement: pageHeader.height + tabBar.height + tabBar.anchors.margins
177+
178+ y: {
179+ if (root.progress < 0.5) {
180+ return -yDisplacement;
181+ } else {
182+ return -yDisplacement + (root.progress - 0.5) * yDisplacement * 2;
183+ }
184+ }
185+ width: parent.width
186+ clip: true
187+ title: i18n.tr("Manage Dash")
188+ scopeStyle: overviewScopeStyle
189+ showSignatureLine: false
190+ searchEntryEnabled: true
191+ searchInProgress: root.scope ? root.scope.searchInProgress : false
192+ }
193+
194+ ScopesOverviewTab {
195+ id: tabBar
196+ anchors {
197+ left: parent.left
198+ right: parent.right
199+ top: pageHeader.bottom
200+ margins: units.gu(2)
201+ }
202+ height: units.gu(4)
203+
204+ enabled: opacity == 1
205+ opacity: !scope || scope.searchQuery == "" ? 1 : 0
206+ Behavior on opacity { UbuntuNumberAnimation { } }
207+ }
208+
209+ Repeater {
210+ id: middleItems
211+ objectName: "scopesOverviewRepeater"
212+ property real overrideOpacity: -1
213+ model: scope && scope.searchQuery == "" ? scope.categories : null
214+ delegate: Loader {
215+ id: loader
216+ objectName: "scopesOverviewRepeaterChild" + index
217+
218+ height: {
219+ if (index == 0) {
220+ return root.height;
221+ } else {
222+ return root.height - pageHeader.height - tabBar.height - tabBar.anchors.margins - units.gu(2)
223+ }
224+ }
225+ width: {
226+ if (index == 0) {
227+ return root.width / scopeScale
228+ } else {
229+ return root.width
230+ }
231+ }
232+ x: {
233+ if (index == 0) {
234+ return (root.width - width) / 2;
235+ } else {
236+ return 0;
237+ }
238+ }
239+ anchors {
240+ bottom: scopesOverviewContent.bottom
241+ }
242+
243+ scale: index == 0 ? scopeScale : 1
244+
245+ opacity: {
246+ if (middleItems.overrideOpacity >= 0)
247+ return middleItems.overrideOpacity;
248+
249+ if (tabBar.currentTab != index)
250+ return 0;
251+
252+ return index == 0 ? 1 : root.progress;
253+ }
254+ Behavior on opacity {
255+ enabled: root.progress == 1
256+ UbuntuNumberAnimation { }
257+ }
258+ enabled: opacity == 1
259+
260+ clip: index == 1
261+
262+ CardTool {
263+ id: cardTool
264+ objectName: "cardTool"
265+ count: results.count
266+ template: model.renderer
267+ components: model.components
268+ viewWidth: parent.width
269+ }
270+
271+ source: {
272+ if (index == 0 && categoryId == "favorites") return "ScopesOverviewFavorites.qml";
273+ else if (index == 1 && categoryId == "all") return "ScopesOverviewAll.qml";
274+ else return "";
275+ }
276+
277+ onLoaded: {
278+ item.model = Qt.binding(function() { return results })
279+ item.cardTool = cardTool;
280+ if (index == 0) {
281+ item.scopeWidth = Qt.binding(function() { return root.width; });
282+ item.scopeHeight = Qt.binding(function() { return root.height; });
283+ item.appliedScale = Qt.binding(function() { return loader.scale });
284+ item.currentIndex = Qt.binding(function() { return root.currentIndex });
285+ } else if (index == 1) {
286+ item.extraHeight = bottomBar.height;
287+ }
288+ }
289+
290+ Connections {
291+ target: loader.item
292+ onClicked: {
293+ if (tabBar.currentTab == 0) {
294+ root.favoriteSelected(itemModel.scopeId)
295+ } else {
296+ var favoriteScopesItem = middleItems.itemAt(0).item;
297+ var scopeIndex = favoriteScopesItem.model.scopeIndex(itemModel.scopeId);
298+ if (scopeIndex >= 0) {
299+ root.allFavoriteSelected(itemModel.scopeId);
300+ } else {
301+ // Will result in an openScope from root.scope
302+ scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
303+ scopesOverviewXYScaler.restoreSize = allCardSize;
304+ root.scope.activate(result);
305+ }
306+ }
307+ }
308+ onPressAndHold: {
309+ // Preview can call openScope so make sure restorePosition and restoreSize are set
310+ scopesOverviewXYScaler.restorePosition = undefined;
311+ scopesOverviewXYScaler.restoreSize = allCardSize;
312+
313+ previewListView.model = target.model;
314+ previewListView.currentIndex = -1
315+ previewListView.currentIndex = index;
316+ previewListView.open = true
317+ }
318+ }
319+ }
320+ }
321+
322+ GenericScopeView {
323+ id: searchResultsViewer
324+ objectName: "searchResultsViewer"
325+ anchors {
326+ top: pageHeader.bottom
327+ right: parent.right
328+ left: parent.left
329+ bottom: parent.bottom
330+ }
331+ scope: root.scope && root.scope.searchQuery != "" ? root.scope : null
332+ scopeStyle: overviewScopeStyle
333+ enabled: opacity == 1
334+ showPageHeader: false
335+ clip: true
336+ opacity: searchResultsViewer.scope ? 1 : 0
337+ Behavior on opacity { UbuntuNumberAnimation { } }
338+
339+ clickOverride: function (index, result, item, itemModel) {
340+ pageHeader.closePopup();
341+ if (itemModel.scopeId) {
342+ // This can end up in openScope so save restorePosition and restoreSize
343+ scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
344+ scopesOverviewXYScaler.restoreSize = Qt.size(item.width, item.height);
345+ root.searchSelected(itemModel.scopeId, result, item.mapToItem(null, 0, 0), Qt.size(item.width, item.height));
346+ } else {
347+ // Not a scope, just activate it
348+ searchResultsViewer.scope.activate(result);
349+ }
350+ }
351+
352+ pressAndHoldOverride: function (index) {
353+ // Do nothing
354+ }
355+ }
356+
357+ Rectangle {
358+ id: bottomBar
359+ color: "black"
360+ height: units.gu(6)
361+ width: parent.width
362+ enabled: opacity == 0.4
363+ opacity: scope && scope.searchQuery == "" ? 0.4 : 0
364+ Behavior on opacity { UbuntuNumberAnimation { } }
365+ y: {
366+ if (root.progress < 0.5) {
367+ return parent.height;
368+ } else {
369+ return parent.height - (root.progress - 0.5) * height * 2;
370+ }
371+ }
372+
373+ AbstractButton {
374+ objectName: "scopesOverviewDoneButton"
375+ width: Math.max(label.width + units.gu(2), units.gu(10))
376+ height: units.gu(4)
377+ anchors {
378+ left: parent.left
379+ leftMargin: units.gu(2)
380+ verticalCenter: parent.verticalCenter
381+ }
382+ Rectangle {
383+ anchors.fill: parent
384+ border.color: "white"
385+ border.width: units.dp(1)
386+ radius: units.dp(10)
387+ color: parent.pressed ? "gray" : "transparent"
388+ }
389+ Label {
390+ id: label
391+ anchors.centerIn: parent
392+ text: i18n.tr("Done")
393+ color: parent.pressed ? "black" : "white"
394+ }
395+ onClicked: root.done();
396+ }
397+
398+ AbstractButton {
399+ objectName: "scopesOverviewStoreButton"
400+ width: Math.max(storeLabel.width, units.gu(10))
401+ height: units.gu(4)
402+ anchors {
403+ right: parent.right
404+ verticalCenter: parent.verticalCenter
405+ }
406+ Image {
407+ id: storeImage
408+ source: "graphics/apps-scope.png"
409+ anchors.horizontalCenter: parent.horizontalCenter
410+ width: units.gu(2)
411+ height: units.gu(2)
412+ fillMode: Image.Stretch
413+ }
414+ Label {
415+ id: storeLabel
416+ anchors.horizontalCenter: parent.horizontalCenter
417+ anchors.top: storeImage.bottom
418+ text: i18n.tr("Store")
419+ color: "white"
420+ }
421+ onClicked: {
422+ // Just zoom from the middle
423+ scopesOverviewXYScaler.restorePosition = undefined;
424+ scopesOverviewXYScaler.restoreSize = allCardSize;
425+ scope.performQuery("scope://com.canonical.scopes.clickstore");
426+ }
427+ }
428+ }
429+ }
430+
431+ PreviewListView {
432+ id: previewListView
433+ objectName: "scopesOverviewPreviewListView"
434+ scope: root.scope
435+ scopeStyle: overviewScopeStyle
436+ visible: x != width
437+ width: parent.width
438+ height: parent.height
439+ anchors.left: scopesOverviewContent.right
440+ }
441+
442+
443+
444+ Item {
445+ id: scopesOverviewXYScaler
446+ width: parent.width
447+ height: parent.height
448+
449+ clip: scale != 1.0
450+ enabled: scale == 1
451+
452+ property bool animationsEnabled: root.showingNonFavoriteScope || root.growingDashFromPos
453+
454+ property var restorePosition
455+ property var restoreSize
456+
457+ Behavior on x {
458+ enabled: scopesOverviewXYScaler.animationsEnabled
459+ UbuntuNumberAnimation { }
460+ }
461+ Behavior on y {
462+ enabled: scopesOverviewXYScaler.animationsEnabled
463+ UbuntuNumberAnimation { }
464+ }
465+ Behavior on opacity {
466+ enabled: scopesOverviewXYScaler.animationsEnabled
467+ UbuntuNumberAnimation { }
468+ }
469+ Behavior on scale {
470+ enabled: scopesOverviewXYScaler.animationsEnabled
471+ UbuntuNumberAnimation {
472+ onRunningChanged: {
473+ if (!running) {
474+ if (root.showingNonFavoriteScope && scopesOverviewXYScaler.scale != 1) {
475+ root.scope.closeScope(tempScopeItem.scope);
476+ tempScopeItem.scope = null;
477+ } else if (root.growingDashFromPos) {
478+ root.growingDashFromPos = false;
479+ }
480+ }
481+ }
482+ }
483+ }
484+
485+ DashBackground
486+ {
487+ anchors.fill: tempScopeItem
488+ visible: tempScopeItem.visible
489+ parent: tempScopeItem.parent
490+ }
491+
492+ GenericScopeView {
493+ id: tempScopeItem
494+ objectName: "scopesOverviewTempScopeItem"
495+
496+ width: parent.width
497+ height: parent.height
498+ scale: dash.contentScale
499+ clip: scale != 1.0
500+ visible: scope != null
501+ hasBackAction: true
502+ isCurrent: visible
503+ onBackClicked: {
504+ var v = scopesOverviewXYScaler.restoreSize.width / tempScopeItem.width;
505+ scopesOverviewXYScaler.scale = v;
506+ if (scopesOverviewXYScaler.restorePosition) {
507+ scopesOverviewXYScaler.x = scopesOverviewXYScaler.restorePosition.x -(tempScopeItem.width - tempScopeItem.width * v) / 2;
508+ scopesOverviewXYScaler.y = scopesOverviewXYScaler.restorePosition.y -(tempScopeItem.height - tempScopeItem.height * v) / 2;
509+ } else {
510+ scopesOverviewXYScaler.x = 0;
511+ scopesOverviewXYScaler.y = 0;
512+ }
513+ scopesOverviewXYScaler.opacity = 0;
514+ middleItems.overrideOpacity = -1;
515+ }
516+ }
517+ }
518+}