on xenial help sign in
Merges
commitdbce7ebc13d3936e509850d6a8d7e106b0b4207c authorAndrea Cimitan <andrea.cimitan@gmail.com> dateMon 21 Mar 2016, 16:06 (11 years ago) parentb5c64db74cbf86
48 files changed+2918−830
plugins/Dash/CardCreator.js+1−1 plugins/Dash/CardSocialActions.qml+12−4 plugins/Dash/ScopeStyle.qml+1−1 qml/Dash/CardTool.qml+12−19 qml/Dash/DashContent.qml+2−2 qml/Dash/DashNavigation.qml+159−78 qml/Dash/DashNavigationButton.qml−233 qml/Dash/DashNavigationHeader.qml+73 qml/Dash/DashNavigationList.qml+31−85 qml/Dash/DashPageHeader.qml+139−131 qml/Dash/Filters/FilterOptionSelector.qml+91 qml/Dash/Filters/FilterRangeInput.qml+136 qml/Dash/Filters/FilterValueSlider.qml+87 qml/Dash/Filters/FilterWidget.qml+29 qml/Dash/Filters/FilterWidgetFactory.qml+79 qml/Dash/FiltersPopover.qml+105 qml/Dash/GenericScopeView.qml+77−47 qml/Dash/PageHeaderExtraPanel.qml+175 tests/mocks/Unity/CMakeLists.txt+13 tests/mocks/Unity/fake_filters.cpp+125 tests/mocks/Unity/fake_filters.h+46 tests/mocks/Unity/fake_navigation.cpp+9−10 tests/mocks/Unity/fake_optionselectorfilter.cpp+65 tests/mocks/Unity/fake_optionselectorfilter.h+52 tests/mocks/Unity/fake_optionselectoroptions.cpp+77 tests/mocks/Unity/fake_optionselectoroptions.h+47 tests/mocks/Unity/fake_rangeinputfilter.cpp+167 tests/mocks/Unity/fake_rangeinputfilter.h+79 tests/mocks/Unity/fake_scope.cpp+52−27 tests/mocks/Unity/fake_scope.h+20−7 tests/mocks/Unity/fake_unity_plugin.cpp+7 tests/mocks/Unity/fake_valuesliderfilter.cpp+84 tests/mocks/Unity/fake_valuesliderfilter.h+55 tests/mocks/Unity/fake_valueslidervalues.cpp+51 tests/mocks/Unity/fake_valueslidervalues.h+36 tests/plugins/Dash/cardcreator/12.tst+1−1 tests/plugins/Dash/cardcreator/9.res.cardcreator−3 tests/qmltests/CMakeLists.txt+4 tests/qmltests/Dash/CardHelpers.js+7−7 tests/qmltests/Dash/Filters/tst_FilterOptionSelector.qml+96 tests/qmltests/Dash/Filters/tst_FilterRangeInput.qml+247 tests/qmltests/Dash/Filters/tst_FilterValueSlider.qml+101 tests/qmltests/Dash/Filters/tst_FilterWidgetFactory.qml+57 tests/qmltests/Dash/tst_Card.qml+4−4 tests/qmltests/Dash/tst_DashContent.qml+181−153 tests/qmltests/Dash/tst_DashPageHeader.qml+24−15 tests/qmltests/Dash/tst_GenericScopeView.qml+1−1 tests/qmltests/Dash/tst_PreviewView.qml+1−1
plugins/Dash/CardCreator.js+1−1
@@ -455,7 +455,7 @@ function cardString(template, components, isCardTool) {
455455 var hasSubtitle = hasTitle && components["subtitle"] || false;
456456 var hasHeaderRow = hasMascot && hasTitle;
457457 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
458− var hasSocialActions = hasTitle && components["socialActions"] || false;
458+ var hasSocialActions = hasTitle && components["social-actions"] || false;
459459 var isAudio = template["quick-preview-type"] === "audio";
460460 var asynchronous = isCardTool ? "false" : "true";
461461
plugins/Dash/CardSocialActions.qml+12−4
@@ -17,7 +17,6 @@
1717import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
20−import "../../../qml/Components/ListItems" as ListItems
2120
2221Column {
2322 id: socialActions
@@ -28,10 +27,17 @@ Column {
2827
2928 signal clicked(var actionId)
3029
31− ListItems.ThinDivider {
30+ Rectangle {
3231 id: divider
3332 visible: repeater.count > 0
34− anchors { left: parent.left; right: parent.right; }
33+ anchors {
34+ left: parent.left;
35+ right: parent.right;
36+ leftMargin:units.dp(1);
37+ rightMargin: units.dp(1);
38+ }
39+ color: Qt.darker(theme.palette.normal.background, 1.12)
40+ height: visible ? units.dp(1) : 0
3541 }
3642
3743 Row {
@@ -74,7 +80,9 @@ Column {
7480 source: urlIcon
7581 color: socialActions.color
7682
77− onUrlIconChanged: if (urlIcon) source = urlIcon
83+ onUrlIconChanged: {
84+ if (urlIcon) source = urlIcon
85+ }
7886 }
7987
8088 onClicked: socialActions.clicked(modelData["id"]);
plugins/Dash/ScopeStyle.qml+1−1
@@ -58,7 +58,7 @@ QtObject {
5858 readonly property url headerLogo: "logo" in d.headerStyle ? d.headerStyle["logo"] : ""
5959
6060 /// Background style for the header
61− readonly property url headerBackground: "background" in d.headerStyle ? d.headerStyle["background"] : "color:///#f5f5f5"
61+ readonly property url headerBackground: "background" in d.headerStyle ? d.headerStyle["background"] : "color:///#ffffff"
6262
6363 /// Foreground color for the header
6464 readonly property color headerForeground: "foreground-color" in d.headerStyle ? d.headerStyle["foreground-color"] : foreground
qml/Dash/CardTool.qml+12−19
@@ -162,7 +162,7 @@ Item {
162162 id: attributesModel
163163 property int numOfAttributes: 0
164164 property var model: []
165− property bool hasAttributes: {
165+ readonly property bool hasAttributes: {
166166 var attributes = components["attributes"];
167167 var hasAttributesFlag = (attributes != undefined) && (attributes["field"] != undefined);
168168
@@ -184,31 +184,23 @@ Item {
184184
185185 Item {
186186 id: socialActionsModel
187− property int numOfActions: 0
188187 property var model: []
189− property bool hasActions: {
190− var actions = components["socialActions"];
188+ readonly property bool hasActions: {
189+ var actions = components["social-actions"];
191190 var hasActionsFlag = (actions != undefined);
192191
192+ model = []
193193 if (hasActionsFlag) {
194− if (actions["max-count"]) {
195− numOfActions = actions["max-count"];
196− }
194+ model.push( {"id":"text", "icon":"image://theme/ok" } );
197195 }
198196 return hasActionsFlag
199197 }
200−
201− onNumOfActionsChanged: {
202− model = []
203− for (var i = 0; i < numOfActions; i++) {
204− model.push( {"id":"text"+(i+1), "icon":"image://theme/ok" } );
205− }
206− }
207198 }
208199
209200 Loader {
210201 id: cardLoader
211− readonly property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "socialActions"]
202+ readonly property var cfields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "social-actions"]
203+ readonly property var dfields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "socialActions"]
212204 readonly property var maxData: {
213205 "art": Qt.resolvedUrl("graphics/pixel.png"),
214206 "mascot": Qt.resolvedUrl("graphics/pixel.png"),
@@ -231,13 +223,14 @@ Item {
231223 }
232224 function updateCardData() {
233225 var data = {};
234− for (var k in fields) {
235− var component = cardTool.components[fields[k]];
236− var key = fields[k];
226+ for (var k in cfields) {
227+ var ckey = cfields[k];
228+ var component = cardTool.components[ckey];
237229 if ((typeof component === "string" && component.length > 0) ||
238230 (typeof component === "object" && component !== null
239231 && typeof component["field"] === "string" && component["field"].length > 0)) {
240− data[key] = maxData[key];
232+ var dkey = dfields[k];
233+ data[dkey] = maxData[dkey];
241234 }
242235 }
243236 item.cardData = data;
qml/Dash/DashContent.qml+2−2
@@ -108,7 +108,7 @@ Item {
108108 objectName: "dashContentList"
109109
110110 interactive: !dashContent.forceNonInteractive && dashContent.scopes.loaded && currentItem
111− && !currentItem.moving && !currentItem.navigationDisableParentInteractive && !currentItem.subPageShown
111+ && !currentItem.moving && !currentItem.subPageShown && !currentItem.extraPanelShown
112112 anchors.fill: parent
113113 orientation: ListView.Horizontal
114114 boundsBehavior: Flickable.DragAndOvershootBounds
@@ -170,7 +170,7 @@ Item {
170170 objectName: "scopeLoader" + index
171171
172172 readonly property bool moving: item ? item.moving : false
173− readonly property bool navigationDisableParentInteractive: item ? item.navigationDisableParentInteractive : false
173+ readonly property bool extraPanelShown: item ? item.extraPanelShown : false
174174 readonly property bool subPageShown: item ? item.subPageShown : false
175175 readonly property var categoryView: item ? item.categoryView : null
176176 readonly property var theScope: scope
qml/Dash/DashNavigation.qml+159−78
@@ -17,101 +17,182 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Dash 0.1
20−import "../Components"
2120
2221Item {
2322 id: root
2423 objectName: "dashNavigation"
2524
25+ // set by parent
2626 property var scope: null
27− property var scopeStyle: null
28−
29− property alias windowWidth: blackRect.width
30− property alias windowHeight: blackRect.height
31− readonly property var openList: {
32− if (navigationButton.showList) return navigationButton.listView;
33− if (altNavigationButton.showList) return altNavigationButton.listView;
34− return null;
35− }
36− readonly property bool disableParentInteractive: {
37− return navigationButton.showList || altNavigationButton.showList ||
38− navigationButton.inverseMousePressed || altNavigationButton.inverseMousePressed;
39− }
27+ property real availableHeight
28+
29+ signal leafClicked()
4030
41− // FIXME this is only here for highlight purposes (see Background.qml, too)
42− readonly property var background: backgroundItem
31+ // internal
32+ readonly property var currentNavigation: scope && scope.hasNavigation ? scope.getNavigation(scope.currentNavigationId) : null
33+ // Are we drilling down the tree or up?
34+ property bool isEnteringChildren: false
4335
4436 visible: height != 0
45− height: navigationButton.currentNavigation || altNavigationButton.currentNavigation ? units.gu(5) : 0
46−
47− QtObject {
48− id: d
49− readonly property color foregroundColor: root.scopeStyle
50− ? root.scopeStyle.getTextColor(backgroundItem.luminance)
51− : theme.palette.normal.baseText
52− readonly property bool bothVisible: altNavigationButton.visible && navigationButton.visible
53− readonly property real navigationWidth: root.width >= units.gu(60) ? units.gu(40) : root.width
54− readonly property real buttonWidth: navigationWidth / (bothVisible ? 2 : 1)
55− }
37+ implicitHeight: scope && scope.hasNavigation ? navigationListView.y + navigationListView.height : 0
5638
57− Rectangle {
58− id: blackRect
59− objectName: "blackRect"
60− color: "black"
61− opacity: openList && openList.currentItem && openList.currentItem.visible ? 0.5 : 0
62− Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
63− anchors { left: parent.left; right: parent.right }
64− visible: opacity > 0
39+ function resetNavigation() {
40+ if (navigationModel.count > 1) {
41+ clear();
42+ }
6543 }
6644
67− Background {
68− id: backgroundItem
69− anchors.fill: parent
70− style: scopeStyle ? scopeStyle.navigationBackground : "color:///#f5f5f5"
71− }
45+ Column {
46+ id: headersColumn
47+ anchors {
48+ top: parent.top
49+ left: parent.left
50+ right: parent.right
51+ }
7252
73− Image {
74− fillMode: Image.Stretch
75− source: scopeStyle.backgroundLuminance > 0.2 ? "graphics/navigation_shadow.png" : "graphics/navigation_shadow_light.png"
76− anchors { top: parent.bottom; left: parent.left; right: parent.right }
77− }
53+ function pop(popsNeeded) {
54+ if (popsNeeded == 0)
55+ return;
56+ isEnteringChildren = false;
57+ navigationListView.currentIndex = navigationListView.currentIndex - popsNeeded;
58+ navigationModel.setProperty(navigationListView.currentIndex, "nullifyNavigation", false);
59+ navigationModel.remove(navigationModel.count - popsNeeded, popsNeeded);
60+
61+ popsNeeded = Math.min(headersModel.count, popsNeeded);
62+ // This is effectively deleting ourselves, so needs to be the last thing of the function
63+ headersModel.remove(headersModel.count - popsNeeded, popsNeeded);
64+ }
65+
66+ Repeater {
67+ model: ListModel {
68+ id: headersModel
69+ // Roles
70+ // headerText: the text to show
71+ // navigationId: the navigation Id that represents
72+ // parentNavigationId: the parent navigation Id
73+ }
74+ delegate: DashNavigationHeader {
75+ objectName: "dashNavigationHeader" + index
76+ height: index == 0 && headersModel.count > 1 ? 0 : units.gu(5)
77+ width: headersColumn.width
78+
79+ backVisible: index != 0
80+ text: headerText
7881
79− DashNavigationButton {
80− id: altNavigationButton
81− objectName: "altNavigationButton"
82− height: root.height
83− width: d.buttonWidth
84− scope: root.scope
85− scopeStyle: root.scopeStyle
86− foregroundColor: d.foregroundColor
87− listView.width: d.navigationWidth
88− isAltNavigation: true
89− showDivider: navigationButton.visible || root.width > d.navigationWidth
90− // needed so that InverseMouseArea is above navigationButton
91− z: listView.height > 0 ? 1 : 0
82+ onBackClicked: {
83+ scope.setNavigationState(parentNavigationId);
84+
85+ var popsNeeded = headersModel.count - index;
86+ headersColumn.pop(popsNeeded);
87+ }
88+
89+ onTextClicked: {
90+ scope.setNavigationState(navigationId);
91+
92+ var popsNeeded = headersModel.count - index - 1;
93+ headersColumn.pop(popsNeeded);
94+
95+ root.leafClicked();
96+ }
97+ }
98+ }
9299 }
93100
94− DashNavigationButton {
95− id: navigationButton
96− objectName: "navigationButton"
97− height: root.height
98− width: altNavigationButton.visible ? d.buttonWidth : d.navigationWidth
99− x: altNavigationButton.visible ? d.buttonWidth : 0
100− scope: root.scope
101− scopeStyle: root.scopeStyle
102− foregroundColor: d.foregroundColor
103− listView.width: d.navigationWidth
104− listView.x: -x
105− showDivider: root.width > d.navigationWidth
101+ ListView {
102+ id: navigationListView
103+ objectName: "navigationListView"
104+ orientation: ListView.Horizontal
105+ interactive: false
106+ model: ListModel {
107+ id: navigationModel
108+ // We have two roles
109+ // navigationId: the navigation id of the navigation the list represents
110+ // nullifyNavigation: overrides navigationId to be null
111+ // This is used to "clear" the delegate when going "right" on the tree
112+ }
113+ anchors {
114+ top: headersColumn.bottom
115+ left: parent.left
116+ right: parent.right
117+ }
118+ readonly property int maxHeight: root.availableHeight - navigationListView.y
119+ property int prevHeight: maxHeight
120+ height: currentItem ? currentItem.height : maxHeight
121+
122+ onHeightChanged: {
123+ if (currentItem) {
124+ prevHeight = currentItem.desiredHeight;
125+ }
126+ }
127+ highlightMoveDuration: UbuntuAnimation.FastDuration
128+ delegate: DashNavigationList {
129+ objectName: "navigation" + index
130+ visible: height > 0
131+ width: navigationListView.width
132+ property real desiredHeight: {
133+ if (navigation && navigation.loaded && x == navigationListView.contentX)
134+ {
135+ return Math.min(implicitHeight, navigationListView.maxHeight);
136+ } else {
137+ return navigationListView.prevHeight;
138+ }
139+ }
140+ height: desiredHeight
141+ navigation: (nullifyNavigation || !scope) ? null : scope.getNavigation(navigationId)
142+ currentNavigation: root.currentNavigation
143+ onEnterNavigation: { // var newNavigationId, string newNavigationLabel, bool hasChildren
144+ scope.setNavigationState(newNavigationId);
145+ // We only need to add a new item to the model
146+ // if we have children, otherwise just load it
147+ if (hasChildren) {
148+ isEnteringChildren = true;
149+ navigationModel.append({"navigationId": newNavigationId, "nullifyNavigation": false});
150+ headersModel.append({"headerText": newNavigationLabel,
151+ "navigationId": newNavigationId,
152+ "parentNavigationId": navigationId
153+ });
154+ navigationListView.currentIndex++;
155+ } else {
156+ root.leafClicked();
157+ }
158+ }
159+ }
160+ onContentXChanged: {
161+ if (navigationListView.highlightMoveDuration == 0)
162+ return;
163+
164+ if (contentX == width * navigationListView.currentIndex) {
165+ if (isEnteringChildren) {
166+ navigationModel.setProperty(navigationListView.currentIndex - 1, "nullifyNavigation", true);
167+ }
168+ }
169+ }
106170 }
107171
108− Image {
109− fillMode: Image.Stretch
110− source: backgroundItem.luminance > 0.7 ? "graphics/navigation_shadow.png" : "graphics/navigation_shadow_light.png"
111− x: navigationButton.listView.height > 0 ? altNavigationButton.x : navigationButton.x
112− width: d.buttonWidth
113− rotation: 180
114− anchors.bottom: parent.bottom
115− visible: d.bothVisible && (navigationButton.listView.height > 0 || altNavigationButton.listView.height > 0)
172+ property bool isFirstLoad: false
173+ onScopeChanged: clear();
174+ function clear() {
175+ navigationModel.clear();
176+ headersModel.clear();
177+ isFirstLoad = true;
178+ }
179+ function setNewNavigation() {
180+ if (isFirstLoad && currentNavigation && currentNavigation.loaded) {
181+ isFirstLoad = false;
182+ if (currentNavigation.count > 0) {
183+ navigationModel.append({"navigationId": scope.currentNavigationId, "nullifyNavigation": false});
184+ } else {
185+ navigationModel.append({"navigationId": currentNavigation.parentNavigationId, "nullifyNavigation": false});
186+ }
187+ headersModel.append({"headerText": currentNavigation.allLabel != "" ? currentNavigation.allLabel : currentNavigation.label,
188+ "navigationId": currentNavigation.navigationId,
189+ "parentNavigationId": currentNavigation.parentNavigationId
190+ });
191+ }
192+ }
193+ Connections {
194+ target: currentNavigation
195+ onLoadedChanged: setNewNavigation();
116196 }
197+ onCurrentNavigationChanged: setNewNavigation();
117198}
qml/Dash/DashNavigationButton.qmldeleted−233
@@ -1,233 +0,0 @@
1−/*
2− * Copyright (C) 2014,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.4
18−import Ubuntu.Components 1.3
19−
20−AbstractButton {
21− id: root
22− objectName: "dashNavigation"
23−
24− // Set by parent
25− property var scope: null
26− property var scopeStyle: null
27− property color foregroundColor: theme.palette.normal.baseText
28− property bool isAltNavigation: false
29− property bool showDivider: false
30−
31− // Used by parent
32− readonly property var currentNavigation: scope && scope[hasNavigation] ? getNavigation(scope[currentNavigationId]) : null
33− readonly property alias listView: navigationListView
34− readonly property bool inverseMousePressed: inverseMouseArea.pressed
35− property bool showList: false
36−
37− // Internal
38− // Are we drilling down the tree or up?
39− property bool isGoingBack: false
40− readonly property string hasNavigation: isAltNavigation ? "hasAltNavigation" : "hasNavigation"
41− readonly property string currentNavigationId: isAltNavigation ? "currentAltNavigationId" : "currentNavigationId"
42− function getNavigation(navId) {
43− if (isAltNavigation) {
44− return scope.getAltNavigation(navId);
45− } else {
46− return scope.getNavigation(navId);
47− }
48− }
49−
50− visible: root.currentNavigation != null
51−
52− onClicked: {
53− navigationListView.updateMaxHeight();
54− root.showList = !root.showList;
55− }
56−
57− Label {
58− anchors.fill: parent
59− anchors.margins: units.gu(2)
60− anchors.rightMargin: units.gu(5)
61− verticalAlignment: Text.AlignVCenter
62− text: root.currentNavigation ? root.currentNavigation.label : ""
63− color: root.foregroundColor
64− elide: Text.ElideRight
65− maximumLineCount: 1
66− }
67−
68− Icon {
69− anchors.verticalCenter: parent.verticalCenter
70− anchors.right: parent.right
71− anchors.rightMargin: units.gu(2)
72− name: showList ? "up" : "down"
73− height: units.gu(2)
74− width: height
75− color: root.foregroundColor
76− }
77−
78− // navigationListView is outside root
79− ListView {
80− id: navigationListView
81− objectName: "navigationListView"
82− visible: height > 0
83− orientation: ListView.Horizontal
84− interactive: false
85− clip: root.width != windowWidth
86− model: ListModel {
87− id: navigationModel
88− // We have two roles
89− // navigationId: the navigation id of the navigation the list represents
90− // nullifyNavigation: overrides navigationId to be null
91− // This is used to "clear" the delegate when going "right" on the tree
92− }
93− anchors.top: root.bottom
94− property int maxHeight: -1
95− Component.onCompleted: updateMaxHeight();
96− function updateMaxHeight()
97− {
98− maxHeight = (windowHeight - mapToItem(null, 0, 0).y) - units.gu(8);
99− }
100− property int prevHeight: maxHeight
101− height: currentItem ? currentItem.height : maxHeight
102− onHeightChanged: {
103− if (root.showList) {
104− prevHeight = currentItem.desiredHeight;
105− }
106− }
107− highlightMoveDuration: UbuntuAnimation.FastDuration
108− delegate: DashNavigationList {
109− objectName: "navigation" + index
110− visible: height > 0
111− width: navigationListView.width
112− scopeStyle: root.scopeStyle
113− foregroundColor: root.foregroundColor
114− property real desiredHeight: {
115− if (root.showList) {
116− if (navigation && navigation.loaded && x == navigationListView.contentX)
117− {
118− navigationListView.updateMaxHeight();
119− return Math.min(implicitHeight, navigationListView.maxHeight);
120− } else {
121− return navigationListView.prevHeight;
122− }
123− } else {
124− return 0;
125− }
126− }
127− height: desiredHeight
128− navigation: (nullifyNavigation || !scope) ? null : getNavigation(navigationId)
129− currentNavigation: root.currentNavigation
130− onEnterNavigation: {
131− scope.setNavigationState(newNavigationId, isAltNavigation);
132− // We only need to add a new item to the model
133− // if we have children, otherwise just load it
134− if (hasChildren) {
135− isGoingBack = false;
136− navigationModel.append({"navigationId": newNavigationId, "nullifyNavigation": false});
137− navigationListView.currentIndex++;
138− } else {
139− showList = false;
140− }
141− }
142− onGoBackToParentClicked: {
143− if (navigationListView.currentIndex == 0) {
144− // This can happen if we jumped to the non root of a deep tree and the user
145− // is now going back, create space in the list for the list to move "left"
146− var aux = navigationListView.highlightMoveDuration;
147− navigationListView.highlightMoveDuration = 0;
148− navigationModel.insert(0, {"navigationId": navigation.parentNavigationId, "nullifyNavigation": false});
149− navigationListView.currentIndex = navigationListView.currentIndex + 1;
150− navigationListView.contentX = width * navigationListView.currentIndex;
151− navigationListView.highlightMoveDuration = aux;
152− }
153−
154− scope.setNavigationState(navigation.parentNavigationId, isAltNavigation);
155− isGoingBack = true;
156− navigationModel.setProperty(navigationListView.currentIndex - 1, "nullifyNavigation", false);
157− navigationListView.currentIndex--;
158− }
159− onAllNavigationClicked: {
160− showList = false;
161− if (root.currentNavigation.parentNavigationId == navigation.navigationId) {
162− // For leaves we have to go to the parent too
163− scope.setNavigationState(root.currentNavigation.parentNavigationId, isAltNavigation);
164− }
165− }
166− }
167− onContentXChanged: {
168− if (navigationListView.highlightMoveDuration == 0)
169− return;
170−
171− if (contentX == width * navigationListView.currentIndex) {
172− if (isGoingBack) {
173− navigationModel.remove(navigationListView.currentIndex + 1);
174− } else {
175− navigationModel.setProperty(navigationListView.currentIndex - 1, "nullifyNavigation", true);
176− }
177− }
178− }
179− }
180−
181− Image {
182− anchors {
183− top: navigationListView.bottom
184− left: navigationListView.left
185− right: navigationListView.right
186− }
187− fillMode: Image.Stretch
188− source: "graphics/navigation_shadow.png"
189− visible: root.showList
190− }
191−
192− property bool isFirstLoad: false
193− onScopeChanged: {
194− navigationModel.clear();
195− isFirstLoad = true;
196− }
197− function setNewNavigation() {
198− if (isFirstLoad && currentNavigation && currentNavigation.loaded) {
199− isFirstLoad = false;
200− if (currentNavigation.count > 0) {
201− navigationModel.append({"navigationId": scope[currentNavigationId], "nullifyNavigation": false});
202− } else {
203− navigationModel.append({"navigationId": currentNavigation.parentNavigationId, "nullifyNavigation": false});
204− }
205− }
206− }
207− Connections {
208− target: currentNavigation
209− onLoadedChanged: setNewNavigation();
210− }
211− onCurrentNavigationChanged: setNewNavigation();
212−
213− InverseMouseArea {
214− id: inverseMouseArea
215− anchors.fill: navigationListView
216− enabled: root.showList
217− onPressed: root.showList = false
218− }
219−
220− Rectangle {
221− visible: root.showDivider
222− anchors {
223− top: parent.top
224− topMargin: units.dp(1)
225− bottom: parent.bottom
226− left: parent.right
227− leftMargin: -units.dp(0.5)
228− }
229− width: units.dp(1)
230− color: root.foregroundColor
231− opacity: 0.2
232− }
233−}
qml/Dash/DashNavigationHeader.qmlnew file+73
@@ -0,0 +1,73 @@
1+/*
2+ * Copyright (C) 2014 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.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3 as ListItem
20+
21+Row {
22+ id: root
23+
24+ property alias backVisible: backImageItem.visible
25+ property alias textEnabled: textItem.enabled
26+ property alias text: textLabel.text
27+
28+ readonly property alias backButtonWidth: backImageItem.width
29+
30+ signal backClicked()
31+ signal textClicked()
32+
33+ ListItem.Standard {
34+ id: backImageItem
35+ objectName: "backButton"
36+ height: parent.height
37+ width: height
38+ showDivider: false
39+
40+ Icon {
41+ id: backImage
42+ anchors.centerIn: parent
43+ name: "back"
44+ height: units.gu(2)
45+ width: height
46+ }
47+
48+ onClicked: root.backClicked();
49+ }
50+
51+ ListItem.Standard {
52+ id: textItem
53+ height: parent.height
54+ showDivider: false
55+
56+ Label {
57+ id: textLabel
58+ anchors {
59+ verticalCenter: parent.verticalCenter
60+ left: parent.left
61+ right: parent.right
62+ leftMargin: backImageItem.visible ? 0 : units.gu(2)
63+ rightMargin: units.gu(2)
64+ }
65+ color: "#5D5D5D"
66+ wrapMode: Text.Wrap
67+ maximumLineCount: 2
68+ elide: Text.ElideMiddle
69+ }
70+
71+ onClicked: root.textClicked();
72+ }
73+}
qml/Dash/DashNavigationList.qml+31−85
@@ -16,27 +16,17 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19−import Ubuntu.Components.ListItems 1.3 as ListItem
2019import "../Components"
2120
2221Item {
2322 id: root
2423 property var navigation: null
2524 property var currentNavigation: null
26− property var scopeStyle: null
27− property color foregroundColor: theme.palette.normal.baseText
28− signal enterNavigation(var newNavigationId, bool hasChildren)
29− signal goBackToParentClicked()
30− signal allNavigationClicked()
25+ signal enterNavigation(var newNavigationId, string newNavigationLabel, bool hasChildren)
3126
3227 readonly property int itemHeight: units.gu(5)
3328 implicitHeight: flickable.contentHeight
3429
35− Background {
36− style: root.scopeStyle ? root.scopeStyle.navigationBackground : "color:///#f5f5f5"
37− anchors.fill: parent
38− }
39−
4030 clip: true
4131
4232 Behavior on height {
@@ -59,89 +49,45 @@ Item {
5949 id: column
6050 width: parent.width
6151
62− ListItem.Standard {
63− id: backButton
64− objectName: "backButton"
65− visible: navigation && !navigation.isRoot || false
66− height: itemHeight
67−
68− onClicked: root.goBackToParentClicked();
69−
70− Icon {
71− id: backImage
52+ Repeater {
53+ model: navigation && navigation.loaded ? navigation : null
54+ clip: true
55+ delegate: ListItem {
56+ objectName: root.objectName + "child" + index
57+ height: root.itemHeight
58+ width: column.width
7259 anchors {
73− verticalCenter: parent.verticalCenter
74− left: parent.left
60+ left: column.left
61+ right: column.right
7562 leftMargin: units.gu(2)
76− }
77− name: "back"
78− height: units.gu(2)
79− width: height
80− color: root.foregroundColor
81− }
82−
83− Label {
84− anchors {
85− verticalCenter: parent.verticalCenter
86− left: backImage.right
87− right: parent.right
88− leftMargin: units.gu(0.5)
8963 rightMargin: units.gu(2)
9064 }
91− text: navigation ? navigation.parentLabel : ""
92− color: root.foregroundColor
93− wrapMode: Text.Wrap
94− maximumLineCount: 2
95− elide: Text.ElideMiddle
96− }
97− }
9865
99− ListItem.Standard {
100− id: allButton
101− objectName: "allButton"
102− visible: navigation && (!navigation.isRoot || (!navigation.hidden && root.currentNavigation && !root.currentNavigation.isRoot && root.currentNavigation.parentNavigationId == navigation.navigationId)) || false
103− height: itemHeight
66+ onClicked: root.enterNavigation(navigationId, allLabel != "" ? allLabel : label, hasChildren)
10467
105− Label {
106− anchors {
107− verticalCenter: parent.verticalCenter
108− left: parent.left
109− right: parent.right
110− leftMargin: units.gu(2)
111− rightMargin: units.gu(2)
68+ Icon {
69+ id: leftIcon
70+ anchors {
71+ verticalCenter: parent.verticalCenter
72+ left: parent.left
73+ }
74+ height: units.gu(2)
75+ width: height
76+ name: "tick"
77+ color: "#3EB34F"
78+ visible: isActive
11279 }
113− text: navigation ? (navigation.allLabel != "" ? navigation.allLabel : navigation.label) : ""
114− font.bold: true
115− color: root.foregroundColor
116− wrapMode: Text.Wrap
117− maximumLineCount: 2
118− elide: Text.ElideMiddle
119− }
120−
121− onClicked: root.allNavigationClicked();
122− }
123−
124− Repeater {
125− model: navigation && navigation.loaded ? navigation : null
126− clip: true
127− delegate: ListItem.Standard {
128− objectName: root.objectName + "child" + index
129− height: root.itemHeight
130− showDivider: index != navigation.count - 1
131− selected: isActive
132−
133− onClicked: root.enterNavigation(navigationId, hasChildren)
13480
13581 Label {
13682 anchors {
13783 verticalCenter: parent.verticalCenter
138− left: parent.left
139− leftMargin: units.gu(2)
140− right: rightIcon.visible ? rightIcon.left : parent.right
141− rightMargin: rightIcon.visible ? units.gu(0.5) : units.gu(2)
84+ left: leftIcon.right
85+ leftMargin: units.gu(1)
86+ right: rightIcon.left
87+ rightMargin: units.gu(2)
14288 }
14389 text: label
144− color: root.foregroundColor
90+ color: isActive ? "#333333" : "#888888"
14591 wrapMode: Text.Wrap
14692 maximumLineCount: 2
14793 elide: Text.ElideMiddle
@@ -152,14 +98,14 @@ Item {
15298 anchors {
15399 verticalCenter: parent.verticalCenter
154100 right: parent.right
155− rightMargin: units.gu(2)
156101 }
157102 height: units.gu(2)
158103 width: height
159− name: hasChildren ? "go-next" : "tick"
160− color: root.foregroundColor
161− visible: hasChildren || isActive
104+ name: "go-next"
105+ visible: hasChildren
162106 }
107+
108+ divider.visible: index != navigation.count - 1
163109 }
164110 }
165111 }
qml/Dash/DashPageHeader.qml+139−131
@@ -17,41 +17,45 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.Components.Themes.Ambiance 1.3
20−import Ubuntu.Components.Popups 1.3
2120import Ubuntu.Components.ListItems 1.3
2221import "../Components"
23−import "../Components/SearchHistoryModel"
2422
2523Item {
2624 id: root
2725 objectName: "pageHeader"
28− implicitHeight: headerContainer.height + bottomContainer.height + (showSignatureLine ? units.gu(2) : 0)
26+ implicitHeight: headerContainer.height + signatureLineHeight
27+ readonly property real signatureLineHeight: showSignatureLine ? units.gu(2) : 0
2928
29+ property int activeFiltersCount: 0
30+ property bool scopeHasFilters: false
3031 property bool showBackButton: false
3132 property bool backIsClose: false
3233 property string title
34+ property var extraPanel
35+ property string navigationTag
3336
3437 property bool storeEntryEnabled: false
3538 property bool searchEntryEnabled: false
3639 property bool settingsEnabled: false
3740 property bool favoriteEnabled: false
3841 property bool favorite: false
39− property ListModel searchHistory: SearchHistoryModel
42+ property ListModel searchHistory
4043 property alias searchQuery: searchTextField.text
4144 property alias searchHint: searchTextField.placeholderText
4245 property bool showSignatureLine: true
4346
44− property alias bottomItem: bottomContainer.children
4547 property int paginationCount: 0
4648 property int paginationIndex: -1
4749
4850 property var scopeStyle: null
4951
52+ signal clearSearch(bool keepPanelOpen)
5053 signal backClicked()
5154 signal storeClicked()
5255 signal settingsClicked()
5356 signal favoriteClicked()
5457 signal searchTextFieldFocused()
58+ signal showFiltersPopup(var item)
5559
5660 onScopeStyleChanged: refreshLogo()
5761 onSearchQueryChanged: {
@@ -60,6 +64,12 @@ Item {
6064 headerContainer.showSearch = true;
6165 }
6266 }
67+ onNavigationTagChanged: {
68+ // Make sure we are at the search page if the navigation tag changes behind our feet
69+ if (navigationTag) {
70+ headerContainer.showSearch = true;
71+ }
72+ }
6373
6474 function triggerSearch() {
6575 if (searchEntryEnabled) {
@@ -69,12 +79,14 @@ Item {
6979 }
7080
7181 function closePopup(keepFocus) {
72− if (headerContainer.popover != null) {
73− headerContainer.popover.unfocusOnDestruction = !keepFocus;
74− PopupUtils.close(headerContainer.popover);
82+ if (extraPanel.visible) {
83+ extraPanel.visible = false;
7584 } else if (!keepFocus) {
7685 unfocus();
7786 }
87+ if (!searchTextField.text && !root.navigationTag && searchHistory.count == 0) {
88+ headerContainer.showSearch = false;
89+ }
7890 }
7991
8092 function resetSearch(keepFocus) {
@@ -87,22 +99,17 @@ Item {
8799
88100 function unfocus() {
89101 searchTextField.focus = false;
90− if (!searchTextField.text) {
102+ if (!searchTextField.text && !root.navigationTag) {
91103 headerContainer.showSearch = false;
92104 }
93105 }
94106
95− function openSearchHistory() {
107+ function openPopup() {
96108 if (openSearchAnimation.running) {
97− openSearchAnimation.openSearchHistory = true;
98− } else if (root.searchHistory.count > 0 && headerContainer.popover == null) {
99− headerContainer.popover = PopupUtils.open(popoverComponent, searchTextField,
100− {
101− "contentWidth": searchTextField.width,
102− "edgeMargins": units.gu(1)
103− }
104− );
105− searchTextField.forceActiveFocus();
109+ openSearchAnimation.openPopup = true;
110+ } else if (extraPanel.hasContents) {
111+ // Show extraPanel
112+ extraPanel.visible = true;
106113 }
107114 }
108115
@@ -121,13 +128,11 @@ Item {
121128 }
122129
123130 InverseMouseArea {
124− anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
131+ anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + (extraPanel ? extraPanel.height : 0) }
125132 visible: headerContainer.showSearch
126133 onPressed: {
134+ extraPanel.visible = false;
127135 closePopup(/* keepFocus */false);
128− if (!searchTextField.text) {
129− headerContainer.showSearch = false;
130− }
131136 mouse.accepted = false;
132137 }
133138 }
@@ -143,7 +148,6 @@ Item {
143148 contentY: showSearch ? 0 : height
144149
145150 property bool showSearch: false
146− property var popover: null
147151
148152 Background {
149153 id: background
@@ -154,12 +158,12 @@ Item {
154158 Behavior on contentY {
155159 UbuntuNumberAnimation {
156160 id: openSearchAnimation
157− property bool openSearchHistory: false
161+ property bool openPopup: false
158162
159163 onRunningChanged: {
160− if (!running && openSearchAnimation.openSearchHistory) {
161− openSearchAnimation.openSearchHistory = false;
162− root.openSearchHistory();
164+ if (!running && openSearchAnimation.openPopup) {
165+ openSearchAnimation.openPopup = false;
166+ root.openPopup();
163167 }
164168 }
165169 }
@@ -183,60 +187,122 @@ Item {
183187 backgroundColor: "transparent"
184188 config: PageHeadConfiguration {
185189 foregroundColor: root.scopeStyle ? root.scopeStyle.headerForeground : theme.palette.normal.baseText
186− backAction: Action {
187− iconName: "back"
188− onTriggered: {
189− root.resetSearch();
190− headerContainer.showSearch = false;
191− }
192− }
193190 }
194− property var contents: TextField {
195− id: searchTextField
196− objectName: "searchTextField"
197− inputMethodHints: Qt.ImhNoPredictiveText
198− hasClearButton: false
199− anchors {
200− fill: parent
201− leftMargin: units.gu(1)
202− topMargin: units.gu(1)
203− bottomMargin: units.gu(1)
204− rightMargin: root.width > units.gu(60) ? root.width - units.gu(40) : units.gu(1)
205− }
191+ property var contents: Item {
192+ anchors.fill: parent
193+
194+ TextField {
195+ id: searchTextField
196+ objectName: "searchTextField"
197+ inputMethodHints: Qt.ImhNoPredictiveText
198+ hasClearButton: false
199+ anchors {
200+ top: parent.top
201+ topMargin: units.gu(1)
202+ left: parent.left
203+ bottom: parent.bottom
204+ bottomMargin: units.gu(1)
205+ right: settingsButton.left
206+ rightMargin: settingsButton.visible ? 0 : units.gu(2)
207+ }
206208
207− secondaryItem: AbstractButton {
208− height: searchTextField.height
209− width: height
210− enabled: searchTextField.text.length > 0
209+ primaryItem: Rectangle {
210+ color: "#F5F4F5"
211+ width: root.navigationTag != "" ? tagLabel.width + units.gu(2) : 0
212+ height: root.navigationTag != "" ? tagLabel.height + units.gu(1) : 0
213+ radius: units.gu(0.5)
214+ Label {
215+ id: tagLabel
216+ text: root.navigationTag
217+ anchors.centerIn: parent
218+ color: "#333333"
219+ }
220+ }
211221
212− Image {
213− objectName: "clearIcon"
214− anchors.fill: parent
215− anchors.margins: units.gu(.75)
216− source: "image://theme/clear"
217− opacity: searchTextField.text.length > 0
218− visible: opacity > 0
219− Behavior on opacity {
220− UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
222+ secondaryItem: AbstractButton {
223+ id: clearButton
224+ height: searchTextField.height
225+ width: height
226+ enabled: searchTextField.text.length > 0 || root.navigationTag != ""
227+
228+ Image {
229+ objectName: "clearIcon"
230+ anchors.fill: parent
231+ anchors.margins: units.gu(1)
232+ source: "image://theme/clear"
233+ opacity: parent.enabled
234+ visible: opacity > 0
235+ Behavior on opacity {
236+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
237+ }
238+ }
239+
240+ onClicked: {
241+ root.clearSearch(true);
221242 }
222243 }
223244
224− onClicked: {
225− root.resetSearch(true);
226− root.openSearchHistory();
245+ onActiveFocusChanged: {
246+ if (activeFocus) {
247+ root.searchTextFieldFocused();
248+ root.openPopup();
249+ }
250+ }
251+
252+ onTextChanged: {
253+ if (text != "") {
254+ closePopup(/* keepFocus */true);
255+ }
227256 }
228257 }
229258
230− onActiveFocusChanged: {
231− if (activeFocus) {
232− root.searchTextFieldFocused();
233− root.openSearchHistory();
259+ AbstractButton {
260+ id: settingsButton
261+
262+ width: root.scopeHasFilters ? height : 0
263+ visible: width > 0
264+ anchors {
265+ top: parent.top
266+ right: cancelButton.left
267+ bottom: parent.bottom
268+ rightMargin: units.gu(-1)
269+ }
270+
271+ Icon {
272+ anchors.fill: parent
273+ anchors.margins: units.gu(2)
274+ name: "filters"
275+ color: root.activeFiltersCount > 0 ? UbuntuColors.orange : Qt.rgba(0.0, 0.0, 0.0, 0.0)
276+ }
277+
278+ onClicked: {
279+ root.showFiltersPopup(settingsButton);
234280 }
235281 }
236282
237− onTextChanged: {
238− if (text != "") {
239− closePopup(/* keepFocus */true);
283+ AbstractButton {
284+ id: cancelButton
285+ width: cancelLabel.width + cancelLabel.anchors.rightMargin + cancelLabel.anchors.leftMargin
286+ anchors {
287+ top: parent.top
288+ right: parent.right
289+ bottom: parent.bottom
290+ }
291+ onClicked: {
292+ root.clearSearch(false);
293+ headerContainer.showSearch = false;
294+ }
295+ Label {
296+ id: cancelLabel
297+ text: i18n.tr("Cancel")
298+ color: header.panelForegroundColor
299+ verticalAlignment: Text.AlignVCenter
300+ anchors {
301+ verticalCenter: parent.verticalCenter
302+ right: parent.right
303+ rightMargin: units.gu(2)
304+ leftMargin: units.gu(1)
305+ }
240306 }
241307 }
242308 }
@@ -322,47 +388,6 @@ Item {
322388 }
323389 }
324390
325− Component {
326− id: popoverComponent
327− Popover {
328− id: popover
329− autoClose: false
330−
331− property bool unfocusOnDestruction: false
332−
333− Component.onDestruction: {
334− headerContainer.popover = null;
335− if (unfocusOnDestruction) {
336− root.unfocus();
337− }
338− }
339−
340− Column {
341− anchors {
342− top: parent.top
343− left: parent.left
344− right: parent.right
345− }
346−
347− Repeater {
348− id: recentSearches
349− objectName: "recentSearches"
350− model: searchHistory
351−
352− delegate: Standard {
353− showDivider: index < recentSearches.count - 1
354− text: query
355− onClicked: {
356− searchHistory.addQuery(text);
357− searchTextField.text = text;
358− closePopup(/* keepFocus */false);
359− }
360− }
361− }
362− }
363− }
364− }
365−
366391 Rectangle {
367392 id: bottomBorder
368393 visible: showSignatureLine
@@ -370,7 +395,7 @@ Item {
370395 top: headerContainer.bottom
371396 left: parent.left
372397 right: parent.right
373− bottom: bottomContainer.top
398+ bottom: parent.bottom
374399 }
375400
376401 color: root.scopeStyle ? root.scopeStyle.headerDividerColor : "#e0e0e0"
@@ -411,7 +436,7 @@ Item {
411436 id: bottomHighlight
412437 visible: bottomBorder.visible
413438 anchors {
414− top: bottomContainer.top
439+ top: parent.bottom
415440 left: parent.left
416441 right: parent.right
417442 }
@@ -419,30 +444,13 @@ Item {
419444 height: units.dp(1)
420445 opacity: 0.6
421446
422− // FIXME this should be a shader when bottomItem exists
423− // to support image backgrounds
424447 Rectangle {
425448 anchors.fill: parent
426− color: if (bottomItem && bottomItem.background) {
427− Qt.lighter(Qt.rgba(bottomItem.background.topColor.r,
428− bottomItem.background.topColor.g,
429− bottomItem.background.topColor.b, 1.0), 1.2);
430− } else if (!bottomItem && root.scopeStyle) {
449+ color: if (root.scopeStyle) {
431450 Qt.lighter(Qt.rgba(root.scopeStyle.background.r,
432451 root.scopeStyle.background.g,
433452 root.scopeStyle.background.b, 1.0), 1.2);
434453 } else "#CCFFFFFF"
435454 }
436455 }
437−
438− Item {
439− id: bottomContainer
440−
441− anchors {
442− left: parent.left
443− right: parent.right
444− bottom: parent.bottom
445− }
446− height: childrenRect.height
447− }
448456}
qml/Dash/Filters/FilterOptionSelector.qmlnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 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.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3 as ListItems
20+
21+/*! Option Selector Filter Widget. */
22+
23+FilterWidget {
24+ id: root
25+
26+ implicitHeight: expandingItem.height
27+
28+ ListItems.Expandable {
29+ id: expandingItem
30+ objectName: "expandingItem"
31+
32+ expandedHeight: collapsedHeight + column.height
33+ width: parent.width
34+ showDivider: false
35+
36+ onClicked: {
37+ expanded = !expanded;
38+ forceActiveFocus();
39+ }
40+
41+ Item {
42+ id: holder
43+ anchors.top: parent.top
44+ height: expandingItem.collapsedHeight
45+ width: parent.width
46+
47+ Label {
48+ anchors.left: parent.left
49+ anchors.right: dropDown.left
50+ anchors.verticalCenter: parent.verticalCenter
51+ text: widgetData.label || ""
52+ }
53+
54+ Image {
55+ id: dropDown
56+ height: units.gu(3)
57+ fillMode: Image.PreserveAspectFit
58+ anchors.right: parent.right
59+ anchors.verticalCenter: parent.verticalCenter
60+ source: expandingItem.expanded ? "image://theme/up" : "image://theme/down"
61+ }
62+ }
63+
64+ Column {
65+ id: column
66+ anchors.top: holder.bottom
67+ width: parent.width
68+ Repeater {
69+ model: widgetData.options
70+
71+ ListItems.Standard {
72+ text: label
73+ objectName: root.objectName + "label" + index;
74+
75+ Image {
76+ height: units.gu(3)
77+ fillMode: Image.PreserveAspectFit
78+ anchors.right: parent.right
79+ anchors.verticalCenter: parent.verticalCenter
80+ source: "image://theme/tick"
81+ visible: checked
82+ }
83+
84+ onClicked: {
85+ widgetData.options.setChecked(index, !checked);
86+ }
87+ }
88+ }
89+ }
90+ }
91+}
qml/Dash/Filters/FilterRangeInput.qmlnew file+136
@@ -0,0 +1,136 @@
1+/*
2+ * Copyright (C) 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.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+
21+/*! Range Input Filter Widget. */
22+
23+FilterWidget {
24+ id: root
25+
26+ implicitHeight: field1.height + units.gu(2)
27+
28+ function setFieldValue(field, hasValue, value) {
29+ if (hasValue) {
30+ // Need this othewise if we are on 4.5 and backspace instead of
31+ // having 4. in the text field we end up with 4 which is confusing
32+ if (field.text != value) {
33+ field.text = value;
34+ }
35+ } else {
36+ field.text = "";
37+ }
38+ }
39+
40+ Connections {
41+ target: widgetData
42+ onStartValueChanged: root.setFieldValue(field1, widgetData.hasStartValue, widgetData.startValue);
43+ onHasStartValueChanged: root.setFieldValue(field1, widgetData.hasStartValue, widgetData.startValue);
44+ onEndValueChanged: root.setFieldValue(field2, widgetData.hasEndValue, widgetData.endValue);
45+ onHasEndValueChanged: root.setFieldValue(field2, widgetData.hasEndValue, widgetData.endValue);
46+ }
47+
48+ onWidgetDataChanged: {
49+ if (widgetData) {
50+ root.setFieldValue(field1, widgetData.hasStartValue, widgetData.startValue);
51+ root.setFieldValue(field2, widgetData.hasEndValue, widgetData.endValue);
52+ } else {
53+ root.setFieldValue(field1, false, -1);
54+ root.setFieldValue(field2, false, -1);
55+ }
56+ }
57+
58+ RowLayout {
59+ anchors {
60+ fill: parent
61+ topMargin: units.gu(1)
62+ bottomMargin: units.gu(1)
63+ }
64+
65+ Item {
66+ Layout.fillWidth: true
67+ }
68+
69+ Label {
70+ text: widgetData.startPrefixLabel
71+ verticalAlignment: Text.AlignVCenter
72+ }
73+
74+ TextField {
75+ id: field1
76+ objectName: "startValueField"
77+ implicitWidth: units.gu(9)
78+ verticalAlignment: Text.AlignVCenter
79+ inputMethodHints: Qt.ImhFormattedNumbersOnly
80+ validator: DoubleValidator {
81+ notation: DoubleValidator.StandardNotation
82+ }
83+ onTextChanged: {
84+ if (text === "") widgetData.eraseStartValue();
85+ else widgetData.startValue = text;
86+ }
87+ }
88+
89+ Label {
90+ text: widgetData.startPostfixLabel
91+ verticalAlignment: Text.AlignVCenter
92+ }
93+
94+ Item {
95+ Layout.fillWidth: true
96+ }
97+
98+ Label {
99+ text: widgetData.centralLabel
100+ verticalAlignment: Text.AlignVCenter
101+ }
102+
103+ Item {
104+ Layout.fillWidth: true
105+ }
106+
107+ Label {
108+ text: widgetData.endPrefixLabel
109+ verticalAlignment: Text.AlignVCenter
110+ }
111+
112+ TextField {
113+ id: field2
114+ objectName: "endValueField"
115+ implicitWidth: units.gu(9)
116+ verticalAlignment: Text.AlignVCenter
117+ inputMethodHints: Qt.ImhFormattedNumbersOnly
118+ validator: DoubleValidator {
119+ notation: DoubleValidator.StandardNotation
120+ }
121+ onTextChanged: {
122+ if (text === "") widgetData.eraseEndValue();
123+ else widgetData.endValue = text;
124+ }
125+ }
126+
127+ Label {
128+ text: widgetData.endPostfixLabel
129+ verticalAlignment: Text.AlignVCenter
130+ }
131+
132+ Item {
133+ Layout.fillWidth: true
134+ }
135+ }
136+}
qml/Dash/Filters/FilterValueSlider.qmlnew file+87
@@ -0,0 +1,87 @@
1+/*
2+ * Copyright (C) 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.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+
21+/*! Value Slider Filter Widget. */
22+
23+FilterWidget {
24+ id: root
25+
26+ implicitHeight: childrenRect.height + units.gu(2)
27+
28+ Connections {
29+ target: widgetData
30+ // One would think that this is not needed since
31+ // we have value: widgetData.value on the slider
32+ // but it is, otherwise reset doesn't seem to work
33+ onValueChanged: {
34+ if (widgetData.value !== slider.value) {
35+ slider.value = widgetData.value;
36+ }
37+ }
38+ }
39+
40+ Slider {
41+ id: slider
42+ objectName: "slider"
43+
44+ anchors {
45+ top: parent.top
46+ topMargin: units.gu(1)
47+ left: parent.left
48+ leftMargin: units.gu(2)
49+ right: parent.right
50+ rightMargin: units.gu(2)
51+ }
52+
53+ minimumValue: widgetData.minValue
54+ maximumValue: widgetData.maxValue
55+ value: widgetData.value
56+ onValueChanged: {
57+ widgetData.value = value;
58+ }
59+ onPressedChanged: {
60+ if (pressed) forceActiveFocus();
61+ }
62+
63+ readonly property Item thumb: __internals.thumb
64+ readonly property real barMinusThumb: __internals.barMinusThumb
65+ }
66+
67+ Repeater {
68+ objectName: "repeater"
69+ model: widgetData.values
70+ delegate: Label {
71+ anchors {
72+ top: slider.bottom
73+ // The slider is too tall, so move a bit up
74+ topMargin: -units.gu(1)
75+ }
76+ text: label
77+ visible: value <= widgetData.maxValue && value >= widgetData.minValue
78+ x: {
79+ var halfThumbDifference = (width - slider.thumb.width) / 2;
80+ var result = (value - widgetData.minValue) * slider.barMinusThumb / (widgetData.maxValue - widgetData.minValue) - halfThumbDifference;
81+ result = Math.max(result, 0);
82+ result = Math.min(result, slider.width - width);
83+ return units.gu(2) + result;
84+ }
85+ }
86+ }
87+}
qml/Dash/Filters/FilterWidget.qmlnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 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.4
18+
19+/*! Interface for filter widgets. */
20+
21+Item {
22+ //! The widget identifier
23+ property string widgetId
24+
25+ //! Variable used to contain widget's data
26+ property var widgetData: null
27+
28+ objectName: widgetId
29+}
qml/Dash/Filters/FilterWidgetFactory.qmlnew file+79
@@ -0,0 +1,79 @@
1+/*
2+ * Copyright (C) 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.4
18+import Ubuntu.Components 1.3
19+import Unity 0.2
20+
21+//! \brief This component loads the widgets based on widgetType.
22+
23+Item {
24+ id: root
25+ //! Identifier of the widget.
26+ property string widgetId: ""
27+
28+ //! Type of the widget to display.
29+ property int widgetType
30+
31+ //! Widget data, forwarded to the widget as is.
32+ property var widgetData: null
33+
34+ implicitHeight: title.height + title.anchors.topMargin + loader.height
35+
36+ Label {
37+ id: title
38+ text: widgetData ? widgetData.title : ""
39+ height: text != "" ? implicitHeight : 0
40+
41+ anchors {
42+ top: parent.top
43+ left: parent.left
44+ right: parent.right
45+
46+ topMargin: height > 0 ? units.gu(1) : 0
47+ leftMargin: units.gu(2)
48+ rightMargin: anchors.leftMargin
49+ }
50+ }
51+
52+ property alias active: loader.active
53+
54+ Loader {
55+ id: loader
56+
57+ anchors {
58+ top: title.bottom
59+ left: parent.left
60+ right: parent.right
61+ }
62+
63+ source: widgetSource
64+
65+ readonly property url widgetSource: {
66+ switch (widgetType) {
67+ case Filters.OptionSelectorFilter: return "FilterOptionSelector.qml";
68+ case Filters.RangeInputFilter: return "FilterRangeInput.qml";
69+ case Filters.ValueSliderFilter: return "FilterValueSlider.qml";
70+ default: return "";
71+ }
72+ }
73+
74+ onLoaded: {
75+ item.widgetId = Qt.binding(function() { return root.widgetId } )
76+ item.widgetData = Qt.binding(function() { return root.widgetData } )
77+ }
78+ }
79+}
qml/Dash/FiltersPopover.qmlnew file+105
@@ -0,0 +1,105 @@
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.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.Popups 1.3
20+import Ubuntu.Components.ListItems 1.3 as ListItems
21+import "Filters" as Filters
22+
23+Popover {
24+ id: root
25+ objectName: "filtersPopover"
26+
27+ Flickable {
28+ id: flickable
29+ anchors {
30+ top: parent.top
31+ left: parent.left
32+ right: parent.right
33+ }
34+ height: {
35+ // Popover doesn't like being 75% or bigger than the screen (counting the "empty" part on top)
36+ var posToRootParent = flickable.mapToItem(null, 0, 0).y;
37+ var threeQuartersParent = root.parent.height * 3 / 4 - posToRootParent - 1;
38+ var parentAndKeyboard = root.parent.height - posToRootParent - (Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height + units.gu(3) : 0)
39+ return Math.min(parentAndKeyboard, Math.min(threeQuartersParent, column.height));
40+ }
41+ clip: true
42+ contentHeight: column.height
43+ contentWidth: width
44+
45+ Column {
46+ id: column
47+ width: parent.width
48+
49+ Item {
50+ width: parent.width
51+ height: resetLabel.height + units.gu(3)
52+
53+ Label {
54+ anchors {
55+ left: parent.left
56+ right: resetLabel.left
57+ margins: units.gu(2)
58+ verticalCenter: parent.verticalCenter
59+ }
60+ text: i18n.tr("Refine your results")
61+ }
62+ Label {
63+ id: resetLabel
64+ anchors {
65+ right: parent.right
66+ rightMargin: units.gu(2)
67+ verticalCenter: parent.verticalCenter
68+ }
69+ text: i18n.tr("Reset")
70+
71+ AbstractButton {
72+ anchors {
73+ fill: parent
74+ rightMargin: units.gu(-2)
75+ leftMargin: units.gu(-2)
76+ topMargin: units.gu(-1)
77+ bottomMargin: units.gu(-1)
78+ }
79+ onClicked: {
80+ scopeView.scope.resetFilters();
81+ }
82+ }
83+ }
84+ }
85+
86+ Repeater {
87+ id: repeater
88+ model: scopeView.scope.filters
89+
90+ delegate: Filters.FilterWidgetFactory {
91+ width: parent.width
92+
93+ widgetId: id
94+ widgetType: type
95+ widgetData: filter
96+
97+ ListItems.ThinDivider {
98+ anchors.bottom: parent.bottom
99+ visible: index != repeater.count - 1
100+ }
101+ }
102+ }
103+ }
104+ }
105+}
qml/Dash/GenericScopeView.qml+77−47
@@ -16,6 +16,8 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import Ubuntu.Components.Popups 1.3
20+import "../Components/SearchHistoryModel"
1921import Utils 0.1
2022import Unity 0.2
2123import Dash 0.1
@@ -25,7 +27,6 @@ import "../Components/ListItems" as ListItems
2527FocusScope {
2628 id: scopeView
2729
28− readonly property bool navigationDisableParentInteractive: pageHeaderLoader.item ? pageHeaderLoader.item.bottomItem[0].disableParentInteractive : false
2930 property bool forceNonInteractive: false
3031 property var scope: null
3132 property UnitySortFilterProxyModel categories: categoryFilter
@@ -34,14 +35,15 @@ FocusScope {
3435 property bool hasBackAction: false
3536 property bool enableHeightBehaviorOnNextCreation: false
3637 property var categoryView: categoryView
37− property bool showPageHeader: true
3838 readonly property alias subPageShown: subPageLoader.subPageShown
39+ readonly property alias extraPanelShown: peExtraPanel.visible
3940 property int paginationCount: 0
4041 property int paginationIndex: 0
4142 property bool visibleToParent: false
4243 property alias pageHeaderTotallyVisible: categoryView.pageHeaderTotallyVisible
4344 property var holdingList: null
4445 property bool wasCurrentOnMoveStart: false
46+ property var filtersPopover: null
4547
4648 property var scopeStyle: ScopeStyle {
4749 style: scope ? scope.customizations : {}
@@ -68,8 +70,7 @@ FocusScope {
6870 }
6971
7072 function resetSearch() {
71− if(pageHeaderLoader.item && showPageHeader)
72− pageHeaderLoader.item.resetSearch()
73+ categoryView.pageHeader.resetSearch()
7374 }
7475
7576 property var maybePreviewResult;
@@ -120,24 +121,26 @@ FocusScope {
120121 if (!holdingList || !holdingList.moving) {
121122 wasCurrentOnMoveStart = scopeView.isCurrent;
122123 }
123− if (pageHeaderLoader.item && showPageHeader) {
124− pageHeaderLoader.item.resetSearch();
125− }
124+ categoryView.pageHeader.resetSearch();
126125 subPageLoader.closeSubPage();
126+ if (filtersPopover) {
127+ PopupUtils.close(filtersPopover)
128+ scopeView.filtersPopover = null;
129+ }
127130 }
128131
129132 Binding {
130133 target: scopeView.scope
131134 property: "searchQuery"
132− value: pageHeaderLoader.item ? pageHeaderLoader.item.searchQuery : ""
133− when: isCurrent && showPageHeader
135+ value: categoryView.pageHeader.searchQuery
136+ when: isCurrent
134137 }
135138
136139 Binding {
137− target: pageHeaderLoader.item
140+ target: categoryView.pageHeader
138141 property: "searchQuery"
139142 value: scopeView.scope ? scopeView.scope.searchQuery : ""
140− when: isCurrent && showPageHeader
143+ when: isCurrent
141144 }
142145
143146 Connections {
@@ -189,8 +192,8 @@ FocusScope {
189192 property string expandedCategoryId: ""
190193 property int runMaximizeAfterSizeChanges: 0
191194
192− readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
193− ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
195+ readonly property bool pageHeaderTotallyVisible:
196+ ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == categoryView.pageHeader.height))
194197
195198 onExpandedCategoryIdChanged: {
196199 var firstCreated = firstCreatedIndex();
@@ -619,41 +622,68 @@ FocusScope {
619622 }
620623 }
621624
622− pageHeader: scopeView.showPageHeader ? pageHeaderLoader : null
623− Loader {
624− id: pageHeaderLoader
625+ pageHeader: DashPageHeader {
626+ objectName: "scopePageHeader"
625627 width: parent.width
626− sourceComponent: scopeView.showPageHeader ? pageHeaderComponent : undefined
627− Component {
628− id: pageHeaderComponent
629− DashPageHeader {
630− objectName: "scopePageHeader"
631− width: parent.width
632− title: scopeView.scope ? scopeView.scope.name : ""
633− searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.ctr("Label: Hint for dash search line edit", "Search")
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− scopeStyle: scopeView.scopeStyle
640− paginationCount: scopeView.paginationCount
641− paginationIndex: scopeView.paginationIndex
642−
643− bottomItem: DashNavigation {
644− scope: scopeView.scope
645− anchors { left: parent.left; right: parent.right }
646− windowHeight: scopeView.height
647− windowWidth: scopeView.width
648− scopeStyle: scopeView.scopeStyle
649− }
650−
651− onBackClicked: scopeView.backClicked()
652− onSettingsClicked: subPageLoader.openSubPage("settings")
653− onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
654− onSearchTextFieldFocused: scopeView.showHeader()
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
643+
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();
655655 }
656656 }
657+ onShowFiltersPopup: { // item
658+ extraPanel.visible = false;
659+ scopeView.filtersPopover = PopupUtils.open(Qt.resolvedUrl("FiltersPopover.qml"), item, { "contentWidth": scopeView.width - units.gu(2) } );
660+ }
661+ }
662+
663+ PageHeaderExtraPanel {
664+ id: peExtraPanel
665+ objectName: "peExtraPanel"
666+ width: parent.width >= units.gu(60) ? units.gu(40) : parent.width
667+ anchors {
668+ top: categoryView.pageHeader.bottom
669+ topMargin: -categoryView.pageHeader.signatureLineHeight
670+ }
671+ z: 1
672+ visible: false
673+
674+ searchHistory: SearchHistoryModel
675+ scope: scopeView.scope
676+ windowHeight: scopeView.height
677+
678+ onHistoryItemClicked: {
679+ SearchHistoryModel.addQuery(text);
680+ categoryView.pageHeader.searchQuery = text;
681+ }
682+
683+ onDashNavigationLeafClicked: {
684+ categoryView.pageHeader.closePopup();
685+ categoryView.pageHeader.unfocus();
686+ }
657687 }
658688 }
659689
@@ -662,7 +692,7 @@ FocusScope {
662692 anchors.left: parent.left
663693 anchors.right: parent.right
664694 anchors.bottom: parent.bottom
665− height: parent.height - pullToRefresh.contentY + (pageHeaderLoader.item ? pageHeaderLoader.item.bottomItem[0].height - pageHeaderLoader.item.height : 0)
695+ height: parent.height - pullToRefresh.contentY - categoryView.pageHeader.height
666696 clip: true
667697
668698 PullToRefresh {
@@ -797,7 +827,7 @@ FocusScope {
797827 open = true;
798828 }
799829
800− onOpenChanged: pageHeaderLoader.item.unfocus()
830+ onOpenChanged: categoryView.pageHeader.unfocus()
801831
802832 onVisibleChanged: if (!visible) subPage = ""
803833
qml/Dash/PageHeaderExtraPanel.qmlnew file+175
@@ -0,0 +1,175 @@
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.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3 as ListItems
20+import "Filters" as Filters
21+
22+Item {
23+ id: root
24+
25+ readonly property real searchesHeight: recentSearchesRepeater.count > 0 ? searchColumn.height + recentSearchesLabels.height + recentSearchesLabels.anchors.topMargin : 0
26+
27+ implicitHeight: searchesHeight + dashNavigation.implicitHeight + dashNavigation.anchors.topMargin + primaryFilter.height + primaryFilter.anchors.topMargin
28+
29+ // Set by parent
30+ property ListModel searchHistory
31+ property var scope: null
32+ property real windowHeight
33+
34+ // Used by PageHeader
35+ readonly property bool hasContents: searchHistory.count > 0 || scope && scope.hasNavigation || scope && scope.primaryNavigationFilter
36+
37+ signal historyItemClicked(string text)
38+ signal dashNavigationLeafClicked()
39+
40+ function resetNavigation() {
41+ dashNavigation.resetNavigation();
42+ }
43+
44+ Rectangle {
45+ color: "white"
46+ anchors.fill: parent
47+ }
48+
49+ ListItems.ThinDivider {
50+ anchors.top: parent.top
51+ }
52+
53+ Label {
54+ id: recentSearchesLabels
55+ text: i18n.tr("Recent Searches")
56+ visible: recentSearchesRepeater.count > 0
57+ anchors {
58+ top: parent.top
59+ left: parent.left
60+ margins: units.gu(2)
61+ topMargin: units.gu(3)
62+ }
63+ }
64+
65+ Label {
66+ text: i18n.tr("Clear All")
67+ fontSize: "small"
68+ visible: recentSearchesRepeater.count > 0
69+ anchors {
70+ top: parent.top
71+ right: parent.right
72+ margins: units.gu(2)
73+ topMargin: units.gu(3)
74+ }
75+
76+ AbstractButton {
77+ anchors.fill: parent
78+ onClicked: searchHistory.clear();
79+ }
80+ }
81+
82+ Column {
83+ id: searchColumn
84+ anchors {
85+ top: recentSearchesLabels.bottom
86+ left: parent.left
87+ right: parent.right
88+ }
89+
90+ Repeater {
91+ id: recentSearchesRepeater
92+ objectName: "recentSearchesRepeater"
93+ model: searchHistory
94+
95+ delegate: ListItem {
96+ anchors {
97+ left: parent.left
98+ right: parent.right
99+ leftMargin: units.gu(2)
100+ rightMargin: units.gu(2)
101+ }
102+ height: units.gu(5)
103+
104+ Icon {
105+ id: searchIcon
106+ anchors {
107+ verticalCenter: parent.verticalCenter
108+ left: parent.left
109+ }
110+ height: units.gu(1.5)
111+ width: height
112+ name: "search"
113+ }
114+
115+ Label {
116+ anchors {
117+ verticalCenter: parent.verticalCenter
118+ left: searchIcon.right
119+ leftMargin: units.gu(1)
120+ right: parent.right
121+ }
122+ text: query
123+ color: "#888888"
124+ }
125+
126+ divider.visible: index != repeater.count - 1 || (scope && scope.hasNavigation) || primaryFilter.active
127+
128+ onClicked: root.historyItemClicked(query)
129+ }
130+ }
131+ }
132+
133+ DashNavigation {
134+ id: dashNavigation
135+ scope: root.scope
136+ anchors {
137+ top: recentSearchesRepeater.count > 0 ? searchColumn.bottom : parent.top
138+ topMargin: implicitHeight && recentSearchesRepeater.count > 0 ? units.gu(2) : 0
139+ left: parent.left
140+ right: parent.right
141+ }
142+ availableHeight: windowHeight * 4 / 6 - searchesHeight
143+
144+ onLeafClicked: root.dashNavigationLeafClicked();
145+ }
146+
147+ Filters.FilterWidgetFactory {
148+ id: primaryFilter
149+ active: scope && !scope.hasNavigation
150+
151+ property var filter: active ? scope.primaryNavigationFilter : null
152+
153+ anchors {
154+ top: recentSearchesRepeater.count > 0 ? searchColumn.bottom : parent.top
155+ topMargin: active && recentSearchesRepeater.count > 0 ? units.gu(2) : 0
156+ left: parent.left
157+ right: parent.right
158+ }
159+
160+ widgetId: filter ? filter.filterId : ""
161+ widgetType: filter ? filter.filterType : -1
162+ widgetData: filter
163+ }
164+
165+ // This is outside the item
166+ Image {
167+ anchors {
168+ top: parent.bottom
169+ left: parent.left
170+ right: parent.right
171+ }
172+ fillMode: Image.Stretch
173+ source: "graphics/navigation_shadow.png"
174+ }
175+}
tests/mocks/Unity/CMakeLists.txt+13
@@ -29,15 +29,28 @@ set(UnityQML_SOURCES
2929 fake_resultsmodel.cpp
3030 fake_previewmodel.cpp
3131 fake_previewwidgetmodel.cpp
32+ fake_filters.cpp
33+ fake_optionselectorfilter.cpp
34+ fake_optionselectoroptions.cpp
35+ fake_rangeinputfilter.cpp
36+ fake_valuesliderfilter.cpp
37+ fake_valueslidervalues.cpp
3238 fake_unity_plugin.cpp
3339 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/CategoriesInterface.h
40+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/FilterBaseInterface.h
41+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/FiltersInterface.h
3442 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/NavigationInterface.h
43+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/OptionSelectorFilterInterface.h
44+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/OptionSelectorOptionsInterface.h
3545 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
3646 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewWidgetModelInterface.h
47+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/RangeInputFilterInterface.h
3748 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ResultsModelInterface.h
3849 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopeInterface.h
3950 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopesInterface.h
4051 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/SettingsModelInterface.h
52+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ValueSliderFilterInterface.h
53+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ValueSliderValuesInterface.h
4154)
4255
4356# Workaround for gcc failure LP: #1417664
tests/mocks/Unity/fake_filters.cppnew file+125
@@ -0,0 +1,125 @@
1+/*
2+ * Copyright (C) 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+#include "fake_filters.h"
18+
19+#include "fake_optionselectorfilter.h"
20+#include "fake_rangeinputfilter.h"
21+#include "fake_valuesliderfilter.h"
22+#include "fake_scope.h"
23+
24+Filters::Filters(Scope* parent)
25+ : unity::shell::scopes::FiltersInterface(parent)
26+{
27+ addFilter(new FakeOptionSelectorFilter("OSF1", "Tag1", "Which Cake you like More", false, QStringList() << "cheese" << "carrot" << "chocolate", this));
28+
29+ FakeRangeInputFilter *rif = new FakeRangeInputFilter("RIF1", "Tag3", this);
30+ rif->setTitle("How much do you want to walk?");
31+ rif->setCentralLabel("to");
32+ rif->setStartPostfixLabel("m");
33+ rif->setEndPostfixLabel("m");
34+ addFilter(rif);
35+
36+ QMap<double, QString> labels;
37+ labels[15] = "Default";
38+ labels[50] = "50";
39+ labels[100] = "100";
40+ FakeValueSliderFilter *vsf = new FakeValueSliderFilter("VS1", "Tag4", 15, 10, 150, labels, this);
41+ addFilter(vsf);
42+
43+ addFilter(new FakeOptionSelectorFilter("OSF2", "Tag2", "Which Countries have you been to?", true, QStringList() << "Germany" << "UK" << "New Zealand", this));
44+}
45+
46+void Filters::addFilter(unity::shell::scopes::FilterBaseInterface *f)
47+{
48+ switch (f->filterType()) {
49+ case FiltersInterface::OptionSelectorFilter: {
50+ FakeOptionSelectorFilter *osf = static_cast<FakeOptionSelectorFilter *>(f);
51+ connect(osf, &FakeOptionSelectorFilter::isActiveChanged, this, &Filters::activeFiltersCountChanged);
52+ }
53+ break;
54+
55+ case FiltersInterface::RangeInputFilter: {
56+ FakeRangeInputFilter *rif = static_cast<FakeRangeInputFilter *>(f);
57+ connect(rif, &FakeRangeInputFilter::isActiveChanged, this, &Filters::activeFiltersCountChanged);
58+ }
59+ break;
60+
61+ case FiltersInterface::ValueSliderFilter: {
62+ // Not counting value slider as active in the mock
63+ }
64+
65+ case Invalid:
66+ break;
67+ }
68+ m_filters << f;
69+}
70+
71+int Filters::rowCount(const QModelIndex &parent) const
72+{
73+ if (parent.isValid())
74+ return 0;
75+
76+ return m_filters.count();
77+}
78+
79+QVariant Filters::data(const QModelIndex &index, int role) const
80+{
81+ const int row = index.row();
82+ if (row < 0 || row >= m_filters.count())
83+ return QVariant();
84+
85+ unity::shell::scopes::FilterBaseInterface *filter = m_filters[row];
86+
87+ switch (role) {
88+ case RoleFilterId:
89+ return filter->filterId();
90+ case RoleFilterType:
91+ return filter->filterType();
92+ case RoleFilter:
93+ return QVariant::fromValue<unity::shell::scopes::FilterBaseInterface *>(filter);
94+ default:
95+ return QVariant();
96+ }
97+}
98+
99+int Filters::activeFiltersCount() const
100+{
101+ int active = 0;
102+ Q_FOREACH(unity::shell::scopes::FilterBaseInterface *f, m_filters) {
103+ switch (f->filterType()) {
104+ case FiltersInterface::OptionSelectorFilter: {
105+ FakeOptionSelectorFilter *osf = static_cast<FakeOptionSelectorFilter *>(f);
106+ if (osf->isActive()) ++active;
107+ }
108+ break;
109+
110+ case FiltersInterface::RangeInputFilter: {
111+ FakeRangeInputFilter *rif = static_cast<FakeRangeInputFilter *>(f);
112+ if (rif->isActive()) ++active;
113+ }
114+ break;
115+
116+ case FiltersInterface::ValueSliderFilter: {
117+ // Not counting value slider as active in the mock
118+ }
119+
120+ case Invalid:
121+ break;
122+ }
123+ }
124+ return active;
125+}
tests/mocks/Unity/fake_filters.hnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 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+#ifndef FAKE_FILTERS_H
18+#define FAKE_FILTERS_H
19+
20+#include <unity/shell/scopes/FiltersInterface.h>
21+#include <unity/shell/scopes/FilterBaseInterface.h>
22+
23+class Scope;
24+
25+class Filters : public unity::shell::scopes::FiltersInterface
26+{
27+ Q_OBJECT
28+
29+public:
30+ Filters(Scope* parent);
31+
32+ int rowCount(const QModelIndex &parent) const override;
33+ QVariant data(const QModelIndex &index, int role) const override;
34+
35+ int activeFiltersCount() const;
36+
37+Q_SIGNALS:
38+ void activeFiltersCountChanged();
39+
40+private:
41+ void addFilter(unity::shell::scopes::FilterBaseInterface* f);
42+
43+ QVector<unity::shell::scopes::FilterBaseInterface*> m_filters;
44+};
45+
46+#endif
tests/mocks/Unity/fake_navigation.cpp+9−10
@@ -32,7 +32,6 @@ Navigation::Navigation(const QString& navigationId, const QString& label, const
3232{
3333 QTimer::singleShot(1500, this, &Navigation::slotLoaded);
3434 connect(scope, &Scope::currentNavigationIdChanged, this, &Navigation::slotCurrentNavigationChanged);
35− connect(scope, &Scope::currentAltNavigationIdChanged, this, &Navigation::slotCurrentNavigationChanged);
3635}
3736
3837QString Navigation::navigationId() const
@@ -78,17 +77,17 @@ int Navigation::count() const
7877
7978bool Navigation::isRoot() const
8079{
81− return m_navigationId == "root" || m_navigationId == "altroot";
80+ return m_navigationId == "root";
8281}
8382
8483bool Navigation::hidden() const
8584{
86− return m_navigationId == "altroot";
85+ return false;
8786}
8887
8988int Navigation::rowCount(const QModelIndex & /*parent*/) const
9089{
91− if (!m_loaded || m_navigationId.startsWith("child") || m_navigationId.startsWith("altrootChild") || m_navigationId == "middle3")
90+ if (!m_loaded ||(m_navigationId.startsWith("child") && !m_navigationId.startsWith("childmiddle4")) || m_navigationId == "middle3")
9291 return 0;
9392 else
9493 return 8;
@@ -102,16 +101,16 @@ QVariant Navigation::data(const QModelIndex &index, int role) const
102101 return QString("middle%1").arg(index.row());
103102 else if (m_navigationId.startsWith("middle"))
104103 return QString("child%1%2").arg(m_navigationId).arg(index.row());
104+ else if (m_navigationId.startsWith("childmiddle"))
105+ return QString("grandchild%1%2").arg(m_navigationId).arg(index.row());
105106 case RoleLabel:
106107 return QString("%1Child%2").arg(m_navigationId).arg(index.row());
108+ case RoleAllLabel:
109+ return QString("all%1Child%2").arg(m_navigationId).arg(index.row());
107110 case RoleHasChildren:
108− return m_navigationId == "root" && index.row() != 3;
111+ return (m_navigationId == "root" && index.row() != 3) || (m_navigationId == "middle4");
109112 case RoleIsActive:
110− if (m_navigationId.startsWith("alt")) {
111− return m_scope->currentAltNavigationId() == data(index, RoleNavigationId);
112− } else {
113− return m_scope->currentNavigationId() == data(index, RoleNavigationId);
114− }
113+ return m_scope->currentNavigationId() == data(index, RoleNavigationId);
115114 }
116115 return QVariant();
117116}
tests/mocks/Unity/fake_optionselectorfilter.cppnew file+65
@@ -0,0 +1,65 @@
1+/*
2+ * Copyright (C) 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+#include "fake_optionselectorfilter.h"
18+
19+#include "fake_optionselectoroptions.h"
20+
21+FakeOptionSelectorFilter::FakeOptionSelectorFilter(const QString &id, const QString &tag, const QString &label, bool multiselect, const QStringList &optionLabels, QObject* parent)
22+ : unity::shell::scopes::OptionSelectorFilterInterface(parent),
23+ m_filterId(id),
24+ m_filterTag(tag),
25+ m_label(label),
26+ m_multiSelect(multiselect)
27+{
28+ m_options = new FakeOptionSelectorOptions(optionLabels, this);
29+ connect(m_options, &FakeOptionSelectorOptions::anyCheckedChanged, this, &FakeOptionSelectorFilter::isActiveChanged);
30+}
31+
32+QString FakeOptionSelectorFilter::filterId() const
33+{
34+ return m_filterId;
35+}
36+
37+QString FakeOptionSelectorFilter::filterTag() const
38+{
39+ return m_filterTag;
40+}
41+
42+QString FakeOptionSelectorFilter::title() const
43+{
44+ return m_title;
45+}
46+
47+QString FakeOptionSelectorFilter::label() const
48+{
49+ return m_label;
50+}
51+
52+bool FakeOptionSelectorFilter::multiSelect() const
53+{
54+ return m_multiSelect;
55+}
56+
57+unity::shell::scopes::OptionSelectorOptionsInterface* FakeOptionSelectorFilter::options() const
58+{
59+ return m_options;
60+}
61+
62+bool FakeOptionSelectorFilter::isActive() const
63+{
64+ return m_options->anyChecked();
65+}
tests/mocks/Unity/fake_optionselectorfilter.hnew file+52
@@ -0,0 +1,52 @@
1+/*
2+ * Copyright (C) 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+#ifndef FAKE_OPTIONSELECTORFILTER_H
18+#define FAKE_OPTIONSELECTORFILTER_H
19+
20+#include <unity/shell/scopes/OptionSelectorFilterInterface.h>
21+
22+class FakeOptionSelectorOptions;
23+
24+class FakeOptionSelectorFilter : public unity::shell::scopes::OptionSelectorFilterInterface
25+{
26+ Q_OBJECT
27+
28+public:
29+ FakeOptionSelectorFilter(const QString &id, const QString &tag, const QString &label, bool multiselect, const QStringList &optionLabels, QObject* parent);
30+
31+ QString filterId() const override;
32+ QString filterTag() const override;
33+ QString title() const override;
34+ QString label() const override;
35+ bool multiSelect() const override;
36+ unity::shell::scopes::OptionSelectorOptionsInterface* options() const override;
37+
38+ bool isActive() const;
39+
40+Q_SIGNALS:
41+ void isActiveChanged();
42+
43+private:
44+ QString m_filterId;
45+ QString m_filterTag;
46+ QString m_title;
47+ QString m_label;
48+ bool m_multiSelect;
49+ FakeOptionSelectorOptions* m_options;
50+};
51+
52+#endif
tests/mocks/Unity/fake_optionselectoroptions.cppnew file+77
@@ -0,0 +1,77 @@
1+/*
2+ * Copyright (C) 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+#include "fake_optionselectoroptions.h"
18+
19+#include "fake_optionselectorfilter.h"
20+
21+FakeOptionSelectorOptions::FakeOptionSelectorOptions(const QStringList &optionLabels, FakeOptionSelectorFilter* parent)
22+ : unity::shell::scopes::OptionSelectorOptionsInterface(parent),
23+ m_optionLabels(optionLabels)
24+{
25+}
26+
27+int FakeOptionSelectorOptions::rowCount(const QModelIndex &parent) const
28+{
29+ if (parent.isValid())
30+ return 0;
31+
32+ return m_optionLabels.count();
33+}
34+
35+QVariant FakeOptionSelectorOptions::data(const QModelIndex &index, int role) const
36+{
37+ const int row = index.row();
38+ if (row < 0 || row >= m_optionLabels.count())
39+ return QVariant();
40+
41+ switch (role) {
42+ case RoleOptionId: {
43+ const QString id = static_cast<FakeOptionSelectorFilter*>(parent())->filterId() + QString::number(row);
44+ return id;
45+ }
46+ case RoleOptionLabel:
47+ return m_optionLabels[row];
48+ case RoleOptionChecked:
49+ return m_checkedIndexes.contains(row);
50+ default:
51+ return QVariant();
52+ }
53+
54+}
55+
56+void FakeOptionSelectorOptions::setChecked(int row, bool checked)
57+{
58+ if (checked) {
59+ if (!static_cast<FakeOptionSelectorFilter*>(parent())->multiSelect()) {
60+ if (!m_checkedIndexes.isEmpty()) {
61+ setChecked(*m_checkedIndexes.begin(), false);
62+ }
63+ Q_ASSERT(m_checkedIndexes.isEmpty());
64+ }
65+ m_checkedIndexes << row;
66+ } else {
67+ m_checkedIndexes.remove(row);
68+ }
69+ const QModelIndex idx = index(row, 0);
70+ Q_EMIT dataChanged(idx, idx, QVector<int>() << RoleOptionChecked);
71+ Q_EMIT anyCheckedChanged();
72+}
73+
74+bool FakeOptionSelectorOptions::anyChecked() const
75+{
76+ return !m_checkedIndexes.isEmpty();
77+}
tests/mocks/Unity/fake_optionselectoroptions.hnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 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+#ifndef FAKE_OPTIONSELECTOROPTIONS_H
18+#define FAKE_OPTIONSELECTOROPTIONS_H
19+
20+#include <unity/shell/scopes/OptionSelectorFilterInterface.h>
21+
22+#include <QSet>
23+
24+class FakeOptionSelectorFilter;
25+
26+class FakeOptionSelectorOptions : public unity::shell::scopes::OptionSelectorOptionsInterface
27+{
28+ Q_OBJECT
29+
30+public:
31+ FakeOptionSelectorOptions(const QStringList &optionLabels, FakeOptionSelectorFilter* parent);
32+
33+ int rowCount(const QModelIndex &parent) const override;
34+ QVariant data(const QModelIndex &index, int role) const override;
35+ Q_INVOKABLE void setChecked(int index, bool checked) override;
36+
37+ bool anyChecked() const;
38+
39+Q_SIGNALS:
40+ void anyCheckedChanged();
41+
42+private:
43+ QStringList m_optionLabels;
44+ QSet<int> m_checkedIndexes;
45+};
46+
47+#endif
tests/mocks/Unity/fake_rangeinputfilter.cppnew file+167
@@ -0,0 +1,167 @@
1+/*
2+ * Copyright (C) 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+#include "fake_rangeinputfilter.h"
18+
19+FakeRangeInputFilter::FakeRangeInputFilter(const QString &id, const QString &tag, QObject* parent)
20+ : unity::shell::scopes::RangeInputFilterInterface(parent),
21+ m_filterId(id),
22+ m_filterTag(tag),
23+ m_hasStartValue(false),
24+ m_hasEndValue(false),
25+ m_startValue(-1),
26+ m_endValue(-1)
27+{
28+ connect(this, &FakeRangeInputFilter::hasStartValueChanged, this, &FakeRangeInputFilter::isActiveChanged);
29+ connect(this, &FakeRangeInputFilter::hasEndValueChanged, this, &FakeRangeInputFilter::isActiveChanged);
30+}
31+
32+QString FakeRangeInputFilter::filterId() const
33+{
34+ return m_filterId;
35+}
36+
37+QString FakeRangeInputFilter::filterTag() const
38+{
39+ return m_filterTag;
40+}
41+
42+QString FakeRangeInputFilter::title() const
43+{
44+ return m_title;
45+}
46+
47+double FakeRangeInputFilter::startValue() const
48+{
49+ return m_startValue;
50+}
51+
52+double FakeRangeInputFilter::endValue() const
53+{
54+ return m_endValue;
55+}
56+
57+void FakeRangeInputFilter::setStartValue(double value)
58+{
59+ if (m_startValue != value) {
60+ m_startValue = value;
61+ Q_EMIT startValueChanged();
62+ }
63+ if (!m_hasStartValue) {
64+ m_hasStartValue = true;
65+ Q_EMIT hasStartValueChanged();
66+ }
67+}
68+
69+void FakeRangeInputFilter::setEndValue(double value)
70+{
71+ if (m_endValue != value) {
72+ m_endValue = value;
73+ Q_EMIT endValueChanged();
74+ }
75+ if (!m_hasEndValue) {
76+ m_hasEndValue = true;
77+ Q_EMIT hasEndValueChanged();
78+ }
79+}
80+
81+QString FakeRangeInputFilter::startPrefixLabel() const
82+{
83+ return m_startPrefixLabel;
84+}
85+
86+QString FakeRangeInputFilter::startPostfixLabel() const
87+{
88+ return m_startPostfixLabel;
89+}
90+
91+QString FakeRangeInputFilter::centralLabel() const
92+{
93+ return m_centralLabel;
94+}
95+
96+QString FakeRangeInputFilter::endPrefixLabel() const
97+{
98+ return m_endPrefixLabel;
99+}
100+
101+QString FakeRangeInputFilter::endPostfixLabel() const
102+{
103+ return m_endPostfixLabel;
104+}
105+
106+bool FakeRangeInputFilter::hasStartValue() const
107+{
108+ return m_hasStartValue;
109+}
110+
111+bool FakeRangeInputFilter::hasEndValue() const
112+{
113+ return m_hasEndValue;
114+}
115+
116+void FakeRangeInputFilter::eraseStartValue()
117+{
118+ m_hasStartValue = false;
119+ Q_EMIT hasStartValueChanged();
120+}
121+
122+void FakeRangeInputFilter::eraseEndValue()
123+{
124+ m_hasEndValue = false;
125+ Q_EMIT hasEndValueChanged();
126+}
127+
128+bool FakeRangeInputFilter::isActive() const
129+{
130+ return hasStartValue() && hasEndValue();
131+}
132+
133+void FakeRangeInputFilter::setTitle(const QString &title)
134+{
135+ m_title = title;
136+ Q_EMIT titleChanged();
137+}
138+
139+void FakeRangeInputFilter::setStartPrefixLabel(const QString &startPrefixLabel)
140+{
141+ m_startPrefixLabel = startPrefixLabel;
142+ Q_EMIT startPrefixLabelChanged();
143+}
144+
145+void FakeRangeInputFilter::setStartPostfixLabel(const QString &startPostfixLabel)
146+{
147+ m_startPostfixLabel = startPostfixLabel;
148+ Q_EMIT startPostfixLabelChanged();
149+}
150+
151+void FakeRangeInputFilter::setCentralLabel(const QString &centralLabel)
152+{
153+ m_centralLabel = centralLabel;
154+ Q_EMIT centralLabelChanged();
155+}
156+
157+void FakeRangeInputFilter::setEndPrefixLabel(const QString &endPrefixLabel)
158+{
159+ m_endPrefixLabel = endPrefixLabel;
160+ Q_EMIT endPrefixLabelChanged();
161+}
162+
163+void FakeRangeInputFilter::setEndPostfixLabel(const QString &endPostfixLabel)
164+{
165+ m_endPostfixLabel = endPostfixLabel;
166+ Q_EMIT endPostfixLabelChanged();
167+}
tests/mocks/Unity/fake_rangeinputfilter.hnew file+79
@@ -0,0 +1,79 @@
1+/*
2+ * Copyright (C) 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+#ifndef FAKE_RANGEINPUTFILTER_H
18+#define FAKE_RANGEINPUTFILTER_H
19+
20+#include <unity/shell/scopes/RangeInputFilterInterface.h>
21+
22+class FakeRangeInputFilter : public unity::shell::scopes::RangeInputFilterInterface
23+{
24+ Q_OBJECT
25+
26+public:
27+ FakeRangeInputFilter(const QString &id, const QString &tag, QObject* parent);
28+
29+ QString filterId() const override;
30+ QString filterTag() const override;
31+ QString title() const override;
32+
33+ double startValue() const override;
34+ double endValue() const override;
35+ void setStartValue(double value) override;
36+ void setEndValue(double value) override;
37+ QString startPrefixLabel() const override;
38+ QString startPostfixLabel() const override;
39+ QString centralLabel() const override;
40+ QString endPrefixLabel() const override;
41+ QString endPostfixLabel() const override;
42+ bool hasStartValue() const override;
43+ bool hasEndValue() const override;
44+
45+ void eraseStartValue() override;
46+ void eraseEndValue() override;
47+
48+ // Not part of the iface, for mock/testing purposes
49+ bool isActive() const;
50+
51+ void setTitle(const QString &title);
52+ void setStartPrefixLabel(const QString &startPrefixLabel);
53+ void setStartPostfixLabel(const QString &startPostfixLabel);
54+ void setCentralLabel(const QString &centralLabel);
55+ void setEndPrefixLabel(const QString &endPrefixLabel);
56+ void setEndPostfixLabel(const QString &endPostfixLabel);
57+
58+Q_SIGNALS:
59+ void isActiveChanged();
60+
61+private:
62+ QString m_filterId;
63+ QString m_filterTag;
64+ QString m_title;
65+
66+ bool m_hasStartValue;
67+ bool m_hasEndValue;
68+
69+ double m_startValue;
70+ double m_endValue;
71+
72+ QString m_startPrefixLabel;
73+ QString m_startPostfixLabel;
74+ QString m_centralLabel;
75+ QString m_endPrefixLabel;
76+ QString m_endPostfixLabel;
77+};
78+
79+#endif
tests/mocks/Unity/fake_scope.cpp+52−27
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013, 2014 Canonical, Ltd.
2+ * Copyright (C) 2013, 2014, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,9 @@
1919
2020#include "fake_scope.h"
2121
22+#include "fake_filters.h"
2223#include "fake_navigation.h"
24+#include "fake_optionselectorfilter.h"
2325#include "fake_resultsmodel.h"
2426#include "fake_scopes.h"
2527#include "fake_settingsmodel.h"
@@ -37,14 +39,18 @@ Scope::Scope(QString const& id, QString const& name, bool favorite, Scopes* pare
3739 , m_searching(false)
3840 , m_favorite(favorite)
3941 , m_isActive(false)
42+ , m_hasNavigation(true)
43+ , m_hasPrimaryFilter(true)
4044 , m_currentNavigationId("root")
41− , m_currentAltNavigationId("altrootChild1")
4245 , m_previewRendererName("preview-generic")
4346 , m_categories(new Categories(categories, this))
4447 , m_openScope(nullptr)
4548 , m_settings(new SettingsModel(this))
49+ , m_filters(new Filters(this))
4650 , m_returnNullPreview(returnNullPreview)
4751{
52+ m_primaryNavigationFilter = new FakeOptionSelectorFilter("OSF3", "PFTag", "Which food you like More", false, QStringList() << "meat" << "vegetables", this);
53+ connect(m_filters, &Filters::activeFiltersCountChanged, this, &Scope::activeFiltersCountChanged);
4854}
4955
5056QString Scope::id() const
@@ -229,17 +235,13 @@ QString Scope::currentNavigationId() const
229235
230236bool Scope::hasNavigation() const
231237{
232− return true;
238+ return m_hasNavigation;
233239}
234240
235−QString Scope::currentAltNavigationId() const
241+void Scope::setHasNavigation(bool hasNavigation)
236242{
237− return m_currentAltNavigationId;
238−}
239−
240−bool Scope::hasAltNavigation() const
241−{
242− return true;
243+ m_hasNavigation = hasNavigation;
244+ Q_EMIT hasNavigationChanged();
243245}
244246
245247Scope::Status Scope::status() const
@@ -276,6 +278,11 @@ void Scope::refresh()
276278 Q_EMIT refreshed();
277279}
278280
281+void Scope::resetFilters()
282+{
283+ qWarning() << "Scope::resetFilters is unimplemented";
284+}
285+
279286void Scope::activateAction(QVariant const& /*result*/, QString const& /*categoryId*/, QString const& /*actionId*/)
280287{
281288 qFatal("Using Scope::activateAction");
@@ -298,31 +305,49 @@ unity::shell::scopes::NavigationInterface* Scope::getNavigation(const QString& i
298305 return new Navigation(id, id, "all"+id, parentId, parentLabel, this);
299306}
300307
301−unity::shell::scopes::NavigationInterface* Scope::getAltNavigation(QString const& id)
308+void Scope::setNavigationState(const QString &navigationId)
302309{
303− if (id.isEmpty())
304− return nullptr;
310+ m_currentNavigationId = navigationId;
311+ Q_EMIT currentNavigationIdChanged();
312+ Q_EMIT primaryNavigationTagChanged();
313+}
305314
306− QString parentId;
307− QString parentLabel;
308− if (id != "altroot") {
309− parentId = "altroot";
310− parentLabel = "altroot";
311− }
312− return new Navigation(id, id, "all"+id, parentId, parentLabel, this);
315+unity::shell::scopes::FilterBaseInterface* Scope::primaryNavigationFilter() const
316+{
317+ return m_hasPrimaryFilter ? m_primaryNavigationFilter : nullptr;
313318}
314319
315−void Scope::setNavigationState(const QString &navigationId, bool isAltNavigation)
320+unity::shell::scopes::FiltersInterface* Scope::filters() const
316321{
317− if (isAltNavigation) {
318− m_currentAltNavigationId = navigationId;
319− Q_EMIT currentAltNavigationIdChanged();
320− } else {
321− m_currentNavigationId = navigationId;
322− Q_EMIT currentNavigationIdChanged();
322+ return m_filters;
323+}
324+
325+QString Scope::primaryNavigationTag() const
326+{
327+ if (m_currentNavigationId == "root")
328+ return QString();
329+ else
330+ return const_cast<Scope*>(this)->getNavigation(m_currentNavigationId)->label();
331+}
332+
333+int Scope::activeFiltersCount() const
334+{
335+ return m_filters->activeFiltersCount();
336+}
337+
338+void Scope::resetPrimaryNavigationTag()
339+{
340+ if (m_currentNavigationId != "root") {
341+ setNavigationState("root");
323342 }
324343}
325344
345+void Scope::setHasPrimaryFilter(bool hasPrimaryFilter)
346+{
347+ m_hasPrimaryFilter = hasPrimaryFilter;
348+ Q_EMIT primaryNavigationFilterChanged();
349+}
350+
326351void Scope::performQuery(const QString& query)
327352{
328353 Q_EMIT queryPerformed(query);
tests/mocks/Unity/fake_scope.h+20−7
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013, 2014 Canonical, Ltd.
2+ * Copyright (C) 2013, 2014, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,9 @@
2424
2525#include <QTimer>
2626
27+class Filters;
2728class Scopes;
29+class FakeOptionSelectorFilter;
2830
2931class Scope : public unity::shell::scopes::ScopeInterface
3032{
@@ -68,11 +70,17 @@ public:
6870
6971 QString currentNavigationId() const override;
7072 bool hasNavigation() const override;
71− QString currentAltNavigationId() const override;
72− bool hasAltNavigation() const override;
7373 Q_INVOKABLE unity::shell::scopes::NavigationInterface* getNavigation(QString const& navigationId) override;
74− Q_INVOKABLE unity::shell::scopes::NavigationInterface* getAltNavigation(QString const& altNavigationId) override;
75− Q_INVOKABLE void setNavigationState(const QString &navigationId, bool isAltNavigation) override;
74+ Q_INVOKABLE void setNavigationState(const QString &navigationId) override;
75+ Q_INVOKABLE void setHasNavigation(bool hasNavigation); // This is not invokable in the Interface, here for testing benefits
76+
77+ unity::shell::scopes::FilterBaseInterface* primaryNavigationFilter() const override;
78+ unity::shell::scopes::FiltersInterface* filters() const override;
79+ QString primaryNavigationTag() const override;
80+ int activeFiltersCount() const override;
81+ Q_INVOKABLE void resetPrimaryNavigationTag() override;
82+ Q_INVOKABLE void setHasPrimaryFilter(bool hasPrimaryFilter); // This is not invokable in the Interface, here for testing benefits
83+
7684 void performQuery(const QString& query) override;
7785
7886 Status status() const override;
@@ -80,7 +88,9 @@ public:
8088
8189 Q_INVOKABLE void refresh() override;
8290
83− Q_INVOKABLE virtual void activateAction(QVariant const& result, QString const& categoryId, QString const& actionId) override;
91+ Q_INVOKABLE void resetFilters() override;
92+
93+ Q_INVOKABLE void activateAction(QVariant const& result, QString const& categoryId, QString const& actionId) override;
8494
8595Q_SIGNALS:
8696 // These are not in the Interface, here for testing benefits
@@ -99,14 +109,17 @@ protected:
99109 bool m_searching;
100110 bool m_favorite;
101111 bool m_isActive;
112+ bool m_hasNavigation;
113+ bool m_hasPrimaryFilter;
102114 QString m_currentNavigationId;
103− QString m_currentAltNavigationId;
104115
105116 QString m_previewRendererName;
106117
107118 unity::shell::scopes::CategoriesInterface* m_categories;
108119 unity::shell::scopes::ScopeInterface* m_openScope;
109120 unity::shell::scopes::SettingsModelInterface* m_settings;
121+ Filters* m_filters;
122+ FakeOptionSelectorFilter* m_primaryNavigationFilter;
110123
111124 bool m_returnNullPreview;
112125};
tests/mocks/Unity/fake_unity_plugin.cpp+7
@@ -22,11 +22,14 @@
2222// local
2323#include "fake_scopes.h"
2424#include "fake_categories.h"
25+#include "fake_filters.h"
2526#include "fake_navigation.h"
27+#include "fake_optionselectoroptions.h"
2628#include "fake_previewmodel.h"
2729#include "fake_previewwidgetmodel.h"
2830#include "fake_resultsmodel.h"
2931#include "fake_settingsmodel.h"
32+#include "fake_valueslidervalues.h"
3033
3134// External
3235#include <glib-object.h>
@@ -52,4 +55,8 @@ void FakeUnityPlugin::registerTypes(const char *uri)
5255 qmlRegisterType<ResultsModel>(uri, 0, 2, "FakeResultsModel");
5356 qmlRegisterType<PreviewModel>(uri, 0, 2, "FakePreviewModel");
5457 qmlRegisterUncreatableType<PreviewWidgetModel>(uri, 0, 2, "PreviewWidgetModel", "Can't create new PreviewWidgetModel in QML. Get them from PreviewModel instance.");
58+ qmlRegisterUncreatableType<unity::shell::scopes::FiltersInterface>(uri, 0, 2, "Filters", "Can't create Filters object in QML. Get them from Scope instance.");
59+ qmlRegisterUncreatableType<unity::shell::scopes::FilterBaseInterface>(uri, 0, 2, "Filter", "Can't create Filter object in QML. Get them from Scope instance.");
60+ qmlRegisterUncreatableType<unity::shell::scopes::OptionSelectorOptionsInterface>(uri, 0, 2, "OptionSelectorOptions", "Can't create OptionSelectorOptions object in QML");
61+ qmlRegisterUncreatableType<unity::shell::scopes::ValueSliderValuesInterface>(uri, 0, 2, "ValueSliderValues", "Can't create ValueSliderValuesInterface object in QML");
5562}
tests/mocks/Unity/fake_valuesliderfilter.cppnew file+84
@@ -0,0 +1,84 @@
1+/*
2+ * Copyright (C) 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+#include "fake_valuesliderfilter.h"
18+#include "fake_valueslidervalues.h"
19+
20+FakeValueSliderFilter::FakeValueSliderFilter(const QString &id, const QString &tag, double value, double minValue, double maxValue, const QMap<double, QString> &labels, QObject* parent)
21+ : unity::shell::scopes::ValueSliderFilterInterface(parent),
22+ m_filterId(id),
23+ m_filterTag(tag),
24+ m_value(value),
25+ m_minValue(minValue),
26+ m_maxValue(maxValue),
27+ m_values(new FakeValueSliderValues(labels, this))
28+{
29+}
30+
31+QString FakeValueSliderFilter::filterId() const
32+{
33+ return m_filterId;
34+}
35+
36+QString FakeValueSliderFilter::filterTag() const
37+{
38+ return m_filterTag;
39+}
40+
41+QString FakeValueSliderFilter::title() const
42+{
43+ return m_title;
44+}
45+
46+double FakeValueSliderFilter::value() const
47+{
48+ return m_value;
49+}
50+
51+void FakeValueSliderFilter::setValue(double value)
52+{
53+ if (value != m_value) {
54+ m_value = value;
55+ Q_EMIT valueChanged();
56+ }
57+}
58+
59+double FakeValueSliderFilter::minValue() const
60+{
61+ return m_minValue;
62+}
63+
64+double FakeValueSliderFilter::maxValue() const
65+{
66+ return m_maxValue;
67+}
68+
69+unity::shell::scopes::ValueSliderValuesInterface* FakeValueSliderFilter::values() const
70+{
71+ return m_values;
72+}
73+
74+bool FakeValueSliderFilter::isActive() const
75+{
76+ // Doesn't really matter
77+ return false;
78+}
79+
80+void FakeValueSliderFilter::setTitle(const QString &title)
81+{
82+ m_title = title;
83+ Q_EMIT titleChanged();
84+}
tests/mocks/Unity/fake_valuesliderfilter.hnew file+55
@@ -0,0 +1,55 @@
1+/*
2+ * Copyright (C) 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+#ifndef FAKE_VALUESLIDERFILTER_H
18+#define FAKE_VALUESLIDERFILTER_H
19+
20+#include <unity/shell/scopes/ValueSliderFilterInterface.h>
21+
22+class FakeValueSliderFilter : public unity::shell::scopes::ValueSliderFilterInterface
23+{
24+ Q_OBJECT
25+
26+public:
27+ FakeValueSliderFilter(const QString &id, const QString &tag, double value, double minValue, double maxValue, const QMap<double, QString> &labels, QObject* parent);
28+
29+ QString filterId() const override;
30+ QString filterTag() const override;
31+ QString title() const override;
32+
33+ double value() const override;
34+ void setValue(double value) override;
35+ double minValue() const override;
36+ double maxValue() const override;
37+ unity::shell::scopes::ValueSliderValuesInterface* values() const override;
38+
39+ // Not part of the iface, for mock/testing purposes
40+ bool isActive() const;
41+
42+ void setTitle(const QString &title);
43+
44+private:
45+ QString m_filterId;
46+ QString m_filterTag;
47+ QString m_title;
48+
49+ double m_value;
50+ double m_minValue;
51+ double m_maxValue;
52+ unity::shell::scopes::ValueSliderValuesInterface *m_values;
53+};
54+
55+#endif
tests/mocks/Unity/fake_valueslidervalues.cppnew file+51
@@ -0,0 +1,51 @@
1+/*
2+ * Copyright (C) 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+#include "fake_valueslidervalues.h"
18+
19+FakeValueSliderValues::FakeValueSliderValues(const QMap<double, QString> &labels, QObject* parent)
20+ : unity::shell::scopes::ValueSliderValuesInterface(parent)
21+ , m_labels(labels)
22+{
23+}
24+
25+int FakeValueSliderValues::rowCount(const QModelIndex &parent) const
26+{
27+ if (parent.isValid())
28+ return 0;
29+
30+ return m_labels.count();
31+}
32+
33+QVariant FakeValueSliderValues::data(const QModelIndex &index, int role) const
34+{
35+ if (!index.isValid())
36+ return QVariant();
37+
38+ const int row = index.row();
39+ auto it = m_labels.begin();
40+ it += row;
41+
42+ switch (role) {
43+ case RoleValue:
44+ return it.key();
45+
46+ case RoleLabel:
47+ return it.value();
48+ }
49+
50+ return QVariant();
51+}
tests/mocks/Unity/fake_valueslidervalues.hnew file+36
@@ -0,0 +1,36 @@
1+/*
2+ * Copyright (C) 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+#ifndef FAKE_VALUESLIDERVALUES_H
18+#define FAKE_VALUESLIDERVALUES_H
19+
20+#include <unity/shell/scopes/ValueSliderValuesInterface.h>
21+
22+class FakeValueSliderValues : public unity::shell::scopes::ValueSliderValuesInterface
23+{
24+ Q_OBJECT
25+
26+public:
27+ FakeValueSliderValues(const QMap<double, QString> &labels, QObject* parent);
28+
29+ int rowCount(const QModelIndex &parent) const override;
30+ QVariant data(const QModelIndex &index, int role) const override;
31+
32+private:
33+ const QMap<double, QString> m_labels;
34+};
35+
36+#endif
tests/plugins/Dash/cardcreator/12.tst+1−1
@@ -1,3 +1,3 @@
11template: {"card-layout":"vertical","card-size":"small","category-layout":"grid","collapsed-rows":2}
2−components: {"art":{"aspect-ratio":0.75,"field":"art"},"subtitle":{"field":"price"},"title":{"field":"title"},"attributes":{},"socialActions":{}}
2+components: {"art":{"aspect-ratio":0.75,"field":"art"},"subtitle":{"field":"price"},"title":{"field":"title"},"attributes":{},"social-actions":{}}
33result: 12.res
tests/plugins/Dash/cardcreator/9.res.cardcreator−3
@@ -90,10 +90,7 @@ CardAudioProgress {
9090 opacity: 0.9;
9191 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
9292 color: "white";
93−<<<<<<< TREE
94−=======
9593 asynchronous: false;
96−>>>>>>> MERGE-SOURCE
9794 }
9895 onClicked: {
9996 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
tests/qmltests/CMakeLists.txt+4
@@ -53,6 +53,10 @@ add_unity8_qmltest(Dash/Previews PreviewTextSummary)
5353add_unity8_qmltest(Dash/Previews PreviewVideoPlayback)
5454add_unity8_qmltest(Dash/Previews PreviewWidgetFactory)
5555add_unity8_qmltest(Dash/Previews PreviewZoomableImage)
56+add_unity8_qmltest(Dash/Filters FilterOptionSelector)
57+add_unity8_qmltest(Dash/Filters FilterRangeInput)
58+add_unity8_qmltest(Dash/Filters FilterValueSlider)
59+add_unity8_qmltest(Dash/Filters FilterWidgetFactory)
5660add_unity8_qmltest(Dash/ScopeSettings ScopeSettingBoolean)
5761add_unity8_qmltest(Dash/ScopeSettings ScopeSettingList)
5862add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
tests/qmltests/Dash/CardHelpers.js+7−7
@@ -16,7 +16,8 @@
1616
1717.pragma library
1818
19−var components = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "socialActions", "overlayColor", "quickPreviewData"]
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "social-actions", "overlayColor", "quickPreviewData"]
20+var dataKeys = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "socialActions", "overlayColor", "quickPreviewData"]
2021
2122var defaultLayout = ' \
2223{ \
@@ -39,7 +40,7 @@ var defaultLayout = ' \
3940 "emblem": null, \
4041 "summary": null, \
4142 "attributes": { "max-count": 2 },
42− "socialActions": { "max-count": 4 } \
43+ "social-actions": null \
4344 }, \
4445 "resources": {} \
4546}'
@@ -54,10 +55,9 @@ var fullMapping = ' \
5455 "overlayColor": "overlayColor", \
5556 "summary": "summary", \
5657 "attributes": "attributes", \
57− "socialActions": "socialActions" \
58+ "social-actions": "socialActions" \
5859}'
5960
60−
6161function tryParse(json, errorLabel) {
6262 var o = undefined;
6363 if (errorLabel !== undefined) {
@@ -84,12 +84,12 @@ function mapData(json, layout, errorLabel) {
8484 for (var k in components) {
8585 try {
8686 if (typeof layout[components[k]] == "object") {
87− d[components[k]] = o[layout[components[k]]['field']];
87+ d[dataKeys[k]] = o[layout[components[k]]['field']];
8888 } else {
89− d[components[k]] = o[layout[components[k]]];
89+ d[dataKeys[k]] = o[layout[components[k]]];
9090 }
9191 } catch(err) {
92− d[components[k]] = undefined;
92+ d[dataKeys[k]] = undefined;
9393 }
9494 }
9595 }
tests/qmltests/Dash/Filters/tst_FilterOptionSelector.qmlnew file+96
@@ -0,0 +1,96 @@
1+/*
2+ * Copyright 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.4
18+import QtTest 1.0
19+import "../../../../qml/Dash/Filters"
20+import Unity.Test 0.1 as UT
21+import Unity 0.2
22+import Ubuntu.Components 1.3
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(60)
27+ height: units.gu(80)
28+ color: theme.palette.selected.background
29+
30+ property var singleSelectionWidgetData
31+
32+ ListModel {
33+ id: optionsSingleSelect
34+ ListElement {
35+ label: "A"
36+ checked: false
37+ }
38+ ListElement {
39+ label: "B"
40+ checked: false
41+ }
42+ ListElement {
43+ label: "C"
44+ checked: false
45+ }
46+
47+ function setChecked(index, checked) {
48+ for (var i = 0; i < optionsSingleSelect.count; ++i)
49+ optionsSingleSelect.setProperty(i, "checked", false);
50+ optionsSingleSelect.setProperty(index, "checked", checked);
51+ }
52+ }
53+
54+ Component.onCompleted: {
55+ singleSelectionWidgetData = { label: "Hola", options: optionsSingleSelect }
56+ }
57+
58+ FilterWidgetFactory {
59+ id: factory
60+ widgetId: "testOptionSelectorFilter"
61+ widgetType: Filters.OptionSelectorFilter
62+ widgetData: singleSelectionWidgetData
63+ anchors {
64+ left: parent.left
65+ right: parent.right
66+ }
67+ }
68+
69+ UT.UnityTestCase {
70+ name: "FilterOptionSelector"
71+ when: windowShown
72+
73+ function test_optionSelector() {
74+ var expandingItem = findChild(factory, "expandingItem");
75+ // Open the selector
76+ mouseClick(factory);
77+ // wait for it to stop growing
78+ tryCompare(factory, "implicitHeight", expandingItem.expandedHeight);
79+
80+ // Check the first option
81+ var option0 = findChild(factory, "testOptionSelectorFilterlabel0");
82+ mouseClick(option0);
83+ verify(optionsSingleSelect.get(0).checked);
84+
85+ // Check the second option
86+ var option1 = findChild(factory, "testOptionSelectorFilterlabel1");
87+ mouseClick(option1);
88+ verify(!optionsSingleSelect.get(0).checked);
89+ verify(optionsSingleSelect.get(1).checked);
90+
91+ // Uncheck the second option
92+ mouseClick(option1);
93+ verify(!optionsSingleSelect.get(1).checked);
94+ }
95+ }
96+}
tests/qmltests/Dash/Filters/tst_FilterRangeInput.qmlnew file+247
@@ -0,0 +1,247 @@
1+/*
2+ * Copyright 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.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import "../../../../qml/Dash/Filters"
21+import Unity.Test 0.1 as UT
22+import Unity 0.2
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(60)
27+ height: units.gu(80)
28+ color: theme.palette.selected.background
29+
30+ QtObject {
31+ id: widgetData1
32+
33+ property string title
34+ property real startValue
35+ property real endValue
36+ property bool hasStartValue
37+ property bool hasEndValue
38+ property string startPrefixLabel
39+ property string startPostfixLabel
40+ property string centralLabel
41+ property string endPrefixLabel
42+ property string endPostfixLabel
43+
44+ onStartValueChanged: {
45+ hasStartValue = true;
46+ }
47+
48+ onEndValueChanged: {
49+ hasEndValue = true;
50+ }
51+
52+ function eraseStartValue() {
53+ hasStartValue = false;
54+ }
55+
56+ function eraseEndValue() {
57+ hasEndValue = false;
58+ }
59+ }
60+
61+ Component.onCompleted: {
62+ generateData();
63+ }
64+
65+ function generateData() {
66+ widgetData1.title = title.text
67+ widgetData1.startValue = startValue.text;
68+ widgetData1.endValue = endValue.text;
69+ widgetData1.hasStartValue = startValueHasValue.checked;
70+ widgetData1.hasEndValue = endValueHasValue.checked;
71+ widgetData1.startPrefixLabel = startValuePrefixLabel.text;
72+ widgetData1.startPostfixLabel = startValuePostfixLabel.text;
73+ widgetData1.centralLabel = centralLabel.text;
74+ widgetData1.endPrefixLabel = endValuePrefixLabel.text;
75+ widgetData1.endPostfixLabel = endValuePostfixLabel.text;
76+
77+ factory.widgetData = widgetData1;
78+ }
79+
80+ FilterWidgetFactory {
81+ id: factory
82+ widgetId: "testRangeInputFilter"
83+ widgetType: Filters.RangeInputFilter
84+ anchors {
85+ left: parent.left
86+ right: parent.right
87+ }
88+ clip: true
89+ }
90+
91+ Column {
92+ anchors.top: factory.bottom
93+ width: parent.width
94+ spacing: units.gu(1)
95+
96+ Text {
97+ text: "Values"
98+ }
99+
100+ Row {
101+ spacing: units.gu(1)
102+ Text {
103+ text: "Title"
104+ verticalAlignment: Text.AlignVCenter
105+ height: parent.height
106+ }
107+ TextField {
108+ id: title
109+ text: ""
110+ }
111+ }
112+
113+ Row {
114+ spacing: units.gu(1)
115+ Text {
116+ text: "Start Value Prefix Label"
117+ verticalAlignment: Text.AlignVCenter
118+ height: parent.height
119+ }
120+ TextField {
121+ id: startValuePrefixLabel
122+ text: "Pre1"
123+ }
124+ }
125+
126+ Row {
127+ spacing: units.gu(1)
128+ Text {
129+ text: "Start Value Postfix Label"
130+ verticalAlignment: Text.AlignVCenter
131+ height: parent.height
132+ }
133+ TextField {
134+ id: startValuePostfixLabel
135+ text: "Post1"
136+ }
137+ }
138+
139+ Row {
140+ spacing: units.gu(1)
141+ Text {
142+ text: "Central Label"
143+ verticalAlignment: Text.AlignVCenter
144+ height: parent.height
145+ }
146+ TextField {
147+ id: centralLabel
148+ text: "to"
149+ }
150+ }
151+
152+ Row {
153+ spacing: units.gu(1)
154+ Text {
155+ text: "End Value Prefix Label"
156+ verticalAlignment: Text.AlignVCenter
157+ height: parent.height
158+ }
159+ TextField {
160+ id: endValuePrefixLabel
161+ text: "Pre2"
162+ }
163+ }
164+
165+ Row {
166+ spacing: units.gu(1)
167+ Text {
168+ text: "End Value Postfix Label"
169+ verticalAlignment: Text.AlignVCenter
170+ height: parent.height
171+ }
172+ TextField {
173+ id: endValuePostfixLabel
174+ text: "Post2"
175+ }
176+ }
177+
178+ Row {
179+ spacing: units.gu(1)
180+ Text {
181+ text: "Start Value"
182+ verticalAlignment: Text.AlignVCenter
183+ height: parent.height
184+ }
185+ TextField {
186+ id: startValue
187+ text: "-1"
188+ }
189+ CheckBox {
190+ id: startValueHasValue
191+ }
192+ }
193+
194+
195+ Row {
196+ spacing: units.gu(1)
197+ Text {
198+ text: "End Value"
199+ verticalAlignment: Text.AlignVCenter
200+ height: parent.height
201+ }
202+ TextField {
203+ id: endValue
204+ text: "5"
205+ }
206+ CheckBox {
207+ id: endValueHasValue
208+ checked: true
209+ }
210+ }
211+
212+ Button {
213+ text: "Set Values"
214+ onClicked: root.generateData();
215+ }
216+ }
217+
218+ UT.UnityTestCase {
219+ name: "FilterRangeInput"
220+ when: windowShown
221+
222+ function init() {
223+ root.generateData();
224+ }
225+
226+ function test_initialStatus() {
227+ var startValueField = findChild(factory, "startValueField");
228+ compare(startValueField.text, "");
229+
230+ var endValueField = findChild(factory, "endValueField");
231+ compare(endValueField.text, "5");
232+ }
233+
234+ function test_dotStays() {
235+ var startValueField = findChild(factory, "startValueField");
236+ compare(startValueField.text, "");
237+
238+ startValueField.text = "4.5";
239+ compare(startValueField.text, "4.5");
240+ compare(widgetData1.startValue, 4.5);
241+
242+ startValueField.text = "4.";
243+ compare(startValueField.text, "4.");
244+ compare(widgetData1.startValue, 4);
245+ }
246+ }
247+}
tests/qmltests/Dash/Filters/tst_FilterValueSlider.qmlnew file+101
@@ -0,0 +1,101 @@
1+/*
2+ * Copyright 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.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import "../../../../qml/Dash/Filters"
21+import Unity.Test 0.1 as UT
22+import Unity 0.2
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(60)
27+ height: units.gu(80)
28+ color: theme.palette.selected.background
29+
30+ QtObject {
31+ id: widgetData1
32+
33+ property string title
34+ property int value: 30
35+ property int minValue: 10
36+ property int maxValue: 150
37+
38+ property ListModel values: ListModel {
39+ ListElement {
40+ value: 10
41+ label: "10"
42+ }
43+ ListElement {
44+ value: 30
45+ label: "30"
46+ }
47+ ListElement {
48+ value: 100
49+ label: "100"
50+ }
51+ ListElement {
52+ value: 150
53+ label: "150"
54+ }
55+ }
56+ }
57+
58+ FilterWidgetFactory {
59+ id: factory
60+ widgetId: "testRangeValueSlider"
61+ widgetType: Filters.ValueSliderFilter
62+ widgetData: widgetData1;
63+ y: 100
64+ anchors {
65+ left: parent.left
66+ right: parent.right
67+ }
68+ }
69+
70+ UT.UnityTestCase {
71+ name: "FilterValueSlider"
72+ when: windowShown
73+
74+ function test_valueChanges() {
75+ var slider = findChild(factory, "slider");
76+ compare(slider.value, 30);
77+
78+ slider.value = 50
79+ compare(widgetData1.value, 50);
80+ }
81+
82+ function test_labelPositions() {
83+ var slider = findChild(factory, "slider");
84+ var repeater = findChild(factory, "repeater");
85+
86+ // It's very hard to check the position of the
87+ // labels is right, but at least we can check
88+ // they all have different X positions and the
89+ // same Y one
90+ compare(repeater.count, 4);
91+ for (var i = 0; i < repeater.count; ++i) {
92+ var itemI = repeater.itemAt(i);
93+ for (var j = i + 1; j < repeater.count; ++j) {
94+ var itemJ = repeater.itemAt(j);
95+ verify(itemI.x != itemJ.x);
96+ verify(itemI.y == itemJ.y);
97+ }
98+ }
99+ }
100+ }
101+}
tests/qmltests/Dash/Filters/tst_FilterWidgetFactory.qmlnew file+57
@@ -0,0 +1,57 @@
1+/*
2+ * Copyright 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.4
18+import QtTest 1.0
19+import "../../../../qml/Dash/Filters"
20+import Unity.Test 0.1 as UT
21+import Unity 0.2
22+import Ubuntu.Components 1.3
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(60)
27+ height: units.gu(80)
28+ color: theme.palette.selected.background
29+
30+ FilterWidgetFactory {
31+ id: factory
32+ anchors {
33+ left: parent.left
34+ right: parent.right
35+ }
36+ }
37+
38+ UT.UnityTestCase {
39+ name: "FilterWidgetFactory"
40+ when: windowShown
41+
42+ function test_mapping_data() {
43+ return [
44+ { tag: "OptionSelector", type: Filters.OptionSelectorFilter, source: "FilterOptionSelector.qml" },
45+ { tag: "RangeInputFilter", type: Filters.RangeInputFilter, source: "FilterRangeInput.qml" },
46+ { tag: "ValueSliderFilter", type: Filters.ValueSliderFilter, source: "FilterValueSlider.qml" }
47+ ];
48+ }
49+
50+ function test_mapping(data) {
51+ factory.widgetData = {};
52+ factory.widgetType = data.type;
53+
54+ verify((String(factory.source)).indexOf(data.source) != -1);
55+ }
56+ }
57+}
tests/qmltests/Dash/tst_Card.qml+4−4
@@ -36,10 +36,10 @@ Rectangle {
3636 "subtitle": "bar",
3737 "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
3838 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
39− "socialActions": [{"id":"like","label":"text1","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
40− {"id":"share","label":"text2","icon":"image://theme/cancel"},
41− {"id":"like","label":"text1","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
42− {"id":"share","label":"text2","icon":"image://theme/cancel"}],
39+ "socialActions": [{"id":"like","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
40+ {"id":"share","icon":"image://theme/cancel"},
41+ {"id":"like","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
42+ {"id":"share","icon":"image://theme/cancel"}],
4343 "quickPreviewData": {"uri": "/some/file", "duration": "14"}
4444 }'
4545
tests/qmltests/Dash/tst_DashContent.qml+181−153
@@ -215,7 +215,7 @@ Item {
215215 verify(pageHeader, "Could not find the scope page header.");
216216 var innerHeader = findChild(pageHeader, "innerPageHeader");
217217 verify(innerHeader, "Could not find the scope page header.");
218− compare(innerHeader.config.title, scopesModel.getScope(data.index).name);
218+ compare(innerHeader.config.contents.text, scopesModel.getScope(data.index).name);
219219 }
220220
221221 function test_is_active_data() {
@@ -284,14 +284,14 @@ Item {
284284 var dashContentList = findChild(dashContent, "dashContentList");
285285 tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
286286 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
287− tryCompare(dashNavigation, "visible", true);
288− var dashNavigationButton = findChild(dashContentList.currentItem, "navigationButton");
289− compare(dashNavigationButton.showList, false);
290− waitForRendering(dashNavigationButton);
291− mouseClick(dashNavigationButton);
292− compare(dashNavigationButton.showList, true);
293−
294− var navigationListView = findChild(dashNavigationButton, "navigationListView");
287+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
288+ var searchButton = findChild(dashContentList.currentItem, "search_action_button");
289+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
290+ compare(peExtraPanel.visible, false);
291+ mouseClick(searchButton);
292+ tryCompare(peExtraPanel, "visible", true);
293+
294+ var navigationListView = findChild(dashNavigation, "navigationListView");
295295 tryCompareFunction(function() {
296296 return navigationListView.currentItem &&
297297 navigationListView.currentItem.navigation &&
@@ -300,235 +300,263 @@ Item {
300300 waitForRendering(navigationListView);
301301 waitForRendering(navigationListView.currentItem);
302302
303− var allButton = findChild(dashNavigationButton, "allButton");
304− compare(allButton.visible, false);
305−
306− var navigation = findChild(dashNavigationButton, "navigation0child3");
303+ var navigation = findChild(dashNavigation, "navigation0child3");
307304 mouseClick(navigation);
308− compare(dashNavigationButton.showList, false);
309− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle3");
305+ compare(peExtraPanel.visible, false);
306+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle3");
310307 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
311308
312− mouseClick(dashNavigationButton);
313− compare(dashNavigationButton.showList, true);
309+ mouseClick(searchTextField);
310+ tryCompare(peExtraPanel, "visible", true);
314311 waitForRendering(navigationListView);
315312 waitForRendering(navigationListView.currentItem);
316− compare(allButton.visible, true);
317313
318− mouseClick(allButton);
319− compare(dashNavigationButton.showList, false);
320− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "root");
314+ var header0 = findChild(dashNavigation, "dashNavigationHeader0");
315+ compare(header0.backVisible, false);
316+ mouseClick(header0);
317+ compare(peExtraPanel.visible, false);
318+ tryCompare(dashNavigation.currentNavigation, "navigationId", "root");
321319 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
322320
323− mouseClick(dashNavigationButton);
324− compare(dashNavigationButton.showList, true);
321+ var headerContainer = findChild(dashContentList.currentItem, "headerContainer");
322+ tryCompare(headerContainer, "contentY", headerContainer.height);
323+ mouseClick(searchButton);
324+ tryCompare(peExtraPanel, "visible", true);
325325 waitForRendering(navigationListView);
326326 waitForRendering(navigationListView.currentItem);
327− compare(allButton.visible, false);
328327
329− navigation = findChild(dashNavigationButton, "navigation0child2");
328+ navigation = findChild(dashNavigation, "navigation0child2");
330329 mouseClick(navigation);
331− compare(dashNavigationButton.showList, true);
332− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
330+ compare(peExtraPanel.visible, true);
331+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
333332 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
334333
335− var navigationList1 = findChild(dashNavigationButton, "navigation1");
336− allButton = findChild(navigationList1, "allButton");
337− var backButton = findChild(findChild(navigationList1, "navigation1"), "backButton");
338− compare(allButton.visible, true);
339− compare(backButton.visible, true);
334+ var navigationList1 = findChild(dashNavigation, "navigation1");
335+ var header1 = findChild(dashNavigation, "dashNavigationHeader1");
336+ compare(header0.height, 0);
337+ compare(header1.backVisible, true);
340338
341339 tryCompare(navigationListView, "contentX", navigationList1.x);
342340 waitForRendering(navigationListView);
343− mouseClick(allButton);
344− compare(dashNavigationButton.showList, false);
345− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
341+ mouseClick(header1);
342+ compare(peExtraPanel.visible, false);
343+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
346344 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
347345
348− mouseClick(dashNavigationButton);
349− compare(dashNavigationButton.showList, true);
346+ mouseClick(searchTextField);
347+ tryCompare(peExtraPanel, "visible", true);
350348 waitForRendering(navigationListView);
351349 waitForRendering(navigationListView.currentItem);
352− compare(allButton.visible, true);
353− compare(backButton.visible, true);
350+ compare(header0.height, 0);
351+ compare(header1.backVisible, true);
354352
355353 tryCompare(navigationList1.navigation, "loaded", true);
356− tryCompare(navigationList1, "implicitHeight", navigationList1.itemHeight * 10);
354+ tryCompare(navigationList1, "implicitHeight", navigationList1.itemHeight * 8);
357355 tryCompare(navigationList1, "height", navigationList1.implicitHeight);
358− navigation = findChild(dashNavigationButton, "navigation1child2");
356+ navigation = findChild(dashNavigation, "navigation1child2");
359357 mouseClick(navigation);
360− compare(dashNavigationButton.showList, false);
361− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "childmiddle22");
358+ compare(peExtraPanel.visible, false);
359+ tryCompare(dashNavigation.currentNavigation, "navigationId", "childmiddle22");
362360 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
363361
364− mouseClick(dashNavigationButton);
365− compare(dashNavigationButton.showList, true);
362+ mouseClick(searchTextField);
363+ tryCompare(peExtraPanel, "visible", true);
366364 waitForRendering(navigationListView);
367365 waitForRendering(navigationListView.currentItem);
368366
369367 tryCompare(navigationList1.navigation, "loaded", true);
370− navigation = findChild(dashNavigationButton, "navigation1child3");
368+ navigation = findChild(dashNavigation, "navigation1child3");
371369 mouseClick(navigation);
372− compare(dashNavigationButton.showList, false);
373− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "childmiddle23");
370+ compare(peExtraPanel.visible, false);
371+ tryCompare(dashNavigation.currentNavigation, "navigationId", "childmiddle23");
374372 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
375373
376− mouseClick(dashNavigationButton);
377− compare(dashNavigationButton.showList, true);
374+ mouseClick(searchTextField);
375+ tryCompare(peExtraPanel, "visible", true);
378376 waitForRendering(navigationListView);
379377 waitForRendering(navigationListView.currentItem);
380− mouseClick(backButton);
378+ mouseClick(findChild(header1, "backButton"));
381379
382− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "root");
380+ tryCompare(dashNavigation.currentNavigation, "navigationId", "root");
383381 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
384− compare(dashNavigationButton.showList, true);
385− mouseClick(dashNavigationButton);
386− compare(dashNavigationButton.showList, false);
382+ compare(peExtraPanel.visible, true);
383+ mouseClick(header0);
384+ compare(peExtraPanel.visible, false);
387385
388− mouseClick(dashNavigationButton);
389− compare(dashNavigationButton.showList, true);
386+ tryCompare(headerContainer, "contentY", headerContainer.height);
387+ mouseClick(searchButton);
388+ tryCompare(peExtraPanel, "visible", true);
390389 tryCompare(navigationListView.currentItem.navigation, "loaded", true);
391− var navigationList0 = findChild(dashNavigationButton, "navigation0");
390+ var navigationList0 = findChild(dashNavigation, "navigation0");
392391 tryCompare(navigationList0, "implicitHeight", navigationList0.itemHeight * 8);
393392 tryCompare(navigationList0, "height", navigationList0.implicitHeight);
394− navigation = findChild(dashNavigationButton, "navigation0child2");
393+ navigation = findChild(dashNavigation, "navigation0child2");
395394 mouseClick(navigation);
396− compare(dashNavigationButton.showList, true);
397− navigationList1 = findChild(dashNavigationButton, "navigation1");
395+ compare(peExtraPanel.visible, true);
396+ navigationList1 = findChild(dashNavigation, "navigation1");
398397 compare(navigationList1.navigation.loaded, false);
399− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
400− allButton = findChild(navigationList1, "allButton");
401− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
402− mouseClick(allButton);
403− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
398+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
399+ mouseClick(header0);
400+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
404401 }
405402
406− function test_altNavigation() {
403+ function goToSecondLevel() {
407404 var dashContentList = findChild(dashContent, "dashContentList");
408405 tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
409406 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
410− tryCompare(dashNavigation, "visible", true);
411− var dashNavigationButton = findChild(dashContentList.currentItem, "altNavigationButton");
412− verify(dashNavigationButton, "Can't find navigation button");
413−
414− compare(dashNavigationButton.showList, false);
415− waitForRendering(dashNavigationButton);
416− mouseClick(dashNavigationButton);
417− compare(dashNavigationButton.showList, true);
418−
419− var navigationListView = findChild(dashNavigationButton, "navigationListView");
407+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
408+ var searchButton = findChild(dashContentList.currentItem, "search_action_button");
409+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
410+ compare(peExtraPanel.visible, false);
411+ mouseClick(searchButton);
412+ tryCompare(peExtraPanel, "visible", true);
413+
414+ var navigationListView = findChild(dashNavigation, "navigationListView");
420415 tryCompareFunction(function() {
421416 return navigationListView.currentItem &&
422417 navigationListView.currentItem.navigation &&
423418 navigationListView.currentItem.navigation.loaded; }, true);
424−
425419 waitForRendering(navigationListView);
426420 waitForRendering(navigationListView.currentItem);
427421
428− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "altrootChild1");
429− tryCompare(navigationListView.currentItem.navigation, "navigationId", "altroot");
430−
431− var allButton = findChild(dashNavigationButton, "allButton");
432− compare(allButton.visible, false);
433− var backButton = findChild(navigationListView, "backButton");
434− compare(backButton.visible, false);
422+ var navigation4 = findChild(dashNavigation, "navigation0child4");
423+ mouseClick(navigation4);
424+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle4");
425+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle4");
426+ tryCompare(dashNavigation.currentNavigation, "loaded", true);
435427
436− var navigation = findChild(dashNavigationButton, "navigation0child3");
437− mouseClick(navigation);
438− compare(dashNavigationButton.showList, false);
439− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "altrootChild3");
440− tryCompare(navigationListView.currentItem.navigation, "navigationId", "altroot");
441−
442− mouseClick(dashNavigationButton);
443− compare(dashNavigationButton.showList, true);
444428 waitForRendering(navigationListView);
445429 waitForRendering(navigationListView.currentItem);
446− compare(allButton.visible, false);
447430
448− navigation = findChild(dashNavigationButton, "navigation0child2");
449− mouseClick(navigation);
450− compare(dashNavigationButton.showList, false);
451− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "altrootChild2");
452− tryCompare(navigationListView.currentItem.navigation, "navigationId", "altroot");
431+ var navigation44 = findChild(dashNavigation, "navigation1child4");
432+ mouseClick(navigation44);
433+ tryCompare(dashNavigation.currentNavigation, "navigationId", "childmiddle44");
434+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "childmiddle44");
435+ tryCompare(dashNavigation.currentNavigation, "loaded", true);
453436
454− mouseClick(dashNavigationButton);
455− compare(dashNavigationButton.showList, true);
456437 waitForRendering(navigationListView);
457438 waitForRendering(navigationListView.currentItem);
458− verify(!backButton.visible, "Back button should not be visible");
459439
460− compare(navigationListView.count, 1, "There should be no second-level navigation");
440+ // header 0 is hidden
441+ var header0 = findChild(dashNavigation, "dashNavigationHeader0");
442+ compare(header0.height, 0);
443+ }
444+
445+ function test_navigationSecondLevelToRoot() {
446+ goToSecondLevel();
447+
448+ var dashContentList = findChild(dashContent, "dashContentList");
449+ var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
450+ var navigationListView = findChild(dashNavigation, "navigationListView");
451+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
452+
453+ // Go directly to the root pressing the back button of header1
454+ var header1 = findChild(dashNavigation, "dashNavigationHeader1");
455+ compare(header1.backVisible, true);
456+ mouseClick(findChild(header1, "backButton"));
461457
462− mouseClick(dashNavigationButton);
463− compare(dashNavigationButton.showList, false);
458+ tryCompare(dashNavigation.currentNavigation, "navigationId", "root");
459+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
460+ compare(peExtraPanel.visible, true);
464461 }
465462
466− function test_navigationsClosing() {
463+ function test_navigationSecondLevelToFirstName() {
464+ goToSecondLevel();
465+
467466 var dashContentList = findChild(dashContent, "dashContentList");
468− tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
469467 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
470− tryCompare(dashNavigation, "visible", true);
471− var dashNavigationButton = findChild(dashContentList.currentItem, "navigationButton");
472− verify(dashNavigationButton, "Can't find navigation button");
473− var dashAltNavigationButton = findChild(dashContentList.currentItem, "altNavigationButton");
474− verify(dashAltNavigationButton, "Can't find alternative navigation button");
468+ var navigationListView = findChild(dashNavigation, "navigationListView");
469+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
475470
476− waitForRendering(dashNavigationButton);
477− waitForRendering(dashAltNavigationButton);
471+ // Go directly to the first pressing the header1
472+ var header1 = findChild(dashNavigation, "dashNavigationHeader1");
473+ mouseClick(header1);
478474
479− compare(dashNavigationButton.showList, false);
480− compare(dashAltNavigationButton.showList, false);
475+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle4");
476+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle4");
477+ compare(peExtraPanel.visible, false);
478+ }
481479
482− mouseClick(dashAltNavigationButton);
483− compare(dashNavigationButton.showList, false);
484− compare(dashAltNavigationButton.showList, true);
480+ function test_navigationSecondLevelToFirstBack() {
481+ goToSecondLevel();
485482
486− var navigationListView = findChild(dashAltNavigationButton, "navigationListView");
487− tryCompareFunction(function() {
488− return navigationListView.currentItem &&
489− navigationListView.currentItem.navigation &&
490− navigationListView.currentItem.navigation.loaded; }, true);
483+ var dashContentList = findChild(dashContent, "dashContentList");
484+ var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
485+ var navigationListView = findChild(dashNavigation, "navigationListView");
486+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
491487
492− var blackRect = findChild(dashNavigation, "blackRect");
493− tryCompare(blackRect, "opacity", 0.5);
488+ // Go back to the first level pressing the back button of header2
489+ var header2 = findChild(dashNavigation, "dashNavigationHeader2");
490+ compare(header2.backVisible, true);
491+ mouseClick(findChild(header2, "backButton"));
494492
495− mouseClick(dashNavigationButton);
496− compare(dashNavigationButton.showList, false);
497− compare(dashAltNavigationButton.showList, false);
493+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle4");
494+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle4");
495+ compare(peExtraPanel.visible, true);
496+ }
498497
499− tryCompare(navigationListView, "visible", false);
498+ function test_clearSearchWithNavigationClosed() {
499+ var dashContentList = findChild(dashContent, "dashContentList");
500+ var searchButton = findChild(dashContentList.currentItem, "search_action_button");
501+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
502+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
500503
501− mouseClick(dashNavigationButton);
502− compare(dashNavigationButton.showList, true);
503− compare(dashAltNavigationButton.showList, false);
504+ compare(peExtraPanel.visible, false);
505+ mouseClick(searchButton);
506+ tryCompare(peExtraPanel, "visible", true);
504507
505− navigationListView = findChild(dashNavigationButton, "navigationListView");
506− tryCompare(navigationListView.currentItem.navigation, "loaded", true);
508+ typeString("A");
509+ compare(peExtraPanel.visible, false);
507510
508− mouseClick(dashAltNavigationButton);
509− compare(dashNavigationButton.showList, false);
510− compare(dashAltNavigationButton.showList, false);
511+ var clearIcon = findChild(searchTextField, "clearIcon");
512+ mouseClick(clearIcon);
513+ tryCompare(peExtraPanel, "visible", true);
511514 }
512515
513− function test_navigationsSwipeToNextScope() {
516+ function test_navigationShowFilterButton() {
517+ goToSecondLevel();
518+
514519 var dashContentList = findChild(dashContent, "dashContentList");
515− tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
516− var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
517− tryCompare(dashNavigation, "visible", true);
518− var dashNavigationButton = findChild(dashContentList.currentItem, "altNavigationButton");
519− verify(dashNavigationButton, "Can't find navigation button");
520+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
521+
522+ verify(!searchTextField.focus)
523+ verify(searchTextField.clearIsSettings)
520524
521− compare(dashNavigationButton.showList, false);
522− waitForRendering(dashNavigationButton);
523− mouseClick(dashNavigationButton, 0, 0);
524− compare(dashNavigationButton.showList, true);
525+ mouseClick(searchTextField)
526+
527+ verify(searchTextField.focus)
528+ verify(!searchTextField.clearIsSettings)
529+ }
525530
526− mouseFlick(dashNavigationButton, dashNavigationButton.width - units.gu(1), units.gu(1), units.gu(1), units.gu(1));
527− compare(dashNavigationButton.showList, false);
531+ function test_navigationShowFilterPopup() {
532+ goToSecondLevel();
528533
529534 var dashContentList = findChild(dashContent, "dashContentList");
530− expectFail("", "should not change the parent list.");
531− tryCompare(dashContentList, "currentIndex", 1);
535+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
536+ var clearIcon = findChild(searchTextField, "clearIcon");
537+
538+ var filtersPopover = findChild(shell, "filtersPopover")
539+ verify(!filtersPopover);
540+
541+ mouseClick(clearIcon);
542+
543+ filtersPopover = findChild(shell, "filtersPopover")
544+
545+ verify(filtersPopover);
546+ }
547+
548+ function test_primaryFilter() {
549+ var dashContentList = findChild(dashContent, "dashContentList");
550+ tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
551+ dashContentList.currentItem.item.scope.setHasNavigation(false);
552+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
553+ var searchButton = findChild(dashContentList.currentItem, "search_action_button");
554+
555+ compare(peExtraPanel.visible, false);
556+ mouseClick(searchButton);
557+ tryCompare(peExtraPanel, "visible", true);
558+
559+ tryCompareFunction(function() { return findChild(peExtraPanel, "OSF3") != null; }, true);
532560 }
533561
534562 function test_searchHint() {
@@ -540,7 +568,7 @@ Item {
540568 var categoryListView = findChild(scope, "categoryListView");
541569 waitForRendering(categoryListView);
542570
543− compare(categoryListView.pageHeader.item.searchHint, "Search People");
571+ compare(categoryListView.pageHeader.searchHint, "Search People");
544572 }
545573
546574 function compareArrays(a, b) {
tests/qmltests/Dash/tst_DashPageHeader.qml+24−15
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import QtTest 1.0
1919import ".."
2020import "../../../qml/Dash"
21+import "../../../qml/Components/SearchHistoryModel"
2122import Ubuntu.Components 1.3
2223import Unity 0.2
2324import Unity.Test 0.1 as UT
@@ -25,7 +26,7 @@ import Unity.Test 0.1 as UT
2526Item {
2627 id: root
2728 width: units.gu(110)
28− height: units.gu(30)
29+ height: units.gu(50)
2930
3031 UT.UnityTestCase {
3132 name: "PageHeaderLabelTest"
@@ -54,7 +55,7 @@ Item {
5455 pageHeader.searchHistory.clear();
5556
5657 // Check initial state
57− tryCompareFunction(function() { return headerContainer.popover === null; }, true);
58+ tryCompare(pageHeader.extraPanel, "visible", false);
5859 compare(pageHeader.searchHistory.count, 0);
5960 }
6061
@@ -146,7 +147,7 @@ Item {
146147 doResetSearch();
147148 }
148149
149− function test_popover() {
150+ function test_extraPanel() {
150151 searchEnabled = true;
151152 pageHeader.searchHistory.clear();
152153
@@ -155,22 +156,18 @@ Item {
155156
156157 pageHeader.triggerSearch();
157158
158− var headerContainer = findChild(pageHeader, "headerContainer");
159− verify(headerContainer !== null, "headerContainer != null");
160− tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
161−
162− tryCompare(headerContainer.popover, "visible", true);
159+ tryCompare(pageHeader.extraPanel, "visible", true);
163160
164161 var searchTextField = findChild(pageHeader, "searchTextField");
165162 compare(searchTextField.focus, true);
166163
167− var recentSearches = findChild(headerContainer.popover, "recentSearches");
168− verify(recentSearches, "Could not find recent searches in the popover");
164+ var recentSearches = findChild(pageHeader.extraPanel, "recentSearchesRepeater");
165+ verify(recentSearches, "Could not find recent searches");
169166 waitForRendering(recentSearches);
170167 mouseClick(recentSearches.itemAt(0));
171168
172169 compare(pageHeader.searchQuery, "Search2");
173− tryCompareFunction(function() { return headerContainer.popover === null; }, true);
170+ tryCompare(pageHeader.extraPanel, "visible", false);
174171 compare(searchTextField.focus, false);
175172 }
176173
@@ -212,9 +209,7 @@ Item {
212209 pageHeader.triggerSearch();
213210
214211 var headerContainer = findChild(pageHeader, "headerContainer");
215− verify(headerContainer !== null, "headerContainer != null");
216− tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
217− compare(headerContainer.popover.visible, true);
212+ tryCompare(pageHeader.extraPanel, "visible", true);
218213
219214 pageHeader.searchQuery = data.searchText;
220215
@@ -225,7 +220,7 @@ Item {
225220 }
226221
227222 tryCompare(headerContainer, "showSearch", !data.hideSearch);
228− tryCompareFunction(function() { return headerContainer.popover === null; }, true);
223+ tryCompare(pageHeader.extraPanel, "visible", false);
229224
230225 doResetSearch();
231226 }
@@ -251,8 +246,10 @@ Item {
251246 right: parent.right
252247 }
253248
249+ searchHistory: SearchHistoryModel
254250 searchEntryEnabled: true
255251 title: "%^$%^%^&%^&%^$%GHR%"
252+ extraPanel: peExtraPanel
256253 scopeStyle: QtObject {
257254 readonly property color foreground: theme.palette.normal.baseText
258255 readonly property url headerLogo: showImageCheckBox.checked ? pageHeader.titleImageSource : ""
@@ -264,6 +261,18 @@ Item {
264261 onBackClicked: lastBackClicked = new Date()
265262 }
266263
264+ PageHeaderExtraPanel {
265+ id: peExtraPanel
266+ width: parent.width
267+ z: 1
268+ visible: false
269+ searchHistory: SearchHistoryModel
270+ onHistoryItemClicked: {
271+ SearchHistoryModel.addQuery(text);
272+ pageHeader.searchQuery = text;
273+ }
274+ }
275+
267276 Row {
268277 spacing: units.gu(1)
269278 anchors {
tests/qmltests/Dash/tst_GenericScopeView.qml+1−1
@@ -478,7 +478,7 @@ Item {
478478 mouseClick(seeAll0);
479479 verify(category0.expanded);
480480 tryCompare(category0, "height", category0.item.expandedHeight + seeAll0.height);
481− tryCompare(genericScopeView.categoryView, "contentY", units.gu(13));
481+ tryCompare(genericScopeView.categoryView, "contentY", units.gu(8));
482482
483483 scrollToEnd();
484484
tests/qmltests/Dash/tst_PreviewView.qml+1−1
@@ -98,7 +98,7 @@ Rectangle {
9898 var header = findChild(view, "innerPageHeader");
9999 verify(header, "Could not find the preview header");
100100
101− compare(header.config.title, "Mock Scope");
101+ compare(header.config.contents.text, "Mock Scope");
102102 }
103103
104104 function test_header_style() {