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