on ca8e75e help sign in
unity8/qml/Dash/GenericScopeView.qml
327 lines 14 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.2
21import Unity.Application 0.1
22import "../Components"
23import "../Components/ListItems" as ListItems
25FocusScope {
26 id: scopeView
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
36 property bool enableHeightBehaviorOnNextCreation: false
37 property var categoryView: categoryView
39 onScopeChanged: {
40 if (scope) {
41 scope.activateApplication.connect(activateApp);
42 }
43 }
45 function activateApp(appId) {
46 shell.activateApplication(appId);
47 }
49 function positionAtBeginning() {
50 categoryView.positionAtBeginning()
51 }
53 function showHeader() {
54 categoryView.showHeader()
55 }
57 Binding {
58 target: scope
59 property: "isActive"
60 value: isCurrent && !previewListView.open
61 }
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 }
72 onIsCurrentChanged: {
73 pageHeader.resetSearch();
74 previewListView.open = false;
75 }
77 Binding {
78 target: scopeView.scope
79 property: "searchQuery"
80 value: pageHeader.searchQuery
81 when: isCurrent
82 }
84 Binding {
85 target: pageHeader
86 property: "searchQuery"
87 value: scopeView.scope ? scopeView.scope.searchQuery : ""
88 when: isCurrent
89 }
91 Connections {
92 target: panel
93 onSearchClicked: if (isCurrent) {
94 pageHeader.triggerSearch()
95 categoryView.showHeader()
96 }
97 }
99 Connections {
100 target: scopeView.scope
101 onShowDash: previewListView.open = false;
102 onHideDash: previewListView.open = false;
103 }
105 ScopeListView {
106 id: categoryView
107 objectName: "categoryListView"
108 anchors.fill: parent
109 model: scopeView.categories
110 forceNoClip: previewListView.open
112 property string expandedCategoryId: ""
114 onContentYChanged: pageHeader.positionRealHeader();
115 onOriginYChanged: pageHeader.positionRealHeader();
116 onContentHeightChanged: pageHeader.positionRealHeader();
118 delegate: ListItems.Base {
119 id: baseItem
120 objectName: "dashCategory" + category
121 highlightWhenPressed: false
122 showDivider: false
124 readonly property bool expandable: rendererLoader.item ? rendererLoader.item.expandable : false
125 readonly property bool filtered: rendererLoader.item ? rendererLoader.item.filtered : true
126 readonly property string category: categoryId
127 readonly property var item: rendererLoader.item
129 CardTool {
130 id: cardTool
132 count: results.count
133 template: model.renderer
134 components: model.components
135 viewWidth: parent.width
136 }
138 Loader {
139 id: rendererLoader
140 anchors {
141 top: parent.top
142 left: parent.left
143 right: parent.right
144 }
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 }
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 item.setFilter(shouldFilter, false /*animate*/);
172 }
173 updateDelegateCreationRange();
174 item.cardTool = cardTool;
175 }
177 Component.onDestruction: {
178 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
179 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
180 }
181 }
183 Connections {
184 target: rendererLoader.item
185 onClicked: {
186 if (scopeView.scope.id === "scopes" || (scopeView.scope.id == "clickscope" && categoryId == "local")) {
187 // TODO Technically it is possible that calling activate() will make the scope emit
188 // previewRequested so that we show a preview but there's no scope that does that yet
189 // so it's not implemented
190 var item = target.model.get(index);
191 scopeView.scope.activate(item.result)
192 } else {
193 previewListView.model = target.model;
194 previewListView.currentIndex = -1
195 previewListView.currentIndex = index;
196 previewListView.open = true
197 }
198 }
199 onPressAndHold: {
200 previewListView.model = target.model;
201 previewListView.currentIndex = -1
202 previewListView.currentIndex = index;
203 previewListView.open = true
204 }
205 }
206 Connections {
207 target: categoryView
208 onExpandedCategoryIdChanged: {
209 collapseAllButExpandedCategory();
210 }
211 function collapseAllButExpandedCategory() {
212 var item = rendererLoader.item;
213 if (item.expandable) {
214 var shouldFilter = categoryId != categoryView.expandedCategoryId;
215 if (shouldFilter != item.filter) {
216 // If the filter animation will be seen start it, otherwise, just flip the switch
217 var shrinkingVisible = shouldFilter && y + item.collapsedHeight < categoryView.height;
218 var growingVisible = !shouldFilter && y + height < categoryView.height;
219 if (!previewListView.open || !shouldFilter) {
220 var animate = shrinkingVisible || growingVisible;
221 item.setFilter(shouldFilter, animate)
222 if (!shouldFilter && !previewListView.open) {
223 categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
224 }
225 }
226 }
227 }
228 }
229 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
230 onContentYChanged: rendererLoader.updateDelegateCreationRange();
231 onHeightChanged: rendererLoader.updateDelegateCreationRange();
232 onContentHeightChanged: rendererLoader.updateDelegateCreationRange();
233 }
235 function updateDelegateCreationRange() {
236 if (categoryView.moving) {
237 // Do not update the range if we are overshooting up or down, since we'll come back
238 // to the stable position and delete/create items without any reason
239 if (categoryView.contentY < categoryView.originY) {
240 return;
241 } else if (categoryView.contentHeight - categoryView.originY > categoryView.height &&
242 categoryView.contentY + categoryView.height > categoryView.contentHeight) {
243 return;
244 }
245 }
247 if (item && item.hasOwnProperty("delegateCreationBegin")) {
248 if (baseItem.y + baseItem.height <= 0) {
249 // Not visible (item at top of the list viewport)
250 item.delegateCreationBegin = item.originY + baseItem.height
251 item.delegateCreationEnd = item.originY + baseItem.height
252 } else if (baseItem.y >= categoryView.height) {
253 // Not visible (item at bottom of the list viewport)
254 item.delegateCreationBegin = item.originY
255 item.delegateCreationEnd = item.originY
256 } else {
257 item.delegateCreationBegin = item.originY + Math.max(-baseItem.y, 0)
258 item.delegateCreationEnd = item.originY + Math.min(categoryView.height + item.delegateCreationBegin, baseItem.height)
259 }
260 }
261 }
263 Image {
264 visible: index != 0
265 anchors {
266 top: parent.top
267 left: parent.left
268 right: parent.right
269 }
270 fillMode: Image.Stretch
271 source: "graphics/dash_divider_top_lightgrad.png"
272 z: -1
273 }
275 Image {
276 // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
277 visible: index != categoryView.model.count - 1
278 anchors {
279 bottom: parent.bottom
280 left: parent.left
281 right: parent.right
282 }
283 fillMode: Image.Stretch
284 source: "graphics/dash_divider_top_darkgrad.png"
285 z: -1
286 }
287 }
289 onHeightChanged: rendererLoader.updateDelegateCreationRange();
290 onYChanged: rendererLoader.updateDelegateCreationRange();
291 }
293 sectionProperty: "name"
294 sectionDelegate: ListItems.Header {
295 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
296 property var delegate: categoryView.item(delegateIndex)
297 width: categoryView.width
298 text: section
299 image: {
300 if (delegate && delegate.expandable)
301 return delegate.filtered ? "graphics/header_handlearrow.png" : "graphics/header_handlearrow2.png"
302 return "";
303 }
304 onClicked: {
305 if (categoryView.expandedCategoryId != delegate.category)
306 categoryView.expandedCategoryId = delegate.category;
307 else
308 categoryView.expandedCategoryId = "";
309 }
310 }
311 pageHeader: Item {
312 implicitHeight: scopeView.tabBarHeight
313 onHeightChanged: {
314 if (scopeView.pageHeader && scopeView.isCurrent) {
315 scopeView.pageHeader.height = height;
316 }
317 }
318 onYChanged: positionRealHeader();
320 function positionRealHeader() {
321 if (scopeView.pageHeader && scopeView.isCurrent) {
322 scopeView.pageHeader.y = y + parent.y;
323 }
324 }
325 }
326 }