on 4fa93a0 help sign in
unity8/qml/Dash/GenericScopeView.qml
396 lines 16 kB history raw
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 */
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19import Utils 0.1
20import Unity 0.1
21import "../Components"
22import "../Components/ListItems" as ListItems
24FocusScope {
25 id: scopeView
27 property Scope scope
28 property SortFilterProxyModel categories: categoryFilter
29 property bool isCurrent
30 property alias moving: categoryView.moving
31 property int tabBarHeight: 0
32 property PageHeader pageHeader: null
33 property OpenEffect openEffect: null
34 property Item previewListView: null
36 signal movementStarted
37 signal positionedAtBeginning
39 property bool enableHeightBehaviorOnNextCreation: false
40 property var categoryView: categoryView
42 // FIXME delay the search so that daemons have time to settle, note that
43 // removing this will break ScopeView::test_changeScope
44 onScopeChanged: {
45 if (scope) {
46 timer.restart();
47 scope.activateApplication.connect(activateApp);
48 }
49 }
51 function activateApp(appId) {
52 shell.activateApplication(appId);
53 }
55 Binding {
56 target: scope
57 property: "isActive"
58 value: isCurrent && !previewListView.onScreen
59 }
61 Timer {
62 id: timer
63 interval: 2000
64 onTriggered: scope.searchQuery = ""
65 }
67 SortFilterProxyModel {
68 id: categoryFilter
69 model: scope ? scope.categories : null
70 dynamicSortFilter: true
71 filterRole: Categories.RoleCount
72 filterRegExp: /^0$/
73 invertMatch: true
74 }
76 onIsCurrentChanged: {
77 pageHeader.resetSearch();
78 previewListView.open = false;
79 }
81 onMovementStarted: categoryView.showHeader()
83 onPositionedAtBeginning: categoryView.positionAtBeginning()
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.searchQuery
96 when: isCurrent
97 }
99 Connections {
100 target: panel
101 onSearchClicked: if (isCurrent) {
102 pageHeader.triggerSearch()
103 categoryView.showHeader()
104 }
105 }
107 Connections {
108 target: scopeView.scope
109 onShowDash: previewListView.open = false;
110 onHideDash: previewListView.open = false;
111 }
113 ScopeListView {
114 id: categoryView
115 objectName: "categoryListView"
116 anchors.fill: parent
117 model: scopeView.categories
118 forceNoClip: previewListView.onScreen
120 property string expandedCategoryId: ""
121 signal correctExpandedCategory();
123 onContentYChanged: pageHeader.positionRealHeader();
124 onOriginYChanged: pageHeader.positionRealHeader();
125 onContentHeightChanged: pageHeader.positionRealHeader();
127 Behavior on contentY {
128 enabled: previewListView.open
129 UbuntuNumberAnimation {}
130 }
132 delegate: ListItems.Base {
133 id: baseItem
134 objectName: "dashCategory" + category
135 highlightWhenPressed: false
137 readonly property bool expandable: rendererLoader.item ? rendererLoader.item.expandable : false
138 readonly property bool filtered: rendererLoader.item ? rendererLoader.item.filter : true
139 readonly property string category: categoryId
140 readonly property var item: rendererLoader.item
142 Loader {
143 id: rendererLoader
144 anchors {
145 top: parent.top
146 left: parent.left
147 right: parent.right
148 }
150 source: getRenderer(model.renderer, model.contentType, model.rendererHint, results)
152 onLoaded: {
153 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
154 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
155 scopeView.enableHeightBehaviorOnNextCreation = false;
156 }
157 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
158 // TODO: the running apps grid doesn't support standard scope results model yet
159 item.firstModel = Qt.binding(function() { return results.firstModel })
160 item.secondModel = Qt.binding(function() { return results.secondModel })
161 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
162 } else {
163 item.model = Qt.binding(function() { return results })
164 }
165 item.objectName = Qt.binding(function() { return categoryId })
166 if (item.expandable) {
167 var shouldFilter = categoryId != categoryView.expandedCategoryId;
168 if (shouldFilter != item.filter) {
169 item.filter = shouldFilter;
170 }
171 }
172 updateDelegateCreationRange();
173 }
175 Component.onDestruction: {
176 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
177 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
178 }
179 }
181 Connections {
182 target: rendererLoader.item
183 onClicked: {
184 // Prepare the preview in case activate() triggers a preview only
185 openEffect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
186 previewListView.categoryId = categoryId
187 previewListView.categoryDelegate = rendererLoader.item
188 previewListView.model = target.model;
189 previewListView.init = true;
190 previewListView.currentIndex = index;
192 var item = target.model.get(index);
194 if ((scopeView.scope.id == "applications.scope" && categoryId == "installed")
195 || (scopeView.scope.id == "home.scope" && categoryId == "applications.scope")) {
196 scopeView.scope.activate(item.uri, item.icon, item.category, 0, item.mimetype, item.title,
197 item.comment, item.dndUri, item.metadata)
198 } else {
199 previewListView.open = true
201 scopeView.scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title,
202 item.comment, item.dndUri, item.metadata)
203 }
204 }
205 onPressAndHold: {
206 openEffect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
207 previewListView.categoryId = categoryId
208 previewListView.categoryDelegate = rendererLoader.item
209 previewListView.model = target.model;
210 previewListView.init = true;
211 previewListView.currentIndex = index;
212 previewListView.open = true
214 var item = target.model.get(index)
215 scopeView.scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title,
216 item.comment, item.dndUri, item.metadata)
217 }
218 }
219 Connections {
220 target: categoryView
221 onExpandedCategoryIdChanged: {
222 collapseAllButExpandedCategory();
223 }
224 onCorrectExpandedCategory: {
225 collapseAllButExpandedCategory();
226 }
227 function collapseAllButExpandedCategory() {
228 var item = rendererLoader.item;
229 if (item.expandable) {
230 var shouldFilter = categoryId != categoryView.expandedCategoryId;
231 if (shouldFilter != item.filter) {
232 // If the filter animation will be seen start it, otherwise, just flip the switch
233 var shrinkingVisible = shouldFilter && y + item.collapsedHeight < categoryView.height;
234 var growingVisible = !shouldFilter && y + height < categoryView.height;
235 if (!previewListView.open || !shouldFilter) {
236 if (shrinkingVisible || growingVisible) {
237 item.startFilterAnimation(shouldFilter)
238 } else {
239 item.filter = shouldFilter;
240 }
241 if (!shouldFilter && !previewListView.open) {
242 categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
243 }
244 }
245 }
246 }
247 }
248 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
249 onContentYChanged: rendererLoader.updateDelegateCreationRange();
250 onHeightChanged: rendererLoader.updateDelegateCreationRange();
251 onContentHeightChanged: rendererLoader.updateDelegateCreationRange();
252 }
254 function updateDelegateCreationRange() {
255 // Do not update the range if we are overshooting up or down, since we'll come back
256 // to the stable position and delete/create items without any reason
257 if (categoryView.contentY < categoryView.originY) {
258 return;
259 } else if (categoryView.contentY + categoryView.height > categoryView.contentHeight) {
260 return;
261 }
263 if (item && item.hasOwnProperty("delegateCreationBegin")) {
264 if (baseItem.y + baseItem.height <= 0) {
265 // Not visible (item at top of the list)
266 item.delegateCreationBegin = baseItem.height
267 item.delegateCreationEnd = baseItem.height
268 } else if (baseItem.y >= categoryView.height) {
269 // Not visible (item at bottom of the list)
270 item.delegateCreationBegin = 0
271 item.delegateCreationEnd = 0
272 } else {
273 item.delegateCreationBegin = Math.max(-baseItem.y, 0)
274 item.delegateCreationEnd = Math.min(categoryView.height + item.delegateCreationBegin, baseItem.height)
275 }
276 }
277 }
278 }
280 onHeightChanged: rendererLoader.updateDelegateCreationRange();
281 onYChanged: rendererLoader.updateDelegateCreationRange();
282 }
284 sectionProperty: "name"
285 sectionDelegate: ListItems.Header {
286 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
287 property var delegate: categoryView.item(delegateIndex)
288 width: categoryView.width
289 text: section
290 image: {
291 if (delegate && delegate.expandable)
292 return delegate.filtered ? "graphics/header_handlearrow.png" : "graphics/header_handlearrow2.png"
293 return "";
294 }
295 onClicked: {
296 if (categoryView.expandedCategoryId != delegate.category)
297 categoryView.expandedCategoryId = delegate.category;
298 else
299 categoryView.expandedCategoryId = "";
300 }
301 }
302 pageHeader: Item {
303 implicitHeight: scopeView.tabBarHeight
304 onHeightChanged: {
305 if (scopeView.pageHeader) {
306 scopeView.pageHeader.height = height;
307 }
308 }
309 onYChanged: positionRealHeader();
311 function positionRealHeader() {
312 if (scopeView.pageHeader) {
313 scopeView.pageHeader.y = y + parent.y;
314 }
315 }
316 }
317 }
319 function getDefaultRendererId(contentType) {
320 switch (contentType) {
321 default: return "grid";
322 }
323 }
325 function getRenderer(rendererId, contentType, rendererHint, results) {
326 if (rendererId == "default") {
327 rendererId = getDefaultRendererId(contentType);
328 }
329 if (rendererId == "carousel") {
330 // Selectively disable carousel, 6 is fixed for now, should change on the form factor
331 if (results.count <= 6)
332 rendererId = "grid"
333 }
334 switch (rendererId) {
335 case "carousel": {
336 switch (contentType) {
337 case "music": return "Music/MusicCarousel.qml";
338 case "video": return "Video/VideoCarousel.qml";
339 default: return "Generic/GenericCarousel.qml";
340 }
341 }
342 case "grid": {
343 switch (contentType) {
344 case "apps": {
345 if (rendererHint == "toggled")
346 return "Apps/DashPluginFilterGrid.qml";
347 else
348 return "Generic/GenericFilterGrid.qml";
349 }
350 case "music": return "Music/MusicFilterGrid.qml";
351 case "video": return "Video/VideoFilterGrid.qml";
352 case "weather": return "Generic/WeatherFilterGrid.qml";
353 default: return "Generic/GenericFilterGrid.qml";
354 }
355 }
356 case "special": {
357 switch (contentType) {
358 case "apps": return "Apps/RunningApplicationsGrid.qml";
359 default: return "Generic/GenericFilterGrid.qml";
360 }
361 }
362 default: return "Generic/GenericFilterGrid.qml";
363 }
364 }
366 Connections {
367 target: scopeView.scope
368 onPreviewReady: {
369 if (previewListView.init) {
370 // Preview was triggered because of a click on the item. Need to expand now.
371 if (!previewListView.open) {
372 previewListView.open = true
373 }
375 var index = previewListView.currentIndex
376 previewListView.currentIndex = -1
377 previewListView.currentIndex = index
378 previewListView.init = false
379 }
380 previewListView.currentItem.previewData = preview
381 }
382 }
384 // TODO: Move as InverseMouseArea to DashPreview
385 MouseArea {
386 objectName: "closePreviewMouseArea"
387 enabled: previewListView.onScreen
388 anchors {
389 fill: parent
390 topMargin: openEffect.bottomGapPx
391 }
392 onClicked: {
393 previewListView.open = false;
394 }
395 }