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