on c5cc48e help sign in
unity8/qml/Dash/GenericScopeView.qml
328 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 signal movementStarted
37 signal positionedAtBeginning
39 property bool enableHeightBehaviorOnNextCreation: false
40 property var categoryView: categoryView
42 onScopeChanged: {
43 if (scope) {
44 scope.activateApplication.connect(activateApp);
45 }
46 }
48 function activateApp(appId) {
49 shell.activateApplication(appId);
50 }
52 Binding {
53 target: scope
54 property: "isActive"
55 value: isCurrent && !previewListView.open
56 }
58 SortFilterProxyModel {
59 id: categoryFilter
60 model: scope ? scope.categories : null
61 dynamicSortFilter: true
62 filterRole: Categories.RoleCount
63 filterRegExp: /^0$/
64 invertMatch: true
65 }
67 onIsCurrentChanged: {
68 pageHeader.resetSearch();
69 previewListView.open = false;
70 }
72 onMovementStarted: categoryView.showHeader()
74 onPositionedAtBeginning: categoryView.positionAtBeginning()
76 Binding {
77 target: scopeView.scope
78 property: "searchQuery"
79 value: pageHeader.searchQuery
80 when: isCurrent
81 }
83 Binding {
84 target: pageHeader
85 property: "searchQuery"
86 value: scopeView.scope ? scopeView.scope.searchQuery : ""
87 when: isCurrent
88 }
90 Connections {
91 target: panel
92 onSearchClicked: if (isCurrent) {
93 pageHeader.triggerSearch()
94 categoryView.showHeader()
95 }
96 }
98 Connections {
99 target: scopeView.scope
100 onShowDash: previewListView.open = false;
101 onHideDash: previewListView.open = false;
102 }
104 ScopeListView {
105 id: categoryView
106 objectName: "categoryListView"
107 anchors.fill: parent
108 model: scopeView.categories
109 forceNoClip: previewListView.open
111 property string expandedCategoryId: ""
113 onContentYChanged: pageHeader.positionRealHeader();
114 onOriginYChanged: pageHeader.positionRealHeader();
115 onContentHeightChanged: pageHeader.positionRealHeader();
117 delegate: ListItems.Base {
118 id: baseItem
119 objectName: "dashCategory" + category
120 highlightWhenPressed: false
121 showDivider: false
123 readonly property bool expandable: rendererLoader.item ? rendererLoader.item.expandable : false
124 readonly property bool filtered: rendererLoader.item ? rendererLoader.item.filter : true
125 readonly property string category: categoryId
126 readonly property var item: rendererLoader.item
128 CardTool {
129 id: cardTool
131 count: results.count
132 template: model.renderer
133 components: model.components
134 viewWidth: parent.width
135 }
137 Loader {
138 id: rendererLoader
139 anchors {
140 top: parent.top
141 left: parent.left
142 right: parent.right
143 }
145 source: {
146 switch (cardTool.categoryLayout) {
147 case "carousel": return "CardCarousel.qml";
148 case "running-apps": return "Apps/RunningApplicationsGrid.qml";
149 case "grid":
150 default: return "CardFilterGrid.qml";
151 }
152 }
154 onLoaded: {
155 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
156 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
157 scopeView.enableHeightBehaviorOnNextCreation = false;
158 }
159 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
160 // TODO: this is still a kludge :D Ideally add some kind of hook so that we
161 // can do this from DashApps.qml or think a better way that needs no special casing
162 item.model = Qt.binding(function() { return runningApps; })
163 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
164 } else {
165 item.model = Qt.binding(function() { return results })
166 }
167 item.objectName = Qt.binding(function() { return categoryId })
168 if (item.expandable) {
169 var shouldFilter = categoryId != categoryView.expandedCategoryId;
170 if (shouldFilter != item.filter) {
171 item.filter = shouldFilter;
172 }
173 }
174 updateDelegateCreationRange();
175 item.cardTool = cardTool;
176 }
178 Component.onDestruction: {
179 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
180 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
181 }
182 }
184 Connections {
185 target: rendererLoader.item
186 onClicked: {
187 if (scopeView.scope.id === "scopes" || (scopeView.scope.id == "clickscope" && categoryId == "local")) {
188 // TODO Technically it is possible that calling activate() will make the scope emit
189 // previewRequested so that we show a preview but there's no scope that does that yet
190 // so it's not implemented
191 var item = target.model.get(index);
192 scopeView.scope.activate(item.result)
193 } else {
194 previewListView.model = target.model;
195 previewListView.currentIndex = -1
196 previewListView.currentIndex = index;
197 previewListView.open = true
198 }
199 }
200 onPressAndHold: {
201 previewListView.model = target.model;
202 previewListView.currentIndex = -1
203 previewListView.currentIndex = index;
204 previewListView.open = true
205 }
206 }
207 Connections {
208 target: categoryView
209 onExpandedCategoryIdChanged: {
210 collapseAllButExpandedCategory();
211 }
212 function collapseAllButExpandedCategory() {
213 var item = rendererLoader.item;
214 if (item.expandable) {
215 var shouldFilter = categoryId != categoryView.expandedCategoryId;
216 if (shouldFilter != item.filter) {
217 // If the filter animation will be seen start it, otherwise, just flip the switch
218 var shrinkingVisible = shouldFilter && y + item.collapsedHeight < categoryView.height;
219 var growingVisible = !shouldFilter && y + height < categoryView.height;
220 if (!previewListView.open || !shouldFilter) {
221 if (shrinkingVisible || growingVisible) {
222 item.startFilterAnimation(shouldFilter)
223 } else {
224 item.filter = shouldFilter;
225 }
226 if (!shouldFilter && !previewListView.open) {
227 categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
228 }
229 }
230 }
231 }
232 }
233 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
234 onContentYChanged: rendererLoader.updateDelegateCreationRange();
235 onHeightChanged: rendererLoader.updateDelegateCreationRange();
236 onContentHeightChanged: rendererLoader.updateDelegateCreationRange();
237 }
239 function updateDelegateCreationRange() {
240 // Do not update the range if we are overshooting up or down, since we'll come back
241 // to the stable position and delete/create items without any reason
242 if (categoryView.contentY < categoryView.originY) {
243 return;
244 } else if (categoryView.contentY + categoryView.height > categoryView.contentHeight) {
245 return;
246 }
248 if (item && item.hasOwnProperty("delegateCreationBegin")) {
249 if (baseItem.y + baseItem.height <= 0) {
250 // Not visible (item at top of the list viewport)
251 item.delegateCreationBegin = item.originY + baseItem.height
252 item.delegateCreationEnd = item.originY + baseItem.height
253 } else if (baseItem.y >= categoryView.height) {
254 // Not visible (item at bottom of the list viewport)
255 item.delegateCreationBegin = item.originY
256 item.delegateCreationEnd = item.originY
257 } else {
258 item.delegateCreationBegin = item.originY + Math.max(-baseItem.y, 0)
259 item.delegateCreationEnd = item.originY + Math.min(categoryView.height + item.delegateCreationBegin, baseItem.height)
260 }
261 }
262 }
264 Image {
265 visible: index != 0
266 anchors {
267 top: parent.top
268 left: parent.left
269 right: parent.right
270 }
271 fillMode: Image.Stretch
272 source: "graphics/dash_divider_top_lightgrad.png"
273 z: -1
274 }
276 Image {
277 // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
278 visible: index != categoryView.model.count - 1
279 anchors {
280 bottom: parent.bottom
281 left: parent.left
282 right: parent.right
283 }
284 fillMode: Image.Stretch
285 source: "graphics/dash_divider_top_darkgrad.png"
286 z: -1
287 }
288 }
290 onHeightChanged: rendererLoader.updateDelegateCreationRange();
291 onYChanged: rendererLoader.updateDelegateCreationRange();
292 }
294 sectionProperty: "name"
295 sectionDelegate: ListItems.Header {
296 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
297 property var delegate: categoryView.item(delegateIndex)
298 width: categoryView.width
299 text: section
300 image: {
301 if (delegate && delegate.expandable)
302 return delegate.filtered ? "graphics/header_handlearrow.png" : "graphics/header_handlearrow2.png"
303 return "";
304 }
305 onClicked: {
306 if (categoryView.expandedCategoryId != delegate.category)
307 categoryView.expandedCategoryId = delegate.category;
308 else
309 categoryView.expandedCategoryId = "";
310 }
311 }
312 pageHeader: Item {
313 implicitHeight: scopeView.tabBarHeight
314 onHeightChanged: {
315 if (scopeView.pageHeader && scopeView.isCurrent) {
316 scopeView.pageHeader.height = height;
317 }
318 }
319 onYChanged: positionRealHeader();
321 function positionRealHeader() {
322 if (scopeView.pageHeader && scopeView.isCurrent) {
323 scopeView.pageHeader.y = y + parent.y;
324 }
325 }
326 }
327 }