Unity 8
 All Classes Functions Properties
GenericScopeView.qml
1 /*
2  * Copyright (C) 2013 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 Scope 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.filter : true
126  readonly property string category: categoryId
127  readonly property var item: rendererLoader.item
128 
129  CardTool {
130  id: cardTool
131 
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  }
145 
146  source: {
147  switch (cardTool.categoryLayout) {
148  case "carousel": return "CardCarousel.qml";
149  case "running-apps": return "Apps/RunningApplicationsGrid.qml";
150  case "grid":
151  default: return "CardFilterGrid.qml";
152  }
153  }
154 
155  onLoaded: {
156  if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
157  item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
158  scopeView.enableHeightBehaviorOnNextCreation = false;
159  }
160  if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
161  // TODO: this is still a kludge :D Ideally add some kind of hook so that we
162  // can do this from DashApps.qml or think a better way that needs no special casing
163  item.model = Qt.binding(function() { return runningApps; })
164  item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
165  } else {
166  item.model = Qt.binding(function() { return results })
167  }
168  item.objectName = Qt.binding(function() { return categoryId })
169  if (item.expandable) {
170  var shouldFilter = categoryId != categoryView.expandedCategoryId;
171  if (shouldFilter != item.filter) {
172  item.filter = shouldFilter;
173  }
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")) {
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  var item = target.model.get(index);
193  scopeView.scope.activate(item.result)
194  } else {
195  previewListView.model = target.model;
196  previewListView.currentIndex = -1
197  previewListView.currentIndex = index;
198  previewListView.open = true
199  }
200  }
201  onPressAndHold: {
202  previewListView.model = target.model;
203  previewListView.currentIndex = -1
204  previewListView.currentIndex = index;
205  previewListView.open = true
206  }
207  }
208  Connections {
209  target: categoryView
210  onExpandedCategoryIdChanged: {
211  collapseAllButExpandedCategory();
212  }
213  function collapseAllButExpandedCategory() {
214  var item = rendererLoader.item;
215  if (item.expandable) {
216  var shouldFilter = categoryId != categoryView.expandedCategoryId;
217  if (shouldFilter != item.filter) {
218  // If the filter animation will be seen start it, otherwise, just flip the switch
219  var shrinkingVisible = shouldFilter && y + item.collapsedHeight < categoryView.height;
220  var growingVisible = !shouldFilter && y + height < categoryView.height;
221  if (!previewListView.open || !shouldFilter) {
222  if (shrinkingVisible || growingVisible) {
223  item.startFilterAnimation(shouldFilter)
224  } else {
225  item.filter = shouldFilter;
226  }
227  if (!shouldFilter && !previewListView.open) {
228  categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
229  }
230  }
231  }
232  }
233  }
234  onOriginYChanged: rendererLoader.updateDelegateCreationRange();
235  onContentYChanged: rendererLoader.updateDelegateCreationRange();
236  onHeightChanged: rendererLoader.updateDelegateCreationRange();
237  onContentHeightChanged: rendererLoader.updateDelegateCreationRange();
238  }
239 
240  function updateDelegateCreationRange() {
241  // Do not update the range if we are overshooting up or down, since we'll come back
242  // to the stable position and delete/create items without any reason
243  if (categoryView.contentY < categoryView.originY) {
244  return;
245  } else if (categoryView.contentHeight > categoryView.height && categoryView.contentY + categoryView.height > categoryView.contentHeight) {
246  return;
247  }
248 
249  if (item && item.hasOwnProperty("delegateCreationBegin")) {
250  if (baseItem.y + baseItem.height <= 0) {
251  // Not visible (item at top of the list viewport)
252  item.delegateCreationBegin = item.originY + baseItem.height
253  item.delegateCreationEnd = item.originY + baseItem.height
254  } else if (baseItem.y >= categoryView.height) {
255  // Not visible (item at bottom of the list viewport)
256  item.delegateCreationBegin = item.originY
257  item.delegateCreationEnd = item.originY
258  } else {
259  item.delegateCreationBegin = item.originY + Math.max(-baseItem.y, 0)
260  item.delegateCreationEnd = item.originY + Math.min(categoryView.height + item.delegateCreationBegin, baseItem.height)
261  }
262  }
263  }
264 
265  Image {
266  visible: index != 0
267  anchors {
268  top: parent.top
269  left: parent.left
270  right: parent.right
271  }
272  fillMode: Image.Stretch
273  source: "graphics/dash_divider_top_lightgrad.png"
274  z: -1
275  }
276 
277  Image {
278  // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
279  visible: index != categoryView.model.count - 1
280  anchors {
281  bottom: parent.bottom
282  left: parent.left
283  right: parent.right
284  }
285  fillMode: Image.Stretch
286  source: "graphics/dash_divider_top_darkgrad.png"
287  z: -1
288  }
289  }
290 
291  onHeightChanged: rendererLoader.updateDelegateCreationRange();
292  onYChanged: rendererLoader.updateDelegateCreationRange();
293  }
294 
295  sectionProperty: "name"
296  sectionDelegate: ListItems.Header {
297  objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
298  property var delegate: categoryView.item(delegateIndex)
299  width: categoryView.width
300  text: section
301  image: {
302  if (delegate && delegate.expandable)
303  return delegate.filtered ? "graphics/header_handlearrow.png" : "graphics/header_handlearrow2.png"
304  return "";
305  }
306  onClicked: {
307  if (categoryView.expandedCategoryId != delegate.category)
308  categoryView.expandedCategoryId = delegate.category;
309  else
310  categoryView.expandedCategoryId = "";
311  }
312  }
313  pageHeader: Item {
314  implicitHeight: scopeView.tabBarHeight
315  onHeightChanged: {
316  if (scopeView.pageHeader && scopeView.isCurrent) {
317  scopeView.pageHeader.height = height;
318  }
319  }
320  onYChanged: positionRealHeader();
321 
322  function positionRealHeader() {
323  if (scopeView.pageHeader && scopeView.isCurrent) {
324  scopeView.pageHeader.y = y + parent.y;
325  }
326  }
327  }
328  }
329 }
Tool for introspecting Card properties.
Definition: CardTool.qml:25
int count
Number of cards.
Definition: CardTool.qml:30