on ff2ea35 help sign in
unity8/qml/Dash/GenericScopeView.qml
433 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 readonly property alias navigationShown: dashNavigation.showList
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
36 property alias paginationCount: pageHeader.paginationCount
37 property alias paginationIndex: pageHeader.paginationIndex
39 property var scopeStyle: ScopeStyle {
40 style: scope ? scope.customizations : {}
41 }
43 readonly property bool processing: scope ? scope.searchInProgress || previewListView.processing : false
45 signal backClicked()
47 function positionAtBeginning() {
48 categoryView.positionAtBeginning()
49 }
51 function showHeader() {
52 categoryView.showHeader()
53 }
55 function closePreview() {
56 previewListView.open = false;
57 }
59 Binding {
60 target: scope
61 property: "isActive"
62 value: isCurrent && !previewListView.open
63 }
65 SortFilterProxyModel {
66 id: categoryFilter
67 model: scope ? scope.categories : null
68 dynamicSortFilter: true
69 filterRole: Categories.RoleCount
70 filterRegExp: /^0$/
71 invertMatch: true
72 }
74 onIsCurrentChanged: {
75 pageHeader.resetSearch();
76 previewListView.open = false;
77 }
79 Binding {
80 target: scopeView.scope
81 property: "searchQuery"
82 value: pageHeader.searchQuery
83 when: isCurrent
84 }
86 Binding {
87 target: pageHeader
88 property: "searchQuery"
89 value: scopeView.scope ? scopeView.scope.searchQuery : ""
90 when: isCurrent
91 }
93 Connections {
94 target: scopeView.scope
95 onShowDash: previewListView.open = false;
96 onHideDash: previewListView.open = false;
97 }
99 Rectangle {
100 anchors.fill: parent
101 color: scopeView.scopeStyle ? scopeView.scopeStyle.background : "transparent"
102 visible: color != "transparent"
103 }
105 ScopeListView {
106 id: categoryView
107 objectName: "categoryListView"
109 x: previewListView.open ? -width : 0
110 Behavior on x { UbuntuNumberAnimation { } }
111 width: parent.width
112 height: parent.height
114 model: scopeView.categories
115 forceNoClip: previewListView.open
116 pixelAligned: true
117 interactive: !dashNavigation.showList
119 property string expandedCategoryId: ""
121 delegate: ListItems.Base {
122 id: baseItem
123 objectName: "dashCategory" + category
124 highlightWhenPressed: false
125 showDivider: false
127 readonly property bool expandable: {
128 if (categoryView.model.count === 1) return false;
129 if (cardTool.template && cardTool.template["collapsed-rows"] === 0) return false;
130 if (item && item.expandedHeight > item.collapsedHeight) return true;
131 return false;
132 }
133 property bool expanded: false
134 readonly property string category: categoryId
135 readonly property string headerLink: model.headerLink
136 readonly property var item: rendererLoader.item
138 function expand(expand, animate) {
139 heightBehaviour.enabled = animate;
140 expanded = expand;
141 }
143 CardTool {
144 id: cardTool
145 objectName: "cardTool"
146 count: results.count
147 template: model.renderer
148 components: model.components
149 viewWidth: parent.width
150 }
152 onExpandableChanged: {
153 // This can happen with the VJ that doesn't know how height it will be on creation
154 // so doesn't set expandable until a bit too late for onLoaded
155 if (expandable) {
156 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
157 baseItem.expand(shouldExpand, false /*animate*/);
158 }
159 }
161 onHeightChanged: rendererLoader.updateDelegateCreationRange();
162 onYChanged: rendererLoader.updateDelegateCreationRange();
164 Loader {
165 id: rendererLoader
166 anchors {
167 top: parent.top
168 left: parent.left
169 right: parent.right
170 topMargin: name != "" ? 0 : units.gu(2)
171 }
173 Behavior on height {
174 id: heightBehaviour
175 enabled: false
176 animation: UbuntuNumberAnimation {
177 onRunningChanged: {
178 if (!running) {
179 heightBehaviour.enabled = false
180 }
181 }
182 }
183 }
185 readonly property bool expanded: baseItem.expanded || !baseItem.expandable
186 height: expanded ? item.expandedHeight : item.collapsedHeight
188 source: {
189 switch (cardTool.categoryLayout) {
190 case "carousel": return "CardCarousel.qml";
191 case "vertical-journal": return "CardVerticalJournal.qml";
192 case "grid":
193 default: return "CardGrid.qml";
194 }
195 }
197 onLoaded: {
198 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
199 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
200 scopeView.enableHeightBehaviorOnNextCreation = false;
201 }
202 item.model = Qt.binding(function() { return results })
203 item.objectName = Qt.binding(function() { return categoryId })
204 item.scopeStyle = scopeView.scopeStyle;
205 if (baseItem.expandable) {
206 var shouldExpand = categoryId === categoryView.expandedCategoryId;
207 baseItem.expand(shouldExpand, false /*animate*/);
208 }
209 updateDelegateCreationRange();
210 if (scope && scope.id === "clickscope" && (categoryId === "predefined" || categoryId === "local")) {
211 // Yeah, hackish :/
212 cardTool.artShapeSize = Qt.size(units.gu(8), units.gu(7.5));
213 }
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 (cardTool.template && cardTool.template["non-interactive"]) {
227 return;
228 }
229 if (scopeView.scope.id === "scopes" || scopeView.scope.id == "clickscope") {
230 // TODO Technically it is possible that calling activate() will make the scope emit
231 // previewRequested so that we show a preview but there's no scope that does that yet
232 // so it's not implemented
233 scopeView.scope.activate(result)
234 } else {
235 openPreview(index);
236 }
237 }
238 onPressAndHold: {
239 if (cardTool.template && cardTool.template["non-interactive"]) {
240 return;
241 }
242 openPreview(index);
243 }
245 function openPreview(index) {
246 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
247 previewLimitModel.model = target.model;
248 previewLimitModel.limit = target.collapsedItemCount;
249 previewListView.model = previewLimitModel;
250 } else {
251 previewListView.model = target.model;
252 }
253 previewListView.currentIndex = -1;
254 previewListView.currentIndex = index;
255 previewListView.open = true;
256 }
257 }
258 Connections {
259 target: categoryView
260 onExpandedCategoryIdChanged: {
261 collapseAllButExpandedCategory();
262 }
263 function collapseAllButExpandedCategory() {
264 var item = rendererLoader.item;
265 if (baseItem.expandable) {
266 var shouldExpand = categoryId === categoryView.expandedCategoryId;
267 if (shouldExpand != baseItem.expanded) {
268 // If the filter animation will be seen start it, otherwise, just flip the switch
269 var shrinkingVisible = !shouldExpand && y + item.collapsedHeight + seeAll.height < categoryView.height;
270 var growingVisible = shouldExpand && y + height < categoryView.height;
271 if (!previewListView.open || shouldExpand) {
272 var animate = shrinkingVisible || growingVisible;
273 baseItem.expand(shouldExpand, animate)
274 if (shouldExpand && !previewListView.open) {
275 categoryView.maximizeVisibleArea(index, item.expandedHeight + seeAll.height);
276 }
277 }
278 }
279 }
280 }
281 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
282 onContentYChanged: rendererLoader.updateDelegateCreationRange();
283 onHeightChanged: rendererLoader.updateDelegateCreationRange();
284 onContentHeightChanged: rendererLoader.updateDelegateCreationRange();
285 }
287 function updateDelegateCreationRange() {
288 if (categoryView.moving) {
289 // Do not update the range if we are overshooting up or down, since we'll come back
290 // to the stable position and delete/create items without any reason
291 if (categoryView.contentY < categoryView.originY) {
292 return;
293 } else if (categoryView.contentHeight - categoryView.originY > categoryView.height &&
294 categoryView.contentY + categoryView.height > categoryView.contentHeight) {
295 return;
296 }
297 }
299 if (item && item.hasOwnProperty("displayMarginBeginning")) {
300 // TODO do we need item.originY here, test 1300302 once we have a silo
301 // and we can run it on the phone
302 if (baseItem.y + baseItem.height <= 0) {
303 // Not visible (item at top of the list viewport)
304 item.displayMarginBeginning = -baseItem.height;
305 item.displayMarginEnd = 0;
306 } else if (baseItem.y >= categoryView.height) {
307 // Not visible (item at bottom of the list viewport)
308 item.displayMarginBeginning = 0;
309 item.displayMarginEnd = -baseItem.height;
310 } else {
311 item.displayMarginBeginning = -Math.max(-baseItem.y, 0);
312 item.displayMarginEnd = -Math.max(baseItem.height - seeAll.height
313 - categoryView.height + baseItem.y, 0)
314 }
315 }
316 }
317 }
319 AbstractButton {
320 id: seeAll
321 objectName: "seeAll"
322 anchors {
323 top: rendererLoader.bottom
324 left: parent.left
325 right: parent.right
326 }
327 height: seeAllLabel.visible ? seeAllLabel.font.pixelSize + units.gu(6) : 0
329 onClicked: {
330 if (categoryView.expandedCategoryId != baseItem.category) {
331 categoryView.expandedCategoryId = baseItem.category;
332 } else {
333 categoryView.expandedCategoryId = "";
334 }
335 }
337 Label {
338 id: seeAllLabel
339 text: baseItem.expanded ? i18n.tr("See less") : i18n.tr("See all")
340 anchors {
341 centerIn: parent
342 verticalCenterOffset: units.gu(-0.5)
343 }
344 fontSize: "small"
345 font.weight: Font.Bold
346 color: scopeStyle ? scopeStyle.foreground : "grey"
347 visible: baseItem.expandable && !baseItem.headerLink
348 }
349 }
351 Image {
352 visible: index != 0
353 anchors {
354 top: parent.top
355 left: parent.left
356 right: parent.right
357 }
358 fillMode: Image.Stretch
359 source: "graphics/dash_divider_top_lightgrad.png"
360 z: -1
361 }
363 Image {
364 // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
365 visible: index != categoryView.model.count - 1
366 anchors {
367 bottom: seeAll.bottom
368 left: parent.left
369 right: parent.right
370 }
371 fillMode: Image.Stretch
372 source: "graphics/dash_divider_top_darkgrad.png"
373 z: -1
374 }
375 }
377 sectionProperty: "name"
378 sectionDelegate: ListItems.Header {
379 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
380 readonly property var delegate: categoryView.item(delegateIndex)
381 width: categoryView.width
382 height: section != "" ? units.gu(5) : 0
383 text: section
384 color: scopeStyle ? scopeStyle.foreground : "grey"
385 iconName: delegate && delegate.headerLink ? "go-next" : ""
386 onClicked: {
387 if (delegate.headerLink) scopeView.scope.performQuery(delegate.headerLink);
388 }
389 }
391 pageHeader: PageHeader {
392 id: pageHeader
393 objectName: "scopePageHeader"
394 width: parent.width
395 title: scopeView.scope ? scopeView.scope.name : ""
396 showBackButton: scopeView.hasBackAction
397 searchEntryEnabled: true
398 scopeStyle: scopeView.scopeStyle
400 bottomItem: DashNavigation {
401 id: dashNavigation
402 scope: scopeView.scope
403 width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
404 anchors.right: parent.right
405 windowHeight: scopeView.height
406 windowWidth: scopeView.width
407 scopeStyle: scopeView.scopeStyle
408 }
410 onBackClicked: scopeView.backClicked()
411 }
412 }
414 LimitProxyModel {
415 id: previewLimitModel
416 }
418 PreviewListView {
419 id: previewListView
420 objectName: "previewListView"
421 visible: x != width
422 scope: scopeView.scope
423 scopeStyle: scopeView.scopeStyle
424 width: parent.width
425 height: parent.height
426 anchors.left: categoryView.right
428 onOpenChanged: {
429 pageHeader.unfocus();
430 }
431 }