2 * Copyright (C) 2013 Canonical, Ltd.
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.
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.
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/>.
18import Ubuntu.Components 0.1
22import "../Components/ListItems" as ListItems
28 property SortFilterProxyModel categories: categoryFilter
29 property bool isCurrent
30 property ListModel searchHistory
31 property alias moving: categoryView.moving
34 signal movementStarted
35 signal positionedAtBeginning
37 readonly property alias previewShown: previewListView.onScreen
38 property bool enableHeightBehaviorOnNextCreation: false
40 // FIXME delay the search so that daemons have time to settle, note that
41 // removing this will break ScopeView::test_changeScope
45 scope.activateApplication.connect(activateApp);
49 function activateApp(appId) {
50 shell.activateApplication(appId);
62 onTriggered: scope.searchQuery = ""
65 SortFilterProxyModel {
67 model: scope ? scope.categories : null
68 dynamicSortFilter: true
69 filterRole: Categories.RoleCount
75 pageHeader.resetSearch();
76 previewListView.open = false;
79 onMovementStarted: categoryView.showHeader()
81 onPositionedAtBeginning: categoryView.positionAtBeginning()
84 target: scopeView.scope
85 property: "searchQuery"
86 value: pageHeader.searchQuery
91 property: "searchQuery"
92 value: scopeView.scope.searchQuery
97 onSearchClicked: if (isCurrent) {
98 pageHeader.triggerSearch()
99 categoryView.showHeader()
104 target: scopeView.scope
105 onShowDash: previewListView.open = false;
106 onHideDash: previewListView.open = false;
111 objectName: "categoryListView"
113 model: scopeView.categories
114 forceNoClip: previewListView.onScreen
116 onAtYEndChanged: if (atYEnd) endReached()
117 onMovingChanged: if (moving && atYEnd) endReached()
119 property string expandedCategoryId: ""
120 signal correctExpandedCategory();
122 Behavior on contentY {
123 enabled: previewListView.open
124 UbuntuNumberAnimation {}
127 delegate: ListItems.Base {
129 objectName: "dashCategory" + category
130 highlightWhenPressed: false
132 readonly property bool expandable: rendererLoader.item ? rendererLoader.item.expandable : false
133 readonly property bool filtered: rendererLoader.item ? rendererLoader.item.filter : true
134 readonly property string category: categoryId
144 source: getRenderer(model.renderer, model.contentType, model.rendererHint, results)
147 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
148 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
149 scopeView.enableHeightBehaviorOnNextCreation = false;
151 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
152 // TODO: the running apps grid doesn't support standard scope results model yet
153 item.firstModel = Qt.binding(function() { return results.firstModel })
154 item.secondModel = Qt.binding(function() { return results.secondModel })
155 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
157 item.model = Qt.binding(function() { return results })
159 item.objectName = Qt.binding(function() { return categoryId })
160 if (item.expandable) {
161 var shouldFilter = categoryId != categoryView.expandedCategoryId;
162 if (shouldFilter != item.filter) {
163 item.filter = shouldFilter;
166 updateDelegateCreationRange();
169 Component.onDestruction: {
170 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
171 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
176 target: rendererLoader.item
178 // Prepare the preview in case activate() triggers a preview only
179 effect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
180 previewListView.categoryId = categoryId
181 previewListView.categoryDelegate = rendererLoader.item
182 previewListView.model = target.model;
183 previewListView.init = true;
184 previewListView.currentIndex = index;
186 var item = target.model.get(index);
188 if ((scopeView.scope.id == "applications.scope" && categoryId == "installed")
189 || (scopeView.scope.id == "home.scope" && categoryId == "applications.scope")) {
190 scopeView.scope.activate(item.uri, item.icon, item.category, 0, item.mimetype, item.title,
191 item.comment, item.dndUri, item.metadata)
193 previewListView.open = true
195 scopeView.scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title,
196 item.comment, item.dndUri, item.metadata)
200 effect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
201 previewListView.categoryId = categoryId
202 previewListView.categoryDelegate = rendererLoader.item
203 previewListView.model = target.model;
204 previewListView.init = true;
205 previewListView.currentIndex = index;
206 previewListView.open = true
208 var item = target.model.get(index)
209 scopeView.scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title,
210 item.comment, item.dndUri, item.metadata)
215 onExpandedCategoryIdChanged: {
216 collapseAllButExpandedCategory();
218 onCorrectExpandedCategory: {
219 collapseAllButExpandedCategory();
221 function collapseAllButExpandedCategory() {
222 var item = rendererLoader.item;
223 if (item.expandable) {
224 var shouldFilter = categoryId != categoryView.expandedCategoryId;
225 if (shouldFilter != item.filter) {
226 // If the filter animation will be seen start it, otherwise, just flip the switch
227 var shrinkingVisible = shouldFilter && y + item.collapsedHeight < categoryView.height;
228 var growingVisible = !shouldFilter && y + height < categoryView.height;
229 if (!previewListView.open || !shouldFilter) {
230 if (shrinkingVisible || growingVisible) {
231 item.startFilterAnimation(shouldFilter)
233 item.filter = shouldFilter;
235 if (!shouldFilter && !previewListView.open) {
236 categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
242 onContentYChanged: rendererLoader.updateDelegateCreationRange();
243 onHeightChanged: rendererLoader.updateDelegateCreationRange();
246 function updateDelegateCreationRange() {
247 // Do not update the range if we are overshooting up or down, since we'll come back
248 // to the stable position and delete/create items without any reason
249 if (categoryView.contentY < categoryView.originY) {
251 } else if (categoryView.contentY + categoryView.height > categoryView.contentHeight) {
255 if (item.hasOwnProperty("delegateCreationBegin")) {
256 if (baseItem.y + baseItem.height <= 0) {
257 // Not visible (item at top of the list)
258 item.delegateCreationBegin = baseItem.height
259 item.delegateCreationEnd = baseItem.height
260 } else if (baseItem.y >= categoryView.height) {
261 // Not visible (item at bottom of the list)
262 item.delegateCreationBegin = 0
263 item.delegateCreationEnd = 0
265 item.delegateCreationBegin = Math.max(-baseItem.y, 0)
266 item.delegateCreationEnd = Math.min(categoryView.height + item.delegateCreationBegin, baseItem.height)
273 sectionProperty: "name"
274 sectionDelegate: ListItems.Header {
275 objectName: "dashSectionHeader" + delegate.category
276 property var delegate: categoryView.item(delegateIndex)
277 width: categoryView.width
280 if (delegate && delegate.expandable)
281 return delegate.filtered ? "graphics/header_handlearrow.png" : "graphics/header_handlearrow2.png"
285 if (categoryView.expandedCategoryId != delegate.category)
286 categoryView.expandedCategoryId = delegate.category;
288 categoryView.expandedCategoryId = "";
291 pageHeader: PageHeader {
293 objectName: "pageHeader"
294 width: categoryView.width
295 text: scopeView.scope.name
296 searchEntryEnabled: true
297 scope: scopeView.scope
298 searchHistory: scopeView.searchHistory
302 function getDefaultRendererId(contentType) {
303 switch (contentType) {
304 default: return "grid";
308 function getRenderer(rendererId, contentType, rendererHint, results) {
309 if (rendererId == "default") {
310 rendererId = getDefaultRendererId(contentType);
312 if (rendererId == "carousel") {
313 // Selectively disable carousel, 6 is fixed for now, should change on the form factor
314 if (results.count <= 6)
317 switch (rendererId) {
319 switch (contentType) {
320 case "music": return "Music/MusicCarousel.qml";
321 case "video": return "Video/VideoCarousel.qml";
322 default: return "Generic/GenericCarousel.qml";
326 switch (contentType) {
328 if (rendererHint == "toggled")
329 return "Apps/DashPluginFilterGrid.qml";
331 return "Generic/GenericFilterGrid.qml";
333 case "music": return "Music/MusicFilterGrid.qml";
334 case "video": return "Video/VideoFilterGrid.qml";
335 case "weather": return "Generic/WeatherFilterGrid.qml";
336 default: return "Generic/GenericFilterGrid.qml";
340 switch (contentType) {
341 case "apps": return "Apps/RunningApplicationsGrid.qml";
342 default: return "Generic/GenericFilterGrid.qml";
345 default: return "Generic/GenericFilterGrid.qml";
351 objectName: "openEffect"
354 bottomMargin: -bottomOverflow
356 sourceItem: categoryView
360 topGapPx: (1 - gap) * positionPx
361 topOpacity: (1 - gap * 1.2)
362 bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
363 bottomOverflow: units.gu(20)
364 live: !expansionAnimation.running
366 property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
367 property real gap: previewListView.open ? 1.0 : 0.0
371 id: expansionAnimation
373 easing.type: Easing.InOutQuad
375 if (!previewListView.open && !running) {
376 previewListView.onScreen = false
381 Behavior on positionPx {
382 enabled: previewListView.open
383 UbuntuNumberAnimation {}
388 target: scopeView.scope
390 if (previewListView.init) {
391 // Preview was triggered because of a click on the item. Need to expand now.
392 if (!previewListView.open) {
393 previewListView.open = true
396 var index = previewListView.currentIndex
397 previewListView.currentIndex = -1
398 previewListView.currentIndex = index
399 previewListView.init = false
401 previewListView.currentItem.previewData = preview
405 PreviewDelegateMapper {
406 id: previewDelegateMapper
411 objectName: "previewListView"
412 height: effect.bottomGapPx - effect.topGapPx
415 topMargin: effect.topGapPx
419 orientation: ListView.Horizontal
420 highlightRangeMode: ListView.StrictlyEnforceRange
421 snapMode: ListView.SnapOneItem
422 boundsBehavior: Flickable.DragAndOvershootBounds
423 highlightMoveDuration: 250
424 flickDeceleration: units.gu(625)
425 maximumFlickVelocity: width * 5
428 // To be set before opening the preview
429 property string categoryId: ""
430 property var categoryDelegate
432 // because the ListView is built asynchronous, setting the
433 // currentIndex directly won't work. We need to refresh it
434 // when the first preview is ready to be displayed.
435 property bool init: true
437 onCurrentIndexChanged: {
438 var row = Math.floor(currentIndex / categoryDelegate.columns);
439 if (categoryDelegate.collapsedRowCount <= row) {
440 categoryView.expandedCategoryId = categoryId
444 categoryDelegate.highlightIndex = currentIndex
447 if (!init && model !== undefined) {
448 var item = model.get(currentIndex)
449 scopeView.scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
452 // Adjust contentY in case we need to change to it to show the next row
453 if (categoryDelegate.rows > 1) {
454 var itemY = categoryView.contentItem.mapFromItem(categoryDelegate.currentItem).y;
456 // Find new contentY and effect.postionPx
457 var newContentY = itemY - effect.positionPx - categoryDelegate.verticalSpacing;
459 // Make sure the item is not covered by a header. Move the effect split down if necessary
460 var headerHeight = pageHeader.height + categoryView.stickyHeaderHeight;
461 var effectAdjust = Math.max(effect.positionPx, headerHeight);
463 // Make sure we don't overscroll the listview. If yes, adjust effect position
464 if (newContentY < 0) {
465 effectAdjust += newContentY;
468 if (newContentY > Math.max(0, categoryView.contentHeight - categoryView.height)) {
469 effectAdjust += -(categoryView.contentHeight - categoryView.height) + newContentY
470 newContentY = categoryView.contentHeight - categoryView.height;
473 effect.positionPx = effectAdjust;
474 categoryView.contentY = newContentY;
478 property bool open: false
479 property bool onScreen: false
484 categoryDelegate.highlightIndex = currentIndex;
485 pageHeader.unfocus();
487 // Cancel any pending preview requests or actions
488 if (previewListView.currentItem.previewData !== undefined) {
489 previewListView.currentItem.previewData.cancelAction();
491 scopeView.scope.cancelActivation();
493 categoryView.correctExpandedCategory();
494 categoryDelegate.highlightIndex = -1;
500 color: Qt.rgba(0, 0, 0, .3)
506 objectName: "previewLoader" + index
507 height: previewListView.height
508 width: previewListView.width
510 source: previewListView.onScreen ?
511 (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
513 onPreviewDataChanged: {
514 if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
515 previewLoader.opacity = 0;
520 if (previewData !== undefined) {
527 target: previewLoader
531 duration: UbuntuAnimation.BriskDuration
534 property var previewData
535 property bool valid: item !== null
538 if (previewListView.onScreen && previewData !== undefined) {
539 item.previewData = Qt.binding(function() { return previewData })
540 item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
545 ignoreUnknownSignals: true
548 previewListView.open = false
552 function closePreviewSpinner() {
554 item.showProcessingAction = false;
561 objectName: "pointerArrow"
563 top: previewListView.bottom
565 leftMargin: previewListView.categoryDelegate !== undefined && previewListView.categoryDelegate.currentItem ?
566 previewListView.categoryDelegate.currentItem.center + (-width + margins) / 2 : 0
568 Behavior on leftMargin {
570 duration: UbuntuAnimation.FastDuration
576 property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
577 opacity: previewListView.open ? .5 : 0
579 source: "graphics/tooltip_arrow.png"
582 // TODO: Move as InverseMouseArea to DashPreview
584 objectName: "closePreviewMouseArea"
585 enabled: previewListView.onScreen
588 topMargin: effect.bottomGapPx
591 previewListView.open = false;
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list