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