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