on a19df9d help sign in
unity8/qml/Dash/GenericScopeView.qml
800 lines 36 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 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 bool navigationDisableParentInteractive: categoryView.pageHeader.bottomItem[0].disableParentInteractive
29 property bool forceNonInteractive: false
30 property var scope: null
31 property UnitySortFilterProxyModel categories: categoryFilter
32 property bool isCurrent: false
33 property alias moving: categoryView.moving
34 property bool hasBackAction: false
35 property bool enableHeightBehaviorOnNextCreation: false
36 property var categoryView: categoryView
37 readonly property alias subPageShown: subPageLoader.subPageShown
38 property int paginationCount: 0
39 property int paginationIndex: 0
40 property bool visibleToParent: false
41 property alias pageHeaderTotallyVisible: categoryView.pageHeaderTotallyVisible
42 property var holdingList: null
43 property bool wasCurrentOnMoveStart: false
45 property var scopeStyle: ScopeStyle {
46 style: scope ? scope.customizations : {}
47 }
49 readonly property bool processing: scope ? scope.searchInProgress || subPageLoader.processing : false
51 signal backClicked()
53 onScopeChanged: {
54 floatingSeeLess.companionBase = null;
55 }
57 function positionAtBeginning() {
58 categoryView.positionAtBeginning()
59 }
61 function showHeader() {
62 categoryView.showHeader()
63 }
65 function closePreview() {
66 subPageLoader.closeSubPage()
67 }
69 function resetSearch() {
70 categoryView.pageHeader.resetSearch()
71 }
73 property var maybePreviewResult;
74 property int maybePreviewIndex;
75 property var maybePreviewResultsModel;
76 property int maybePreviewLimitedCategoryItemCount;
77 property string maybePreviewCategoryId;
79 function clearMaybePreviewData() {
80 scopeView.maybePreviewResult = undefined;
81 scopeView.maybePreviewIndex = -1;
82 scopeView.maybePreviewResultsModel = undefined;
83 scopeView.maybePreviewLimitedCategoryItemCount = -1;
84 scopeView.maybePreviewCategoryId = "";
85 }
87 function itemClicked(index, result, itemModel, resultsModel, limitedCategoryItemCount, categoryId) {
88 scopeView.maybePreviewResult = result;
89 scopeView.maybePreviewIndex = index;
90 scopeView.maybePreviewResultsModel = resultsModel;
91 scopeView.maybePreviewLimitedCategoryItemCount = limitedCategoryItemCount;
92 scopeView.maybePreviewCategoryId = categoryId;
94 scope.activate(result, categoryId);
95 }
97 function itemPressedAndHeld(index, result, resultsModel, limitedCategoryItemCount, categoryId) {
98 clearMaybePreviewData();
100 openPreview(result, index, resultsModel, limitedCategoryItemCount, categoryId);
101 }
103 function openPreview(result, index, resultsModel, limitedCategoryItemCount, categoryId) {
104 var previewStack = scope.preview(result, categoryId);
105 if (previewStack) {
106 if (limitedCategoryItemCount > 0) {
107 previewLimitModel.model = resultsModel;
108 previewLimitModel.limit = limitedCategoryItemCount;
109 subPageLoader.model = previewLimitModel;
110 } else {
111 subPageLoader.model = resultsModel;
112 }
113 subPageLoader.initialIndex = -1;
114 subPageLoader.initialIndex = index;
115 subPageLoader.categoryId = categoryId;
116 subPageLoader.previewStack = previewStack;
117 subPageLoader.openSubPage("preview");
118 }
119 }
121 Binding {
122 target: scope
123 property: "isActive"
124 value: isCurrent && !subPageLoader.open && (Qt.application.state == Qt.ApplicationActive)
125 }
127 UnitySortFilterProxyModel {
128 id: categoryFilter
129 model: scope ? scope.categories : null
130 dynamicSortFilter: true
131 filterRole: Categories.RoleCount
132 filterRegExp: /^0$/
133 invertMatch: true
134 }
136 onIsCurrentChanged: {
137 if (!holdingList || !holdingList.moving) {
138 wasCurrentOnMoveStart = scopeView.isCurrent;
139 }
140 categoryView.pageHeader.resetSearch();
141 subPageLoader.closeSubPage();
142 }
144 Binding {
145 target: scopeView.scope
146 property: "searchQuery"
147 value: categoryView.pageHeader.searchQuery
148 when: isCurrent
149 }
151 Binding {
152 target: categoryView.pageHeader
153 property: "searchQuery"
154 value: scopeView.scope ? scopeView.scope.searchQuery : ""
155 when: isCurrent
156 }
158 Connections {
159 target: scopeView.scope
160 onShowDash: subPageLoader.closeSubPage()
161 onHideDash: subPageLoader.closeSubPage()
162 onPreviewRequested: { // (QVariant const& result)
163 if (result === scopeView.maybePreviewResult) {
164 openPreview(result,
165 scopeView.maybePreviewIndex,
166 scopeView.maybePreviewResultsModel,
167 scopeView.maybePreviewLimitedCategoryItemCount,
168 scopeView.maybePreviewCategoryId);
170 clearMaybePreviewData();
171 }
172 }
173 }
175 Connections {
176 target: holdingList
177 onMovingChanged: {
178 if (!moving) {
179 wasCurrentOnMoveStart = scopeView.isCurrent;
180 }
181 }
182 }
184 Rectangle {
185 anchors.fill: parent
186 color: scopeView.scopeStyle ? scopeView.scopeStyle.background : "transparent"
187 visible: color != "transparent"
188 }
190 ScopeListView {
191 id: categoryView
192 objectName: "categoryListView"
193 interactive: !forceNonInteractive
195 x: subPageLoader.open ? -width : 0
196 visible: x != -width
197 Behavior on x { UbuntuNumberAnimation { } }
198 width: parent.width
199 height: floatingSeeLess.visible ? parent.height - floatingSeeLess.height + floatingSeeLess.yOffset
200 : parent.height
201 clip: height != parent.height
203 model: scopeView.categories
204 forceNoClip: subPageLoader.open
205 pixelAligned: true
207 property string expandedCategoryId: ""
208 property int runMaximizeAfterSizeChanges: 0
210 readonly property bool pageHeaderTotallyVisible:
211 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == categoryView.pageHeader.height))
213 onExpandedCategoryIdChanged: {
214 var firstCreated = firstCreatedIndex();
215 var shrinkingAny = false;
216 var shrinkHeightDifference = 0;
217 for (var i = 0; i < createdItemCount(); ++i) {
218 var baseItem = item(firstCreated + i);
219 if (baseItem.expandable) {
220 var shouldExpand = baseItem.category === expandedCategoryId;
221 if (shouldExpand != baseItem.expanded) {
222 var animate = false;
223 if (!subPageLoader.open) {
224 var animateShrinking = !shouldExpand && baseItem.y + baseItem.item.collapsedHeight + baseItem.seeAllButton.height < categoryView.height;
225 var animateGrowing = shouldExpand && baseItem.y + baseItem.height < categoryView.height;
226 animate = shrinkingAny || animateShrinking || animateGrowing;
227 }
229 if (!shouldExpand) {
230 shrinkingAny = true;
231 shrinkHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
232 }
234 if (shouldExpand && !subPageLoader.open) {
235 if (!shrinkingAny) {
236 categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
237 } else {
238 // If the space that shrinking is smaller than the one we need to grow we'll call maximizeVisibleArea
239 // after the shrink/grow animation ends
240 var growHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
241 if (growHeightDifference > shrinkHeightDifference) {
242 runMaximizeAfterSizeChanges = 2;
243 } else {
244 runMaximizeAfterSizeChanges = 0;
245 }
246 }
247 }
249 baseItem.expand(shouldExpand, animate);
250 }
251 }
252 }
253 }
255 delegate: DashCategoryBase {
256 id: baseItem
257 objectName: "dashCategory" + category
259 property Item seeAllButton: seeAll
261 readonly property bool expandable: {
262 if (categoryView.model.count === 1) return false;
263 if (cardTool.template && cardTool.template["collapsed-rows"] === 0) return false;
264 if (item && item.expandedHeight > item.collapsedHeight) return true;
265 return false;
266 }
267 property bool expanded: false
268 readonly property string category: categoryId
269 readonly property string headerLink: model.headerLink
270 readonly property var item: rendererLoader.item
272 function expand(expand, animate) {
273 heightBehaviour.enabled = animate;
274 expanded = expand;
275 }
277 CardTool {
278 id: cardTool
279 objectName: "cardTool"
280 count: results ? results.count : 0
281 template: model.renderer
282 components: model.components
283 viewWidth: parent.width
284 }
286 onExpandableChanged: {
287 // This can happen with the VJ that doesn't know how height it will be on creation
288 // so doesn't set expandable until a bit too late for onLoaded
289 if (expandable) {
290 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
291 baseItem.expand(shouldExpand, false /*animate*/);
292 }
293 }
295 onHeightChanged: rendererLoader.updateRanges();
296 onYChanged: rendererLoader.updateRanges();
298 Loader {
299 id: rendererLoader
300 anchors {
301 top: parent.top
302 left: parent.left
303 right: parent.right
304 topMargin: name != "" ? 0 : units.gu(2)
305 }
307 Behavior on height {
308 id: heightBehaviour
309 enabled: false
310 animation: UbuntuNumberAnimation {
311 duration: UbuntuAnimation.FastDuration
312 onRunningChanged: {
313 if (!running) {
314 heightBehaviour.enabled = false
315 if (categoryView.runMaximizeAfterSizeChanges > 0) {
316 categoryView.runMaximizeAfterSizeChanges--;
317 if (categoryView.runMaximizeAfterSizeChanges == 0) {
318 var firstCreated = categoryView.firstCreatedIndex();
319 for (var i = 0; i < categoryView.createdItemCount(); ++i) {
320 var baseItem = categoryView.item(firstCreated + i);
321 if (baseItem.category === categoryView.expandedCategoryId) {
322 categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
323 break;
324 }
325 }
326 }
327 }
328 }
329 }
330 }
331 }
333 readonly property bool expanded: baseItem.expanded || !baseItem.expandable
334 height: expanded ? item.expandedHeight : item.collapsedHeight
336 source: {
337 switch (cardTool.categoryLayout) {
338 case "carousel": return "CardCarousel.qml";
339 case "vertical-journal": return "CardVerticalJournal.qml";
340 case "horizontal-list": return "CardHorizontalList.qml";
341 case "grid":
342 default: return "CardGrid.qml";
343 }
344 }
346 onLoaded: {
347 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
348 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
349 scopeView.enableHeightBehaviorOnNextCreation = false;
350 }
351 item.model = Qt.binding(function() { return results })
352 item.objectName = Qt.binding(function() { return categoryId })
353 item.scopeStyle = scopeView.scopeStyle;
354 if (baseItem.expandable) {
355 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
356 baseItem.expand(shouldExpand, false /*animate*/);
357 }
358 updateRanges();
359 if (scope && scope.id === "clickscope" && (categoryId === "predefined" || categoryId === "local")) {
360 // Yeah, hackish :/
361 if (scopeView.width > units.gu(45)) {
362 if (scopeView.width >= units.gu(70)) {
363 cardTool.cardWidth = units.gu(9);
364 } else {
365 cardTool.cardWidth = units.gu(10);
366 }
367 }
368 cardTool.artShapeSize = Qt.size(units.gu(8), units.gu(7.5));
369 }
370 item.cardTool = cardTool;
371 }
373 Component.onDestruction: {
374 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
375 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
376 }
377 }
379 Connections {
380 target: rendererLoader.item
381 onClicked: { // (int index, var result, var item, var itemModel)
382 scopeView.itemClicked(index, result, itemModel, target.model, categoryItemCount(), baseItem.category);
383 }
385 onPressAndHold: { // (int index, var result, var itemModel)
386 scopeView.itemPressedAndHeld(index, result, target.model, categoryItemCount(), baseItem.category);
387 }
389 function categoryItemCount() {
390 var categoryItemCount = -1;
391 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
392 categoryItemCount = target.collapsedItemCount;
393 }
394 return categoryItemCount;
395 }
396 }
397 Connections {
398 target: categoryView
399 onOriginYChanged: rendererLoader.updateRanges();
400 onContentYChanged: rendererLoader.updateRanges();
401 onHeightChanged: rendererLoader.updateRanges();
402 onContentHeightChanged: rendererLoader.updateRanges();
403 }
404 Connections {
405 target: scopeView
406 onIsCurrentChanged: rendererLoader.updateRanges();
407 onVisibleToParentChanged: rendererLoader.updateRanges();
408 }
409 Connections {
410 target: holdingList
411 onMovingChanged: if (!moving) rendererLoader.updateRanges();
412 }
414 function updateRanges() {
415 // Don't want to create stress by requesting more items during scope
416 // changes so unless you're not part of the visible scopes just return.
417 // For the visible scopes we need to do some work, the previously non visible
418 // scope needs to adjust its ranges so that we define the new visible range,
419 // that still means no creation/destruction of delegates, it's just about changing
420 // the culling of the items so they are actually visible
421 if (holdingList && holdingList.moving && !scopeView.visibleToParent) {
422 return;
423 }
425 if (categoryView.moving) {
426 // Do not update the range if we are overshooting up or down, since we'll come back
427 // to the stable position and delete/create items without any reason
428 if (categoryView.contentY < categoryView.originY) {
429 return;
430 } else if (categoryView.contentHeight - categoryView.originY > categoryView.height &&
431 categoryView.contentY + categoryView.height > categoryView.contentHeight) {
432 return;
433 }
434 }
436 if (item && item.hasOwnProperty("displayMarginBeginning")) {
437 var buffer = wasCurrentOnMoveStart ? categoryView.height * 1.5 : 0;
438 var onViewport = baseItem.y + baseItem.height > 0 &&
439 baseItem.y < categoryView.height;
440 var onBufferViewport = baseItem.y + baseItem.height > -buffer &&
441 baseItem.y < categoryView.height + buffer;
443 if (item.growsVertically) {
444 // A item view creates its delegates synchronously from
445 // -displayMarginBeginning
446 // to
447 // height + displayMarginEnd
448 // Around that area it adds the cacheBuffer area where delegates are created async
449 //
450 // We adjust displayMarginBeginning and displayMarginEnd so
451 // * In non visible scopes nothing is considered visible and we set cacheBuffer
452 // so that creates the items that would be in the viewport asynchronously
453 // * For the current scope set the visible range to the viewport and then
454 // use cacheBuffer to create extra items for categoryView.height * 1.5
455 // to make scrolling nicer by mantaining a higher number of
456 // cached items
457 // * For non current but visible scopes (i.e. when the user changes from one scope
458 // to the next, we set the visible range to the viewport so
459 // items are not culled (invisible) but still use no cacheBuffer
460 // (it will be set once the scope is the current one)
461 var displayMarginBeginning = baseItem.y + rendererLoader.anchors.topMargin;
462 displayMarginBeginning = -Math.max(-displayMarginBeginning, 0);
463 displayMarginBeginning = -Math.min(-displayMarginBeginning, baseItem.height);
464 displayMarginBeginning = Math.round(displayMarginBeginning);
465 var displayMarginEnd = -baseItem.height + seeAll.height + categoryView.height - baseItem.y;
466 displayMarginEnd = -Math.max(-displayMarginEnd, 0);
467 displayMarginEnd = -Math.min(-displayMarginEnd, baseItem.height);
468 displayMarginEnd = Math.round(displayMarginEnd);
470 if (onBufferViewport && (scopeView.isCurrent || scopeView.visibleToParent)) {
471 item.displayMarginBeginning = displayMarginBeginning;
472 item.displayMarginEnd = displayMarginEnd;
473 if (holdingList && holdingList.moving) {
474 // If we are moving we need to reset the cache buffer of the
475 // view that was not visible (i.e. !wasCurrentOnMoveStart) to 0 since
476 // otherwise the cache buffer we had set to preload the items of the
477 // visible range will trigger some item creations and we want move to
478 // be as smooth as possible meaning no need creations
479 if (!wasCurrentOnMoveStart) {
480 item.cacheBuffer = 0;
481 }
482 } else {
483 // Protect us against cases where the item hasn't yet been positioned
484 if (!(categoryView.contentY === 0 && baseItem.y === 0 && index !== 0)) {
485 item.cacheBuffer = categoryView.height * 1.5;
486 }
487 }
488 } else {
489 var visibleRange = baseItem.height + displayMarginEnd + displayMarginBeginning;
490 if (visibleRange < 0) {
491 item.displayMarginBeginning = displayMarginBeginning;
492 item.displayMarginEnd = displayMarginEnd;
493 item.cacheBuffer = 0;
494 } else {
495 // This should be visibleRange/2 in each of the properties
496 // but some item views still (like GridView) like creating sync delegates even if
497 // the visible range is 0 so let's make sure the visible range is negative
498 item.displayMarginBeginning = displayMarginBeginning - visibleRange;
499 item.displayMarginEnd = displayMarginEnd - visibleRange;
500 item.cacheBuffer = visibleRange;
501 }
502 }
503 } else {
504 if (!onBufferViewport) {
505 // If not on the buffered viewport, don't load anything
506 item.displayMarginBeginning = 0;
507 item.displayMarginEnd = -item.innerWidth;
508 item.cacheBuffer = 0;
509 } else {
510 if (onViewport && (scopeView.isCurrent || scopeView.visibleToParent)) {
511 // If on the buffered viewport and the viewport and the on a visible scope
512 // Set displayMargin so that cards are rendered
513 // And if not moving the parent list also give it some extra asynchronously
514 // buffering
515 item.displayMarginBeginning = 0;
516 item.displayMarginEnd = 0;
517 if (holdingList && holdingList.moving) {
518 // If we are moving we need to reset the cache buffer of the
519 // view that was not visible (i.e. !wasCurrentOnMoveStart) to 0 since
520 // otherwise the cache buffer we had set to preload the items of the
521 // visible range will trigger some item creations and we want move to
522 // be as smooth as possible meaning no need creations
523 if (!wasCurrentOnMoveStart) {
524 item.cacheBuffer = 0;
525 }
526 } else {
527 item.cacheBuffer = baseItem.width * 1.5;
528 }
529 } else {
530 // If on the buffered viewport but either not in the real viewport
531 // or in the viewport of the non current scope, use displayMargin + cacheBuffer
532 // to render asynchronously the width of cards
533 item.displayMarginBeginning = 0;
534 item.displayMarginEnd = -item.innerWidth;
535 item.cacheBuffer = item.innerWidth;
536 }
537 }
538 }
539 }
540 }
541 }
543 AbstractButton {
544 id: seeAll
545 objectName: "seeAll"
546 anchors {
547 top: rendererLoader.bottom
548 left: parent.left
549 right: parent.right
550 }
551 height: baseItem.expandable && !baseItem.headerLink ? seeAllLabel.font.pixelSize + units.gu(4) : 0
552 visible: height != 0
554 onClicked: {
555 if (categoryView.expandedCategoryId !== baseItem.category) {
556 categoryView.expandedCategoryId = baseItem.category;
557 floatingSeeLess.companionBase = baseItem;
558 } else {
559 categoryView.expandedCategoryId = "";
560 }
561 }
563 Label {
564 id: seeAllLabel
565 text: baseItem.expanded ? i18n.tr("See less") : i18n.tr("See all")
566 anchors {
567 centerIn: parent
568 verticalCenterOffset: units.gu(-0.5)
569 }
570 fontSize: "small"
571 font.weight: Font.Bold
572 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
573 }
574 }
576 Image {
577 visible: index != 0
578 anchors {
579 top: parent.top
580 left: parent.left
581 right: parent.right
582 }
583 fillMode: Image.Stretch
584 source: "graphics/dash_divider_top_lightgrad.png"
585 z: -1
586 }
588 Image {
589 // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
590 visible: index != categoryView.model.count - 1
591 anchors {
592 bottom: seeAll.bottom
593 left: parent.left
594 right: parent.right
595 }
596 fillMode: Image.Stretch
597 source: "graphics/dash_divider_top_darkgrad.png"
598 z: -1
599 }
600 }
602 sectionProperty: "name"
603 sectionDelegate: ListItems.Header {
604 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
605 readonly property var delegate: categoryView.item(delegateIndex)
606 width: categoryView.width
607 height: section != "" ? units.gu(5) : 0
608 text: section
609 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
610 iconName: delegate && delegate.headerLink ? "go-next" : ""
611 onClicked: {
612 if (delegate.headerLink) scopeView.scope.performQuery(delegate.headerLink);
613 }
614 }
616 pageHeader: DashPageHeader {
617 objectName: "scopePageHeader"
618 width: parent.width
619 title: scopeView.scope ? scopeView.scope.name : ""
620 searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.ctr("Label: Hint for dash search line edit", "Search")
621 showBackButton: scopeView.hasBackAction
622 searchEntryEnabled: true
623 settingsEnabled: scopeView.scope && scopeView.scope.settings && scopeView.scope.settings.count > 0 || false
624 favoriteEnabled: scopeView.scope && scopeView.scope.id !== "clickscope"
625 favorite: scopeView.scope && scopeView.scope.favorite
626 scopeStyle: scopeView.scopeStyle
627 paginationCount: scopeView.paginationCount
628 paginationIndex: scopeView.paginationIndex
630 bottomItem: DashNavigation {
631 scope: scopeView.scope
632 anchors { left: parent.left; right: parent.right }
633 windowHeight: scopeView.height
634 windowWidth: scopeView.width
635 scopeStyle: scopeView.scopeStyle
636 }
638 onBackClicked: scopeView.backClicked()
639 onSettingsClicked: subPageLoader.openSubPage("settings")
640 onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
641 onSearchTextFieldFocused: scopeView.showHeader()
642 }
643 }
645 Item {
646 id: pullToRefreshClippingItem
647 anchors.left: parent.left
648 anchors.right: parent.right
649 anchors.bottom: parent.bottom
650 height: parent.height - pullToRefresh.contentY + (categoryView.pageHeader.bottomItem[0].height - categoryView.pageHeader.height)
651 clip: true
653 PullToRefresh {
654 id: pullToRefresh
655 objectName: "pullToRefresh"
656 target: categoryView
658 readonly property real contentY: categoryView.contentY - categoryView.originY
659 y: -contentY - units.gu(5)
661 onRefresh: {
662 refreshing = true
663 scopeView.scope.refresh()
664 }
665 anchors.left: parent.left
666 anchors.right: parent.right
668 Connections {
669 target: scopeView
670 onProcessingChanged: if (!scopeView.processing) pullToRefresh.refreshing = false
671 }
673 style: PullToRefreshScopeStyle {
674 anchors.fill: parent
675 activationThreshold: units.gu(14)
676 }
677 }
678 }
680 AbstractButton {
681 id: floatingSeeLess
682 objectName: "floatingSeeLess"
684 property Item companionTo: companionBase ? companionBase.seeAllButton : null
685 property Item companionBase: null
686 property bool showBecausePosition: false
687 property real yOffset: 0
689 anchors {
690 left: categoryView.left
691 right: categoryView.right
692 }
693 y: parent.height - height + yOffset
694 height: seeLessLabel.font.pixelSize + units.gu(4)
695 visible: companionTo && showBecausePosition
697 onClicked: categoryView.expandedCategoryId = "";
699 function updateVisibility() {
700 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
701 showBecausePosition = companionPos.y > 0;
703 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
704 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
705 yOffset = Math.min(yOffset, height);
707 if (!showBecausePosition && categoryView.expandedCategoryId === "") {
708 companionBase = null;
709 }
710 }
712 Label {
713 id: seeLessLabel
714 text: i18n.tr("See less")
715 anchors {
716 centerIn: parent
717 verticalCenterOffset: units.gu(-0.5)
718 }
719 fontSize: "small"
720 font.weight: Font.Bold
721 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
722 }
724 Connections {
725 target: floatingSeeLess.companionTo ? categoryView : null
726 onContentYChanged: floatingSeeLess.updateVisibility();
727 }
729 Connections {
730 target: floatingSeeLess.companionTo
731 onYChanged: floatingSeeLess.updateVisibility();
732 }
733 }
735 LimitProxyModel {
736 id: previewLimitModel
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 previewStack;
752 property string categoryId
753 property var model: null
755 readonly property bool processing: item && item.processing || false
756 readonly property int count: item && item.count || 0
757 readonly property int currentIndex: item && item.currentIndex || 0
758 readonly property var currentItem: item && item.currentItem || null
760 property string subPage: ""
761 readonly property bool subPageShown: visible && status === Loader.Ready
763 function openSubPage(page) {
764 subPage = page;
765 }
767 function closeSubPage() {
768 open = false;
769 }
771 source: switch(subPage) {
772 case "preview": return "PreviewListView.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.initialIndex = Qt.binding(function() { return subPageLoader.initialIndex; } )
783 item.model = Qt.binding(function() { return subPageLoader.model; } )
784 item.categoryId = Qt.binding(function() { return subPageLoader.categoryId; } )
785 item.initialIndexPreviewStack = subPageLoader.previewStack;
786 subPageLoader.previewStack = null;
787 }
788 open = true;
789 }
791 onOpenChanged: categoryView.pageHeader.unfocus()
793 onVisibleChanged: if (!visible) subPage = ""
795 Connections {
796 target: subPageLoader.item
797 onBackClicked: subPageLoader.closeSubPage()
798 }
799 }