on 016cde7 help sign in
unity8/qml/Dash/GenericScopeView.qml
807 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.0
18import Ubuntu.Components 1.1
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 if (scope.preview(result, categoryId)) {
103 openPreview(index, resultsModel, limitedCategoryItemCount, categoryId);
104 }
105 }
107 function openPreview(index, resultsModel, limitedCategoryItemCount, categoryId) {
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.openSubPage("preview");
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 if (pageHeaderLoader.item && showPageHeader) {
141 pageHeaderLoader.item.resetSearch();
142 }
143 subPageLoader.closeSubPage();
144 }
146 Binding {
147 target: scopeView.scope
148 property: "searchQuery"
149 value: pageHeaderLoader.item ? pageHeaderLoader.item.searchQuery : ""
150 when: isCurrent && showPageHeader
151 }
153 Binding {
154 target: pageHeaderLoader.item
155 property: "searchQuery"
156 value: scopeView.scope ? scopeView.scope.searchQuery : ""
157 when: isCurrent && showPageHeader
158 }
160 Connections {
161 target: scopeView.scope
162 onShowDash: subPageLoader.closeSubPage()
163 onHideDash: subPageLoader.closeSubPage()
164 onPreviewRequested: { // (QVariant const& result)
165 if (result === scopeView.maybePreviewResult) {
166 openPreview(scopeView.maybePreviewIndex,
167 scopeView.maybePreviewResultsModel,
168 scopeView.maybePreviewLimitedCategoryItemCount,
169 scopeView.maybePreviewCategoryId);
171 clearMaybePreviewData();
172 }
173 }
174 }
176 Connections {
177 target: holdingList
178 onMovingChanged: {
179 if (!moving) {
180 wasCurrentOnMoveStart = scopeView.isCurrent;
181 }
182 }
183 }
185 Rectangle {
186 anchors.fill: parent
187 color: scopeView.scopeStyle ? scopeView.scopeStyle.background : "transparent"
188 visible: color != "transparent"
189 }
191 ScopeListView {
192 id: categoryView
193 objectName: "categoryListView"
194 interactive: !forceNonInteractive
196 x: subPageLoader.open ? -width : 0
197 visible: x != -width
198 Behavior on x { UbuntuNumberAnimation { } }
199 width: parent.width
200 height: floatingSeeLess.visible ? parent.height - floatingSeeLess.height + floatingSeeLess.yOffset
201 : parent.height
202 clip: height != parent.height
204 model: scopeView.categories
205 forceNoClip: subPageLoader.open
206 pixelAligned: true
208 property string expandedCategoryId: ""
209 property int runMaximizeAfterSizeChanges: 0
211 readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
212 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
214 onExpandedCategoryIdChanged: {
215 var firstCreated = firstCreatedIndex();
216 var shrinkingAny = false;
217 var shrinkHeightDifference = 0;
218 for (var i = 0; i < createdItemCount(); ++i) {
219 var baseItem = item(firstCreated + i);
220 if (baseItem.expandable) {
221 var shouldExpand = baseItem.category === expandedCategoryId;
222 if (shouldExpand != baseItem.expanded) {
223 var animate = false;
224 if (!subPageLoader.open) {
225 var animateShrinking = !shouldExpand && baseItem.y + baseItem.item.collapsedHeight + baseItem.seeAllButton.height < categoryView.height;
226 var animateGrowing = shouldExpand && baseItem.y + baseItem.height < categoryView.height;
227 animate = shrinkingAny || animateShrinking || animateGrowing;
228 }
230 if (!shouldExpand) {
231 shrinkingAny = true;
232 shrinkHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
233 }
235 if (shouldExpand && !subPageLoader.open) {
236 if (!shrinkingAny) {
237 categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
238 } else {
239 // If the space that shrinking is smaller than the one we need to grow we'll call maximizeVisibleArea
240 // after the shrink/grow animation ends
241 var growHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
242 if (growHeightDifference > shrinkHeightDifference) {
243 runMaximizeAfterSizeChanges = 2;
244 } else {
245 runMaximizeAfterSizeChanges = 0;
246 }
247 }
248 }
250 baseItem.expand(shouldExpand, animate);
251 }
252 }
253 }
254 }
256 delegate: DashCategoryBase {
257 id: baseItem
258 objectName: "dashCategory" + category
260 property Item seeAllButton: seeAll
262 readonly property bool expandable: {
263 if (categoryView.model.count === 1) return false;
264 if (cardTool.template && cardTool.template["collapsed-rows"] === 0) return false;
265 if (item && item.expandedHeight > item.collapsedHeight) return true;
266 return false;
267 }
268 property bool expanded: false
269 readonly property string category: categoryId
270 readonly property string headerLink: model.headerLink
271 readonly property var item: rendererLoader.item
273 function expand(expand, animate) {
274 heightBehaviour.enabled = animate;
275 expanded = expand;
276 }
278 CardTool {
279 id: cardTool
280 objectName: "cardTool"
281 count: results ? results.count : 0
282 template: model.renderer
283 components: model.components
284 viewWidth: parent.width
285 }
287 onExpandableChanged: {
288 // This can happen with the VJ that doesn't know how height it will be on creation
289 // so doesn't set expandable until a bit too late for onLoaded
290 if (expandable) {
291 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
292 baseItem.expand(shouldExpand, false /*animate*/);
293 }
294 }
296 onHeightChanged: rendererLoader.updateRanges();
297 onYChanged: rendererLoader.updateRanges();
299 Loader {
300 id: rendererLoader
301 anchors {
302 top: parent.top
303 left: parent.left
304 right: parent.right
305 topMargin: name != "" ? 0 : units.gu(2)
306 }
308 Behavior on height {
309 id: heightBehaviour
310 enabled: false
311 animation: UbuntuNumberAnimation {
312 duration: UbuntuAnimation.FastDuration
313 onRunningChanged: {
314 if (!running) {
315 heightBehaviour.enabled = false
316 if (categoryView.runMaximizeAfterSizeChanges > 0) {
317 categoryView.runMaximizeAfterSizeChanges--;
318 if (categoryView.runMaximizeAfterSizeChanges == 0) {
319 var firstCreated = categoryView.firstCreatedIndex();
320 for (var i = 0; i < categoryView.createdItemCount(); ++i) {
321 var baseItem = categoryView.item(firstCreated + i);
322 if (baseItem.category === categoryView.expandedCategoryId) {
323 categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
324 break;
325 }
326 }
327 }
328 }
329 }
330 }
331 }
332 }
334 readonly property bool expanded: baseItem.expanded || !baseItem.expandable
335 height: expanded ? item.expandedHeight : item.collapsedHeight
337 source: {
338 switch (cardTool.categoryLayout) {
339 case "carousel": return "CardCarousel.qml";
340 case "vertical-journal": return "CardVerticalJournal.qml";
341 case "horizontal-list": return "CardHorizontalList.qml";
342 case "grid":
343 default: return "CardGrid.qml";
344 }
345 }
347 onLoaded: {
348 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
349 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
350 scopeView.enableHeightBehaviorOnNextCreation = false;
351 }
352 item.model = Qt.binding(function() { return results })
353 item.objectName = Qt.binding(function() { return categoryId })
354 item.scopeStyle = scopeView.scopeStyle;
355 if (baseItem.expandable) {
356 var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
357 baseItem.expand(shouldExpand, false /*animate*/);
358 }
359 updateRanges();
360 if (scope && scope.id === "clickscope" && (categoryId === "predefined" || categoryId === "local")) {
361 // Yeah, hackish :/
362 if (scopeView.width > units.gu(45)) {
363 if (scopeView.width >= units.gu(70)) {
364 cardTool.cardWidth = units.gu(9);
365 } else {
366 cardTool.cardWidth = units.gu(10);
367 }
368 }
369 cardTool.artShapeSize = Qt.size(units.gu(8), units.gu(7.5));
370 }
371 item.cardTool = cardTool;
372 }
374 Component.onDestruction: {
375 if (item.enableHeightBehavior !== undefined && item.enableHeightBehaviorOnNextCreation !== undefined) {
376 scopeView.enableHeightBehaviorOnNextCreation = item.enableHeightBehaviorOnNextCreation;
377 }
378 }
380 Connections {
381 target: rendererLoader.item
382 onClicked: { // (int index, var result, var item, var itemModel)
383 scopeView.itemClicked(index, result, itemModel, target.model, categoryItemCount(), baseItem.category);
384 }
386 onPressAndHold: { // (int index, var result, var itemModel)
387 scopeView.itemPressedAndHeld(index, result, target.model, categoryItemCount(), baseItem.category);
388 }
390 function categoryItemCount() {
391 var categoryItemCount = -1;
392 if (!rendererLoader.expanded && !seeAllLabel.visible && target.collapsedItemCount > 0) {
393 categoryItemCount = target.collapsedItemCount;
394 }
395 return categoryItemCount;
396 }
397 }
398 Connections {
399 target: categoryView
400 onOriginYChanged: rendererLoader.updateRanges();
401 onContentYChanged: rendererLoader.updateRanges();
402 onHeightChanged: rendererLoader.updateRanges();
403 onContentHeightChanged: rendererLoader.updateRanges();
404 }
405 Connections {
406 target: scopeView
407 onIsCurrentChanged: rendererLoader.updateRanges();
408 onVisibleToParentChanged: rendererLoader.updateRanges();
409 }
410 Connections {
411 target: holdingList
412 onMovingChanged: if (!moving) rendererLoader.updateRanges();
413 }
415 function updateRanges() {
416 // Don't want to create stress by requesting more items during scope
417 // changes so unless you're not part of the visible scopes just return.
418 // For the visible scopes we need to do some work, the previously non visible
419 // scope needs to adjust its ranges so that we define the new visible range,
420 // that still means no creation/destruction of delegates, it's just about changing
421 // the culling of the items so they are actually visible
422 if (holdingList && holdingList.moving && !scopeView.visibleToParent) {
423 return;
424 }
426 if (categoryView.moving) {
427 // Do not update the range if we are overshooting up or down, since we'll come back
428 // to the stable position and delete/create items without any reason
429 if (categoryView.contentY < categoryView.originY) {
430 return;
431 } else if (categoryView.contentHeight - categoryView.originY > categoryView.height &&
432 categoryView.contentY + categoryView.height > categoryView.contentHeight) {
433 return;
434 }
435 }
437 if (item && item.hasOwnProperty("displayMarginBeginning")) {
438 var buffer = wasCurrentOnMoveStart ? categoryView.height * 1.5 : 0;
439 var onViewport = baseItem.y + baseItem.height > 0 &&
440 baseItem.y < categoryView.height;
441 var onBufferViewport = baseItem.y + baseItem.height > -buffer &&
442 baseItem.y < categoryView.height + buffer;
444 if (item.growsVertically) {
445 // A item view creates its delegates synchronously from
446 // -displayMarginBeginning
447 // to
448 // height + displayMarginEnd
449 // Around that area it adds the cacheBuffer area where delegates are created async
450 //
451 // We adjust displayMarginBeginning and displayMarginEnd so
452 // * In non visible scopes nothing is considered visible and we set cacheBuffer
453 // so that creates the items that would be in the viewport asynchronously
454 // * For the current scope set the visible range to the viewport and then
455 // use cacheBuffer to create extra items for categoryView.height * 1.5
456 // to make scrolling nicer by mantaining a higher number of
457 // cached items
458 // * For non current but visible scopes (i.e. when the user changes from one scope
459 // to the next, we set the visible range to the viewport so
460 // items are not culled (invisible) but still use no cacheBuffer
461 // (it will be set once the scope is the current one)
462 var displayMarginBeginning = baseItem.y + rendererLoader.anchors.topMargin;
463 displayMarginBeginning = -Math.max(-displayMarginBeginning, 0);
464 displayMarginBeginning = -Math.min(-displayMarginBeginning, baseItem.height);
465 displayMarginBeginning = Math.round(displayMarginBeginning);
466 var displayMarginEnd = -baseItem.height + seeAll.height + categoryView.height - baseItem.y;
467 displayMarginEnd = -Math.max(-displayMarginEnd, 0);
468 displayMarginEnd = -Math.min(-displayMarginEnd, baseItem.height);
469 displayMarginEnd = Math.round(displayMarginEnd);
471 if (onBufferViewport && (scopeView.isCurrent || scopeView.visibleToParent)) {
472 item.displayMarginBeginning = displayMarginBeginning;
473 item.displayMarginEnd = displayMarginEnd;
474 if (holdingList && holdingList.moving) {
475 // If we are moving we need to reset the cache buffer of the
476 // view that was not visible (i.e. !wasCurrentOnMoveStart) to 0 since
477 // otherwise the cache buffer we had set to preload the items of the
478 // visible range will trigger some item creations and we want move to
479 // be as smooth as possible meaning no need creations
480 if (!wasCurrentOnMoveStart) {
481 item.cacheBuffer = 0;
482 }
483 } else {
484 // Protect us against cases where the item hasn't yet been positioned
485 if (!(categoryView.contentY === 0 && baseItem.y === 0 && index !== 0)) {
486 item.cacheBuffer = categoryView.height * 1.5;
487 }
488 }
489 } else {
490 var visibleRange = baseItem.height + displayMarginEnd + displayMarginBeginning;
491 if (visibleRange < 0) {
492 item.displayMarginBeginning = displayMarginBeginning;
493 item.displayMarginEnd = displayMarginEnd;
494 item.cacheBuffer = 0;
495 } else {
496 // This should be visibleRange/2 in each of the properties
497 // but some item views still (like GridView) like creating sync delegates even if
498 // the visible range is 0 so let's make sure the visible range is negative
499 item.displayMarginBeginning = displayMarginBeginning - visibleRange;
500 item.displayMarginEnd = displayMarginEnd - visibleRange;
501 item.cacheBuffer = visibleRange;
502 }
503 }
504 } else {
505 if (!onBufferViewport) {
506 // If not on the buffered viewport, don't load anything
507 item.displayMarginBeginning = 0;
508 item.displayMarginEnd = -item.innerWidth;
509 item.cacheBuffer = 0;
510 } else {
511 if (onViewport && (scopeView.isCurrent || scopeView.visibleToParent)) {
512 // If on the buffered viewport and the viewport and the on a visible scope
513 // Set displayMargin so that cards are rendered
514 // And if not moving the parent list also give it some extra asynchronously
515 // buffering
516 item.displayMarginBeginning = 0;
517 item.displayMarginEnd = 0;
518 if (holdingList && holdingList.moving) {
519 // If we are moving we need to reset the cache buffer of the
520 // view that was not visible (i.e. !wasCurrentOnMoveStart) to 0 since
521 // otherwise the cache buffer we had set to preload the items of the
522 // visible range will trigger some item creations and we want move to
523 // be as smooth as possible meaning no need creations
524 if (!wasCurrentOnMoveStart) {
525 item.cacheBuffer = 0;
526 }
527 } else {
528 item.cacheBuffer = baseItem.width * 1.5;
529 }
530 } else {
531 // If on the buffered viewport but either not in the real viewport
532 // or in the viewport of the non current scope, use displayMargin + cacheBuffer
533 // to render asynchronously the width of cards
534 item.displayMarginBeginning = 0;
535 item.displayMarginEnd = -item.innerWidth;
536 item.cacheBuffer = item.innerWidth;
537 }
538 }
539 }
540 }
541 }
542 }
544 AbstractButton {
545 id: seeAll
546 objectName: "seeAll"
547 anchors {
548 top: rendererLoader.bottom
549 left: parent.left
550 right: parent.right
551 }
552 height: baseItem.expandable && !baseItem.headerLink ? seeAllLabel.font.pixelSize + units.gu(4) : 0
553 visible: height != 0
555 onClicked: {
556 if (categoryView.expandedCategoryId !== baseItem.category) {
557 categoryView.expandedCategoryId = baseItem.category;
558 floatingSeeLess.companionBase = baseItem;
559 } else {
560 categoryView.expandedCategoryId = "";
561 }
562 }
564 Label {
565 id: seeAllLabel
566 text: baseItem.expanded ? i18n.tr("See less") : i18n.tr("See all")
567 anchors {
568 centerIn: parent
569 verticalCenterOffset: units.gu(-0.5)
570 }
571 fontSize: "small"
572 font.weight: Font.Bold
573 color: scopeStyle ? scopeStyle.foreground : Theme.palette.normal.baseText
574 }
575 }
577 Image {
578 visible: index != 0
579 anchors {
580 top: parent.top
581 left: parent.left
582 right: parent.right
583 }
584 fillMode: Image.Stretch
585 source: "graphics/dash_divider_top_lightgrad.png"
586 z: -1
587 }
589 Image {
590 // FIXME Should not rely on model.count but view.count, but ListViewWithPageHeader doesn't expose it yet.
591 visible: index != categoryView.model.count - 1
592 anchors {
593 bottom: seeAll.bottom
594 left: parent.left
595 right: parent.right
596 }
597 fillMode: Image.Stretch
598 source: "graphics/dash_divider_top_darkgrad.png"
599 z: -1
600 }
601 }
603 sectionProperty: "name"
604 sectionDelegate: ListItems.Header {
605 objectName: "dashSectionHeader" + (delegate ? delegate.category : "")
606 readonly property var delegate: categoryView.item(delegateIndex)
607 width: categoryView.width
608 height: section != "" ? units.gu(5) : 0
609 text: section
610 color: scopeStyle ? scopeStyle.foreground : Theme.palette.normal.baseText
611 iconName: delegate && delegate.headerLink ? "go-next" : ""
612 onClicked: {
613 if (delegate.headerLink) scopeView.scope.performQuery(delegate.headerLink);
614 }
615 }
617 pageHeader: scopeView.showPageHeader ? pageHeaderLoader : null
618 Loader {
619 id: pageHeaderLoader
620 width: parent.width
621 sourceComponent: scopeView.showPageHeader ? pageHeaderComponent : undefined
622 Component {
623 id: pageHeaderComponent
624 PageHeader {
625 objectName: "scopePageHeader"
626 width: parent.width
627 title: scopeView.scope ? scopeView.scope.name : ""
628 searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.ctr("Label: Hint for dash search line edit", "Search")
629 showBackButton: scopeView.hasBackAction
630 searchEntryEnabled: true
631 settingsEnabled: scopeView.scope && scopeView.scope.settings && scopeView.scope.settings.count > 0 || false
632 favoriteEnabled: scopeView.scope && scopeView.scope.id !== "clickscope"
633 favorite: scopeView.scope && scopeView.scope.favorite
634 scopeStyle: scopeView.scopeStyle
635 paginationCount: scopeView.paginationCount
636 paginationIndex: scopeView.paginationIndex
638 bottomItem: DashNavigation {
639 scope: scopeView.scope
640 anchors { left: parent.left; right: parent.right }
641 windowHeight: scopeView.height
642 windowWidth: scopeView.width
643 scopeStyle: scopeView.scopeStyle
644 }
646 onBackClicked: scopeView.backClicked()
647 onSettingsClicked: subPageLoader.openSubPage("settings")
648 onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
649 onSearchTextFieldFocused: scopeView.showHeader()
650 }
651 }
652 }
653 }
655 Item {
656 id: pullToRefreshClippingItem
657 anchors.left: parent.left
658 anchors.right: parent.right
659 anchors.bottom: parent.bottom
660 height: parent.height - pullToRefresh.contentY + (pageHeaderLoader.item ? pageHeaderLoader.item.bottomItem[0].height - pageHeaderLoader.item.height : 0)
661 clip: true
663 PullToRefresh {
664 id: pullToRefresh
665 objectName: "pullToRefresh"
666 target: categoryView
668 readonly property real contentY: categoryView.contentY - categoryView.originY
669 y: -contentY - units.gu(5)
671 onRefresh: {
672 refreshing = true
673 scopeView.scope.refresh()
674 }
675 anchors.left: parent.left
676 anchors.right: parent.right
678 Connections {
679 target: scopeView
680 onProcessingChanged: if (!scopeView.processing) pullToRefresh.refreshing = false
681 }
683 style: PullToRefreshScopeStyle {
684 anchors.fill: parent
685 activationThreshold: units.gu(14)
686 }
687 }
688 }
690 AbstractButton {
691 id: floatingSeeLess
692 objectName: "floatingSeeLess"
694 property Item companionTo: companionBase ? companionBase.seeAllButton : null
695 property Item companionBase: null
696 property bool showBecausePosition: false
697 property real yOffset: 0
699 anchors {
700 left: categoryView.left
701 right: categoryView.right
702 }
703 y: parent.height - height + yOffset
704 height: seeLessLabel.font.pixelSize + units.gu(4)
705 visible: companionTo && showBecausePosition
707 onClicked: categoryView.expandedCategoryId = "";
709 function updateVisibility() {
710 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
711 showBecausePosition = companionPos.y > 0;
713 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
714 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
715 yOffset = Math.min(yOffset, height);
717 if (!showBecausePosition && categoryView.expandedCategoryId === "") {
718 companionBase = null;
719 }
720 }
722 Label {
723 id: seeLessLabel
724 text: i18n.tr("See less")
725 anchors {
726 centerIn: parent
727 verticalCenterOffset: units.gu(-0.5)
728 }
729 fontSize: "small"
730 font.weight: Font.Bold
731 color: scopeStyle ? scopeStyle.foreground : Theme.palette.normal.baseText
732 }
734 Connections {
735 target: floatingSeeLess.companionTo ? categoryView : null
736 onContentYChanged: floatingSeeLess.updateVisibility();
737 }
739 Connections {
740 target: floatingSeeLess.companionTo
741 onYChanged: floatingSeeLess.updateVisibility();
742 }
743 }
745 LimitProxyModel {
746 id: previewLimitModel
747 }
749 Loader {
750 id: subPageLoader
751 objectName: "subPageLoader"
752 visible: x != width
753 width: parent.width
754 height: parent.height
755 anchors.left: categoryView.right
757 property bool open: false
758 property var scope: scopeView.scope
759 property var scopeStyle: scopeView.scopeStyle
760 property int initialIndex: -1
761 property string categoryId
762 property var model: null
764 readonly property bool processing: item && item.processing || false
765 readonly property int count: item && item.count || 0
766 readonly property int currentIndex: item && item.currentIndex || 0
767 readonly property var currentItem: item && item.currentItem || null
769 property string subPage: ""
770 readonly property bool subPageShown: visible && status === Loader.Ready
772 function openSubPage(page) {
773 subPage = page;
774 }
776 function closeSubPage() {
777 open = false;
778 }
780 source: switch(subPage) {
781 case "preview": return "PreviewListView.qml";
782 case "settings": return "ScopeSettingsPage.qml";
783 default: return "";
784 }
786 onLoaded: {
787 item.scope = Qt.binding(function() { return subPageLoader.scope; } )
788 item.scopeStyle = Qt.binding(function() { return subPageLoader.scopeStyle; } )
789 if (subPage == "preview") {
790 item.open = Qt.binding(function() { return subPageLoader.open; } )
791 item.initialIndex = Qt.binding(function() { return subPageLoader.initialIndex; } )
792 item.model = Qt.binding(function() { return subPageLoader.model; } )
793 item.categoryId = Qt.binding(function() { return subPageLoader.categoryId; } )
794 }
795 open = true;
796 }
798 onOpenChanged: pageHeaderLoader.item.unfocus()
800 onVisibleChanged: if (!visible) subPage = ""
802 Connections {
803 target: subPageLoader.item
804 onBackClicked: subPageLoader.closeSubPage()
805 }
806 }