Unity 8
 All Classes Functions Properties
GenericScopeView.qml
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 import Ubuntu.Components 0.1
19 import Utils 0.1
20 import Unity 0.2
21 import Unity.Application 0.1
22 import Dash 0.1
23 import "../Components"
24 import "../Components/ListItems" as ListItems
25 
26 FocusScope {
27  id: scopeView
28 
29  property var scope: null
30  property SortFilterProxyModel categories: categoryFilter
31  property bool isCurrent: false
32  property alias moving: categoryView.moving
33  property bool hasBackAction: false
34  property bool enableHeightBehaviorOnNextCreation: false
35  property var categoryView: categoryView
36 
37  property var scopeStyle: ScopeStyle {
38  style: scope ? scope.customizations : {}
39  }
40 
41  signal backClicked()
42 
43  onScopeChanged: {
44  if (scope) {
45  scope.activateApplication.connect(activateApp);
46  }
47  }
48 
49  function activateApp(appId) {
50  shell.activateApplication(appId);
51  }
52 
53  function positionAtBeginning() {
54  categoryView.positionAtBeginning()
55  }
56 
57  function showHeader() {
58  categoryView.showHeader()
59  }
60 
61  function closePreview() {
62  previewListView.open = false;
63  }
64 
65  Binding {
66  target: scope
67  property: "isActive"
68  value: isCurrent && !previewListView.open
69  }
70 
71  SortFilterProxyModel {
72  id: categoryFilter
73  model: scope ? scope.categories : null
74  dynamicSortFilter: true
75  filterRole: Categories.RoleCount
76  filterRegExp: /^0$/
77  invertMatch: true
78  }
79 
80  onIsCurrentChanged: {
81  pageHeader.resetSearch();
82  previewListView.open = false;
83  }
84 
85  Binding {
86  target: scopeView.scope
87  property: "searchQuery"
88  value: pageHeader.searchQuery
89  when: isCurrent
90  }
91 
92  Binding {
93  target: pageHeader
94  property: "searchQuery"
95  value: scopeView.scope ? scopeView.scope.searchQuery : ""
96  when: isCurrent
97  }
98 
99  Connections {
100  target: scopeView.scope
101  onShowDash: previewListView.open = false;
102  onHideDash: previewListView.open = false;
103  }
104 
105  Rectangle {
106  anchors.fill: parent
107  color: scopeView.scopeStyle ? scopeView.scopeStyle.background : "transparent"
108  visible: color != "transparent"
109  }
110 
111  ScopeListView {
112  id: categoryView
113  objectName: "categoryListView"
114 
115  x: previewListView.open ? -width : 0
116  Behavior on x { UbuntuNumberAnimation { } }
117  width: parent.width
118  height: parent.height
119 
120  model: scopeView.categories
121  forceNoClip: previewListView.open
122 
123  property string expandedCategoryId: ""
124 
125  delegate: ListItems.Base {
126  id: baseItem
127  objectName: "dashCategory" + category
128  highlightWhenPressed: false
129  showDivider: false
130 
131  readonly property bool expandable: rendererLoader.item ? rendererLoader.item.expandable : false
132  readonly property bool filtered: rendererLoader.item ? rendererLoader.item.filtered : true
133  readonly property string category: categoryId
134  readonly property var item: rendererLoader.item
135 
136  CardTool {
137  id: cardTool
138  objectName: "cardTool"
139  count: results.count
140  template: model.renderer
141  components: model.components
142  viewWidth: parent.width
143  }
144 
145  Loader {
146  id: rendererLoader
147  anchors {
148  top: parent.top
149  left: parent.left
150  right: parent.right
151  topMargin: hasSectionHeader ? 0 : units.gu(2)
152  }
153 
154  source: {
155  switch (cardTool.categoryLayout) {
156  case "carousel": return "CardCarousel.qml";
157  case "vertical-journal": return "CardVerticalJournal.qml";
158  case "running-apps": return "Apps/RunningApplicationsGrid.qml";
159  case "grid":
160  default: return "CardFilterGrid.qml";
161  }
162  }
163 
164  onLoaded: {
165  if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
166  item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
167  scopeView.enableHeightBehaviorOnNextCreation = false;
168  }
169  if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
170  // TODO: this is still a kludge :D Ideally add some kind of hook so that we
171  // can do this from DashApps.qml or think a better way that needs no special casing
172  item.model = Qt.binding(function() { return runningApps; })
173  item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
174  } else {
175  item.model = Qt.binding(function() { return results })
176  }
177  item.objectName = Qt.binding(function() { return categoryId })
178  item.scopeStyle = scopeView.scopeStyle;
179  if (item.expandable) {
180  var shouldFilter = categoryId != categoryView.expandedCategoryId;
181  item.setFilter(shouldFilter, false /*animate*/);
182  }
183  updateDelegateCreationRange();
184  item.cardTool = cardTool;
185  }
186 
187  Component.onDestruction: {
188  if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
189  scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
190  }
191  }
192 
193  Connections {
194  target: rendererLoader.item
195  onClicked: {
196  if (scopeView.scope.id === "scopes" || scopeView.scope.id == "clickscope") {
197  // TODO Technically it is possible that calling activate() will make the scope emit
198  // previewRequested so that we show a preview but there's no scope that does that yet
199  // so it's not implemented
200  scopeView.scope.activate(result)
201  } else {
202  previewListView.model = target.model;
203  previewListView.currentIndex = -1
204  previewListView.currentIndex = index;
205  previewListView.open = true
206  }
207  }
208  onPressAndHold: {
209  previewListView.model = target.model;
210  previewListView.currentIndex = -1
211  previewListView.currentIndex = index;
212  previewListView.open = true
213  }
214  onExpandableChanged: {
215  // This can happen with the VJ that doesn't know how height it will be on creation
216  // so doesn't set expandable until a bit too late for onLoaded
217  if (rendererLoader.item.expandable) {
218  var shouldFilter = baseItem.category != categoryView.expandedCategoryId;
219  rendererLoader.item.setFilter(shouldFilter, false /*animate*/);
220  }
221  }
222  }
223  Connections {
224  target: categoryView
225  onExpandedCategoryIdChanged: {
226  collapseAllButExpandedCategory();
227  }
228  function collapseAllButExpandedCategory() {
229  var item = rendererLoader.item;
230  if (item.expandable) {
231  var shouldFilter = categoryId != categoryView.expandedCategoryId;
232  if (shouldFilter != item.filter) {
233  // If the filter animation will be seen start it, otherwise, just flip the switch
234  var shrinkingVisible = shouldFilter && y + item.collapsedHeight < categoryView.height;
235  var growingVisible = !shouldFilter && y + height < categoryView.height;
236  if (!previewListView.open || !shouldFilter) {
237  var animate = shrinkingVisible || growingVisible;
238  item.setFilter(shouldFilter, animate)
239  if (!shouldFilter && !previewListView.open) {
240  categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
241  }
242  }
243  }
244  }
245  }
246  onOriginYChanged: rendererLoader.updateDelegateCreationRange();
247  onContentYChanged: rendererLoader.updateDelegateCreationRange();
248  onHeightChanged: rendererLoader.updateDelegateCreationRange();
249  onContentHeightChanged: rendererLoader.updateDelegateCreationRange();
250  }
251 
252  function updateDelegateCreationRange() {
253  if (categoryView.moving) {
254  // Do not update the range if we are overshooting up or down, since we'll come back
255  // to the stable position and delete/create items without any reason
256  if (categoryView.contentY < categoryView.originY) {
257  return;
258  } else if (categoryView.contentHeight - categoryView.originY > categoryView.height &&
259  categoryView.contentY + categoryView.height > categoryView.contentHeight) {
260  return;
261  }
262  }
263 
264  if (item && item.hasOwnProperty("displayMarginBeginning")) {
265  // TODO do we need item.originY here, test 1300302 once we have a silo
266  // and we can run it on the phone
267  if (baseItem.y + baseItem.height <= 0) {
268  // Not visible (item at top of the list viewport)
269  item.displayMarginBeginning = -baseItem.height;
270  item.displayMarginEnd = 0;
271  } else if (baseItem.y >= categoryView.height) {
272  // Not visible (item at bottom of the list viewport)
273  item.displayMarginBeginning = 0;
274  item.displayMarginEnd = -baseItem.height;
275  } else {
276  item.displayMarginBeginning = -Math.max(-baseItem.y, 0);
277  item.displayMarginEnd = -Math.max(baseItem.height - categoryView.height + baseItem.y, 0)
278  }
279  }
280  }
281 
282  Image {
283  visible: index != 0
284  anchors {
285  top: parent.top
286  left: parent.left
287  right: parent.right
288  }
289  fillMode: Image.Stretch
290  source: "graphics/dash_divider_top_lightgrad.png"
291  z: -1
292  }
293 
294  Image {
295  // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
296  visible: index != categoryView.model.count - 1
297  anchors {
298  bottom: parent.bottom
299  left: parent.left
300  right: parent.right
301  }
302  fillMode: Image.Stretch
303  source: "graphics/dash_divider_top_darkgrad.png"
304  z: -1
305  }
306  }
307 
308  onHeightChanged: rendererLoader.updateDelegateCreationRange();
309  onYChanged: rendererLoader.updateDelegateCreationRange();
310  }
311 
312  sectionProperty: "name"
313  sectionDelegate: ListItems.Header {
314  objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
315  property var delegate: categoryView.item(delegateIndex)
316  width: categoryView.width
317  text: section
318  textColor: scopeStyle ? scopeStyle.foreground : "grey"
319  image: {
320  if (delegate && delegate.expandable)
321  return delegate.filtered ? "graphics/header_handlearrow.png" : "graphics/header_handlearrow2.png"
322  return "";
323  }
324  onClicked: {
325  if (categoryView.expandedCategoryId != delegate.category)
326  categoryView.expandedCategoryId = delegate.category;
327  else
328  categoryView.expandedCategoryId = "";
329  }
330  }
331 
332  pageHeader: PageHeader {
333  id: pageHeader
334  objectName: "scopePageHeader"
335  width: parent.width
336  title: scopeView.scope ? scopeView.scope.name : ""
337  showBackButton: scopeView.hasBackAction
338  searchEntryEnabled: true
339  searchInProgress: scopeView.scope ? scopeView.scope.searchInProgress : false
340  scopeStyle: scopeView.scopeStyle
341 
342  bottomItem: DashDepartments {
343  scope: scopeView.scope
344  width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
345  anchors.right: parent.right
346  windowHeight: scopeView.height
347  windowWidth: scopeView.width
348  scopeStyle: scopeView.scopeStyle
349  }
350 
351  onBackClicked: scopeView.backClicked()
352  }
353  }
354 
355  PreviewListView {
356  id: previewListView
357  objectName: "previewListView"
358  visible: x != width
359  scope: scopeView.scope
360  scopeStyle: scopeView.scopeStyle
361  width: parent.width
362  height: parent.height
363  anchors.left: categoryView.right
364 
365  onOpenChanged: {
366  pageHeader.unfocus();
367  }
368  }
369 
370 }
Tool for introspecting Card properties.
Definition: CardTool.qml:25
int count
Number of cards.
Definition: CardTool.qml:30