Merge lp:~aacid/unity8/optionselector_filter into lp:unity8

Proposed by Albert Astals Cid on 2015-10-02
Status: Merged
Approved by: Andrea Cimitan on 2015-12-18
Approved revision: 2019
Merged at revision: 2296
Proposed branch: lp:~aacid/unity8/optionselector_filter
Merge into: lp:unity8
Prerequisite: lp:~aacid/unity8/new_dash_navigation
Diff against target: 1035 lines (+621/-30)
21 files modified
qml/Dash/DashNavigation.qml (+1/-1)
qml/Dash/DashPageHeader.qml (+36/-16)
qml/Dash/Filters/FilterOptionSelector.qml (+89/-0)
qml/Dash/Filters/FilterWidget.qml (+29/-0)
qml/Dash/Filters/FilterWidgetFactory.qml (+49/-0)
qml/Dash/FiltersPopover.qml (+97/-0)
qml/Dash/GenericScopeView.qml (+11/-0)
qml/Dash/PageHeaderExtraPanel.qml (+20/-2)
tests/mocks/Unity/fake_filters.cpp (+17/-3)
tests/mocks/Unity/fake_filters.h (+9/-2)
tests/mocks/Unity/fake_optionselectorfilter.cpp (+6/-0)
tests/mocks/Unity/fake_optionselectorfilter.h (+8/-1)
tests/mocks/Unity/fake_optionselectoroptions.cpp (+12/-0)
tests/mocks/Unity/fake_optionselectoroptions.h (+5/-0)
tests/mocks/Unity/fake_scope.cpp (+20/-4)
tests/mocks/Unity/fake_scope.h (+8/-1)
tests/mocks/Unity/fake_unity_plugin.cpp (+5/-0)
tests/qmltests/CMakeLists.txt (+2/-0)
tests/qmltests/Dash/Filters/tst_FilterOptionSelector.qml (+96/-0)
tests/qmltests/Dash/Filters/tst_FilterWidgetFactory.qml (+55/-0)
tests/qmltests/Dash/tst_DashContent.qml (+46/-0)
To merge this branch: bzr merge lp:~aacid/unity8/optionselector_filter
Reviewer Review Type Date Requested Status
Andrea Cimitan (community) 2015-10-02 Approve on 2015-12-18
PS Jenkins bot continuous-integration Needs Fixing on 2015-12-15
Review via email: mp+273194@code.launchpad.net

Commit Message

Filter implementation with Option Selector filter as the only filter

Description of the Change

* Are there any related MPs required for this MP to build/function as expected?
Branches from https://requests.ci-train.ubuntu.com/#/ticket/506

 * Did you perform an exploratory manual test run of your code change and any related functionality?
Yes

 * Did you make sure that your branch does not contain spurious tags?
Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

 * If you changed the UI, has there been a design review?
Not yet

To post a comment you must log in.
1994. By Albert Astals Cid on 2015-10-13

Implement Scope::activeFiltersCount in the mock plugin

1995. By Albert Astals Cid on 2015-10-13

Number with the active filters

1996. By Albert Astals Cid on 2015-10-14

Fix whitespace

1997. By Albert Astals Cid on 2015-10-16

Merge

1998. By Albert Astals Cid on 2015-10-20

Merge

1999. By Albert Astals Cid on 2015-10-22

Wrap the filters in a flickable

2000. By Albert Astals Cid on 2015-10-22

Merge

2001. By Albert Astals Cid on 2015-10-23

Merge

2002. By Albert Astals Cid on 2015-10-26

Merge

2003. By Albert Astals Cid on 2015-10-29

mege

2004. By Albert Astals Cid on 2015-11-06

Merge

2005. By Albert Astals Cid on 2015-11-06

Merge

2006. By Albert Astals Cid on 2015-11-06

Merge

2007. By Albert Astals Cid on 2015-11-06

Proper name

2008. By Albert Astals Cid on 2015-11-12

Fix import versions

2009. By Albert Astals Cid on 2015-11-16

Merge

2010. By Albert Astals Cid on 2015-11-18

Merge

2011. By Albert Astals Cid on 2015-11-23

Merge

2012. By Albert Astals Cid on 2015-11-23

Add "Refine your results" + "Reset" to the filter popover

Andrea Cimitan (cimi) wrote :

couple of comments for now

review: Needs Fixing
2013. By Albert Astals Cid on 2015-11-30

drop unneeded id

2014. By Albert Astals Cid on 2015-11-30

Better wait for it to check for growth to have stopped

Albert Astals Cid (aacid) wrote :

Both fixed

2015. By Albert Astals Cid on 2015-11-30

Merge

Andrea Cimitan (cimi) wrote :

Thanks! A couple more

2016. By Albert Astals Cid on 2015-12-01

More reviews fixes

Albert Astals Cid (aacid) wrote :

> Thanks! A couple more

Done.

2017. By Albert Astals Cid on 2015-12-09

Merge

2018. By Albert Astals Cid on 2015-12-14

Merge

2019. By Albert Astals Cid on 2015-12-15

Merge

Andrea Cimitan (cimi) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
yes, silo 54
 * Did CI run pass? If not, please explain why.
unrelated
 * Did you make sure that the branch does not contain spurious tags?
y

review: Approve
2020. By Albert Astals Cid on 2016-01-11

Merge

2021. By Albert Astals Cid on 2016-01-13

Merge

2022. By Albert Astals Cid on 2016-01-22

Hide popover when scope stops being current

2023. By Albert Astals Cid on 2016-01-26

Remove clicking on the scope title to trigger search

Was a bug in the prototype i ended up copying ^_^

2024. By Albert Astals Cid on 2016-01-27

Take into account OSK height for limiting the popover height

2025. By Albert Astals Cid on 2016-02-04

