on xenial help sign in
Merges
dbce7ebAndrea Cimitan
plugins/Dash/CardCreator.js+1−1
@@ -455,7 +455,7 @@ function cardString(template, components, isCardTool) {
455 var hasSubtitle = hasTitle && components["subtitle"] || false;
455 var hasSubtitle = hasTitle && components["subtitle"] || false;
456 var hasHeaderRow = hasMascot && hasTitle;
456 var hasHeaderRow = hasMascot && hasTitle;
457 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
457 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
458− var hasSocialActions = hasTitle && components["socialActions"] || false;
458+ var hasSocialActions = hasTitle && components["social-actions"] || false;
459 var isAudio = template["quick-preview-type"] === "audio";
459 var isAudio = template["quick-preview-type"] === "audio";
460 var asynchronous = isCardTool ? "false" : "true";
460 var asynchronous = isCardTool ? "false" : "true";
461
461
plugins/Dash/CardSocialActions.qml+12−4
@@ -17,7 +17,6 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20−import "../../../qml/Components/ListItems" as ListItems
21
20
22Column {
21Column {
23 id: socialActions
22 id: socialActions
@@ -28,10 +27,17 @@ Column {
28
27
29 signal clicked(var actionId)
28 signal clicked(var actionId)
30
29
31− ListItems.ThinDivider {
30+ Rectangle {
32 id: divider
31 id: divider
33 visible: repeater.count > 0
32 visible: repeater.count > 0
33+ anchors {
34+ left: parent.left;
34− anchors { left: parent.left; right: parent.right; }
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
35 }
41 }
36
42
37 Row {
43 Row {
@@ -74,7 +80,9 @@ Column {
74 source: urlIcon
80 source: urlIcon
75 color: socialActions.color
81 color: socialActions.color
76
82
83+ onUrlIconChanged: {
77− onUrlIconChanged: if (urlIcon) source = urlIcon
84+ if (urlIcon) source = urlIcon
85+ }
78 }
86 }
79
87
80 onClicked: socialActions.clicked(modelData["id"]);
88 onClicked: socialActions.clicked(modelData["id"]);
plugins/Dash/ScopeStyle.qml+1−1
@@ -58,7 +58,7 @@ QtObject {
58 readonly property url headerLogo: "logo" in d.headerStyle ? d.headerStyle["logo"] : ""
58 readonly property url headerLogo: "logo" in d.headerStyle ? d.headerStyle["logo"] : ""
59
59
60 /// Background style for the header
60 /// 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"
62
62
63 /// Foreground color for the header
63 /// Foreground color for the header
64 readonly property color headerForeground: "foreground-color" in d.headerStyle ? d.headerStyle["foreground-color"] : foreground
64 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 {
162 id: attributesModel
162 id: attributesModel
163 property int numOfAttributes: 0
163 property int numOfAttributes: 0
164 property var model: []
164 property var model: []
165− property bool hasAttributes: {
165+ readonly property bool hasAttributes: {
166 var attributes = components["attributes"];
166 var attributes = components["attributes"];
167 var hasAttributesFlag = (attributes != undefined) && (attributes["field"] != undefined);
167 var hasAttributesFlag = (attributes != undefined) && (attributes["field"] != undefined);
168
168
@@ -184,31 +184,23 @@ Item {
184
184
185 Item {
185 Item {
186 id: socialActionsModel
186 id: socialActionsModel
187− property int numOfActions: 0
188 property var model: []
187 property var model: []
189− property bool hasActions: {
188+ readonly property bool hasActions: {
190− var actions = components["socialActions"];
189+ var actions = components["social-actions"];
191 var hasActionsFlag = (actions != undefined);
190 var hasActionsFlag = (actions != undefined);
192
191
192+ model = []
193 if (hasActionsFlag) {
193 if (hasActionsFlag) {
194− if (actions["max-count"]) {
194+ model.push( {"id":"text", "icon":"image://theme/ok" } );
195− numOfActions = actions["max-count"];
196− }
197 }
195 }
198 return hasActionsFlag
196 return hasActionsFlag
199 }
197 }
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− }
207 }
198 }
208
199
209 Loader {
200 Loader {
210 id: cardLoader
201 id: cardLoader
202+ readonly property var cfields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "social-actions"]
211− readonly property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "socialActions"]
203+ readonly property var dfields: ["art", "mascot", "title", "subtitle", "summary", "attributes", "socialActions"]
212 readonly property var maxData: {
204 readonly property var maxData: {
213 "art": Qt.resolvedUrl("graphics/pixel.png"),
205 "art": Qt.resolvedUrl("graphics/pixel.png"),
214 "mascot": Qt.resolvedUrl("graphics/pixel.png"),
206 "mascot": Qt.resolvedUrl("graphics/pixel.png"),
@@ -231,13 +223,14 @@ Item {
231 }
223 }
232 function updateCardData() {
224 function updateCardData() {
233 var data = {};
225 var data = {};
234− for (var k in fields) {
226+ for (var k in cfields) {
227+ var ckey = cfields[k];
235− var component = cardTool.components[fields[k]];
228+ var component = cardTool.components[ckey];
236− var key = fields[k];
237 if ((typeof component === "string" && component.length > 0) ||
229 if ((typeof component === "string" && component.length > 0) ||
238 (typeof component === "object" && component !== null
230 (typeof component === "object" && component !== null
239 && typeof component["field"] === "string" && component["field"].length > 0)) {
231 && typeof component["field"] === "string" && component["field"].length > 0)) {
232+ var dkey = dfields[k];
240− data[key] = maxData[key];
233+ data[dkey] = maxData[dkey];
241 }
234 }
242 }
235 }
243 item.cardData = data;
236 item.cardData = data;
qml/Dash/DashContent.qml+2−2
@@ -108,7 +108,7 @@ Item {
108 objectName: "dashContentList"
108 objectName: "dashContentList"
109
109
110 interactive: !dashContent.forceNonInteractive && dashContent.scopes.loaded && currentItem
110 interactive: !dashContent.forceNonInteractive && dashContent.scopes.loaded && currentItem
111− && !currentItem.moving && !currentItem.navigationDisableParentInteractive && !currentItem.subPageShown
111+ && !currentItem.moving && !currentItem.subPageShown && !currentItem.extraPanelShown
112 anchors.fill: parent
112 anchors.fill: parent
113 orientation: ListView.Horizontal
113 orientation: ListView.Horizontal
114 boundsBehavior: Flickable.DragAndOvershootBounds
114 boundsBehavior: Flickable.DragAndOvershootBounds
@@ -170,7 +170,7 @@ Item {
170 objectName: "scopeLoader" + index
170 objectName: "scopeLoader" + index
171
171
172 readonly property bool moving: item ? item.moving : false
172 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
174 readonly property bool subPageShown: item ? item.subPageShown : false
174 readonly property bool subPageShown: item ? item.subPageShown : false
175 readonly property var categoryView: item ? item.categoryView : null
175 readonly property var categoryView: item ? item.categoryView : null
176 readonly property var theScope: scope
176 readonly property var theScope: scope
qml/Dash/DashNavigation.qml+159−78
@@ -17,101 +17,182 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19import Dash 0.1
19import Dash 0.1
20−import "../Components"
21
20
22Item {
21Item {
23 id: root
22 id: root
24 objectName: "dashNavigation"
23 objectName: "dashNavigation"
25
24
25+ // set by parent
26 property var scope: null
26 property var scope: null
27− property var scopeStyle: null
27+ property real availableHeight
28−
28+
29− property alias windowWidth: blackRect.width
29+ signal leafClicked()
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− }
40
30
41− // FIXME this is only here for highlight purposes (see Background.qml, too)
31+ // internal
42− readonly property var background: backgroundItem
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
43
35
44 visible: height != 0
36 visible: height != 0
45− height: navigationButton.currentNavigation || altNavigationButton.currentNavigation ? units.gu(5) : 0
37+ implicitHeight: scope && scope.hasNavigation ? navigationListView.y + navigationListView.height : 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− }
56
38
57− Rectangle {
39+ function resetNavigation() {
58− id: blackRect
40+ if (navigationModel.count > 1) {
59− objectName: "blackRect"
41+ clear();
60− color: "black"
42+ }
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
65 }
43 }
66
44
67− Background {
45+ Column {
68− id: backgroundItem
46+ id: headersColumn
69− anchors.fill: parent
47+ anchors {
70− style: scopeStyle ? scopeStyle.navigationBackground : "color:///#f5f5f5"
48+ top: parent.top
49+ left: parent.left
50+ right: parent.right
71− }
51+ }
72
52
73− Image {
53+ function pop(popsNeeded) {
74− fillMode: Image.Stretch
54+ if (popsNeeded == 0)
75− source: scopeStyle.backgroundLuminance > 0.2 ? "graphics/navigation_shadow.png" : "graphics/navigation_shadow_light.png"
55+ return;
76− anchors { top: parent.bottom; left: parent.left; right: parent.right }
56+ isEnteringChildren = false;
77− }
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
78
81
79− DashNavigationButton {
82+ onBackClicked: {
80− id: altNavigationButton
83+ scope.setNavigationState(parentNavigationId);
81− objectName: "altNavigationButton"
84+
82− height: root.height
85+ var popsNeeded = headersModel.count - index;
83− width: d.buttonWidth
86+ headersColumn.pop(popsNeeded);
84− scope: root.scope
87+ }
85− scopeStyle: root.scopeStyle
88+
86− foregroundColor: d.foregroundColor
89+ onTextClicked: {
87− listView.width: d.navigationWidth
90+ scope.setNavigationState(navigationId);
88− isAltNavigation: true
91+
89− showDivider: navigationButton.visible || root.width > d.navigationWidth
92+ var popsNeeded = headersModel.count - index - 1;
90− // needed so that InverseMouseArea is above navigationButton
93+ headersColumn.pop(popsNeeded);
91− z: listView.height > 0 ? 1 : 0
94+
95+ root.leafClicked();
96+ }
97+ }
98+ }
92 }
99 }
93
100
94− DashNavigationButton {
101+ ListView {
95− id: navigationButton
102+ id: navigationListView
96− objectName: "navigationButton"
103+ objectName: "navigationListView"
97− height: root.height
104+ orientation: ListView.Horizontal
98− width: altNavigationButton.visible ? d.buttonWidth : d.navigationWidth
105+ interactive: false
99− x: altNavigationButton.visible ? d.buttonWidth : 0
106+ model: ListModel {
100− scope: root.scope
107+ id: navigationModel
101− scopeStyle: root.scopeStyle
108+ // We have two roles
102− foregroundColor: d.foregroundColor
109+ // navigationId: the navigation id of the navigation the list represents
103− listView.width: d.navigationWidth
110+ // nullifyNavigation: overrides navigationId to be null
104− listView.x: -x
111+ // This is used to "clear" the delegate when going "right" on the tree
105− showDivider: root.width > d.navigationWidth
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+ }
106 }
170 }
107
171
108− Image {
172+ property bool isFirstLoad: false
109− fillMode: Image.Stretch
173+ onScopeChanged: clear();
110− source: backgroundItem.luminance > 0.7 ? "graphics/navigation_shadow.png" : "graphics/navigation_shadow_light.png"
174+ function clear() {
111− x: navigationButton.listView.height > 0 ? altNavigationButton.x : navigationButton.x
175+ navigationModel.clear();
112− width: d.buttonWidth
176+ headersModel.clear();
113− rotation: 180
177+ isFirstLoad = true;
114− anchors.bottom: parent.bottom
178+ }
115− visible: d.bothVisible && (navigationButton.listView.height > 0 || altNavigationButton.listView.height > 0)
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();
116 }
196 }
197+ onCurrentNavigationChanged: setNewNavigation();
117}
198}
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 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19−import Ubuntu.Components.ListItems 1.3 as ListItem
20import "../Components"
19import "../Components"
21
20
22Item {
21Item {
23 id: root
22 id: root
24 property var navigation: null
23 property var navigation: null
25 property var currentNavigation: null
24 property var currentNavigation: null
26− property var scopeStyle: null
27− property color foregroundColor: theme.palette.normal.baseText
28− signal enterNavigation(var newNavigationId, bool hasChildren)
25+ signal enterNavigation(var newNavigationId, string newNavigationLabel, bool hasChildren)
29− signal goBackToParentClicked()
30− signal allNavigationClicked()
31
26
32 readonly property int itemHeight: units.gu(5)
27 readonly property int itemHeight: units.gu(5)
33 implicitHeight: flickable.contentHeight
28 implicitHeight: flickable.contentHeight
34
29
35− Background {
36− style: root.scopeStyle ? root.scopeStyle.navigationBackground : "color:///#f5f5f5"
37− anchors.fill: parent
38− }
39−
40 clip: true
30 clip: true
41
31
42 Behavior on height {
32 Behavior on height {
@@ -59,89 +49,45 @@ Item {
59 id: column
49 id: column
60 width: parent.width
50 width: parent.width
61
51
62− ListItem.Standard {
52+ Repeater {
63− id: backButton
64− objectName: "backButton"
65− visible: navigation && !navigation.isRoot || false
53+ model: navigation && navigation.loaded ? navigation : null
54+ clip: true
55+ delegate: ListItem {
56+ objectName: root.objectName + "child" + index
66− height: itemHeight
57+ height: root.itemHeight
67−
58+ width: column.width
68− onClicked: root.goBackToParentClicked();
69−
70− Icon {
71− id: backImage
72 anchors {
59 anchors {
73− verticalCenter: parent.verticalCenter
60+ left: column.left
74− left: parent.left
61+ right: column.right
75 leftMargin: units.gu(2)
62 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)
89 rightMargin: units.gu(2)
63 rightMargin: units.gu(2)
90 }
64 }
91− text: navigation ? navigation.parentLabel : ""
92− color: root.foregroundColor
93− wrapMode: Text.Wrap
94− maximumLineCount: 2
95− elide: Text.ElideMiddle
96− }
97− }
98
65
99− ListItem.Standard {
66+ onClicked: root.enterNavigation(navigationId, allLabel != "" ? allLabel : label, hasChildren)
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
104
67
105− Label {
68+ Icon {
69+ id: leftIcon
106− anchors {
70+ anchors {
107− verticalCenter: parent.verticalCenter
71+ verticalCenter: parent.verticalCenter
108− left: parent.left
72+ left: parent.left
109− right: parent.right
73+ }
110− leftMargin: units.gu(2)
74+ height: units.gu(2)
111− rightMargin: units.gu(2)
75+ width: height
76+ name: "tick"
77+ color: "#3EB34F"
78+ visible: isActive
112 }
79 }
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)
134
80
135 Label {
81 Label {
136 anchors {
82 anchors {
137 verticalCenter: parent.verticalCenter
83 verticalCenter: parent.verticalCenter
138− left: parent.left
84+ left: leftIcon.right
139− leftMargin: units.gu(2)
85+ leftMargin: units.gu(1)
140− right: rightIcon.visible ? rightIcon.left : parent.right
86+ right: rightIcon.left
141− rightMargin: rightIcon.visible ? units.gu(0.5) : units.gu(2)
87+ rightMargin: units.gu(2)
142 }
88 }
143 text: label
89 text: label
144− color: root.foregroundColor
90+ color: isActive ? "#333333" : "#888888"
145 wrapMode: Text.Wrap
91 wrapMode: Text.Wrap
146 maximumLineCount: 2
92 maximumLineCount: 2
147 elide: Text.ElideMiddle
93 elide: Text.ElideMiddle
@@ -152,14 +98,14 @@ Item {
152 anchors {
98 anchors {
153 verticalCenter: parent.verticalCenter
99 verticalCenter: parent.verticalCenter
154 right: parent.right
100 right: parent.right
155− rightMargin: units.gu(2)
156 }
101 }
157 height: units.gu(2)
102 height: units.gu(2)
158 width: height
103 width: height
159− name: hasChildren ? "go-next" : "tick"
104+ name: "go-next"
160− color: root.foregroundColor
161− visible: hasChildren || isActive
105+ visible: hasChildren
162 }
106 }
107+
108+ divider.visible: index != navigation.count - 1
163 }
109 }
164 }
110 }
165 }
111 }
qml/Dash/DashPageHeader.qml+139−131
@@ -17,41 +17,45 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19import Ubuntu.Components.Themes.Ambiance 1.3
19import Ubuntu.Components.Themes.Ambiance 1.3
20−import Ubuntu.Components.Popups 1.3
21import Ubuntu.Components.ListItems 1.3
20import Ubuntu.Components.ListItems 1.3
22import "../Components"
21import "../Components"
23−import "../Components/SearchHistoryModel"
24
22
25Item {
23Item {
26 id: root
24 id: root
27 objectName: "pageHeader"
25 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
29
28
29+ property int activeFiltersCount: 0
30+ property bool scopeHasFilters: false
30 property bool showBackButton: false
31 property bool showBackButton: false
31 property bool backIsClose: false
32 property bool backIsClose: false
32 property string title
33 property string title
34+ property var extraPanel
35+ property string navigationTag
33
36
34 property bool storeEntryEnabled: false
37 property bool storeEntryEnabled: false
35 property bool searchEntryEnabled: false
38 property bool searchEntryEnabled: false
36 property bool settingsEnabled: false
39 property bool settingsEnabled: false
37 property bool favoriteEnabled: false
40 property bool favoriteEnabled: false
38 property bool favorite: false
41 property bool favorite: false
39− property ListModel searchHistory: SearchHistoryModel
42+ property ListModel searchHistory
40 property alias searchQuery: searchTextField.text
43 property alias searchQuery: searchTextField.text
41 property alias searchHint: searchTextField.placeholderText
44 property alias searchHint: searchTextField.placeholderText
42 property bool showSignatureLine: true
45 property bool showSignatureLine: true
43
46
44− property alias bottomItem: bottomContainer.children
45 property int paginationCount: 0
47 property int paginationCount: 0
46 property int paginationIndex: -1
48 property int paginationIndex: -1
47
49
48 property var scopeStyle: null
50 property var scopeStyle: null
49
51
52+ signal clearSearch(bool keepPanelOpen)
50 signal backClicked()
53 signal backClicked()
51 signal storeClicked()
54 signal storeClicked()
52 signal settingsClicked()
55 signal settingsClicked()
53 signal favoriteClicked()
56 signal favoriteClicked()
54 signal searchTextFieldFocused()
57 signal searchTextFieldFocused()
58+ signal showFiltersPopup(var item)
55
59
56 onScopeStyleChanged: refreshLogo()
60 onScopeStyleChanged: refreshLogo()
57 onSearchQueryChanged: {
61 onSearchQueryChanged: {
@@ -60,6 +64,12 @@ Item {
60 headerContainer.showSearch = true;
64 headerContainer.showSearch = true;
61 }
65 }
62 }
66 }
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+ }
63
73
64 function triggerSearch() {
74 function triggerSearch() {
65 if (searchEntryEnabled) {
75 if (searchEntryEnabled) {
@@ -69,12 +79,14 @@ Item {
69 }
79 }
70
80
71 function closePopup(keepFocus) {
81 function closePopup(keepFocus) {
72− if (headerContainer.popover != null) {
82+ if (extraPanel.visible) {
73− headerContainer.popover.unfocusOnDestruction = !keepFocus;
83+ extraPanel.visible = false;
74− PopupUtils.close(headerContainer.popover);
75 } else if (!keepFocus) {
84 } else if (!keepFocus) {
76 unfocus();
85 unfocus();
77 }
86 }
87+ if (!searchTextField.text && !root.navigationTag && searchHistory.count == 0) {
88+ headerContainer.showSearch = false;
89+ }
78 }
90 }
79
91
80 function resetSearch(keepFocus) {
92 function resetSearch(keepFocus) {
@@ -87,22 +99,17 @@ Item {
87
99
88 function unfocus() {
100 function unfocus() {
89 searchTextField.focus = false;
101 searchTextField.focus = false;
90− if (!searchTextField.text) {
102+ if (!searchTextField.text && !root.navigationTag) {
91 headerContainer.showSearch = false;
103 headerContainer.showSearch = false;
92 }
104 }
93 }
105 }
94
106
95− function openSearchHistory() {
107+ function openPopup() {
96 if (openSearchAnimation.running) {
108 if (openSearchAnimation.running) {
97− openSearchAnimation.openSearchHistory = true;
109+ openSearchAnimation.openPopup = true;
98− } else if (root.searchHistory.count > 0 && headerContainer.popover == null) {
110+ } else if (extraPanel.hasContents) {
99− headerContainer.popover = PopupUtils.open(popoverComponent, searchTextField,
111+ // Show extraPanel
100− {
112+ extraPanel.visible = true;
101− "contentWidth": searchTextField.width,
102− "edgeMargins": units.gu(1)
103− }
104− );
105− searchTextField.forceActiveFocus();
106 }
113 }
107 }
114 }
108
115
@@ -121,13 +128,11 @@ Item {
121 }
128 }
122
129
123 InverseMouseArea {
130 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) }
125 visible: headerContainer.showSearch
132 visible: headerContainer.showSearch
126 onPressed: {
133 onPressed: {
134+ extraPanel.visible = false;
127 closePopup(/* keepFocus */false);
135 closePopup(/* keepFocus */false);
128− if (!searchTextField.text) {
129− headerContainer.showSearch = false;
130− }
131 mouse.accepted = false;
136 mouse.accepted = false;
132 }
137 }
133 }
138 }
@@ -143,7 +148,6 @@ Item {
143 contentY: showSearch ? 0 : height
148 contentY: showSearch ? 0 : height
144
149
145 property bool showSearch: false
150 property bool showSearch: false
146− property var popover: null
147
151
148 Background {
152 Background {
149 id: background
153 id: background
@@ -154,12 +158,12 @@ Item {
154 Behavior on contentY {
158 Behavior on contentY {
155 UbuntuNumberAnimation {
159 UbuntuNumberAnimation {
156 id: openSearchAnimation
160 id: openSearchAnimation
157− property bool openSearchHistory: false
161+ property bool openPopup: false
158
162
159 onRunningChanged: {
163 onRunningChanged: {
160− if (!running && openSearchAnimation.openSearchHistory) {
164+ if (!running && openSearchAnimation.openPopup) {
161− openSearchAnimation.openSearchHistory = false;
165+ openSearchAnimation.openPopup = false;
162− root.openSearchHistory();
166+ root.openPopup();
163 }
167 }
164 }
168 }
165 }
169 }
@@ -183,60 +187,122 @@ Item {
183 backgroundColor: "transparent"
187 backgroundColor: "transparent"
184 config: PageHeadConfiguration {
188 config: PageHeadConfiguration {
185 foregroundColor: root.scopeStyle ? root.scopeStyle.headerForeground : theme.palette.normal.baseText
189 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− }
193 }
190 }
194− property var contents: TextField {
191+ property var contents: Item {
192+ anchors.fill: parent
193+
194+ TextField {
195− id: searchTextField
195+ id: searchTextField
196− objectName: "searchTextField"
196+ objectName: "searchTextField"
197− inputMethodHints: Qt.ImhNoPredictiveText
197+ inputMethodHints: Qt.ImhNoPredictiveText
198− hasClearButton: false
198+ hasClearButton: false
199− anchors {
199+ anchors {
200− fill: parent
200+ top: parent.top
201− leftMargin: units.gu(1)
202− topMargin: units.gu(1)
201+ topMargin: units.gu(1)
202+ left: parent.left
203+ bottom: parent.bottom
203− bottomMargin: units.gu(1)
204+ bottomMargin: units.gu(1)
204− rightMargin: root.width > units.gu(60) ? root.width - units.gu(40) : units.gu(1)
205+ right: settingsButton.left
206+ rightMargin: settingsButton.visible ? 0 : units.gu(2)
205− }
207+ }
206
208
207− secondaryItem: AbstractButton {
209+ primaryItem: Rectangle {
208− height: searchTextField.height
210+ color: "#F5F4F5"
209− width: height
211+ width: root.navigationTag != "" ? tagLabel.width + units.gu(2) : 0
210− enabled: searchTextField.text.length > 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+ }
211
221
222+ secondaryItem: AbstractButton {
223+ id: clearButton
224+ height: searchTextField.height
225+ width: height
226+ enabled: searchTextField.text.length > 0 || root.navigationTag != ""
227+
212− Image {
228+ Image {
213− objectName: "clearIcon"
229+ objectName: "clearIcon"
214− anchors.fill: parent
230+ anchors.fill: parent
215− anchors.margins: units.gu(.75)
231+ anchors.margins: units.gu(1)
216− source: "image://theme/clear"
232+ source: "image://theme/clear"
217− opacity: searchTextField.text.length > 0
233+ opacity: parent.enabled
218− visible: opacity > 0
234+ visible: opacity > 0
219− Behavior on opacity {
235+ Behavior on opacity {
220− UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
236+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
237+ }
238+ }
239+
240+ onClicked: {
241+ root.clearSearch(true);
221 }
242 }
222 }
243 }
223
244
224− onClicked: {
245+ onActiveFocusChanged: {
225− root.resetSearch(true);
246+ if (activeFocus) {
226− root.openSearchHistory();
247+ root.searchTextFieldFocused();
248+ root.openPopup();
249+ }
250+ }
251+
252+ onTextChanged: {
253+ if (text != "") {
254+ closePopup(/* keepFocus */true);
255+ }
227 }
256 }
228 }
257 }
229
258
230− onActiveFocusChanged: {
259+ AbstractButton {
231− if (activeFocus) {
260+ id: settingsButton
232− root.searchTextFieldFocused();
261+
233− root.openSearchHistory();
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);
234 }
280 }
235 }
281 }
236
282
237− onTextChanged: {
283+ AbstractButton {
238− if (text != "") {
284+ id: cancelButton
239− closePopup(/* keepFocus */true);
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+ }
240 }
306 }
241 }
307 }
242 }
308 }
@@ -322,47 +388,6 @@ Item {
322 }
388 }
323 }
389 }
324
390
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−
366 Rectangle {
391 Rectangle {
367 id: bottomBorder
392 id: bottomBorder
368 visible: showSignatureLine
393 visible: showSignatureLine
@@ -370,7 +395,7 @@ Item {
370 top: headerContainer.bottom
395 top: headerContainer.bottom
371 left: parent.left
396 left: parent.left
372 right: parent.right
397 right: parent.right
373− bottom: bottomContainer.top
398+ bottom: parent.bottom
374 }
399 }
375
400
376 color: root.scopeStyle ? root.scopeStyle.headerDividerColor : "#e0e0e0"
401 color: root.scopeStyle ? root.scopeStyle.headerDividerColor : "#e0e0e0"
@@ -411,7 +436,7 @@ Item {
411 id: bottomHighlight
436 id: bottomHighlight
412 visible: bottomBorder.visible
437 visible: bottomBorder.visible
413 anchors {
438 anchors {
414− top: bottomContainer.top
439+ top: parent.bottom
415 left: parent.left
440 left: parent.left
416 right: parent.right
441 right: parent.right
417 }
442 }
@@ -419,30 +444,13 @@ Item {
419 height: units.dp(1)
444 height: units.dp(1)
420 opacity: 0.6
445 opacity: 0.6
421
446
422− // FIXME this should be a shader when bottomItem exists
423− // to support image backgrounds
424 Rectangle {
447 Rectangle {
425 anchors.fill: parent
448 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) {
431 Qt.lighter(Qt.rgba(root.scopeStyle.background.r,
450 Qt.lighter(Qt.rgba(root.scopeStyle.background.r,
432 root.scopeStyle.background.g,
451 root.scopeStyle.background.g,
433 root.scopeStyle.background.b, 1.0), 1.2);
452 root.scopeStyle.background.b, 1.0), 1.2);
434 } else "#CCFFFFFF"
453 } else "#CCFFFFFF"
435 }
454 }
436 }
455 }
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− }
448}
456}
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 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import Ubuntu.Components.Popups 1.3
20+import "../Components/SearchHistoryModel"
19import Utils 0.1
21import Utils 0.1
20import Unity 0.2
22import Unity 0.2
21import Dash 0.1
23import Dash 0.1
@@ -25,7 +27,6 @@ import "../Components/ListItems" as ListItems
25FocusScope {
27FocusScope {
26 id: scopeView
28 id: scopeView
27
29
28− readonly property bool navigationDisableParentInteractive: pageHeaderLoader.item ? pageHeaderLoader.item.bottomItem[0].disableParentInteractive : false
29 property bool forceNonInteractive: false
30 property bool forceNonInteractive: false
30 property var scope: null
31 property var scope: null
31 property UnitySortFilterProxyModel categories: categoryFilter
32 property UnitySortFilterProxyModel categories: categoryFilter
@@ -34,14 +35,15 @@ FocusScope {
34 property bool hasBackAction: false
35 property bool hasBackAction: false
35 property bool enableHeightBehaviorOnNextCreation: false
36 property bool enableHeightBehaviorOnNextCreation: false
36 property var categoryView: categoryView
37 property var categoryView: categoryView
37− property bool showPageHeader: true
38 readonly property alias subPageShown: subPageLoader.subPageShown
38 readonly property alias subPageShown: subPageLoader.subPageShown
39+ readonly property alias extraPanelShown: peExtraPanel.visible
39 property int paginationCount: 0
40 property int paginationCount: 0
40 property int paginationIndex: 0
41 property int paginationIndex: 0
41 property bool visibleToParent: false
42 property bool visibleToParent: false
42 property alias pageHeaderTotallyVisible: categoryView.pageHeaderTotallyVisible
43 property alias pageHeaderTotallyVisible: categoryView.pageHeaderTotallyVisible
43 property var holdingList: null
44 property var holdingList: null
44 property bool wasCurrentOnMoveStart: false
45 property bool wasCurrentOnMoveStart: false
46+ property var filtersPopover: null
45
47
46 property var scopeStyle: ScopeStyle {
48 property var scopeStyle: ScopeStyle {
47 style: scope ? scope.customizations : {}
49 style: scope ? scope.customizations : {}
@@ -68,8 +70,7 @@ FocusScope {
68 }
70 }
69
71
70 function resetSearch() {
72 function resetSearch() {
71− if(pageHeaderLoader.item && showPageHeader)
73+ categoryView.pageHeader.resetSearch()
72− pageHeaderLoader.item.resetSearch()
73 }
74 }
74
75
75 property var maybePreviewResult;
76 property var maybePreviewResult;
@@ -120,24 +121,26 @@ FocusScope {
120 if (!holdingList || !holdingList.moving) {
121 if (!holdingList || !holdingList.moving) {
121 wasCurrentOnMoveStart = scopeView.isCurrent;
122 wasCurrentOnMoveStart = scopeView.isCurrent;
122 }
123 }
123− if (pageHeaderLoader.item && showPageHeader) {
124+ categoryView.pageHeader.resetSearch();
124− pageHeaderLoader.item.resetSearch();
125− }
126 subPageLoader.closeSubPage();
125 subPageLoader.closeSubPage();
126+ if (filtersPopover) {
127+ PopupUtils.close(filtersPopover)
128+ scopeView.filtersPopover = null;
129+ }
127 }
130 }
128
131
129 Binding {
132 Binding {
130 target: scopeView.scope
133 target: scopeView.scope
131 property: "searchQuery"
134 property: "searchQuery"
132− value: pageHeaderLoader.item ? pageHeaderLoader.item.searchQuery : ""
135+ value: categoryView.pageHeader.searchQuery
133− when: isCurrent && showPageHeader
136+ when: isCurrent
134 }
137 }
135
138
136 Binding {
139 Binding {
137− target: pageHeaderLoader.item
140+ target: categoryView.pageHeader
138 property: "searchQuery"
141 property: "searchQuery"
139 value: scopeView.scope ? scopeView.scope.searchQuery : ""
142 value: scopeView.scope ? scopeView.scope.searchQuery : ""
140− when: isCurrent && showPageHeader
143+ when: isCurrent
141 }
144 }
142
145
143 Connections {
146 Connections {
@@ -189,8 +192,8 @@ FocusScope {
189 property string expandedCategoryId: ""
192 property string expandedCategoryId: ""
190 property int runMaximizeAfterSizeChanges: 0
193 property int runMaximizeAfterSizeChanges: 0
191
194
192− readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
195+ readonly property bool pageHeaderTotallyVisible:
193− ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
196+ ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == categoryView.pageHeader.height))
194
197
195 onExpandedCategoryIdChanged: {
198 onExpandedCategoryIdChanged: {
196 var firstCreated = firstCreatedIndex();
199 var firstCreated = firstCreatedIndex();
@@ -619,41 +622,68 @@ FocusScope {
619 }
622 }
620 }
623 }
621
624
622− pageHeader: scopeView.showPageHeader ? pageHeaderLoader : null
625+ pageHeader: DashPageHeader {
623− Loader {
626+ objectName: "scopePageHeader"
624− id: pageHeaderLoader
625 width: parent.width
627 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 : ""
628+ title: scopeView.scope ? scopeView.scope.name : ""
629+ extraPanel: peExtraPanel
630+ searchHistory: SearchHistoryModel
633− searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.ctr("Label: Hint for dash search line edit", "Search")
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
634+ showBackButton: scopeView.hasBackAction
635− searchEntryEnabled: true
635+ searchEntryEnabled: true
636− settingsEnabled: scopeView.scope && scopeView.scope.settings && scopeView.scope.settings.count > 0 || false
636+ settingsEnabled: scopeView.scope && scopeView.scope.settings && scopeView.scope.settings.count > 0 || false
637− favoriteEnabled: scopeView.scope && scopeView.scope.id !== "clickscope"
637+ favoriteEnabled: scopeView.scope && scopeView.scope.id !== "clickscope"
638− favorite: scopeView.scope && scopeView.scope.favorite
638+ favorite: scopeView.scope && scopeView.scope.favorite
639− scopeStyle: scopeView.scopeStyle
639+ navigationTag: scopeView.scope ? scopeView.scope.primaryNavigationTag : ""
640− paginationCount: scopeView.paginationCount
640+ scopeStyle: scopeView.scopeStyle
641− paginationIndex: scopeView.paginationIndex
641+ paginationCount: scopeView.paginationCount
642−
642+ paginationIndex: scopeView.paginationIndex
643− bottomItem: DashNavigation {
643+
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()
644+ onBackClicked: scopeView.backClicked()
652− onSettingsClicked: subPageLoader.openSubPage("settings")
645+ onSettingsClicked: subPageLoader.openSubPage("settings")
653− onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
646+ onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
654− onSearchTextFieldFocused: scopeView.showHeader()
647+ onSearchTextFieldFocused: scopeView.showHeader()
648+ onClearSearch: { // keepPanelOpen
649+ var panelOpen = peExtraPanel.visible;
650+ resetSearch(keepPanelOpen);
651+ scopeView.scope.resetPrimaryNavigationTag();
652+ peExtraPanel.resetNavigation();
653+ if ((panelOpen || searchHistory.count > 0) && keepPanelOpen) {
654+ openPopup();
655 }
655 }
656 }
656 }
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+ }
657 }
687 }
658 }
688 }
659
689
@@ -662,7 +692,7 @@ FocusScope {
662 anchors.left: parent.left
692 anchors.left: parent.left
663 anchors.right: parent.right
693 anchors.right: parent.right
664 anchors.bottom: parent.bottom
694 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
666 clip: true
696 clip: true
667
697
668 PullToRefresh {
698 PullToRefresh {
@@ -797,7 +827,7 @@ FocusScope {
797 open = true;
827 open = true;
798 }
828 }
799
829
800− onOpenChanged: pageHeaderLoader.item.unfocus()
830+ onOpenChanged: categoryView.pageHeader.unfocus()
801
831
802 onVisibleChanged: if (!visible) subPage = ""
832 onVisibleChanged: if (!visible) subPage = ""
803
833
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
29 fake_resultsmodel.cpp
29 fake_resultsmodel.cpp
30 fake_previewmodel.cpp
30 fake_previewmodel.cpp
31 fake_previewwidgetmodel.cpp
31 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
32 fake_unity_plugin.cpp
38 fake_unity_plugin.cpp
33 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/CategoriesInterface.h
39 ${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
34 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/NavigationInterface.h
42 ${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
35 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
45 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
36 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewWidgetModelInterface.h
46 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewWidgetModelInterface.h
47+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/RangeInputFilterInterface.h
37 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ResultsModelInterface.h
48 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ResultsModelInterface.h
38 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopeInterface.h
49 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopeInterface.h
39 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopesInterface.h
50 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopesInterface.h
40 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/SettingsModelInterface.h
51 ${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
41)
54)
42
55
43# Workaround for gcc failure LP: #1417664
56# 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
32{
32{
33 QTimer::singleShot(1500, this, &Navigation::slotLoaded);
33 QTimer::singleShot(1500, this, &Navigation::slotLoaded);
34 connect(scope, &Scope::currentNavigationIdChanged, this, &Navigation::slotCurrentNavigationChanged);
34 connect(scope, &Scope::currentNavigationIdChanged, this, &Navigation::slotCurrentNavigationChanged);
35− connect(scope, &Scope::currentAltNavigationIdChanged, this, &Navigation::slotCurrentNavigationChanged);
36}
35}
37
36
38QString Navigation::navigationId() const
37QString Navigation::navigationId() const
@@ -78,17 +77,17 @@ int Navigation::count() const
78
77
79bool Navigation::isRoot() const
78bool Navigation::isRoot() const
80{
79{
81− return m_navigationId == "root" || m_navigationId == "altroot";
80+ return m_navigationId == "root";
82}
81}
83
82
84bool Navigation::hidden() const
83bool Navigation::hidden() const
85{
84{
86− return m_navigationId == "altroot";
85+ return false;
87}
86}
88
87
89int Navigation::rowCount(const QModelIndex & /*parent*/) const
88int Navigation::rowCount(const QModelIndex & /*parent*/) const
90{
89{
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")
92 return 0;
91 return 0;
93 else
92 else
94 return 8;
93 return 8;
@@ -102,16 +101,16 @@ QVariant Navigation::data(const QModelIndex &index, int role) const
102 return QString("middle%1").arg(index.row());
101 return QString("middle%1").arg(index.row());
103 else if (m_navigationId.startsWith("middle"))
102 else if (m_navigationId.startsWith("middle"))
104 return QString("child%1%2").arg(m_navigationId).arg(index.row());
103 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());
105 case RoleLabel:
106 case RoleLabel:
106 return QString("%1Child%2").arg(m_navigationId).arg(index.row());
107 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());
107 case RoleHasChildren:
110 case RoleHasChildren:
108− return m_navigationId == "root" && index.row() != 3;
111+ return (m_navigationId == "root" && index.row() != 3) || (m_navigationId == "middle4");
109 case RoleIsActive:
112 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);
113+ return m_scope->currentNavigationId() == data(index, RoleNavigationId);
114− }
115 }
114 }
116 return QVariant();
115 return QVariant();
117}
116}
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 @@
1/*
1/*
2− * Copyright (C) 2013, 2014 Canonical, Ltd.
2+ * Copyright (C) 2013, 2014, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,9 @@
19
19
20#include "fake_scope.h"
20#include "fake_scope.h"
21
21
22+#include "fake_filters.h"
22#include "fake_navigation.h"
23#include "fake_navigation.h"
24+#include "fake_optionselectorfilter.h"
23#include "fake_resultsmodel.h"
25#include "fake_resultsmodel.h"
24#include "fake_scopes.h"
26#include "fake_scopes.h"
25#include "fake_settingsmodel.h"
27#include "fake_settingsmodel.h"
@@ -37,14 +39,18 @@ Scope::Scope(QString const& id, QString const& name, bool favorite, Scopes* pare
37 , m_searching(false)
39 , m_searching(false)
38 , m_favorite(favorite)
40 , m_favorite(favorite)
39 , m_isActive(false)
41 , m_isActive(false)
42+ , m_hasNavigation(true)
43+ , m_hasPrimaryFilter(true)
40 , m_currentNavigationId("root")
44 , m_currentNavigationId("root")
41− , m_currentAltNavigationId("altrootChild1")
42 , m_previewRendererName("preview-generic")
45 , m_previewRendererName("preview-generic")
43 , m_categories(new Categories(categories, this))
46 , m_categories(new Categories(categories, this))
44 , m_openScope(nullptr)
47 , m_openScope(nullptr)
45 , m_settings(new SettingsModel(this))
48 , m_settings(new SettingsModel(this))
49+ , m_filters(new Filters(this))
46 , m_returnNullPreview(returnNullPreview)
50 , m_returnNullPreview(returnNullPreview)
47{
51{
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);
48}
54}
49
55
50QString Scope::id() const
56QString Scope::id() const
@@ -229,17 +235,13 @@ QString Scope::currentNavigationId() const
229
235
230bool Scope::hasNavigation() const
236bool Scope::hasNavigation() const
231{
237{
232− return true;
238+ return m_hasNavigation;
233}
239}
234
240
235−QString Scope::currentAltNavigationId() const
241+void Scope::setHasNavigation(bool hasNavigation)
236{
242{
237− return m_currentAltNavigationId;
243+ m_hasNavigation = hasNavigation;
238−}
244+ Q_EMIT hasNavigationChanged();
239−
240−bool Scope::hasAltNavigation() const
241−{
242− return true;
243}
245}
244
246
245Scope::Status Scope::status() const
247Scope::Status Scope::status() const
@@ -276,6 +278,11 @@ void Scope::refresh()
276 Q_EMIT refreshed();
278 Q_EMIT refreshed();
277}
279}
278
280
281+void Scope::resetFilters()
282+{
283+ qWarning() << "Scope::resetFilters is unimplemented";
284+}
285+
279void Scope::activateAction(QVariant const& /*result*/, QString const& /*categoryId*/, QString const& /*actionId*/)
286void Scope::activateAction(QVariant const& /*result*/, QString const& /*categoryId*/, QString const& /*actionId*/)
280{
287{
281 qFatal("Using Scope::activateAction");
288 qFatal("Using Scope::activateAction");
@@ -298,31 +305,49 @@ unity::shell::scopes::NavigationInterface* Scope::getNavigation(const QString& i
298 return new Navigation(id, id, "all"+id, parentId, parentLabel, this);
305 return new Navigation(id, id, "all"+id, parentId, parentLabel, this);
299}
306}
300
307
301−unity::shell::scopes::NavigationInterface* Scope::getAltNavigation(QString const& id)
308+void Scope::setNavigationState(const QString &navigationId)
302{
309{
303− if (id.isEmpty())
310+ m_currentNavigationId = navigationId;
304− return nullptr;
311+ Q_EMIT currentNavigationIdChanged();
312+ Q_EMIT primaryNavigationTagChanged();
313+}
305
314
306− QString parentId;
315+unity::shell::scopes::FilterBaseInterface* Scope::primaryNavigationFilter() const
307− QString parentLabel;
316+{
308− if (id != "altroot") {
317+ return m_hasPrimaryFilter ? m_primaryNavigationFilter : nullptr;
309− parentId = "altroot";
310− parentLabel = "altroot";
311− }
312− return new Navigation(id, id, "all"+id, parentId, parentLabel, this);
313}
318}
314
319
315−void Scope::setNavigationState(const QString &navigationId, bool isAltNavigation)
320+unity::shell::scopes::FiltersInterface* Scope::filters() const
316{
321{
317− if (isAltNavigation) {
322+ return m_filters;
318− m_currentAltNavigationId = navigationId;
323+}
319− Q_EMIT currentAltNavigationIdChanged();
324+
325+QString Scope::primaryNavigationTag() const
326+{
327+ if (m_currentNavigationId == "root")
328+ return QString();
320− } else {
329+ else
321− m_currentNavigationId = navigationId;
330+ return const_cast<Scope*>(this)->getNavigation(m_currentNavigationId)->label();
322− Q_EMIT currentNavigationIdChanged();
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");
323 }
342 }
324}
343}
325
344
345+void Scope::setHasPrimaryFilter(bool hasPrimaryFilter)
346+{
347+ m_hasPrimaryFilter = hasPrimaryFilter;
348+ Q_EMIT primaryNavigationFilterChanged();
349+}
350+
326void Scope::performQuery(const QString& query)
351void Scope::performQuery(const QString& query)
327{
352{
328 Q_EMIT queryPerformed(query);
353 Q_EMIT queryPerformed(query);
tests/mocks/Unity/fake_scope.h+20−7
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013, 2014 Canonical, Ltd.
2+ * Copyright (C) 2013, 2014, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,9 @@
24
24
25#include <QTimer>
25#include <QTimer>
26
26
27+class Filters;
27class Scopes;
28class Scopes;
29+class FakeOptionSelectorFilter;
28
30
29class Scope : public unity::shell::scopes::ScopeInterface
31class Scope : public unity::shell::scopes::ScopeInterface
30{
32{
@@ -68,11 +70,17 @@ public:
68
70
69 QString currentNavigationId() const override;
71 QString currentNavigationId() const override;
70 bool hasNavigation() const override;
72 bool hasNavigation() const override;
71− QString currentAltNavigationId() const override;
72− bool hasAltNavigation() const override;
73 Q_INVOKABLE unity::shell::scopes::NavigationInterface* getNavigation(QString const& navigationId) override;
73 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+
76 void performQuery(const QString& query) override;
84 void performQuery(const QString& query) override;
77
85
78 Status status() const override;
86 Status status() const override;
@@ -80,7 +88,9 @@ public:
80
88
81 Q_INVOKABLE void refresh() override;
89 Q_INVOKABLE void refresh() override;
82
90
91+ Q_INVOKABLE void resetFilters() override;
92+
83− Q_INVOKABLE virtual void activateAction(QVariant const& result, QString const& categoryId, QString const& actionId) override;
93+ Q_INVOKABLE void activateAction(QVariant const& result, QString const& categoryId, QString const& actionId) override;
84
94
85Q_SIGNALS:
95Q_SIGNALS:
86 // These are not in the Interface, here for testing benefits
96 // These are not in the Interface, here for testing benefits
@@ -99,14 +109,17 @@ protected:
99 bool m_searching;
109 bool m_searching;
100 bool m_favorite;
110 bool m_favorite;
101 bool m_isActive;
111 bool m_isActive;
112+ bool m_hasNavigation;
113+ bool m_hasPrimaryFilter;
102 QString m_currentNavigationId;
114 QString m_currentNavigationId;
103− QString m_currentAltNavigationId;
104
115
105 QString m_previewRendererName;
116 QString m_previewRendererName;
106
117
107 unity::shell::scopes::CategoriesInterface* m_categories;
118 unity::shell::scopes::CategoriesInterface* m_categories;
108 unity::shell::scopes::ScopeInterface* m_openScope;
119 unity::shell::scopes::ScopeInterface* m_openScope;
109 unity::shell::scopes::SettingsModelInterface* m_settings;
120 unity::shell::scopes::SettingsModelInterface* m_settings;
121+ Filters* m_filters;
122+ FakeOptionSelectorFilter* m_primaryNavigationFilter;
110
123
111 bool m_returnNullPreview;
124 bool m_returnNullPreview;
112};
125};
tests/mocks/Unity/fake_unity_plugin.cpp+7
@@ -22,11 +22,14 @@
22// local
22// local
23#include "fake_scopes.h"
23#include "fake_scopes.h"
24#include "fake_categories.h"
24#include "fake_categories.h"
25+#include "fake_filters.h"
25#include "fake_navigation.h"
26#include "fake_navigation.h"
27+#include "fake_optionselectoroptions.h"
26#include "fake_previewmodel.h"
28#include "fake_previewmodel.h"
27#include "fake_previewwidgetmodel.h"
29#include "fake_previewwidgetmodel.h"
28#include "fake_resultsmodel.h"
30#include "fake_resultsmodel.h"
29#include "fake_settingsmodel.h"
31#include "fake_settingsmodel.h"
32+#include "fake_valueslidervalues.h"
30
33
31// External
34// External
32#include <glib-object.h>
35#include <glib-object.h>
@@ -52,4 +55,8 @@ void FakeUnityPlugin::registerTypes(const char *uri)
52 qmlRegisterType<ResultsModel>(uri, 0, 2, "FakeResultsModel");
55 qmlRegisterType<ResultsModel>(uri, 0, 2, "FakeResultsModel");
53 qmlRegisterType<PreviewModel>(uri, 0, 2, "FakePreviewModel");
56 qmlRegisterType<PreviewModel>(uri, 0, 2, "FakePreviewModel");
54 qmlRegisterUncreatableType<PreviewWidgetModel>(uri, 0, 2, "PreviewWidgetModel", "Can't create new PreviewWidgetModel in QML. Get them from PreviewModel instance.");
57 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");
55}
62}
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 @@
1template: {"card-layout":"vertical","card-size":"small","category-layout":"grid","collapsed-rows":2}
1template: {"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":{}}
3result: 12.res
3result: 12.res
tests/plugins/Dash/cardcreator/9.res.cardcreator−3
@@ -90,10 +90,7 @@ CardAudioProgress {
90 opacity: 0.9;
90 opacity: 0.9;
91 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
91 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
92 color: "white";
92 color: "white";
93−<<<<<<< TREE
94−=======
95 asynchronous: false;
93 asynchronous: false;
96−>>>>>>> MERGE-SOURCE
97 }
94 }
98 onClicked: {
95 onClicked: {
99 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
96 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
tests/qmltests/CMakeLists.txt+4
@@ -53,6 +53,10 @@ add_unity8_qmltest(Dash/Previews PreviewTextSummary)
53add_unity8_qmltest(Dash/Previews PreviewVideoPlayback)
53add_unity8_qmltest(Dash/Previews PreviewVideoPlayback)
54add_unity8_qmltest(Dash/Previews PreviewWidgetFactory)
54add_unity8_qmltest(Dash/Previews PreviewWidgetFactory)
55add_unity8_qmltest(Dash/Previews PreviewZoomableImage)
55add_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)
56add_unity8_qmltest(Dash/ScopeSettings ScopeSettingBoolean)
60add_unity8_qmltest(Dash/ScopeSettings ScopeSettingBoolean)
57add_unity8_qmltest(Dash/ScopeSettings ScopeSettingList)
61add_unity8_qmltest(Dash/ScopeSettings ScopeSettingList)
58add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
62add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
tests/qmltests/Dash/CardHelpers.js+7−7
@@ -16,7 +16,8 @@
16
16
17.pragma library
17.pragma library
18
18
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"]
20
21
21var defaultLayout = ' \
22var defaultLayout = ' \
22{ \
23{ \
@@ -39,7 +40,7 @@ var defaultLayout = ' \
39 "emblem": null, \
40 "emblem": null, \
40 "summary": null, \
41 "summary": null, \
41 "attributes": { "max-count": 2 },
42 "attributes": { "max-count": 2 },
42− "socialActions": { "max-count": 4 } \
43+ "social-actions": null \
43 }, \
44 }, \
44 "resources": {} \
45 "resources": {} \
45}'
46}'
@@ -54,10 +55,9 @@ var fullMapping = ' \
54 "overlayColor": "overlayColor", \
55 "overlayColor": "overlayColor", \
55 "summary": "summary", \
56 "summary": "summary", \
56 "attributes": "attributes", \
57 "attributes": "attributes", \
57− "socialActions": "socialActions" \
58+ "social-actions": "socialActions" \
58}'
59}'
59
60
60−
61function tryParse(json, errorLabel) {
61function tryParse(json, errorLabel) {
62 var o = undefined;
62 var o = undefined;
63 if (errorLabel !== undefined) {
63 if (errorLabel !== undefined) {
@@ -84,12 +84,12 @@ function mapData(json, layout, errorLabel) {
84 for (var k in components) {
84 for (var k in components) {
85 try {
85 try {
86 if (typeof layout[components[k]] == "object") {
86 if (typeof layout[components[k]] == "object") {
87− d[components[k]] = o[layout[components[k]]['field']];
87+ d[dataKeys[k]] = o[layout[components[k]]['field']];
88 } else {
88 } else {
89− d[components[k]] = o[layout[components[k]]];
89+ d[dataKeys[k]] = o[layout[components[k]]];
90 }
90 }
91 } catch(err) {
91 } catch(err) {
92− d[components[k]] = undefined;
92+ d[dataKeys[k]] = undefined;
93 }
93 }
94 }
94 }
95 }
95 }
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 {
36 "subtitle": "bar",
36 "subtitle": "bar",
37 "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.",
37 "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.",
38 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
38 "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"},
39+ "socialActions": [{"id":"like","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
40− {"id":"share","label":"text2","icon":"image://theme/cancel"},
40+ {"id":"share","icon":"image://theme/cancel"},
41− {"id":"like","label":"text1","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
41+ {"id":"like","icon":"image://theme/ok","temporaryIcon":"image://theme/undo"},
42− {"id":"share","label":"text2","icon":"image://theme/cancel"}],
42+ {"id":"share","icon":"image://theme/cancel"}],
43 "quickPreviewData": {"uri": "/some/file", "duration": "14"}
43 "quickPreviewData": {"uri": "/some/file", "duration": "14"}
44 }'
44 }'
45
45
tests/qmltests/Dash/tst_DashContent.qml+181−153
@@ -215,7 +215,7 @@ Item {
215 verify(pageHeader, "Could not find the scope page header.");
215 verify(pageHeader, "Could not find the scope page header.");
216 var innerHeader = findChild(pageHeader, "innerPageHeader");
216 var innerHeader = findChild(pageHeader, "innerPageHeader");
217 verify(innerHeader, "Could not find the scope page header.");
217 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);
219 }
219 }
220
220
221 function test_is_active_data() {
221 function test_is_active_data() {
@@ -284,14 +284,14 @@ Item {
284 var dashContentList = findChild(dashContent, "dashContentList");
284 var dashContentList = findChild(dashContent, "dashContentList");
285 tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
285 tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
286 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
286 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
287− tryCompare(dashNavigation, "visible", true);
287+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
288− var dashNavigationButton = findChild(dashContentList.currentItem, "navigationButton");
288+ var searchButton = findChild(dashContentList.currentItem, "search_action_button");
289− compare(dashNavigationButton.showList, false);
289+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
290− waitForRendering(dashNavigationButton);
290+ compare(peExtraPanel.visible, false);
291− mouseClick(dashNavigationButton);
291+ mouseClick(searchButton);
292− compare(dashNavigationButton.showList, true);
292+ tryCompare(peExtraPanel, "visible", true);
293−
293+
294− var navigationListView = findChild(dashNavigationButton, "navigationListView");
294+ var navigationListView = findChild(dashNavigation, "navigationListView");
295 tryCompareFunction(function() {
295 tryCompareFunction(function() {
296 return navigationListView.currentItem &&
296 return navigationListView.currentItem &&
297 navigationListView.currentItem.navigation &&
297 navigationListView.currentItem.navigation &&
@@ -300,235 +300,263 @@ Item {
300 waitForRendering(navigationListView);
300 waitForRendering(navigationListView);
301 waitForRendering(navigationListView.currentItem);
301 waitForRendering(navigationListView.currentItem);
302
302
303− var allButton = findChild(dashNavigationButton, "allButton");
304− compare(allButton.visible, false);
305−
306− var navigation = findChild(dashNavigationButton, "navigation0child3");
303+ var navigation = findChild(dashNavigation, "navigation0child3");
307 mouseClick(navigation);
304 mouseClick(navigation);
308− compare(dashNavigationButton.showList, false);
305+ compare(peExtraPanel.visible, false);
309− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle3");
306+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle3");
310 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
307 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
311
308
312− mouseClick(dashNavigationButton);
309+ mouseClick(searchTextField);
313− compare(dashNavigationButton.showList, true);
310+ tryCompare(peExtraPanel, "visible", true);
314 waitForRendering(navigationListView);
311 waitForRendering(navigationListView);
315 waitForRendering(navigationListView.currentItem);
312 waitForRendering(navigationListView.currentItem);
316− compare(allButton.visible, true);
317
313
314+ var header0 = findChild(dashNavigation, "dashNavigationHeader0");
315+ compare(header0.backVisible, false);
318− mouseClick(allButton);
316+ mouseClick(header0);
319− compare(dashNavigationButton.showList, false);
317+ compare(peExtraPanel.visible, false);
320− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "root");
318+ tryCompare(dashNavigation.currentNavigation, "navigationId", "root");
321 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
319 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
322
320
323− mouseClick(dashNavigationButton);
321+ var headerContainer = findChild(dashContentList.currentItem, "headerContainer");
324− compare(dashNavigationButton.showList, true);
322+ tryCompare(headerContainer, "contentY", headerContainer.height);
323+ mouseClick(searchButton);
324+ tryCompare(peExtraPanel, "visible", true);
325 waitForRendering(navigationListView);
325 waitForRendering(navigationListView);
326 waitForRendering(navigationListView.currentItem);
326 waitForRendering(navigationListView.currentItem);
327− compare(allButton.visible, false);
328
327
329− navigation = findChild(dashNavigationButton, "navigation0child2");
328+ navigation = findChild(dashNavigation, "navigation0child2");
330 mouseClick(navigation);
329 mouseClick(navigation);
331− compare(dashNavigationButton.showList, true);
330+ compare(peExtraPanel.visible, true);
332− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
331+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
333 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
332 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
334
333
335− var navigationList1 = findChild(dashNavigationButton, "navigation1");
334+ var navigationList1 = findChild(dashNavigation, "navigation1");
336− allButton = findChild(navigationList1, "allButton");
335+ var header1 = findChild(dashNavigation, "dashNavigationHeader1");
337− var backButton = findChild(findChild(navigationList1, "navigation1"), "backButton");
336+ compare(header0.height, 0);
338− compare(allButton.visible, true);
337+ compare(header1.backVisible, true);
339− compare(backButton.visible, true);
340
338
341 tryCompare(navigationListView, "contentX", navigationList1.x);
339 tryCompare(navigationListView, "contentX", navigationList1.x);
342 waitForRendering(navigationListView);
340 waitForRendering(navigationListView);
343− mouseClick(allButton);
341+ mouseClick(header1);
344− compare(dashNavigationButton.showList, false);
342+ compare(peExtraPanel.visible, false);
345− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
343+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
346 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
344 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
347
345
348− mouseClick(dashNavigationButton);
346+ mouseClick(searchTextField);
349− compare(dashNavigationButton.showList, true);
347+ tryCompare(peExtraPanel, "visible", true);
350 waitForRendering(navigationListView);
348 waitForRendering(navigationListView);
351 waitForRendering(navigationListView.currentItem);
349 waitForRendering(navigationListView.currentItem);
352− compare(allButton.visible, true);
350+ compare(header0.height, 0);
353− compare(backButton.visible, true);
351+ compare(header1.backVisible, true);
354
352
355 tryCompare(navigationList1.navigation, "loaded", true);
353 tryCompare(navigationList1.navigation, "loaded", true);
356− tryCompare(navigationList1, "implicitHeight", navigationList1.itemHeight * 10);
354+ tryCompare(navigationList1, "implicitHeight", navigationList1.itemHeight * 8);
357 tryCompare(navigationList1, "height", navigationList1.implicitHeight);
355 tryCompare(navigationList1, "height", navigationList1.implicitHeight);
358− navigation = findChild(dashNavigationButton, "navigation1child2");
356+ navigation = findChild(dashNavigation, "navigation1child2");
359 mouseClick(navigation);
357 mouseClick(navigation);
360− compare(dashNavigationButton.showList, false);
358+ compare(peExtraPanel.visible, false);
361− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "childmiddle22");
359+ tryCompare(dashNavigation.currentNavigation, "navigationId", "childmiddle22");
362 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
360 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
363
361
364− mouseClick(dashNavigationButton);
362+ mouseClick(searchTextField);
365− compare(dashNavigationButton.showList, true);
363+ tryCompare(peExtraPanel, "visible", true);
366 waitForRendering(navigationListView);
364 waitForRendering(navigationListView);
367 waitForRendering(navigationListView.currentItem);
365 waitForRendering(navigationListView.currentItem);
368
366
369 tryCompare(navigationList1.navigation, "loaded", true);
367 tryCompare(navigationList1.navigation, "loaded", true);
370− navigation = findChild(dashNavigationButton, "navigation1child3");
368+ navigation = findChild(dashNavigation, "navigation1child3");
371 mouseClick(navigation);
369 mouseClick(navigation);
372− compare(dashNavigationButton.showList, false);
370+ compare(peExtraPanel.visible, false);
373− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "childmiddle23");
371+ tryCompare(dashNavigation.currentNavigation, "navigationId", "childmiddle23");
374 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
372 tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle2");
375
373
376− mouseClick(dashNavigationButton);
374+ mouseClick(searchTextField);
377− compare(dashNavigationButton.showList, true);
375+ tryCompare(peExtraPanel, "visible", true);
378 waitForRendering(navigationListView);
376 waitForRendering(navigationListView);
379 waitForRendering(navigationListView.currentItem);
377 waitForRendering(navigationListView.currentItem);
380− mouseClick(backButton);
378+ mouseClick(findChild(header1, "backButton"));
381
379
382− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "root");
380+ tryCompare(dashNavigation.currentNavigation, "navigationId", "root");
383 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
381 tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
384− compare(dashNavigationButton.showList, true);
382+ compare(peExtraPanel.visible, true);
385− mouseClick(dashNavigationButton);
383+ mouseClick(header0);
386− compare(dashNavigationButton.showList, false);
384+ compare(peExtraPanel.visible, false);
387
385
388− mouseClick(dashNavigationButton);
386+ tryCompare(headerContainer, "contentY", headerContainer.height);
389− compare(dashNavigationButton.showList, true);
387+ mouseClick(searchButton);
388+ tryCompare(peExtraPanel, "visible", true);
390 tryCompare(navigationListView.currentItem.navigation, "loaded", true);
389 tryCompare(navigationListView.currentItem.navigation, "loaded", true);
391− var navigationList0 = findChild(dashNavigationButton, "navigation0");
390+ var navigationList0 = findChild(dashNavigation, "navigation0");
392 tryCompare(navigationList0, "implicitHeight", navigationList0.itemHeight * 8);
391 tryCompare(navigationList0, "implicitHeight", navigationList0.itemHeight * 8);
393 tryCompare(navigationList0, "height", navigationList0.implicitHeight);
392 tryCompare(navigationList0, "height", navigationList0.implicitHeight);
394− navigation = findChild(dashNavigationButton, "navigation0child2");
393+ navigation = findChild(dashNavigation, "navigation0child2");
395 mouseClick(navigation);
394 mouseClick(navigation);
396− compare(dashNavigationButton.showList, true);
395+ compare(peExtraPanel.visible, true);
397− navigationList1 = findChild(dashNavigationButton, "navigation1");
396+ navigationList1 = findChild(dashNavigation, "navigation1");
398 compare(navigationList1.navigation.loaded, false);
397 compare(navigationList1.navigation.loaded, false);
399− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
400− allButton = findChild(navigationList1, "allButton");
401− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
398+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
402− mouseClick(allButton);
399+ mouseClick(header0);
403− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "middle2");
400+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle2");
404 }
401 }
405
402
406− function test_altNavigation() {
403+ function goToSecondLevel() {
407 var dashContentList = findChild(dashContent, "dashContentList");
404 var dashContentList = findChild(dashContent, "dashContentList");
408 tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
405 tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
409 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
406 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
410− tryCompare(dashNavigation, "visible", true);
407+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
411− var dashNavigationButton = findChild(dashContentList.currentItem, "altNavigationButton");
408+ var searchButton = findChild(dashContentList.currentItem, "search_action_button");
412− verify(dashNavigationButton, "Can't find navigation button");
409+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
413−
410+ compare(peExtraPanel.visible, false);
414− compare(dashNavigationButton.showList, false);
411+ mouseClick(searchButton);
415− waitForRendering(dashNavigationButton);
412+ tryCompare(peExtraPanel, "visible", true);
416− mouseClick(dashNavigationButton);
413+
417− compare(dashNavigationButton.showList, true);
418−
419− var navigationListView = findChild(dashNavigationButton, "navigationListView");
414+ var navigationListView = findChild(dashNavigation, "navigationListView");
420 tryCompareFunction(function() {
415 tryCompareFunction(function() {
421 return navigationListView.currentItem &&
416 return navigationListView.currentItem &&
422 navigationListView.currentItem.navigation &&
417 navigationListView.currentItem.navigation &&
423 navigationListView.currentItem.navigation.loaded; }, true);
418 navigationListView.currentItem.navigation.loaded; }, true);
424−
425 waitForRendering(navigationListView);
419 waitForRendering(navigationListView);
426 waitForRendering(navigationListView.currentItem);
420 waitForRendering(navigationListView.currentItem);
427
421
422+ var navigation4 = findChild(dashNavigation, "navigation0child4");
423+ mouseClick(navigation4);
428− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "altrootChild1");
424+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle4");
429− tryCompare(navigationListView.currentItem.navigation, "navigationId", "altroot");
425+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle4");
430−
426+ tryCompare(dashNavigation.currentNavigation, "loaded", true);
431− var allButton = findChild(dashNavigationButton, "allButton");
432− compare(allButton.visible, false);
433− var backButton = findChild(navigationListView, "backButton");
434− compare(backButton.visible, false);
435
427
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);
444 waitForRendering(navigationListView);
428 waitForRendering(navigationListView);
445 waitForRendering(navigationListView.currentItem);
429 waitForRendering(navigationListView.currentItem);
446− compare(allButton.visible, false);
447
430
448− navigation = findChild(dashNavigationButton, "navigation0child2");
431+ var navigation44 = findChild(dashNavigation, "navigation1child4");
449− mouseClick(navigation);
432+ mouseClick(navigation44);
450− compare(dashNavigationButton.showList, false);
451− tryCompare(dashNavigationButton.currentNavigation, "navigationId", "altrootChild2");
433+ tryCompare(dashNavigation.currentNavigation, "navigationId", "childmiddle44");
452− tryCompare(navigationListView.currentItem.navigation, "navigationId", "altroot");
434+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "childmiddle44");
435+ tryCompare(dashNavigation.currentNavigation, "loaded", true);
453
436
454− mouseClick(dashNavigationButton);
455− compare(dashNavigationButton.showList, true);
456 waitForRendering(navigationListView);
437 waitForRendering(navigationListView);
457 waitForRendering(navigationListView.currentItem);
438 waitForRendering(navigationListView.currentItem);
458− verify(!backButton.visible, "Back button should not be visible");
459
439
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"));
461
457
462− mouseClick(dashNavigationButton);
458+ tryCompare(dashNavigation.currentNavigation, "navigationId", "root");
463− compare(dashNavigationButton.showList, false);
459+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "root");
460+ compare(peExtraPanel.visible, true);
464 }
461 }
465
462
466− function test_navigationsClosing() {
463+ function test_navigationSecondLevelToFirstName() {
464+ goToSecondLevel();
465+
467 var dashContentList = findChild(dashContent, "dashContentList");
466 var dashContentList = findChild(dashContent, "dashContentList");
468− tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
469 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
467 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
470− tryCompare(dashNavigation, "visible", true);
468+ var navigationListView = findChild(dashNavigation, "navigationListView");
471− var dashNavigationButton = findChild(dashContentList.currentItem, "navigationButton");
472− verify(dashNavigationButton, "Can't find navigation button");
473− var dashAltNavigationButton = findChild(dashContentList.currentItem, "altNavigationButton");
469+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
474− verify(dashAltNavigationButton, "Can't find alternative navigation button");
475
470
476− waitForRendering(dashNavigationButton);
471+ // Go directly to the first pressing the header1
477− waitForRendering(dashAltNavigationButton);
472+ var header1 = findChild(dashNavigation, "dashNavigationHeader1");
473+ mouseClick(header1);
478
474
479− compare(dashNavigationButton.showList, false);
475+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle4");
480− compare(dashAltNavigationButton.showList, false);
476+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle4");
477+ compare(peExtraPanel.visible, false);
478+ }
481
479
482− mouseClick(dashAltNavigationButton);
480+ function test_navigationSecondLevelToFirstBack() {
483− compare(dashNavigationButton.showList, false);
481+ goToSecondLevel();
484− compare(dashAltNavigationButton.showList, true);
485
482
486− var navigationListView = findChild(dashAltNavigationButton, "navigationListView");
483+ var dashContentList = findChild(dashContent, "dashContentList");
487− tryCompareFunction(function() {
484+ var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
488− return navigationListView.currentItem &&
485+ var navigationListView = findChild(dashNavigation, "navigationListView");
489− navigationListView.currentItem.navigation &&
486+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
490− navigationListView.currentItem.navigation.loaded; }, true);
491
487
488+ // Go back to the first level pressing the back button of header2
492− var blackRect = findChild(dashNavigation, "blackRect");
489+ var header2 = findChild(dashNavigation, "dashNavigationHeader2");
493− tryCompare(blackRect, "opacity", 0.5);
490+ compare(header2.backVisible, true);
491+ mouseClick(findChild(header2, "backButton"));
494
492
495− mouseClick(dashNavigationButton);
493+ tryCompare(dashNavigation.currentNavigation, "navigationId", "middle4");
496− compare(dashNavigationButton.showList, false);
494+ tryCompare(navigationListView.currentItem.navigation, "navigationId", "middle4");
497− compare(dashAltNavigationButton.showList, false);
495+ compare(peExtraPanel.visible, true);
496+ }
498
497
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");
500
503
501− mouseClick(dashNavigationButton);
504+ compare(peExtraPanel.visible, false);
502− compare(dashNavigationButton.showList, true);
505+ mouseClick(searchButton);
503− compare(dashAltNavigationButton.showList, false);
506+ tryCompare(peExtraPanel, "visible", true);
504
507
505− navigationListView = findChild(dashNavigationButton, "navigationListView");
508+ typeString("A");
506− tryCompare(navigationListView.currentItem.navigation, "loaded", true);
509+ compare(peExtraPanel.visible, false);
507
510
508− mouseClick(dashAltNavigationButton);
511+ var clearIcon = findChild(searchTextField, "clearIcon");
509− compare(dashNavigationButton.showList, false);
512+ mouseClick(clearIcon);
510− compare(dashAltNavigationButton.showList, false);
513+ tryCompare(peExtraPanel, "visible", true);
511 }
514 }
512
515
513− function test_navigationsSwipeToNextScope() {
516+ function test_navigationShowFilterButton() {
517+ goToSecondLevel();
518+
514 var dashContentList = findChild(dashContent, "dashContentList");
519 var dashContentList = findChild(dashContent, "dashContentList");
515− tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
516− var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
520+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
517− tryCompare(dashNavigation, "visible", true);
521+
518− var dashNavigationButton = findChild(dashContentList.currentItem, "altNavigationButton");
522+ verify(!searchTextField.focus)
519− verify(dashNavigationButton, "Can't find navigation button");
523+ verify(searchTextField.clearIsSettings)
520
524
521− compare(dashNavigationButton.showList, false);
525+ mouseClick(searchTextField)
522− waitForRendering(dashNavigationButton);
526+
523− mouseClick(dashNavigationButton, 0, 0);
527+ verify(searchTextField.focus)
524− compare(dashNavigationButton.showList, true);
528+ verify(!searchTextField.clearIsSettings)
529+ }
525
530
526− mouseFlick(dashNavigationButton, dashNavigationButton.width - units.gu(1), units.gu(1), units.gu(1), units.gu(1));
531+ function test_navigationShowFilterPopup() {
527− compare(dashNavigationButton.showList, false);
532+ goToSecondLevel();
528
533
529 var dashContentList = findChild(dashContent, "dashContentList");
534 var dashContentList = findChild(dashContent, "dashContentList");
530− expectFail("", "should not change the parent list.");
535+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
531− tryCompare(dashContentList, "currentIndex", 1);
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);
532 }
560 }
533
561
534 function test_searchHint() {
562 function test_searchHint() {
@@ -540,7 +568,7 @@ Item {
540 var categoryListView = findChild(scope, "categoryListView");
568 var categoryListView = findChild(scope, "categoryListView");
541 waitForRendering(categoryListView);
569 waitForRendering(categoryListView);
542
570
543− compare(categoryListView.pageHeader.item.searchHint, "Search People");
571+ compare(categoryListView.pageHeader.searchHint, "Search People");
544 }
572 }
545
573
546 function compareArrays(a, b) {
574 function compareArrays(a, b) {
tests/qmltests/Dash/tst_DashPageHeader.qml+24−15
@@ -18,6 +18,7 @@ import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Dash"
20import "../../../qml/Dash"
21+import "../../../qml/Components/SearchHistoryModel"
21import Ubuntu.Components 1.3
22import Ubuntu.Components 1.3
22import Unity 0.2
23import Unity 0.2
23import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
@@ -25,7 +26,7 @@ import Unity.Test 0.1 as UT
25Item {
26Item {
26 id: root
27 id: root
27 width: units.gu(110)
28 width: units.gu(110)
28− height: units.gu(30)
29+ height: units.gu(50)
29
30
30 UT.UnityTestCase {
31 UT.UnityTestCase {
31 name: "PageHeaderLabelTest"
32 name: "PageHeaderLabelTest"
@@ -54,7 +55,7 @@ Item {
54 pageHeader.searchHistory.clear();
55 pageHeader.searchHistory.clear();
55
56
56 // Check initial state
57 // Check initial state
57− tryCompareFunction(function() { return headerContainer.popover === null; }, true);
58+ tryCompare(pageHeader.extraPanel, "visible", false);
58 compare(pageHeader.searchHistory.count, 0);
59 compare(pageHeader.searchHistory.count, 0);
59 }
60 }
60
61
@@ -146,7 +147,7 @@ Item {
146 doResetSearch();
147 doResetSearch();
147 }
148 }
148
149
149− function test_popover() {
150+ function test_extraPanel() {
150 searchEnabled = true;
151 searchEnabled = true;
151 pageHeader.searchHistory.clear();
152 pageHeader.searchHistory.clear();
152
153
@@ -155,22 +156,18 @@ Item {
155
156
156 pageHeader.triggerSearch();
157 pageHeader.triggerSearch();
157
158
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);
163
160
164 var searchTextField = findChild(pageHeader, "searchTextField");
161 var searchTextField = findChild(pageHeader, "searchTextField");
165 compare(searchTextField.focus, true);
162 compare(searchTextField.focus, true);
166
163
167− var recentSearches = findChild(headerContainer.popover, "recentSearches");
164+ var recentSearches = findChild(pageHeader.extraPanel, "recentSearchesRepeater");
168− verify(recentSearches, "Could not find recent searches in the popover");
165+ verify(recentSearches, "Could not find recent searches");
169 waitForRendering(recentSearches);
166 waitForRendering(recentSearches);
170 mouseClick(recentSearches.itemAt(0));
167 mouseClick(recentSearches.itemAt(0));
171
168
172 compare(pageHeader.searchQuery, "Search2");
169 compare(pageHeader.searchQuery, "Search2");
173− tryCompareFunction(function() { return headerContainer.popover === null; }, true);
170+ tryCompare(pageHeader.extraPanel, "visible", false);
174 compare(searchTextField.focus, false);
171 compare(searchTextField.focus, false);
175 }
172 }
176
173
@@ -212,9 +209,7 @@ Item {
212 pageHeader.triggerSearch();
209 pageHeader.triggerSearch();
213
210
214 var headerContainer = findChild(pageHeader, "headerContainer");
211 var headerContainer = findChild(pageHeader, "headerContainer");
215− verify(headerContainer !== null, "headerContainer != null");
212+ tryCompare(pageHeader.extraPanel, "visible", true);
216− tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
217− compare(headerContainer.popover.visible, true);
218
213
219 pageHeader.searchQuery = data.searchText;
214 pageHeader.searchQuery = data.searchText;
220
215
@@ -225,7 +220,7 @@ Item {
225 }
220 }
226
221
227 tryCompare(headerContainer, "showSearch", !data.hideSearch);
222 tryCompare(headerContainer, "showSearch", !data.hideSearch);
228− tryCompareFunction(function() { return headerContainer.popover === null; }, true);
223+ tryCompare(pageHeader.extraPanel, "visible", false);
229
224
230 doResetSearch();
225 doResetSearch();
231 }
226 }
@@ -251,8 +246,10 @@ Item {
251 right: parent.right
246 right: parent.right
252 }
247 }
253
248
249+ searchHistory: SearchHistoryModel
254 searchEntryEnabled: true
250 searchEntryEnabled: true
255 title: "%^$%^%^&%^&%^$%GHR%"
251 title: "%^$%^%^&%^&%^$%GHR%"
252+ extraPanel: peExtraPanel
256 scopeStyle: QtObject {
253 scopeStyle: QtObject {
257 readonly property color foreground: theme.palette.normal.baseText
254 readonly property color foreground: theme.palette.normal.baseText
258 readonly property url headerLogo: showImageCheckBox.checked ? pageHeader.titleImageSource : ""
255 readonly property url headerLogo: showImageCheckBox.checked ? pageHeader.titleImageSource : ""
@@ -264,6 +261,18 @@ Item {
264 onBackClicked: lastBackClicked = new Date()
261 onBackClicked: lastBackClicked = new Date()
265 }
262 }
266
263
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+
267 Row {
276 Row {
268 spacing: units.gu(1)
277 spacing: units.gu(1)
269 anchors {
278 anchors {
tests/qmltests/Dash/tst_GenericScopeView.qml+1−1
@@ -478,7 +478,7 @@ Item {
478 mouseClick(seeAll0);
478 mouseClick(seeAll0);
479 verify(category0.expanded);
479 verify(category0.expanded);
480 tryCompare(category0, "height", category0.item.expandedHeight + seeAll0.height);
480 tryCompare(category0, "height", category0.item.expandedHeight + seeAll0.height);
481− tryCompare(genericScopeView.categoryView, "contentY", units.gu(13));
481+ tryCompare(genericScopeView.categoryView, "contentY", units.gu(8));
482
482
483 scrollToEnd();
483 scrollToEnd();
484
484
tests/qmltests/Dash/tst_PreviewView.qml+1−1
@@ -98,7 +98,7 @@ Rectangle {
98 var header = findChild(view, "innerPageHeader");
98 var header = findChild(view, "innerPageHeader");
99 verify(header, "Could not find the preview header");
99 verify(header, "Could not find the preview header");
100
100
101− compare(header.config.title, "Mock Scope");
101+ compare(header.config.contents.text, "Mock Scope");
102 }
102 }
103
103
104 function test_header_style() {
104 function test_header_style() {