on 4635884 help sign in
unity8/qml/Dash/GenericScopeView.qml
797 lines 35 kB history raw
1/*
2 * Copyright (C) 2013-2015 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.4
18import Ubuntu.Components 1.3
19import Ubuntu.Components.Popups 1.3
20import "../Components/SearchHistoryModel"
21import Utils 0.1
22import Unity 0.2
23import Dash 0.1
24import "../Components"
25import "Previews/PreviewSingleton"
27FocusScope {
28 id: scopeView
30 property bool forceNonInteractive: false
31 property var scope: null
32 property UnitySortFilterProxyModel categories: categoryFilter
33 property bool isCurrent: false
34 property alias moving: categoryView.moving
35 property bool hasBackAction: false
36 property bool enableHeightBehaviorOnNextCreation: false
37 property var categoryView: categoryView
38 readonly property alias subPageShown: subPageLoader.subPageShown
39 readonly property alias extraPanelShown: peExtraPanel.visible
40 property int paginationCount: 0
41 property int paginationIndex: 0
42 property bool visibleToParent: false
43 property alias pageHeaderTotallyVisible: categoryView.pageHeaderTotallyVisible
44 property var holdingList: null
45 property bool wasCurrentOnMoveStart: false
46 property var filtersPopover: null
48 property var scopeStyle: ScopeStyle {
49 style: scope ? scope.customizations : {}
50 }
52 readonly property bool processing: scope ? (scope.searchInProgress || scope.activationInProgress || subPageLoader.processing) : false
54 signal backClicked()
56 onScopeChanged: {
57 floatingSeeLess.companionBase = null;
58 }
60 function positionAtBeginning() {
61 categoryView.positionAtBeginning()
62 }
64 function showHeader() {
65 categoryView.showHeader()
66 }
68 function closePreview() {
69 subPageLoader.closeSubPage()
70 }
72 function resetSearch() {
73 categoryView.pageHeader.resetSearch()
74 }
76 property var maybePreviewResult;
77 property string maybePreviewCategoryId;
79 function clearMaybePreviewData() {
80 scopeView.maybePreviewResult = undefined;
81 scopeView.maybePreviewCategoryId = "";
82 }
84 function itemClicked(result, categoryId) {
85 scopeView.maybePreviewResult = result;
86 scopeView.maybePreviewCategoryId = categoryId;
88 scope.activate(result, categoryId);
89 }
91 function itemPressedAndHeld(result, categoryId) {
92 clearMaybePreviewData();
94 openPreview(result, categoryId);
95 }
97 function openPreview(result, categoryId) {
98 var previewModel = scope.preview(result, categoryId);
99 if (previewModel) {
100 subPageLoader.previewModel = previewModel;
101 subPageLoader.openSubPage("preview");
102 }
103 }
105 Binding {
106 target: scope
107 property: "isActive"
108 value: isCurrent && !subPageLoader.open && (Qt.application.state == Qt.ApplicationActive)
109 }
111 UnitySortFilterProxyModel {
112 id: categoryFilter
113 model: scope ? scope.categories : null
114 dynamicSortFilter: true
115 filterRole: Categories.RoleCount
116 filterRegExp: /^0$/
117 invertMatch: true
118 }
120 onIsCurrentChanged: {
121 if (!holdingList || !holdingList.moving) {
122 wasCurrentOnMoveStart = scopeView.isCurrent;
123 }
124 categoryView.pageHeader.resetSearch();
125 subPageLoader.closeSubPage();
126 if (filtersPopover) {
127 PopupUtils.close(filtersPopover)
128 scopeView.filtersPopover = null;
129 }
130 }
132 Binding {
133 target: scopeView.scope
134 property: "searchQuery"
135 value: categoryView.pageHeader.searchQuery
136 when: isCurrent
137 }
139 Binding {
140 target: categoryView.pageHeader
141 property: "searchQuery"
142 value: scopeView.scope ? scopeView.scope.searchQuery : ""
143 when: isCurrent
144 }
146 Connections {
147 target: scopeView.scope
148 onShowDash: subPageLoader.closeSubPage()
149 onHideDash: subPageLoader.closeSubPage()
150 onPreviewRequested: { // (QVariant const& result)
151 if (result === scopeView.maybePreviewResult) {
152 openPreview(result,
153 scopeView.maybePreviewCategoryId);
155 clearMaybePreviewData();
156 }
157 }
158 }
160 Connections {
161 target: holdingList
162 onMovingChanged: {
163 if (!moving) {
164 wasCurrentOnMoveStart = scopeView.isCurrent;
165 }
166 }
167 }
169 Rectangle {
170 anchors.fill: parent
171 color: scopeView.scopeStyle ? scopeView.scopeStyle.background : "transparent"
172 visible: color != "transparent"
173 }
175 ScopeListView {
176 id: categoryView
177 objectName: "categoryListView"
178 interactive: !forceNonInteractive
180 x: subPageLoader.open ? -width : 0
181 visible: x != -width
182 Behavior on x { UbuntuNumberAnimation { } }
183 width: parent.width
184 height: floatingSeeLess.visible ? parent.height - floatingSeeLess.height + floatingSeeLess.yOffset
185 : parent.height
186 clip: height != parent.height
188 model: scopeView.categories
189 forceNoClip: subPageLoader.open
190 pixelAligned: true
192 property string expandedCategoryId: ""
193 property int runMaximizeAfterSizeChanges: 0
195 readonly property bool pageHeaderTotallyVisible:
196 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == categoryView.pageHeader.height))
198 onExpandedCategoryIdChanged: {
199 var firstCreated = firstCreatedIndex();
200 var shrinkingAny = false;
201 var shrinkHeightDifference = 0;
202 for (var i = 0; i < createdItemCount(); ++i) {
203 var baseItem = item(firstCreated + i);
204 if (baseItem.expandable) {
205 var shouldExpand = baseItem.category === expandedCategoryId;
206 if (shouldExpand != baseItem.expanded) {
207 var animate = false;
208 if (!subPageLoader.open) {
209 var animateShrinking = !shouldExpand && baseItem.y + baseItem.item.collapsedHeight + baseItem.seeAllButton.height < categoryView.height;
210 var animateGrowing = shouldExpand && baseItem.y + baseItem.height < categoryView.height;
211 animate = shrinkingAny || animateShrinking || animateGrowing;
212 }
214 if (!shouldExpand) {
215 shrinkingAny = true;
216 shrinkHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
217 }
219 if (shouldExpand && !subPageLoader.open) {
220 if (!shrinkingAny) {
221 categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
222 } else {
223 // If the space that shrinking is smaller than the one we need to grow we'll call maximizeVisibleArea
224 // after the shrink/grow animation ends
225 var growHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
226 if (growHeightDifference > shrinkHeightDifference) {
227 runMaximizeAfterSizeChanges = 2;
228 } else {
229 runMaximizeAfterSizeChanges = 0;
230 }
231 }
232 }
234 baseItem.expand(shouldExpand, animate);
235 }
236 }
237 }
238 }
240 delegate: DashCategoryBase {
241 id: baseItem
242 objectName: "dashCategory" + category
244 property Item seeAllButton: seeAll
246 readonly property bool forceNotExpandable: cardTool.template && cardTool.template["expandable"] === false
247 readonly property bool expandable: {
248 if (categoryView.model.count === 1) return false;
249 if (forceNotExpandable) return false;
250 if (cardTool.template && cardTool.template["collapsed-rows"] === 0) return false;
251 if (item && item.expandedHeight > item.collapsedHeight) return true;
252 return false;
253 }
254 property bool expanded: false
255 readonly property string category: categoryId
256 readonly property string headerLink: model.headerLink
257 readonly property var item: rendererLoader.item
259 function expand(expand, animate) {
260 heightBehaviour.enabled = animate;
261 expanded = expand;
262 }
264 CardTool {
265 id: cardTool
266 objectName: "cardTool"
267 count: results ? results.count : 0
268 template: model.renderer
269 components: model.components
270 viewWidth: parent.width
271 scopeId: scope ? scope.id : ""
272 categoryId: baseItem.category
273 }
275 onExpandableChanged: {
276 // This can happen with the VJ that doesn't know how height it will be on creation
277 // so doesn't set expandable until a bit too late for onLoaded
278 if (expandable) {
279 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
280 baseItem.expand(shouldExpand, false /*animate*/);
281 }
282 }
284 onHeightChanged: rendererLoader.updateRanges();
285 onYChanged: rendererLoader.updateRanges();
287 Loader {
288 id: rendererLoader
289 anchors {
290 top: parent.top
291 left: parent.left
292 right: parent.right
293 topMargin: name != "" ? 0 : units.gu(2)
294 }
296 Behavior on height {
297 id: heightBehaviour
298 enabled: false
299 animation: UbuntuNumberAnimation {
300 duration: UbuntuAnimation.FastDuration
301 onRunningChanged: {
302 if (!running) {
303 heightBehaviour.enabled = false
304 if (categoryView.runMaximizeAfterSizeChanges > 0) {
305 categoryView.runMaximizeAfterSizeChanges--;
306 if (categoryView.runMaximizeAfterSizeChanges == 0) {
307 var firstCreated = categoryView.firstCreatedIndex();
308 for (var i = 0; i < categoryView.createdItemCount(); ++i) {
309 var baseItem = categoryView.item(firstCreated + i);
310 if (baseItem.category === categoryView.expandedCategoryId) {
311 categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
312 break;
313 }
314 }
315 }
316 }
317 }
318 }
319 }
320 }
322 readonly property bool expanded: !baseItem.forceNotExpandable && (baseItem.expanded || !baseItem.expandable)
323 height: expanded ? item.expandedHeight : item.collapsedHeight
325 source: {
326 switch (cardTool.categoryLayout) {
327 case "carousel": return "CardCarousel.qml";
328 case "vertical-journal": return "CardVerticalJournal.qml";
329 case "horizontal-list": return "CardHorizontalList.qml";
330 case "grid":
331 default: return "CardGrid.qml";
332 }
333 }
335 onLoaded: {
336 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
337 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
338 scopeView.enableHeightBehaviorOnNextCreation = false;
339 }
340 item.model = Qt.binding(function() { return results })
341 item.objectName = Qt.binding(function() { return categoryId })
342 item.scopeStyle = scopeView.scopeStyle;
343 if (baseItem.expandable) {
344 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
345 baseItem.expand(shouldExpand, false /*animate*/);
346 }
347 updateRanges();
348 item.cardTool = cardTool;
349 }
351 Component.onDestruction: {
352 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
353 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
354 }
355 }
357 Connections {
358 target: rendererLoader.item
359 onClicked: { // (int index, var result, var item, var itemModel)
360 scopeView.itemClicked(result, baseItem.category);
361 }
363 onPressAndHold: { // (int index, var result, var itemModel)
364 scopeView.itemPressedAndHeld(result, baseItem.category);
365 }
367 onAction: { // (int index, var result, var actionId)
368 scope.activateAction(result, baseItem.category, actionId);
369 }
371 function categoryItemCount() {
372 var categoryItemCount = -1;
373 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
374 categoryItemCount = target.collapsedItemCount;
375 }
376 return categoryItemCount;
377 }
378 }
379 Connections {
380 target: categoryView
381 onOriginYChanged: rendererLoader.updateRanges();
382 onContentYChanged: rendererLoader.updateRanges();
383 onHeightChanged: rendererLoader.updateRanges();
384 onContentHeightChanged: rendererLoader.updateRanges();
385 }
386 Connections {
387 target: scopeView
388 onIsCurrentChanged: rendererLoader.updateRanges();
389 onVisibleToParentChanged: rendererLoader.updateRanges();
390 }
391 Connections {
392 target: holdingList
393 onMovingChanged: if (!moving) rendererLoader.updateRanges();
394 }
396 function updateRanges() {
397 // Don't want to create stress by requesting more items during scope
398 // changes so unless you're not part of the visible scopes just return.
399 // For the visible scopes we need to do some work, the previously non visible
400 // scope needs to adjust its ranges so that we define the new visible range,
401 // that still means no creation/destruction of delegates, it's just about changing
402 // the culling of the items so they are actually visible
403 if (holdingList && holdingList.moving && !scopeView.visibleToParent) {
404 return;
405 }
407 if (categoryView.moving) {
408 // Do not update the range if we are overshooting up or down, since we'll come back
409 // to the stable position and delete/create items without any reason
410 if (categoryView.contentY < categoryView.originY) {
411 return;
412 } else if (categoryView.contentHeight - categoryView.originY > categoryView.height &&
413 categoryView.contentY + categoryView.height > categoryView.contentHeight) {
414 return;
415 }
416 }
418 if (item && item.hasOwnProperty("displayMarginBeginning")) {
419 var buffer = wasCurrentOnMoveStart ? categoryView.height * 1.5 : 0;
420 var onViewport = baseItem.y + baseItem.height > 0 &&
421 baseItem.y < categoryView.height;
422 var onBufferViewport = baseItem.y + baseItem.height > -buffer &&
423 baseItem.y < categoryView.height + buffer;
425 if (item.growsVertically) {
426 // A item view creates its delegates synchronously from
427 // -displayMarginBeginning
428 // to
429 // height + displayMarginEnd
430 // Around that area it adds the cacheBuffer area where delegates are created async
431 //
432 // We adjust displayMarginBeginning and displayMarginEnd so
433 // * In non visible scopes nothing is considered visible and we set cacheBuffer
434 // so that creates the items that would be in the viewport asynchronously
435 // * For the current scope set the visible range to the viewport and then
436 // use cacheBuffer to create extra items for categoryView.height * 1.5
437 // to make scrolling nicer by mantaining a higher number of
438 // cached items
439 // * For non current but visible scopes (i.e. when the user changes from one scope
440 // to the next, we set the visible range to the viewport so
441 // items are not culled (invisible) but still use no cacheBuffer
442 // (it will be set once the scope is the current one)
443 var displayMarginBeginning = baseItem.y + rendererLoader.anchors.topMargin;
444 displayMarginBeginning = -Math.max(-displayMarginBeginning, 0);
445 displayMarginBeginning = -Math.min(-displayMarginBeginning, baseItem.height);
446 displayMarginBeginning = Math.round(displayMarginBeginning);
447 var displayMarginEnd = -baseItem.height + seeAll.height + categoryView.height - baseItem.y;
448 displayMarginEnd = -Math.max(-displayMarginEnd, 0);
449 displayMarginEnd = -Math.min(-displayMarginEnd, baseItem.height);
450 displayMarginEnd = Math.round(displayMarginEnd);
452 if (onBufferViewport && (scopeView.isCurrent || scopeView.visibleToParent)) {
453 item.displayMarginBeginning = displayMarginBeginning;
454 item.displayMarginEnd = displayMarginEnd;
455 if (holdingList && holdingList.moving) {
456 // If we are moving we need to reset the cache buffer of the
457 // view that was not visible (i.e. !wasCurrentOnMoveStart) to 0 since
458 // otherwise the cache buffer we had set to preload the items of the
459 // visible range will trigger some item creations and we want move to
460 // be as smooth as possible meaning no need creations
461 if (!wasCurrentOnMoveStart) {
462 item.cacheBuffer = 0;
463 }
464 } else {
465 // Protect us against cases where the item hasn't yet been positioned
466 if (!(categoryView.contentY === 0 && baseItem.y === 0 && index !== 0)) {
467 item.cacheBuffer = categoryView.height * 1.5;
468 }
469 }
470 } else {
471 var visibleRange = baseItem.height + displayMarginEnd + displayMarginBeginning;
472 if (visibleRange < 0) {
473 item.displayMarginBeginning = displayMarginBeginning;
474 item.displayMarginEnd = displayMarginEnd;
475 item.cacheBuffer = 0;
476 } else {
477 // This should be visibleRange/2 in each of the properties
478 // but some item views still (like GridView) like creating sync delegates even if
479 // the visible range is 0 so let's make sure the visible range is negative
480 item.displayMarginBeginning = displayMarginBeginning - visibleRange;
481 item.displayMarginEnd = displayMarginEnd - visibleRange;
482 item.cacheBuffer = visibleRange;
483 }
484 }
485 } else {
486 if (!onBufferViewport) {
487 // If not on the buffered viewport, don't load anything
488 item.displayMarginBeginning = 0;
489 item.displayMarginEnd = -item.innerWidth;
490 item.cacheBuffer = 0;
491 } else {
492 if (onViewport && (scopeView.isCurrent || scopeView.visibleToParent)) {
493 // If on the buffered viewport and the viewport and the on a visible scope
494 // Set displayMargin so that cards are rendered
495 // And if not moving the parent list also give it some extra asynchronously
496 // buffering
497 item.displayMarginBeginning = 0;
498 item.displayMarginEnd = 0;
499 if (holdingList && holdingList.moving) {
500 // If we are moving we need to reset the cache buffer of the
501 // view that was not visible (i.e. !wasCurrentOnMoveStart) to 0 since
502 // otherwise the cache buffer we had set to preload the items of the
503 // visible range will trigger some item creations and we want move to
504 // be as smooth as possible meaning no need creations
505 if (!wasCurrentOnMoveStart) {
506 item.cacheBuffer = 0;
507 }
508 } else {
509 item.cacheBuffer = baseItem.width * 1.5;
510 }
511 } else {
512 // If on the buffered viewport but either not in the real viewport
513 // or in the viewport of the non current scope, use displayMargin + cacheBuffer
514 // to render asynchronously the width of cards
515 item.displayMarginBeginning = 0;
516 item.displayMarginEnd = -item.innerWidth;
517 item.cacheBuffer = item.innerWidth;
518 }
519 }
520 }
521 }
522 }
523 }
525 AbstractButton {
526 id: seeAll
527 objectName: "seeAll"
528 anchors {
529 top: rendererLoader.bottom
530 left: parent.left
531 right: parent.right
532 }
533 height: baseItem.expandable && !baseItem.headerLink ? seeAllLabel.font.pixelSize + units.gu(4) : 0
534 visible: height != 0
536 onClicked: {
537 if (categoryView.expandedCategoryId !== baseItem.category) {
538 categoryView.expandedCategoryId = baseItem.category;
539 floatingSeeLess.companionBase = baseItem;
540 } else {
541 categoryView.expandedCategoryId = "";
542 }
543 }
545 Label {
546 id: seeAllLabel
547 text: baseItem.expanded ? i18n.tr("Show less") : i18n.tr("Show all")
548 anchors {
549 centerIn: parent
550 verticalCenterOffset: units.gu(-0.5)
551 }
552 fontSize: "small"
553 font.weight: Font.Bold
554 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
555 }
556 }
557 }
559 sectionProperty: "name"
560 sectionDelegate: DashSectionHeader {
561 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
562 property var delegate: null
563 width: categoryView.width
564 height: text != "" ? units.gu(5) : 0
565 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
566 iconName: delegate && delegate.headerLink ? "toolkit_chevron-ltr_1gu" : ""
567 onClicked: {
568 if (delegate.headerLink) scopeView.scope.performQuery(delegate.headerLink);
569 }
570 }
572 pageHeader: DashPageHeader {
573 objectName: "scopePageHeader"
574 width: parent.width
575 title: scopeView.scope ? scopeView.scope.name : ""
576 extraPanel: peExtraPanel
577 searchHistory: SearchHistoryModel
578 searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.ctr("Label: Hint for dash search line edit", "Search")
579 scopeHasFilters: scopeView.scope.filters != null
580 activeFiltersCount: scopeView.scope.activeFiltersCount
581 showBackButton: scopeView.hasBackAction
582 showSignatureLine: !showBackButton
583 searchEntryEnabled: true
584 settingsEnabled: scopeView.scope && scopeView.scope.settings && scopeView.scope.settings.count > 0 || false
585 favoriteEnabled: scopeView.scope && scopeView.scope.id !== "clickscope"
586 favorite: scopeView.scope && scopeView.scope.favorite
587 navigationTag: scopeView.scope ? scopeView.scope.primaryNavigationTag : ""
588 scopeStyle: scopeView.scopeStyle
589 paginationCount: scopeView.paginationCount
590 paginationIndex: scopeView.paginationIndex
592 onBackClicked: scopeView.backClicked()
593 onSettingsClicked: subPageLoader.openSubPage("settings")
594 onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
595 onSearchTextFieldFocused: scopeView.showHeader()
596 onClearSearch: { // keepPanelOpen
597 var panelOpen = peExtraPanel.visible;
598 resetSearch(keepPanelOpen);
599 scopeView.scope.resetPrimaryNavigationTag();
600 peExtraPanel.resetNavigation();
601 if ((panelOpen || searchHistory.count > 0) && keepPanelOpen) {
602 openPopup();
603 }
604 }
605 onShowFiltersPopup: { // item
606 extraPanel.visible = false;
607 scopeView.filtersPopover = PopupUtils.open(Qt.resolvedUrl("FiltersPopover.qml"), item, { "contentWidth": Qt.binding(function() { return scopeView.width - units.gu(2); } ) } );
608 scopeView.filtersPopover.Component.onDestruction.connect(function () {
609 categoryView.pageHeader.closePopup(false, true);
610 categoryView.pageHeader.unfocus(true); // remove the focus from the search field
611 })
612 }
613 }
615 PageHeaderExtraPanel {
616 id: peExtraPanel
617 objectName: "peExtraPanel"
618 width: parent.width >= units.gu(60) ? units.gu(40) : parent.width
619 anchors {
620 top: categoryView.pageHeader.bottom
621 topMargin: -categoryView.pageHeader.signatureLineHeight
622 }
623 z: 1
624 visible: false
626 searchHistory: SearchHistoryModel
627 scope: scopeView.scope
628 windowHeight: scopeView.height
630 onHistoryItemClicked: {
631 SearchHistoryModel.addQuery(text);
632 categoryView.pageHeader.searchQuery = text;
633 categoryView.pageHeader.unfocus();
634 }
636 onDashNavigationLeafClicked: {
637 categoryView.pageHeader.closePopup();
638 categoryView.pageHeader.unfocus();
639 }
641 onExtraPanelOptionSelected: {
642 categoryView.pageHeader.closePopup();
643 categoryView.pageHeader.unfocus();
644 }
645 }
646 }
648 Item {
649 id: pullToRefreshClippingItem
650 anchors.left: parent.left
651 anchors.right: parent.right
652 anchors.bottom: parent.bottom
653 height: parent.height - pullToRefresh.contentY - categoryView.pageHeader.height
654 clip: true
656 PullToRefresh {
657 id: pullToRefresh
658 objectName: "pullToRefresh"
659 target: categoryView
661 readonly property real contentY: categoryView.contentY - categoryView.originY
662 readonly property color pullLabelColor: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
663 y: -contentY - units.gu(5)
665 onRefresh: {
666 refreshing = true
667 scopeView.scope.refresh()
668 }
669 anchors.left: parent.left
670 anchors.right: parent.right
672 Connections {
673 target: scopeView
674 onProcessingChanged: if (!scopeView.processing) pullToRefresh.refreshing = false
675 }
677 style: PullToRefreshScopeStyle {
678 anchors.fill: parent
679 activationThreshold: Math.min(units.gu(14), scopeView.height / 5)
680 }
681 }
682 }
684 AbstractButton {
685 id: floatingSeeLess
686 objectName: "floatingSeeLess"
688 property Item companionTo: companionBase ? companionBase.seeAllButton : null
689 property Item companionBase: null
690 property bool showBecausePosition: false
691 property real yOffset: 0
693 anchors {
694 left: categoryView.left
695 right: categoryView.right
696 }
697 y: parent.height - height + yOffset
698 height: seeLessLabel.font.pixelSize + units.gu(4)
699 visible: companionTo && showBecausePosition
701 onClicked: categoryView.expandedCategoryId = "";
703 function updateVisibility() {
704 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
705 showBecausePosition = Math.round(companionPos.y) > 0;
707 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
708 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
709 yOffset = Math.min(yOffset, height);
711 if (!showBecausePosition && categoryView.expandedCategoryId === "") {
712 companionBase = null;
713 }
714 }
716 Label {
717 id: seeLessLabel
718 text: i18n.tr("Show less")
719 anchors {
720 centerIn: parent
721 verticalCenterOffset: units.gu(-0.5)
722 }
723 fontSize: "small"
724 font.weight: Font.Bold
725 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
726 }
728 Connections {
729 target: floatingSeeLess.companionTo ? categoryView : null
730 onContentYChanged: floatingSeeLess.updateVisibility();
731 }
733 Connections {
734 target: floatingSeeLess.companionTo
735 onYChanged: floatingSeeLess.updateVisibility();
736 }
737 }
739 Loader {
740 id: subPageLoader
741 objectName: "subPageLoader"
742 visible: x != width
743 width: parent.width
744 height: parent.height
745 anchors.left: categoryView.right
747 property bool open: false
748 property var scope: scopeView.scope
749 property var scopeStyle: scopeView.scopeStyle
750 property int initialIndex: -1
751 property var previewModel;
753 readonly property bool processing: item && item.processing || false
754 readonly property int count: item && item.count || 0
755 readonly property var currentItem: item && item.currentItem || null
757 property string subPage: ""
758 readonly property bool subPageShown: visible && status === Loader.Ready
760 function openSubPage(page) {
761 subPage = page;
762 }
764 function closeSubPage() {
765 if (subPage == "preview") {
766 PreviewSingleton.widgetExtraData = new Object();
767 }
768 open = false;
769 }
771 source: switch(subPage) {
772 case "preview": return "PreviewView.qml";
773 case "settings": return "ScopeSettingsPage.qml";
774 default: return "";
775 }
777 onLoaded: {
778 item.scope = Qt.binding(function() { return subPageLoader.scope; } )
779 item.scopeStyle = Qt.binding(function() { return subPageLoader.scopeStyle; } )
780 if (subPage == "preview") {
781 item.open = Qt.binding(function() { return subPageLoader.open; } )
782 item.previewModel = subPageLoader.previewModel;
783 subPageLoader.previewModel = null;
784 }
785 open = true;
786 }
788 onOpenChanged: categoryView.pageHeader.unfocus()
790 onVisibleChanged: if (!visible) subPage = ""
792 Connections {
793 target: subPageLoader.item
794 onBackClicked: subPageLoader.closeSubPage()
795 }
796 }