Give some space between the popover and the OSK

3gu decided by Patty

2026. By Albert Astals Cid on 2016-02-19

Merge

2027. By Albert Astals Cid on 2016-02-22

Merge

2028. By Albert Astals Cid on 2016-03-03

Merge

2029. By Albert Astals Cid on 2016-03-07

Merge

2030. By Albert Astals Cid on 2016-03-08

Merge

2031. By Albert Astals Cid on 2016-03-14

Merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qml/Dash/DashNavigation.qml'
2--- qml/Dash/DashNavigation.qml 2016-03-14 08:53:26 +0000
3+++ qml/Dash/DashNavigation.qml 2016-03-14 08:53:26 +0000
4@@ -35,7 +35,7 @@
5 property bool isEnteringChildren: false
6
7 visible: height != 0
8- implicitHeight: navigationListView.y + navigationListView.height
9+ implicitHeight: scope && scope.hasNavigation ? navigationListView.y + navigationListView.height : 0
10
11 function resetNavigation() {
12 if (navigationModel.count > 1) {
13
14=== modified file 'qml/Dash/DashPageHeader.qml'
15--- qml/Dash/DashPageHeader.qml 2016-03-14 08:53:26 +0000
16+++ qml/Dash/DashPageHeader.qml 2016-03-14 08:53:26 +0000
17@@ -17,7 +17,6 @@
18 import QtQuick 2.4
19 import Ubuntu.Components 1.3
20 import Ubuntu.Components.Themes.Ambiance 1.3
21-import Ubuntu.Components.Popups 1.3
22 import Ubuntu.Components.ListItems 1.3
23 import "../Components"
24
25@@ -27,6 +26,8 @@
26 implicitHeight: headerContainer.height + signatureLineHeight
27 readonly property real signatureLineHeight: showSignatureLine ? units.gu(2) : 0
28
29+ property int activeFiltersCount: 0
30+ property bool scopeHasFilters: false
31 property bool showBackButton: false
32 property bool backIsClose: false
33 property string title
34@@ -54,6 +55,7 @@
35 signal settingsClicked()
36 signal favoriteClicked()
37 signal searchTextFieldFocused()
38+ signal showFiltersPopup(var item)
39
40 onScopeStyleChanged: refreshLogo()
41 onSearchQueryChanged: {
42@@ -204,29 +206,47 @@
43 rightMargin: units.gu(1)
44 }
45
46+ readonly property bool clearIsSettings: !searchTextField.focus && root.scopeHasFilters
47+
48 primaryItem: Label {
49 text: root.navigationTag
50 }
51
52- secondaryItem: AbstractButton {
53+ secondaryItem: Row {
54 height: searchTextField.height
55- width: height
56- enabled: searchTextField.text.length > 0 || root.navigationTag != ""
57-
58- Image {
59- objectName: "clearIcon"
60- anchors.fill: parent
61- anchors.margins: units.gu(.75)
62- source: "image://theme/clear"
63- opacity: parent.enabled
64- visible: opacity > 0
65- Behavior on opacity {
66- UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
67+
68+ AbstractButton {
69+ id: clearOrSettingsButton
70+ height: parent.height
71+ width: height
72+ enabled: searchTextField.text.length > 0 || root.navigationTag != ""
73+
74+ Image {
75+ objectName: "clearIcon"
76+ anchors.fill: parent
77+ anchors.margins: units.gu(.75)
78+ source: searchTextField.clearIsSettings ? "image://theme/settings" : "image://theme/clear"
79+ opacity: parent.enabled
80+ visible: opacity > 0
81+ Behavior on opacity {
82+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
83+ }
84+ }
85+
86+ onClicked: {
87+ if (searchTextField.clearIsSettings) {
88+ root.showFiltersPopup(clearOrSettingsButton);
89+ } else {
90+ root.clearSearch(true);
91+ }
92 }
93 }
94
95- onClicked: {
96- root.clearSearch(true);
97+ Label {
98+ visible: searchTextField.clearIsSettings && root.activeFiltersCount > 0
99+ height: parent.height
100+ text: root.activeFiltersCount
101+ verticalAlignment: Text.AlignVCenter
102 }
103 }
104
105
106=== added directory 'qml/Dash/Filters'
107=== added file 'qml/Dash/Filters/FilterOptionSelector.qml'
108--- qml/Dash/Filters/FilterOptionSelector.qml 1970-01-01 00:00:00 +0000
109+++ qml/Dash/Filters/FilterOptionSelector.qml 2016-03-14 08:53:26 +0000
110@@ -0,0 +1,89 @@
111+/*
112+ * Copyright (C) 2015 Canonical, Ltd.
113+ *
114+ * This program is free software; you can redistribute it and/or modify
115+ * it under the terms of the GNU General Public License as published by
116+ * the Free Software Foundation; version 3.
117+ *
118+ * This program is distributed in the hope that it will be useful,
119+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
120+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
121+ * GNU General Public License for more details.
122+ *
123+ * You should have received a copy of the GNU General Public License
124+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
125+ */
126+
127+import QtQuick 2.4
128+import Ubuntu.Components 1.3
129+import Ubuntu.Components.ListItems 1.3 as ListItems
130+
131+/*! Option Selector Filter Widget. */
132+
133+FilterWidget {
134+ id: root
135+
136+ implicitHeight: expandingItem.height
137+
138+ ListItems.Expandable {
139+ id: expandingItem
140+ objectName: "expandingItem"
141+
142+ expandedHeight: collapsedHeight + column.height
143+ width: parent.width
144+
145+ onClicked: {
146+ expanded = !expanded;
147+ }
148+
149+ Item {
150+ id: holder
151+ anchors.top: parent.top
152+ height: expandingItem.collapsedHeight
153+ width: parent.width
154+
155+ Label {
156+ anchors.left: parent.left
157+ anchors.right: dropDown.left
158+ anchors.verticalCenter: parent.verticalCenter
159+ text: widgetData.label || ""
160+ }
161+
162+ Image {
163+ id: dropDown
164+ height: units.gu(3)
165+ fillMode: Image.PreserveAspectFit
166+ anchors.right: parent.right
167+ anchors.verticalCenter: parent.verticalCenter
168+ source: expandingItem.expanded ? "image://theme/up" : "image://theme/down"
169+ }
170+ }
171+
172+ Column {
173+ id: column
174+ anchors.top: holder.bottom
175+ width: parent.width
176+ Repeater {
177+ model: widgetData.options
178+
179+ ListItems.Standard {
180+ text: label
181+ objectName: root.objectName + "label" + index;
182+
183+ Image {
184+ height: units.gu(3)
185+ fillMode: Image.PreserveAspectFit
186+ anchors.right: parent.right
187+ anchors.verticalCenter: parent.verticalCenter
188+ source: "image://theme/tick"
189+ visible: checked
190+ }
191+
192+ onClicked: {
193+ widgetData.options.setChecked(index, !checked);
194+ }
195+ }
196+ }
197+ }
198+ }
199+}
200
201=== added file 'qml/Dash/Filters/FilterWidget.qml'
202--- qml/Dash/Filters/FilterWidget.qml 1970-01-01 00:00:00 +0000
203+++ qml/Dash/Filters/FilterWidget.qml 2016-03-14 08:53:26 +0000
204@@ -0,0 +1,29 @@
205+/*
206+ * Copyright (C) 2015 Canonical, Ltd.
207+ *
208+ * This program is free software; you can redistribute it and/or modify
209+ * it under the terms of the GNU General Public License as published by
210+ * the Free Software Foundation; version 3.
211+ *
212+ * This program is distributed in the hope that it will be useful,
213+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
214+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
215+ * GNU General Public License for more details.
216+ *
217+ * You should have received a copy of the GNU General Public License
218+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
219+ */
220+
221+import QtQuick 2.4
222+
223+/*! Interface for filter widgets. */
224+
225+Item {
226+ //! The widget identifier
227+ property string widgetId
228+
229+ //! Variable used to contain widget's data
230+ property var widgetData
231+
232+ objectName: widgetId
233+}
234
235=== added file 'qml/Dash/Filters/FilterWidgetFactory.qml'
236--- qml/Dash/Filters/FilterWidgetFactory.qml 1970-01-01 00:00:00 +0000
237+++ qml/Dash/Filters/FilterWidgetFactory.qml 2016-03-14 08:53:26 +0000
238@@ -0,0 +1,49 @@
239+/*
240+ * Copyright (C) 2015 Canonical, Ltd.
241+ *
242+ * This program is free software; you can redistribute it and/or modify
243+ * it under the terms of the GNU General Public License as published by
244+ * the Free Software Foundation; version 3.
245+ *
246+ * This program is distributed in the hope that it will be useful,
247+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
248+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
249+ * GNU General Public License for more details.
250+ *
251+ * You should have received a copy of the GNU General Public License
252+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
253+ */
254+
255+import QtQuick 2.4
256+import Unity 0.2
257+
258+//! \brief This component loads the widgets based on widgetType.
259+
260+Loader {
261+ id: root
262+
263+ //! Identifier of the widget.
264+ property string widgetId: ""
265+
266+ //! Type of the widget to display.
267+ property int widgetType
268+
269+ //! Widget data, forwarded to the widget as is.
270+ property var widgetData: null
271+
272+ source: widgetSource
273+
274+ //! \cond private
275+ readonly property url widgetSource: {
276+ switch (widgetType) {
277+ case Filters.OptionSelectorFilter: return "FilterOptionSelector.qml";
278+ default: return "";
279+ }
280+ }
281+ //! \endcond
282+
283+ onLoaded: {
284+ item.widgetId = Qt.binding(function() { return root.widgetId } )
285+ item.widgetData = Qt.binding(function() { return root.widgetData } )
286+ }
287+}
288
289=== added file 'qml/Dash/FiltersPopover.qml'
290--- qml/Dash/FiltersPopover.qml 1970-01-01 00:00:00 +0000
291+++ qml/Dash/FiltersPopover.qml 2016-03-14 08:53:26 +0000
292@@ -0,0 +1,97 @@
293+/*
294+ * Copyright (C) 2013-2015 Canonical, Ltd.
295+ *
296+ * This program is free software; you can redistribute it and/or modify
297+ * it under the terms of the GNU General Public License as published by
298+ * the Free Software Foundation; version 3.
299+ *
300+ * This program is distributed in the hope that it will be useful,
301+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
302+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
303+ * GNU General Public License for more details.
304+ *
305+ * You should have received a copy of the GNU General Public License
306+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
307+ */
308+
309+import QtQuick 2.4
310+import Ubuntu.Components 1.3
311+import Ubuntu.Components.Popups 1.3
312+import "Filters" as Filters
313+
314+Popover {
315+ id: root
316+ objectName: "filtersPopover"
317+
318+ Flickable {
319+ id: flickable
320+ anchors {
321+ top: parent.top
322+ left: parent.left
323+ right: parent.right
324+ }
325+ height: {
326+ // Popover doesn't like being 75% or bigger than the screen (counting the "empty" part on top)
327+ var posToRootParent = flickable.mapToItem(null, 0, 0).y;
328+ var threeQuartersParent = root.parent.height * 3 / 4 - posToRootParent - 1;
329+ var parentAndKeyboard = root.parent.height - posToRootParent - (Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height + units.gu(3) : 0)
330+ return Math.min(parentAndKeyboard, Math.min(threeQuartersParent, column.height));
331+ }
332+ contentHeight: column.height
333+ contentWidth: width
334+
335+ Column {
336+ id: column
337+ width: parent.width
338+
339+ Item {
340+ width: parent.width
341+ height: resetLabel.height + units.gu(3)
342+
343+ Label {
344+ anchors {
345+ left: parent.left
346+ right: resetLabel.left
347+ margins: units.gu(2)
348+ verticalCenter: parent.verticalCenter
349+ }
350+ text: i18n.tr("Refine your results")
351+ }
352+ Label {
353+ id: resetLabel
354+ anchors {
355+ right: parent.right
356+ rightMargin: units.gu(2)
357+ verticalCenter: parent.verticalCenter
358+ }
359+ text: i18n.tr("Reset")
360+
361+ AbstractButton {
362+ anchors {
363+ fill: parent
364+ rightMargin: units.gu(-2)
365+ leftMargin: units.gu(-2)
366+ topMargin: units.gu(-1)
367+ bottomMargin: units.gu(-1)
368+ }
369+ onClicked: {
370+ scopeView.scope.resetFilters();
371+ }
372+ }
373+ }
374+ }
375+
376+ Repeater {
377+ model: scopeView.scope.filters
378+
379+ delegate: Filters.FilterWidgetFactory {
380+ width: parent.width
381+
382+ widgetId: id
383+ widgetType: type
384+ widgetData: filter
385+ }
386+ }
387+ }
388+ }
389+}
390
391=== modified file 'qml/Dash/GenericScopeView.qml'
392--- qml/Dash/GenericScopeView.qml 2016-03-14 08:53:26 +0000
393+++ qml/Dash/GenericScopeView.qml 2016-03-14 08:53:26 +0000
394@@ -16,6 +16,7 @@
395
396 import QtQuick 2.4
397 import Ubuntu.Components 1.3
398+import Ubuntu.Components.Popups 1.3
399 import "../Components/SearchHistoryModel"
400 import Utils 0.1
401 import Unity 0.2
402@@ -42,6 +43,7 @@
403 property alias pageHeaderTotallyVisible: categoryView.pageHeaderTotallyVisible
404 property var holdingList: null
405 property bool wasCurrentOnMoveStart: false
406+ property var filtersPopover: null
407
408 property var scopeStyle: ScopeStyle {
409 style: scope ? scope.customizations : {}
410@@ -121,6 +123,10 @@
411 }
412 categoryView.pageHeader.resetSearch();
413 subPageLoader.closeSubPage();
414+ if (filtersPopover) {
415+ PopupUtils.close(filtersPopover)
416+ scopeView.filtersPopover = null;
417+ }
418 }
419
420 Binding {
421@@ -619,6 +625,8 @@
422 extraPanel: peExtraPanel
423 searchHistory: SearchHistoryModel
424 searchHint: scopeView.scope && scopeView.scope.searchHint || i18n.ctr("Label: Hint for dash search line edit", "Search")
425+ scopeHasFilters: scopeView.scope.filters != null
426+ activeFiltersCount: scopeView.scope.activeFiltersCount
427 showBackButton: scopeView.hasBackAction
428 searchEntryEnabled: true
429 settingsEnabled: scopeView.scope && scopeView.scope.settings && scopeView.scope.settings.count > 0 || false
430@@ -642,6 +650,9 @@
431 openPopup();
432 }
433 }
434+ onShowFiltersPopup: { // item
435+ scopeView.filtersPopover = PopupUtils.open(Qt.resolvedUrl("FiltersPopover.qml"), item, { "contentWidth": scopeView.width - units.gu(2) } );
436+ }
437 }
438
439 PageHeaderExtraPanel {
440
441=== modified file 'qml/Dash/PageHeaderExtraPanel.qml'
442--- qml/Dash/PageHeaderExtraPanel.qml 2016-03-14 08:53:26 +0000
443+++ qml/Dash/PageHeaderExtraPanel.qml 2016-03-14 08:53:26 +0000
444@@ -17,13 +17,14 @@
445 import QtQuick 2.4
446 import Ubuntu.Components 1.3
447 import Ubuntu.Components.ListItems 1.3
448+import "Filters" as Filters
449
450 Item {
451 id: root
452
453 readonly property real searchesHeight: recentSearchesRepeater.count > 0 ? searchColumn.height + recentSearchesLabels.height + recentSearchesLabels.anchors.margins : 0
454
455- implicitHeight: searchesHeight + dashNavigation.implicitHeight
456+ implicitHeight: searchesHeight + dashNavigation.implicitHeight + primaryFilter.height
457
458 // Set by parent
459 property ListModel searchHistory
460@@ -32,7 +33,7 @@
461 property real windowHeight
462
463 // Used by PageHeader
464- readonly property bool hasContents: searchHistory.count > 0 || scope && scope.hasNavigation
465+ readonly property bool hasContents: searchHistory.count > 0 || scope && scope.hasNavigation || scope && scope.primaryNavigationFilter
466
467 signal historyItemClicked(string text)
468 signal dashNavigationLeafClicked()
469@@ -111,6 +112,23 @@
470 onLeafClicked: root.dashNavigationLeafClicked();
471 }
472
473+ Filters.FilterWidgetFactory {
474+ id: primaryFilter
475+ active: scope && !scope.hasNavigation
476+
477+ property var filter: active ? scope.primaryNavigationFilter : null
478+
479+ anchors {
480+ top: recentSearchesRepeater.count > 0 ? searchColumn.bottom : parent.top
481+ left: parent.left
482+ right: parent.right
483+ }
484+
485+ widgetId: filter ? filter.filterId : ""
486+ widgetType: filter ? filter.filterType : -1
487+ widgetData: filter
488+ }
489+
490 // This is outside the item
491 Image {
492 anchors {
493
494=== modified file 'tests/mocks/Unity/fake_filters.cpp'
495--- tests/mocks/Unity/fake_filters.cpp 2016-03-14 08:53:26 +0000
496+++ tests/mocks/Unity/fake_filters.cpp 2016-03-14 08:53:26 +0000
497@@ -22,9 +22,14 @@
498 Filters::Filters(Scope* parent)
499 : unity::shell::scopes::FiltersInterface(parent)
500 {
501- auto optionFilter1 = new FakeOptionSelectorFilter("OSF1", "Tag1", "Which Cake you like More", false, QStringList() << "cheese" << "carrot" << "chocolate", this);
502- auto optionFilter2 = new FakeOptionSelectorFilter("OSF2", "Tag2", "Which Countries have you been to?", true, QStringList() << "Germany" << "UK" << "New Zealand", this);
503- m_filters << optionFilter1 << optionFilter2;
504+ addFilter(new FakeOptionSelectorFilter("OSF1", "Tag1", "Which Cake you like More", false, QStringList() << "cheese" << "carrot" << "chocolate", this));
505+ addFilter(new FakeOptionSelectorFilter("OSF2", "Tag2", "Which Countries have you been to?", true, QStringList() << "Germany" << "UK" << "New Zealand", this));
506+}
507+
508+void Filters::addFilter(FakeOptionSelectorFilter *f)
509+{
510+ connect(f, &FakeOptionSelectorFilter::isActiveChanged, this, &Filters::activeFiltersCountChanged);
511+ m_filters << f;
512 }
513
514 int Filters::rowCount(const QModelIndex &parent) const
515@@ -54,3 +59,12 @@
516 return QVariant();
517 }
518 }
519+
520+int Filters::activeFiltersCount() const
521+{
522+ int active = 0;
523+ Q_FOREACH(FakeOptionSelectorFilter *f, m_filters) {
524+ if (f->isActive()) ++active;
525+ }
526+ return active;
527+}
528
529=== modified file 'tests/mocks/Unity/fake_filters.h'
530--- tests/mocks/Unity/fake_filters.h 2016-03-14 08:53:26 +0000
531+++ tests/mocks/Unity/fake_filters.h 2016-03-14 08:53:26 +0000
532@@ -18,9 +18,9 @@
533 #define FAKE_FILTERS_H
534
535 #include <unity/shell/scopes/FiltersInterface.h>
536-#include <unity/shell/scopes/FilterBaseInterface.h>
537
538 class Scope;
539+class FakeOptionSelectorFilter;
540
541 class Filters : public unity::shell::scopes::FiltersInterface
542 {
543@@ -32,8 +32,15 @@
544 int rowCount(const QModelIndex &parent) const override;
545 QVariant data(const QModelIndex &index, int role) const override;
546
547+ int activeFiltersCount() const;
548+
549+Q_SIGNALS:
550+ void activeFiltersCountChanged();
551+
552 private:
553- QVector<unity::shell::scopes::FilterBaseInterface*> m_filters;
554+ void addFilter(FakeOptionSelectorFilter* f);
555+
556+ QVector<FakeOptionSelectorFilter*> m_filters;
557 };
558
559 #endif
560
561=== modified file 'tests/mocks/Unity/fake_optionselectorfilter.cpp'
562--- tests/mocks/Unity/fake_optionselectorfilter.cpp 2016-03-14 08:53:26 +0000
563+++ tests/mocks/Unity/fake_optionselectorfilter.cpp 2016-03-14 08:53:26 +0000
564@@ -26,6 +26,7 @@
565 m_multiSelect(multiselect)
566 {
567 m_options = new FakeOptionSelectorOptions(optionLabels, this);
568+ connect(m_options, &FakeOptionSelectorOptions::anyCheckedChanged, this, &FakeOptionSelectorFilter::isActiveChanged);
569 }
570
571 QString FakeOptionSelectorFilter::filterId() const
572@@ -57,3 +58,8 @@
573 {
574 return m_options;
575 }
576+
577+bool FakeOptionSelectorFilter::isActive() const
578+{
579+ return m_options->anyChecked();
580+}
581
582=== modified file 'tests/mocks/Unity/fake_optionselectorfilter.h'
583--- tests/mocks/Unity/fake_optionselectorfilter.h 2016-03-14 08:53:26 +0000
584+++ tests/mocks/Unity/fake_optionselectorfilter.h 2016-03-14 08:53:26 +0000
585@@ -19,6 +19,8 @@
586
587 #include <unity/shell/scopes/OptionSelectorFilterInterface.h>
588
589+class FakeOptionSelectorOptions;
590+
591 class FakeOptionSelectorFilter : public unity::shell::scopes::OptionSelectorFilterInterface
592 {
593 Q_OBJECT
594@@ -33,13 +35,18 @@
595 bool multiSelect() const override;
596 unity::shell::scopes::OptionSelectorOptionsInterface* options() const override;
597
598+ bool isActive() const;
599+
600+Q_SIGNALS:
601+ void isActiveChanged();
602+
603 private:
604 QString m_filterId;
605 QString m_filterTag;
606 QString m_title;
607 QString m_label;
608 bool m_multiSelect;
609- unity::shell::scopes::OptionSelectorOptionsInterface* m_options;
610+ FakeOptionSelectorOptions* m_options;
611 };
612
613 #endif
614
615=== modified file 'tests/mocks/Unity/fake_optionselectoroptions.cpp'
616--- tests/mocks/Unity/fake_optionselectoroptions.cpp 2016-03-14 08:53:26 +0000
617+++ tests/mocks/Unity/fake_optionselectoroptions.cpp 2016-03-14 08:53:26 +0000
618@@ -56,10 +56,22 @@
619 void FakeOptionSelectorOptions::setChecked(int row, bool checked)
620 {
621 if (checked) {
622+ if (!static_cast<FakeOptionSelectorFilter*>(parent())->multiSelect()) {
623+ if (!m_checkedIndexes.isEmpty()) {
624+ setChecked(*m_checkedIndexes.begin(), false);
625+ }
626+ Q_ASSERT(m_checkedIndexes.isEmpty());
627+ }
628 m_checkedIndexes << row;
629 } else {
630 m_checkedIndexes.remove(row);
631 }
632 const QModelIndex idx = index(row, 0);
633 Q_EMIT dataChanged(idx, idx, QVector<int>() << RoleOptionChecked);
634+ Q_EMIT anyCheckedChanged();
635+}
636+
637+bool FakeOptionSelectorOptions::anyChecked() const
638+{
639+ return !m_checkedIndexes.isEmpty();
640 }
641
642=== modified file 'tests/mocks/Unity/fake_optionselectoroptions.h'
643--- tests/mocks/Unity/fake_optionselectoroptions.h 2016-03-14 08:53:26 +0000
644+++ tests/mocks/Unity/fake_optionselectoroptions.h 2016-03-14 08:53:26 +0000
645@@ -34,6 +34,11 @@
646 QVariant data(const QModelIndex &index, int role) const override;
647 Q_INVOKABLE void setChecked(int index, bool checked) override;
648
649+ bool anyChecked() const;
650+
651+Q_SIGNALS:
652+ void anyCheckedChanged();
653+
654 private:
655 QStringList m_optionLabels;
656 QSet<int> m_checkedIndexes;
657
658=== modified file 'tests/mocks/Unity/fake_scope.cpp'
659--- tests/mocks/Unity/fake_scope.cpp 2016-03-14 08:53:26 +0000
660+++ tests/mocks/Unity/fake_scope.cpp 2016-03-14 08:53:26 +0000
661@@ -21,6 +21,7 @@
662
663 #include "fake_filters.h"
664 #include "fake_navigation.h"
665+#include "fake_optionselectorfilter.h"
666 #include "fake_resultsmodel.h"
667 #include "fake_scopes.h"
668 #include "fake_settingsmodel.h"
669@@ -38,6 +39,8 @@
670 , m_searching(false)
671 , m_favorite(favorite)
672 , m_isActive(false)
673+ , m_hasNavigation(true)
674+ , m_hasPrimaryFilter(true)
675 , m_currentNavigationId("root")
676 , m_previewRendererName("preview-generic")
677 , m_categories(new Categories(categories, this))
678@@ -46,6 +49,8 @@
679 , m_filters(new Filters(this))
680 , m_returnNullPreview(returnNullPreview)
681 {
682+ m_primaryNavigationFilter = new FakeOptionSelectorFilter("OSF3", "PFTag", "Which food you like More", false, QStringList() << "meat" << "vegetables", this);
683+ connect(m_filters, &Filters::activeFiltersCountChanged, this, &Scope::activeFiltersCountChanged);
684 }
685
686 QString Scope::id() const
687@@ -230,7 +235,13 @@
688
689 bool Scope::hasNavigation() const
690 {
691- return true;
692+ return m_hasNavigation;
693+}
694+
695+void Scope::setHasNavigation(bool hasNavigation)
696+{
697+ m_hasNavigation = hasNavigation;
698+ Q_EMIT hasNavigationChanged();
699 }
700
701 Scope::Status Scope::status() const
702@@ -303,7 +314,7 @@
703
704 unity::shell::scopes::FilterBaseInterface* Scope::primaryNavigationFilter() const
705 {
706- return nullptr;
707+ return m_hasPrimaryFilter ? m_primaryNavigationFilter : nullptr;
708 }
709
710 unity::shell::scopes::FiltersInterface* Scope::filters() const
711@@ -321,8 +332,7 @@
712
713 int Scope::activeFiltersCount() const
714 {
715- // TODO
716- return 0;
717+ return m_filters->activeFiltersCount();
718 }
719
720 void Scope::resetPrimaryNavigationTag()
721@@ -332,6 +342,12 @@
722 }
723 }
724
725+void Scope::setHasPrimaryFilter(bool hasPrimaryFilter)
726+{
727+ m_hasPrimaryFilter = hasPrimaryFilter;
728+ Q_EMIT primaryNavigationFilterChanged();
729+}
730+
731 void Scope::performQuery(const QString& query)
732 {
733 Q_EMIT queryPerformed(query);
734
735=== modified file 'tests/mocks/Unity/fake_scope.h'
736--- tests/mocks/Unity/fake_scope.h 2016-03-14 08:53:26 +0000
737+++ tests/mocks/Unity/fake_scope.h 2016-03-14 08:53:26 +0000
738@@ -24,7 +24,9 @@
739
740 #include <QTimer>
741
742+class Filters;
743 class Scopes;
744+class FakeOptionSelectorFilter;
745
746 class Scope : public unity::shell::scopes::ScopeInterface
747 {
748@@ -70,12 +72,14 @@
749 bool hasNavigation() const override;
750 Q_INVOKABLE unity::shell::scopes::NavigationInterface* getNavigation(QString const& navigationId) override;
751 Q_INVOKABLE void setNavigationState(const QString &navigationId) override;
752+ Q_INVOKABLE void setHasNavigation(bool hasNavigation); // This is not invokable in the Interface, here for testing benefits
753
754 unity::shell::scopes::FilterBaseInterface* primaryNavigationFilter() const override;
755 unity::shell::scopes::FiltersInterface* filters() const override;
756 QString primaryNavigationTag() const override;
757 int activeFiltersCount() const override;
758 Q_INVOKABLE void resetPrimaryNavigationTag() override;
759+ Q_INVOKABLE void setHasPrimaryFilter(bool hasPrimaryFilter); // This is not invokable in the Interface, here for testing benefits
760
761 void performQuery(const QString& query) override;
762
763@@ -105,6 +109,8 @@
764 bool m_searching;
765 bool m_favorite;
766 bool m_isActive;
767+ bool m_hasNavigation;
768+ bool m_hasPrimaryFilter;
769 QString m_currentNavigationId;
770
771 QString m_previewRendererName;
772@@ -112,7 +118,8 @@
773 unity::shell::scopes::CategoriesInterface* m_categories;
774 unity::shell::scopes::ScopeInterface* m_openScope;
775 unity::shell::scopes::SettingsModelInterface* m_settings;
776- unity::shell::scopes::FiltersInterface* m_filters;
777+ Filters* m_filters;
778+ FakeOptionSelectorFilter* m_primaryNavigationFilter;
779
780 bool m_returnNullPreview;
781 };
782
783=== modified file 'tests/mocks/Unity/fake_unity_plugin.cpp'
784--- tests/mocks/Unity/fake_unity_plugin.cpp 2016-02-19 11:42:42 +0000
785+++ tests/mocks/Unity/fake_unity_plugin.cpp 2016-03-14 08:53:26 +0000
786@@ -22,7 +22,9 @@
787 // local
788 #include "fake_scopes.h"
789 #include "fake_categories.h"
790+#include "fake_filters.h"
791 #include "fake_navigation.h"
792+#include "fake_optionselectoroptions.h"
793 #include "fake_previewmodel.h"
794 #include "fake_previewwidgetmodel.h"
795 #include "fake_resultsmodel.h"
796@@ -52,4 +54,7 @@
797 qmlRegisterType<ResultsModel>(uri, 0, 2, "FakeResultsModel");
798 qmlRegisterType<PreviewModel>(uri, 0, 2, "FakePreviewModel");
799 qmlRegisterUncreatableType<PreviewWidgetModel>(uri, 0, 2, "PreviewWidgetModel", "Can't create new PreviewWidgetModel in QML. Get them from PreviewModel instance.");
800+ qmlRegisterUncreatableType<unity::shell::scopes::FiltersInterface>(uri, 0, 2, "Filters", "Can't create Filters object in QML. Get them from Scope instance.");
801+ qmlRegisterUncreatableType<unity::shell::scopes::FilterBaseInterface>(uri, 0, 2, "Filter", "Can't create Filter object in QML. Get them from Scope instance.");
802+ qmlRegisterUncreatableType<unity::shell::scopes::OptionSelectorOptionsInterface>(uri, 0, 2, "OptionSelectorOptions", "Can't create Filters object in QML. Get them from OptionSelector instance.");
803 }
804
805=== modified file 'tests/qmltests/CMakeLists.txt'
806--- tests/qmltests/CMakeLists.txt 2016-03-10 22:41:38 +0000
807+++ tests/qmltests/CMakeLists.txt 2016-03-14 08:53:26 +0000
808@@ -53,6 +53,8 @@
809 add_unity8_qmltest(Dash/Previews PreviewVideoPlayback)
810 add_unity8_qmltest(Dash/Previews PreviewWidgetFactory)
811 add_unity8_qmltest(Dash/Previews PreviewZoomableImage)
812+add_unity8_qmltest(Dash/Filters FilterOptionSelector)
813+add_unity8_qmltest(Dash/Filters FilterWidgetFactory)
814 add_unity8_qmltest(Dash/ScopeSettings ScopeSettingBoolean)
815 add_unity8_qmltest(Dash/ScopeSettings ScopeSettingList)
816 add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
817
818=== added directory 'tests/qmltests/Dash/Filters'
819=== added file 'tests/qmltests/Dash/Filters/tst_FilterOptionSelector.qml'
820--- tests/qmltests/Dash/Filters/tst_FilterOptionSelector.qml 1970-01-01 00:00:00 +0000
821+++ tests/qmltests/Dash/Filters/tst_FilterOptionSelector.qml 2016-03-14 08:53:26 +0000
822@@ -0,0 +1,96 @@
823+/*
824+ * Copyright 2015 Canonical Ltd.
825+ *
826+ * This program is free software; you can redistribute it and/or modify
827+ * it under the terms of the GNU General Public License as published by
828+ * the Free Software Foundation; version 3.
829+ *
830+ * This program is distributed in the hope that it will be useful,
831+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
832+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
833+ * GNU General Public License for more details.
834+ *
835+ * You should have received a copy of the GNU General Public License
836+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
837+ */
838+
839+import QtQuick 2.4
840+import QtTest 1.0
841+import "../../../../qml/Dash/Filters"
842+import Unity.Test 0.1 as UT
843+import Unity 0.2
844+import Ubuntu.Components 1.3
845+
846+Rectangle {
847+ id: root
848+ width: units.gu(60)
849+ height: units.gu(80)
850+ color: theme.palette.selected.background
851+
852+ property var singleSelectionWidgetData
853+
854+ ListModel {
855+ id: optionsSingleSelect
856+ ListElement {
857+ label: "A"
858+ checked: false
859+ }
860+ ListElement {
861+ label: "B"
862+ checked: false
863+ }
864+ ListElement {
865+ label: "C"
866+ checked: false
867+ }
868+
869+ function setChecked(index, checked) {
870+ for (var i = 0; i < optionsSingleSelect.count; ++i)
871+ optionsSingleSelect.setProperty(i, "checked", false);
872+ optionsSingleSelect.setProperty(index, "checked", checked);
873+ }
874+ }
875+
876+ Component.onCompleted: {
877+ singleSelectionWidgetData = { label: "Hola", options: optionsSingleSelect }
878+ }
879+
880+ FilterWidgetFactory {
881+ id: factory
882+ widgetId: "testOptionSelectorFilter"
883+ widgetType: Filters.OptionSelectorFilter
884+ widgetData: singleSelectionWidgetData
885+ anchors {
886+ left: parent.left
887+ right: parent.right
888+ }
889+ }
890+
891+ UT.UnityTestCase {
892+ name: "FilterOptionSelector"
893+ when: windowShown
894+
895+ function test_optionSelector() {
896+ var expandingItem = findChild(factory, "expandingItem");
897+ // Open the selector
898+ mouseClick(factory);
899+ // wait for it to stop growing
900+ tryCompare(factory, "implicitHeight", expandingItem.expandedHeight);
901+
902+ // Check the first option
903+ var option0 = findChild(factory, "testOptionSelectorFilterlabel0");
904+ mouseClick(option0);
905+ verify(optionsSingleSelect.get(0).checked);
906+
907+ // Check the second option
908+ var option1 = findChild(factory, "testOptionSelectorFilterlabel1");
909+ mouseClick(option1);
910+ verify(!optionsSingleSelect.get(0).checked);
911+ verify(optionsSingleSelect.get(1).checked);
912+
913+ // Uncheck the second option
914+ mouseClick(option1);
915+ verify(!optionsSingleSelect.get(1).checked);
916+ }
917+ }
918+}
919
920=== added file 'tests/qmltests/Dash/Filters/tst_FilterWidgetFactory.qml'
921--- tests/qmltests/Dash/Filters/tst_FilterWidgetFactory.qml 1970-01-01 00:00:00 +0000
922+++ tests/qmltests/Dash/Filters/tst_FilterWidgetFactory.qml 2016-03-14 08:53:26 +0000
923@@ -0,0 +1,55 @@
924+/*
925+ * Copyright 2015 Canonical Ltd.
926+ *
927+ * This program is free software; you can redistribute it and/or modify
928+ * it under the terms of the GNU General Public License as published by
929+ * the Free Software Foundation; version 3.
930+ *
931+ * This program is distributed in the hope that it will be useful,
932+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
933+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
934+ * GNU General Public License for more details.
935+ *
936+ * You should have received a copy of the GNU General Public License
937+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
938+ */
939+
940+import QtQuick 2.4
941+import QtTest 1.0
942+import "../../../../qml/Dash/Filters"
943+import Unity.Test 0.1 as UT
944+import Unity 0.2
945+import Ubuntu.Components 1.3
946+
947+Rectangle {
948+ id: root
949+ width: units.gu(60)
950+ height: units.gu(80)
951+ color: theme.palette.selected.background
952+
953+ FilterWidgetFactory {
954+ id: factory
955+ anchors {
956+ left: parent.left
957+ right: parent.right
958+ }
959+ }
960+
961+ UT.UnityTestCase {
962+ name: "FilterWidgetFactory"
963+ when: windowShown
964+
965+ function test_mapping_data() {
966+ return [
967+ { tag: "OptionSelector", type: Filters.OptionSelectorFilter, source: "FilterOptionSelector.qml" }
968+ ];
969+ }
970+
971+ function test_mapping(data) {
972+ factory.widgetData = {};
973+ factory.widgetType = data.type;
974+
975+ verify((String(factory.source)).indexOf(data.source) != -1);
976+ }
977+ }
978+}
979
980=== modified file 'tests/qmltests/Dash/tst_DashContent.qml'
981--- tests/qmltests/Dash/tst_DashContent.qml 2016-03-14 08:53:26 +0000
982+++ tests/qmltests/Dash/tst_DashContent.qml 2016-03-14 08:53:26 +0000
983@@ -513,6 +513,52 @@
984 tryCompare(peExtraPanel, "visible", true);
985 }
986
987+ function test_navigationShowFilterButton() {
988+ goToSecondLevel();
989+
990+ var dashContentList = findChild(dashContent, "dashContentList");
991+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
992+
993+ verify(!searchTextField.focus)
994+ verify(searchTextField.clearIsSettings)
995+
996+ mouseClick(searchTextField)
997+
998+ verify(searchTextField.focus)
999+ verify(!searchTextField.clearIsSettings)
1000+ }
1001+
1002+ function test_navigationShowFilterPopup() {
1003+ goToSecondLevel();
1004+
1005+ var dashContentList = findChild(dashContent, "dashContentList");
1006+ var searchTextField = findChild(dashContentList.currentItem, "searchTextField");
1007+ var clearIcon = findChild(searchTextField, "clearIcon");
1008+
1009+ var filtersPopover = findChild(shell, "filtersPopover")
1010+ verify(!filtersPopover);
1011+
1012+ mouseClick(clearIcon);
1013+
1014+ filtersPopover = findChild(shell, "filtersPopover")
1015+
1016+ verify(filtersPopover);
1017+ }
1018+
1019+ function test_primaryFilter() {
1020+ var dashContentList = findChild(dashContent, "dashContentList");
1021+ tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
1022+ dashContentList.currentItem.item.scope.setHasNavigation(false);
1023+ var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
1024+ var searchButton = findChild(dashContentList.currentItem, "search_action_button");
1025+
1026+ compare(peExtraPanel.visible, false);
1027+ mouseClick(searchButton);
1028+ tryCompare(peExtraPanel, "visible", true);
1029+
1030+ tryCompareFunction(function() { return findChild(peExtraPanel, "OSF3") != null; }, true);
1031+ }
1032+
1033 function test_searchHint() {
1034 var dashContentList = findChild(dashContent, "dashContentList");
1035 verify(dashContentList !== null);

Subscribers

People subscribed via source and target branches