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