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