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