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