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