on 29019df help sign in
unity8/qml/Dash/GenericScopeView.qml
425 lines 17 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 Dash 0.1
22import "../Components"
23import "../Components/ListItems" as ListItems
25FocusScope {
26 id: scopeView
28 property var scope: null
29 property SortFilterProxyModel categories: categoryFilter
30 property bool isCurrent: false
31 property alias moving: categoryView.moving
32 property bool hasBackAction: false
33 property bool enableHeightBehaviorOnNextCreation: false
34 property var categoryView: categoryView
36 property var scopeStyle: ScopeStyle {
37 style: scope ? scope.customizations : {}
38 }
40 signal backClicked()
42 onScopeChanged: {
43 if (scope) {
44 scope.activateApplication.connect(activateApp);
45 }
46 }
48 function activateApp(appId) {
49 Qt.openUrlExternally(appId);
50 }
52 function positionAtBeginning() {
53 categoryView.positionAtBeginning()
54 }
56 function showHeader() {
57 categoryView.showHeader()
58 }
60 function closePreview() {
61 previewListView.open = false;
62 }
64 Binding {
65 target: scope
66 property: "isActive"
67 value: isCurrent && !previewListView.open
68 }
70 SortFilterProxyModel {
71 id: categoryFilter
72 model: scope ? scope.categories : null
73 dynamicSortFilter: true
74 filterRole: Categories.RoleCount
75 filterRegExp: /^0$/
76 invertMatch: true
77 }
79 onIsCurrentChanged: {
80 pageHeader.resetSearch();
81 previewListView.open = false;
82 }
84 Binding {
85 target: scopeView.scope
86 property: "searchQuery"
87 value: pageHeader.searchQuery
88 when: isCurrent
89 }
91 Binding {
92 target: pageHeader
93 property: "searchQuery"
94 value: scopeView.scope ? scopeView.scope.searchQuery : ""
95 when: isCurrent
96 }
98 Connections {
99 target: scopeView.scope
100 onShowDash: previewListView.open = false;
101 onHideDash: previewListView.open = false;
102 }
104 Rectangle {
105 anchors.fill: parent
106 color: scopeView.scopeStyle ? scopeView.scopeStyle.background : "transparent"
107 visible: color != "transparent"
108 }
110 ScopeListView {
111 id: categoryView
112 objectName: "categoryListView"
114 x: previewListView.open ? -width : 0
115 Behavior on x { UbuntuNumberAnimation { } }
116 width: parent.width
117 height: parent.height
119 model: scopeView.categories
120 forceNoClip: previewListView.open
121 pixelAligned: true
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 string headerLink: model.headerLink
140 readonly property var item: rendererLoader.item
142 function expand(expand, animate) {
143 heightBehaviour.enabled = animate;
144 expanded = expand;
145 }
147 CardTool {
148 id: cardTool
149 objectName: "cardTool"
150 count: results.count
151 template: model.renderer
152 components: model.components
153 viewWidth: parent.width
154 }
156 onExpandableChanged: {
157 // This can happen with the VJ that doesn't know how height it will be on creation
158 // so doesn't set expandable until a bit too late for onLoaded
159 if (expandable) {
160 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
161 baseItem.expand(shouldExpand, false /*animate*/);
162 }
163 }
165 onHeightChanged: rendererLoader.updateDelegateCreationRange();
166 onYChanged: rendererLoader.updateDelegateCreationRange();
168 Loader {
169 id: rendererLoader
170 anchors {
171 top: parent.top
172 left: parent.left
173 right: parent.right
174 topMargin: name != "" ? 0 : units.gu(2)
175 }
177 Behavior on height {
178 id: heightBehaviour
179 enabled: false
180 animation: UbuntuNumberAnimation {
181 onRunningChanged: {
182 if (!running) {
183 heightBehaviour.enabled = false
184 }
185 }
186 }
187 }
189 readonly property bool expanded: baseItem.expanded || !baseItem.expandable
190 height: expanded ? item.expandedHeight : item.collapsedHeight
192 source: {
193 switch (cardTool.categoryLayout) {
194 case "carousel": return "CardCarousel.qml";
195 case "vertical-journal": return "CardVerticalJournal.qml";
196 case "grid":
197 default: return "CardGrid.qml";
198 }
199 }
201 onLoaded: {
202 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
203 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
204 scopeView.enableHeightBehaviorOnNextCreation = false;
205 }
206 item.model = Qt.binding(function() { return results })
207 item.objectName = Qt.binding(function() { return categoryId })
208 item.scopeStyle = scopeView.scopeStyle;
209 if (baseItem.expandable) {
210 var shouldExpand = categoryId === categoryView.expandedCategoryId;
211 baseItem.expand(shouldExpand, false /*animate*/);
212 }
213 updateDelegateCreationRange();
214 item.cardTool = cardTool;
215 }
217 Component.onDestruction: {
218 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
219 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
220 }
221 }
223 Connections {
224 target: rendererLoader.item
225 onClicked: {
226 if (scopeView.scope.id === "scopes" || scopeView.scope.id == "clickscope") {
227 // TODO Technically it is possible that calling activate() will make the scope emit
228 // previewRequested so that we show a preview but there's no scope that does that yet
229 // so it's not implemented
230 scopeView.scope.activate(result)
231 } else {
232 openPreview(index);
233 }
234 }
235 onPressAndHold: openPreview(index)
237 function openPreview(index) {
238 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
239 previewLimitModel.model = target.model;
240 previewLimitModel.limit = target.collapsedItemCount;
241 previewListView.model = previewLimitModel;
242 } else {
243 previewListView.model = target.model;
244 }
245 previewListView.currentIndex = -1;
246 previewListView.currentIndex = index;
247 previewListView.open = true;
248 }
249 }
250 Connections {
251 target: categoryView
252 onExpandedCategoryIdChanged: {
253 collapseAllButExpandedCategory();
254 }
255 function collapseAllButExpandedCategory() {
256 var item = rendererLoader.item;
257 if (baseItem.expandable) {
258 var shouldExpand = categoryId === categoryView.expandedCategoryId;
259 if (shouldExpand != baseItem.expanded) {
260 // If the filter animation will be seen start it, otherwise, just flip the switch
261 var shrinkingVisible = !shouldExpand && y + item.collapsedHeight + seeAll.height < categoryView.height;
262 var growingVisible = shouldExpand && y + height < categoryView.height;
263 if (!previewListView.open || shouldExpand) {
264 var animate = shrinkingVisible || growingVisible;
265 baseItem.expand(shouldExpand, animate)
266 if (shouldExpand && !previewListView.open) {
267 categoryView.maximizeVisibleArea(index, item.expandedHeight + seeAll.height);
268 }
269 }
270 }
271 }
272 }
273 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
274 onContentYChanged: rendererLoader.updateDelegateCreationRange();
275 onHeightChanged: rendererLoader.updateDelegateCreationRange();
276 onContentHeightChanged: rendererLoader.updateDelegateCreationRange();
277 }
279 function updateDelegateCreationRange() {
280 if (categoryView.moving) {
281 // Do not update the range if we are overshooting up or down, since we'll come back
282 // to the stable position and delete/create items without any reason
283 if (categoryView.contentY < categoryView.originY) {
284 return;
285 } else if (categoryView.contentHeight - categoryView.originY > categoryView.height &&
286 categoryView.contentY + categoryView.height > categoryView.contentHeight) {
287 return;
288 }
289 }
291 if (item && item.hasOwnProperty("displayMarginBeginning")) {
292 // TODO do we need item.originY here, test 1300302 once we have a silo
293 // and we can run it on the phone
294 if (baseItem.y + baseItem.height <= 0) {
295 // Not visible (item at top of the list viewport)
296 item.displayMarginBeginning = -baseItem.height;
297 item.displayMarginEnd = 0;
298 } else if (baseItem.y >= categoryView.height) {
299 // Not visible (item at bottom of the list viewport)
300 item.displayMarginBeginning = 0;
301 item.displayMarginEnd = -baseItem.height;
302 } else {
303 item.displayMarginBeginning = -Math.max(-baseItem.y, 0);
304 item.displayMarginEnd = -Math.max(baseItem.height - seeAll.height
305 - categoryView.height + baseItem.y, 0)
306 }
307 }
308 }
309 }
311 AbstractButton {
312 id: seeAll
313 objectName: "seeAll"
314 anchors {
315 top: rendererLoader.bottom
316 left: parent.left
317 right: parent.right
318 }
319 height: seeAllLabel.visible ? seeAllLabel.font.pixelSize + units.gu(6) : 0
321 onClicked: {
322 if (categoryView.expandedCategoryId != baseItem.category) {
323 categoryView.expandedCategoryId = baseItem.category;
324 } else {
325 categoryView.expandedCategoryId = "";
326 }
327 }
329 Label {
330 id: seeAllLabel
331 text: baseItem.expanded ? i18n.tr("See less") : i18n.tr("See all")
332 anchors {
333 centerIn: parent
334 verticalCenterOffset: units.gu(-0.5)
335 }
336 fontSize: "small"
337 font.weight: Font.Bold
338 color: scopeStyle ? scopeStyle.foreground : "grey"
339 visible: baseItem.expandable && !baseItem.headerLink
340 }
341 }
343 Image {
344 visible: index != 0
345 anchors {
346 top: parent.top
347 left: parent.left
348 right: parent.right
349 }
350 fillMode: Image.Stretch
351 source: "graphics/dash_divider_top_lightgrad.png"
352 z: -1
353 }
355 Image {
356 // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
357 visible: index != categoryView.model.count - 1
358 anchors {
359 bottom: seeAll.bottom
360 left: parent.left
361 right: parent.right
362 }
363 fillMode: Image.Stretch
364 source: "graphics/dash_divider_top_darkgrad.png"
365 z: -1
366 }
367 }
369 sectionProperty: "name"
370 sectionDelegate: ListItems.Header {
371 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
372 readonly property var delegate: categoryView.item(delegateIndex)
373 width: categoryView.width
374 height: section != "" ? units.gu(5) : 0
375 text: section
376 color: scopeStyle ? scopeStyle.foreground : "grey"
377 iconName: delegate && delegate.headerLink ? "go-next" : ""
378 onClicked: {
379 if (delegate.headerLink) scopeView.scope.performQuery(delegate.headerLink);
380 }
381 }
383 pageHeader: PageHeader {
384 id: pageHeader
385 objectName: "scopePageHeader"
386 width: parent.width
387 title: scopeView.scope ? scopeView.scope.name : ""
388 showBackButton: scopeView.hasBackAction
389 searchEntryEnabled: true
390 searchInProgress: scopeView.scope ? scopeView.scope.searchInProgress : false
391 scopeStyle: scopeView.scopeStyle
393 bottomItem: DashDepartments {
394 scope: scopeView.scope
395 width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
396 anchors.right: parent.right
397 windowHeight: scopeView.height
398 windowWidth: scopeView.width
399 scopeStyle: scopeView.scopeStyle
400 }
402 onBackClicked: scopeView.backClicked()
403 }
404 }
406 LimitProxyModel {
407 id: previewLimitModel
408 }
410 PreviewListView {
411 id: previewListView
412 objectName: "previewListView"
413 visible: x != width
414 scope: scopeView.scope
415 scopeStyle: scopeView.scopeStyle
416 width: parent.width
417 height: parent.height
418 anchors.left: categoryView.right
420 onOpenChanged: {
421 pageHeader.unfocus();
422 }
423 }