Merge lp:~mhr3/unity8/use-dee-filtermodel into lp:unity8

Proposed by Michal Hruby
Status: Merged
Approved by: Michał Sawicz
Approved revision: 58
Merged at revision: 85
Proposed branch: lp:~mhr3/unity8/use-dee-filtermodel
Merge into: lp:unity8
Diff against target: 1483 lines (+681/-334)
19 files modified
Dash/GenericScopeView.qml (+2/-2)
plugins/Unity/CMakeLists.txt (+1/-2)
plugins/Unity/categories.cpp (+65/-51)
plugins/Unity/categories.h (+14/-17)
plugins/Unity/categoryfilter.cpp (+0/-45)
plugins/Unity/categoryfilter.h (+0/-48)
plugins/Unity/categoryresults.cpp (+96/-0)
plugins/Unity/categoryresults.h (+67/-0)
plugins/Unity/plugin.cpp (+2/-2)
plugins/Unity/scope.cpp (+20/-58)
plugins/Unity/scope.h (+8/-12)
tests/mocks/Unity/CMakeLists.txt (+2/-3)
tests/mocks/Unity/fake_categories.cpp (+178/-0)
tests/mocks/Unity/fake_categories.h (+69/-0)
tests/mocks/Unity/fake_scope.cpp (+108/-77)
tests/mocks/Unity/fake_scope.h (+43/-11)
tests/mocks/Unity/fake_unity_plugin.cpp (+3/-3)
tests/plugins/Unity/CMakeLists.txt (+1/-1)
tests/qmltests/Dash/qml/FakeScopeView.qml (+2/-2)
To merge this branch: bzr merge lp:~mhr3/unity8/use-dee-filtermodel
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Michał Sawicz Approve
Review via email: mp+171846@code.launchpad.net

Commit message

Implement CategoryResults based on DeeFilterModel.

Description of the change

Stop using Qt's filter model and use the lower level (and hopefully more performant) DeeFilterModel.

Implemented using new CategoryResults class that can be extended with properties we'll require (like renderer size hints).

To post a comment you must log in.
lp:~mhr3/unity8/use-dee-filtermodel updated
49. By Michal Hruby

Fix column references

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

> scope.cpp:123:27: error: 'class unity::dash::Scope' has no member named 'form_factor'

So libunity-core not updated yet?

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

> > scope.cpp:123:27: error: 'class unity::dash::Scope' has no member named
> 'form_factor'
>
> So libunity-core not updated yet?

Not in S yet, although it's in trunk...

lp:~mhr3/unity8/use-dee-filtermodel updated
50. By Michal Hruby

Clean up unnecessary methods

51. By Michal Hruby

Make things safe

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mhr3/unity8/use-dee-filtermodel updated
52. By Michal Hruby

Add more result set fields

53. By Michal Hruby

Merge trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

tests/qmltests/plugins/Unity/fake_categories.h: bad whitespace in line 33

review: Needs Fixing
lp:~mhr3/unity8/use-dee-filtermodel updated
54. By Michal Hruby

Fix whitespace

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~mhr3/unity8/use-dee-filtermodel updated
55. By Michal Hruby

Merge trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :
Download full text (3.8 KiB)

9 + text: title ? title : "" // FIXME: this shouldn't be necessary

13 + source: icon ? IconUtil.from_gicon(icon) : "" // FIXME: ditto

text: title and source: IconUtil.from_gicon(icon) should be enough now, so the FIXMEs can go away.

=====

53 Categories::~Categories()
54 {
55 - qDeleteAll(m_filters);
56 }

Can probably go away completely?

=====

60 Categories::getFilter(int index) const

getResults instead?

=====

70 + auto results = new CategoryResults;

These should probably be parented to Categories, then, if not deleted in the c'tor.

=====

85 +void Categories::onCategoriesModelChanged(unity::glib::Object<DeeModel> model)
86 +{
87 + m_updatedCategories.clear();
88 + // FIXME: this might destroy the renderer view and re-create it, optimize?
89 + m_results.clear();
90 + setModel(model);
91 }
92
93 void
94 -Categories::setResultModel(DeeListModel* model)
95 +Categories::setUnityScope(const unity::dash::Scope::Ptr& scope)
96 {
97 - if (model != m_resultModel) {
98 - m_resultModel = model;
99 -
100 - Q_FOREACH(CategoryFilter* filter, m_filters) {
101 - filter->setModel(m_resultModel);
102 - }
103 -
104 - Q_EMIT resultModelChanged(m_resultModel);
105 - }
106 + m_unityScope = scope;
107 +
108 + // no need to call this, we'll get notified
109 + //setModel(m_unityScope->categories()->model());
110 +
111 + m_unityScope->categories()->model.changed.connect(sigc::mem_fun(this, &Categories::onCategoriesModelChanged));
112 }

You need to make sure to emit QAbstractListModel::beginResetModel and ::endResetModel [1] when applicable. Whether optimization here (the FIXME) makes sense is arguable. We'll really only ever set the model once on start-up, right? Should we not disconnect here from the previous m_unityScope?

=====

108 + // no need to call this, we'll get notified
109 + //setModel(m_unityScope->categories()->model());

Will the changed signal be emitted even when the categories model settled before?

=====

132 + Q_FOREACH(int cat_index, m_updatedCategories) {

Please use camelCase and ideally no abreviations - categoryIndex.

=====

133 + if (m_results[cat_index].isNull()) continue;

Is this really needed? Will there ever be a case like this?

=====

147 + return DeeListModel::data(index, 1); //DISPLAY_NAME

150 + return DeeListModel::data(index, 2); //ICON_HINT

153 + return DeeListModel::data(index, 3); //RENDERER_NAME

157 + auto hints = DeeListModel::data(index, 4).toHash();

162 + return DeeListModel::data(index, 4); //HINTS

Are those not available as enums somewhere?

=====

232 - QSet<CategoryFilter*> m_timerFilters;
233 + QSet<int> m_updatedCategories;

Why did you go for ints here?

=====

Can you update the Authors headers? I wonder if we should drop them altogether...

=====

381 + m_roles[CategoryResults::RoleDndUri] = "dnd_uri";

dndUri please.

=====

385 +CategoryResults::~CategoryResults()
386 +{
387 +}

Why the empty destructor?

=====

417 + return DeeListModel::data(index, 0);
418 + case RoleIconHint:
419 + return DeeListModel::data(index, 1);
420 + case RoleCategory:
421 + return DeeListModel::data(index, 2);
422 + case RoleMimetype:
423 + return DeeListModel::data(index, 4);
424 + case RoleTitle:
425 + return DeeLis...

Read more...

review: Needs Fixing
lp:~mhr3/unity8/use-dee-filtermodel updated
56. By Michal Hruby

Fix issues brought up in review

57. By Michal Hruby

Few more fixes

Revision history for this message
Michal Hruby (mhr3) wrote :

> text: title and source: IconUtil.from_gicon(icon) should be enough now, so the
> FIXMEs can go away.

Fixed.

> 53 Categories::~Categories()
> 54 {
> 55 - qDeleteAll(m_filters);
> 56 }
>
> Can probably go away completely?

Now that it's proper parent, yes.

> 60 Categories::getFilter(int index) const
>
> getResults instead?

Changed.
> 70 + auto results = new CategoryResults;
>
> These should probably be parented to Categories, then, if not deleted in the
> c'tor.

Indeed.

> You need to make sure to emit QAbstractListModel::beginResetModel and
> ::endResetModel [1] when applicable. Whether optimization here (the FIXME)
> makes sense is arguable. We'll really only ever set the model once on start-
> up, right? Should we not disconnect here from the previous m_unityScope?

setModel emits the reset, disconnect added. The categories will change if a scope crashes and is restarted.

> 108 + // no need to call this, we'll get notified
> 109 + //setModel(m_unityScope->categories()->model());
>
> Will the changed signal be emitted even when the categories model settled
> before?

No, let's say such usage is not supported. If categories change, also the results models change, so this handles all the changes well enough.

> 132 + Q_FOREACH(int cat_index, m_updatedCategories) {
>
> Please use camelCase and ideally no abreviations - categoryIndex.

Changed.

> 133 + if (m_results[cat_index].isNull()) continue;
>
> Is this really needed? Will there ever be a case like this?

Now that Categories is parent, no.

> Are those not available as enums somewhere?

Not in any of the includes, added at least enums internally.

> 232 - QSet<CategoryFilter*> m_timerFilters;
> 233 + QSet<int> m_updatedCategories;
>
> Why did you go for ints here?

Because I don't want to be passing a dangling pointer into a timer callback, as the results models might have been deleted in between.

> Can you update the Authors headers? I wonder if we should drop them
> altogether...

Fixed.

> 381 + m_roles[CategoryResults::RoleDndUri] = "dnd_uri";
>
> dndUri please.

Changed.

> 385 +CategoryResults::~CategoryResults()
> 386 +{
> 387 +}
>
> Why the empty destructor?

It's always nice to have it ready :) Removed.

> 506 + int m_category_index;
>
> m_categoryIndex, please.

Changed.

> 528 + qmlRegisterType<CategoryResults>(uri, 0, 1, "CategoryResults");
>
> Should probably qmlRegisterUncreatableType?

Fixed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~mhr3/unity8/use-dee-filtermodel updated
58. By Michal Hruby

Fix a typo

Revision history for this message
Michał Sawicz (saviq) wrote :

OK good for me now, letting mzanetti look at the tests.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

Regarding the tests:

So this merge updates the mocks for the existing qml UI tests (very poorly tested btw) which is good.

However, the new code itself is not tested. I guess the reason is because the surrounding and replaced code does not have tests either. Do you think we could add some in this merge or better having another TODO to test the whole scopes+category stuff?

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

To resolve the conflicts, the stuff in /tests/qmltests/plugins/ needs to be moved to /tests/mocks/

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> To resolve the conflicts, the stuff in /tests/qmltests/plugins/ needs to be
> moved to /tests/mocks/

Scratch that... Surprisingly it merges them over on its own.

Revision history for this message
Michal Hruby (mhr3) wrote :

> Regarding the tests:
>
> So this merge updates the mocks for the existing qml UI tests (very poorly
> tested btw) which is good.
>
> However, the new code itself is not tested. I guess the reason is because the
> surrounding and replaced code does not have tests either. Do you think we
> could add some in this merge or better having another TODO to test the whole
> scopes+category stuff?

This involves communication with real scopes, so ideally it should talk to real scopes, which means autopilot tests would be ideal. There are multiple branches landing these days that will allow full usage of scopes including activation and previewing, once we have all this and some actual scopes that the device will be running, we should add tests for all these aspects.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> > Regarding the tests:
> >
> > So this merge updates the mocks for the existing qml UI tests (very poorly
> > tested btw) which is good.
> >
> > However, the new code itself is not tested. I guess the reason is because
> the
> > surrounding and replaced code does not have tests either. Do you think we
> > could add some in this merge or better having another TODO to test the whole
> > scopes+category stuff?
>
> This involves communication with real scopes, so ideally it should talk to
> real scopes, which means autopilot tests would be ideal. There are multiple
> branches landing these days that will allow full usage of scopes including
> activation and previewing, once we have all this and some actual scopes that
> the device will be running, we should add tests for all these aspects.

Ok, if this is on the roadmap its fine with me.

The updated mocks for the qmltests look good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Dash/GenericScopeView.qml'
2--- Dash/GenericScopeView.qml 2013-06-27 14:56:17 +0000
3+++ Dash/GenericScopeView.qml 2013-07-05 12:05:30 +0000
4@@ -72,10 +72,10 @@
5 delegate: Tile {
6 width: filtergrid.cellWidth
7 height: filtergrid.cellHeight
8- text: column_5 ? column_5 : "" // FIXME: this shouldn't be necessary
9+ text: title
10 imageWidth: units.gu(11)
11 imageHeight: units.gu(16)
12- source: column_1 ? IconUtil.from_gicon(column_1) : "" // FIXME: ditto
13+ source: IconUtil.from_gicon(icon)
14 }
15 }
16 }
17
18=== modified file 'plugins/Unity/CMakeLists.txt'
19--- plugins/Unity/CMakeLists.txt 2013-07-04 10:34:48 +0000
20+++ plugins/Unity/CMakeLists.txt 2013-07-05 12:05:30 +0000
21@@ -12,11 +12,10 @@
22 )
23
24 set(QMLPLUGIN_SRC
25- ../Utils/qsortfilterproxymodelqml.cpp # FIXME evaluate a more generic approach for using other plugins
26 scope.cpp
27 scopes.cpp
28 categories.cpp
29- categoryfilter.cpp
30+ categoryresults.cpp
31 plugin.cpp
32 bottombarvisibilitycommunicatorshell.cpp
33 launchermodel.cpp
34
35=== modified file 'plugins/Unity/categories.cpp'
36--- plugins/Unity/categories.cpp 2013-06-25 15:59:20 +0000
37+++ plugins/Unity/categories.cpp 2013-07-05 12:05:30 +0000
38@@ -3,6 +3,7 @@
39 *
40 * Authors:
41 * Michał Sawicz <michal.sawicz@canonical.com>
42+ * Michal Hruby <michal.hruby@canonical.com>
43 *
44 * This program is free software; you can redistribute it and/or modify
45 * it under the terms of the GNU General Public License as published by
46@@ -19,18 +20,25 @@
47
48 // self
49 #include "categories.h"
50-#include "categoryfilter.h"
51+#include "categoryresults.h"
52+
53+// TODO: use something from libunity once it's public
54+enum CategoryColumn {
55+ ID,
56+ DISPLAY_NAME,
57+ ICON_HINT,
58+ RENDERER_NAME,
59+ HINTS
60+};
61
62 Categories::Categories(QObject* parent)
63 : DeeListModel(parent)
64- , m_resultModel(0)
65 {
66- // FIXME: need to clean up unused filters on countChanged
67 m_roles[Categories::RoleId] = "id";
68 m_roles[Categories::RoleName] = "name";
69 m_roles[Categories::RoleIcon] = "icon";
70 m_roles[Categories::RoleRenderer] = "renderer";
71- m_roles[Categories::RoleContentType] = "content_type";
72+ m_roles[Categories::RoleContentType] = "contentType";
73 m_roles[Categories::RoleHints] = "hints";
74 m_roles[Categories::RoleResults] = "results";
75 m_roles[Categories::RoleCount] = "count";
76@@ -40,49 +48,57 @@
77 // change of the search term harder to reproduce
78 m_timer.setSingleShot(true);
79 m_timer.setInterval(50);
80- connect(&m_timer, SIGNAL(timeout()), this, SLOT(onEmitCountChanged()));
81-}
82-
83-Categories::~Categories()
84-{
85- qDeleteAll(m_filters);
86-}
87-
88-CategoryFilter*
89-Categories::getFilter(int index) const
90-{
91- if (!m_filters.contains(index)) {
92- CategoryFilter* filter = new CategoryFilter();
93- connect(filter, SIGNAL(countChanged()), this, SLOT(onCountChanged()));
94- filter->setModel(m_resultModel);
95- filter->setIndex(index);
96-
97- m_filters.insert(index, filter);
98- }
99-
100- return m_filters[index];
101+ connect(&m_timer, &QTimer::timeout, this, &Categories::onEmitCountChanged);
102+}
103+
104+DeeListModel*
105+Categories::getResults(int index) const
106+{
107+ if (!m_results.contains(index)) {
108+ CategoryResults* results = new CategoryResults(const_cast<Categories*>(this));
109+ results->setCategoryIndex(index);
110+ connect(results, &DeeListModel::countChanged, this, &Categories::onCountChanged);
111+
112+ unsigned categoryIndex = static_cast<unsigned>(index);
113+ auto unity_results = m_unityScope->GetResultsForCategory(categoryIndex);
114+ results->setModel(unity_results->model());
115+
116+ m_results.insert(index, results);
117+ }
118+
119+ return m_results[index];
120+}
121+
122+void Categories::onCategoriesModelChanged(unity::glib::Object<DeeModel> model)
123+{
124+ m_updatedCategories.clear();
125+ // FIXME: this might destroy the renderer view and re-create it, optimize?
126+ Q_FOREACH(DeeListModel* model, m_results) {
127+ delete model;
128+ }
129+ m_results.clear();
130+ setModel(model);
131 }
132
133 void
134-Categories::setResultModel(DeeListModel* model)
135+Categories::setUnityScope(const unity::dash::Scope::Ptr& scope)
136 {
137- if (model != m_resultModel) {
138- m_resultModel = model;
139-
140- Q_FOREACH(CategoryFilter* filter, m_filters) {
141- filter->setModel(m_resultModel);
142- }
143-
144- Q_EMIT resultModelChanged(m_resultModel);
145- }
146+ m_unityScope = scope;
147+
148+ // no need to call this, we'll get notified
149+ //setModel(m_unityScope->categories()->model());
150+
151+ m_categoriesChangedConnection.disconnect();
152+ m_categoriesChangedConnection =
153+ m_unityScope->categories()->model.changed.connect(sigc::mem_fun(this, &Categories::onCategoriesModelChanged));
154 }
155
156 void
157 Categories::onCountChanged()
158 {
159- CategoryFilter* filter = qobject_cast<CategoryFilter*>(sender());
160- if (filter) {
161- m_timerFilters << filter;
162+ CategoryResults* results = qobject_cast<CategoryResults*>(sender());
163+ if (results) {
164+ m_updatedCategories << results->categoryIndex();
165 m_timer.start();
166 }
167 }
168@@ -92,11 +108,12 @@
169 {
170 QVector<int> roles;
171 roles.append(Categories::RoleCount);
172- Q_FOREACH(CategoryFilter* filter, m_timerFilters) {
173- QModelIndex changedIndex = index(filter->index());
174+ Q_FOREACH(int categoryIndex, m_updatedCategories) {
175+ if (!m_results.contains(categoryIndex)) continue;
176+ QModelIndex changedIndex = index(categoryIndex);
177 Q_EMIT dataChanged(changedIndex, changedIndex, roles);
178 }
179- m_timerFilters.clear();
180+ m_updatedCategories.clear();
181 }
182
183 QHash<int, QByteArray>
184@@ -116,25 +133,22 @@
185 case RoleId:
186 return QVariant::fromValue(index.row());
187 case RoleName:
188- return QVariant::fromValue(DeeListModel::data(index, 1)); //DISPLAY_NAME
189+ return DeeListModel::data(index, CategoryColumn::DISPLAY_NAME);
190 case RoleIcon:
191- return QVariant::fromValue(DeeListModel::data(index, 2)); //ICON_HINT
192+ return DeeListModel::data(index, CategoryColumn::ICON_HINT);
193 case RoleRenderer:
194- return QVariant::fromValue(DeeListModel::data(index, 3)); //RENDERER_NAME
195+ return DeeListModel::data(index, CategoryColumn::RENDERER_NAME);
196 case RoleContentType:
197 {
198- auto hints = QVariant::fromValue(DeeListModel::data(index, 4)).toHash();
199+ auto hints = DeeListModel::data(index, CategoryColumn::HINTS).toHash();
200 return hints.contains("content-type") ? hints["content-type"] : QVariant(QString("default"));
201 }
202 case RoleHints:
203- return QVariant::fromValue(DeeListModel::data(index, 4)); //HINTS
204+ return DeeListModel::data(index, CategoryColumn::HINTS);
205 case RoleResults:
206- return QVariant::fromValue(getFilter(index.row()));
207+ return QVariant::fromValue(getResults(index.row()));
208 case RoleCount:
209- {
210- CategoryFilter* filter = getFilter(index.row());
211- return QVariant::fromValue(filter->rowCount());
212- }
213+ return QVariant::fromValue(getResults(index.row())->rowCount());
214 default:
215 return QVariant();
216 }
217
218=== modified file 'plugins/Unity/categories.h'
219--- plugins/Unity/categories.h 2013-06-25 15:39:10 +0000
220+++ plugins/Unity/categories.h 2013-07-05 12:05:30 +0000
221@@ -3,6 +3,7 @@
222 *
223 * Authors:
224 * Michał Sawicz <michal.sawicz@canonical.com>
225+ * Michal Hruby <michal.hruby@canonical.com>
226 *
227 * This program is free software; you can redistribute it and/or modify
228 * it under the terms of the GNU General Public License as published by
229@@ -21,25 +22,24 @@
230 #ifndef CATEGORIES_H
231 #define CATEGORIES_H
232
233+// unity-core
234+#include <UnityCore/Scope.h>
235+
236 // dee-qt
237 #include "deelistmodel.h"
238
239+#include <QPointer>
240 #include <QSet>
241 #include <QTimer>
242
243-class CategoryFilter;
244-
245 class Categories : public DeeListModel
246 {
247 Q_OBJECT
248
249 Q_ENUMS(Roles)
250
251- Q_PROPERTY(DeeListModel* resultModel READ resultModel WRITE setResultModel NOTIFY resultModelChanged)
252-
253 public:
254 explicit Categories(QObject* parent = 0);
255- ~Categories();
256
257 enum Roles {
258 RoleId,
259@@ -56,27 +56,24 @@
260
261 QHash<int, QByteArray> roleNames() const;
262
263- /* getters */
264- DeeListModel* resultModel() { return m_resultModel; }
265-
266 /* setters */
267- void setResultModel(DeeListModel*);
268-
269-Q_SIGNALS:
270- void resultModelChanged(DeeListModel*);
271+ void setUnityScope(const unity::dash::Scope::Ptr& scope);
272
273 private Q_SLOTS:
274 void onCountChanged();
275 void onEmitCountChanged();
276
277 private:
278- CategoryFilter* getFilter(int index) const;
279-
280+ void onCategoriesModelChanged(unity::glib::Object<DeeModel> model);
281+
282+ DeeListModel* getResults(int index) const;
283+
284+ unity::dash::Scope::Ptr m_unityScope;
285 QTimer m_timer;
286- QSet<CategoryFilter*> m_timerFilters;
287+ QSet<int> m_updatedCategories;
288 QHash<int, QByteArray> m_roles;
289- DeeListModel* m_resultModel;
290- mutable QMap<int, CategoryFilter*> m_filters;
291+ mutable QMap<int, DeeListModel*> m_results;
292+ sigc::connection m_categoriesChangedConnection;
293 };
294
295 #endif // CATEGORIES_H
296
297=== removed file 'plugins/Unity/categoryfilter.cpp'
298--- plugins/Unity/categoryfilter.cpp 2013-06-05 22:03:08 +0000
299+++ plugins/Unity/categoryfilter.cpp 1970-01-01 00:00:00 +0000
300@@ -1,45 +0,0 @@
301-/*
302- * Copyright (C) 2013 Canonical, Ltd.
303- *
304- * Authors:
305- * Michał Sawicz <michal.sawicz@canonical.com>
306- *
307- * This program is free software; you can redistribute it and/or modify
308- * it under the terms of the GNU General Public License as published by
309- * the Free Software Foundation; version 3.
310- *
311- * This program is distributed in the hope that it will be useful,
312- * but WITHOUT ANY WARRANTY; without even the implied warranty of
313- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
314- * GNU General Public License for more details.
315- *
316- * You should have received a copy of the GNU General Public License
317- * along with this program. If not, see <http://www.gnu.org/licenses/>.
318- */
319-
320-#include "categoryfilter.h"
321-
322-int const CATEGORY_COLUMN = 2;
323-
324-CategoryFilter::CategoryFilter(QObject* parent)
325- : QSortFilterProxyModelQML(parent)
326- , m_index(-1)
327-{
328- setDynamicSortFilter(true);
329- setFilterRole(CATEGORY_COLUMN);
330- setFilterRegExp(QString("^%1$").arg(m_index));
331-}
332-
333-int CategoryFilter::index() const
334-{
335- return m_index;
336-}
337-
338-void CategoryFilter::setIndex(int index)
339-{
340- if (index != m_index) {
341- m_index = index;
342- setFilterRegExp(QString("^%1$").arg(m_index));
343- Q_EMIT indexChanged(m_index);
344- }
345-}
346
347=== removed file 'plugins/Unity/categoryfilter.h'
348--- plugins/Unity/categoryfilter.h 2013-06-05 22:03:08 +0000
349+++ plugins/Unity/categoryfilter.h 1970-01-01 00:00:00 +0000
350@@ -1,48 +0,0 @@
351-/*
352- * Copyright (C) 2013 Canonical, Ltd.
353- *
354- * Authors:
355- * Michał Sawicz <michal.sawicz@canonical.com>
356- *
357- * This program is free software; you can redistribute it and/or modify
358- * it under the terms of the GNU General Public License as published by
359- * the Free Software Foundation; version 3.
360- *
361- * This program is distributed in the hope that it will be useful,
362- * but WITHOUT ANY WARRANTY; without even the implied warranty of
363- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
364- * GNU General Public License for more details.
365- *
366- * You should have received a copy of the GNU General Public License
367- * along with this program. If not, see <http://www.gnu.org/licenses/>.
368- */
369-
370-#ifndef CATEGORY_FILTER_H
371-#define CATEGORY_FILTER_H
372-
373-// Utils
374-#include "plugins/Utils/qsortfilterproxymodelqml.h"
375-
376-class CategoryFilter : public QSortFilterProxyModelQML
377-{
378- Q_OBJECT
379-
380- Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
381-
382-public:
383- explicit CategoryFilter(QObject* parent = 0);
384-
385- /* getters */
386- int index() const;
387-
388- /* setters */
389- void setIndex(int index);
390-
391-Q_SIGNALS:
392- void indexChanged(int index);
393-
394-private:
395- int m_index;
396-};
397-
398-#endif // CATEGORY_FILTER_H
399
400=== added file 'plugins/Unity/categoryresults.cpp'
401--- plugins/Unity/categoryresults.cpp 1970-01-01 00:00:00 +0000
402+++ plugins/Unity/categoryresults.cpp 2013-07-05 12:05:30 +0000
403@@ -0,0 +1,96 @@
404+/*
405+ * Copyright (C) 2013 Canonical, Ltd.
406+ *
407+ * Authors:
408+ * Michal Hruby <michal.hruby@canonical.com>
409+ *
410+ * This program is free software; you can redistribute it and/or modify
411+ * it under the terms of the GNU General Public License as published by
412+ * the Free Software Foundation; version 3.
413+ *
414+ * This program is distributed in the hope that it will be useful,
415+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
416+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
417+ * GNU General Public License for more details.
418+ *
419+ * You should have received a copy of the GNU General Public License
420+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
421+ */
422+
423+// self
424+#include "categoryresults.h"
425+
426+// TODO: use something from libunity once it's public
427+enum ResultsColumn {
428+ URI,
429+ ICON_HINT,
430+ CATEGORY,
431+ RESULT_TYPE,
432+ MIMETYPE,
433+ TITLE,
434+ COMMENT,
435+ DND_URI,
436+ METADATA
437+};
438+
439+CategoryResults::CategoryResults(QObject* parent)
440+ : DeeListModel(parent)
441+ , m_categoryIndex(-1)
442+{
443+ m_roles[CategoryResults::RoleUri] = "uri";
444+ m_roles[CategoryResults::RoleIconHint] = "icon";
445+ m_roles[CategoryResults::RoleCategory] = "category";
446+ m_roles[CategoryResults::RoleMimetype] = "mimetype";
447+ m_roles[CategoryResults::RoleTitle] = "title";
448+ m_roles[CategoryResults::RoleComment] = "comment";
449+ m_roles[CategoryResults::RoleDndUri] = "dndUri";
450+ m_roles[CategoryResults::RoleMetadata] = "metadata";
451+}
452+
453+int CategoryResults::categoryIndex() const
454+{
455+ return m_categoryIndex;
456+}
457+
458+void CategoryResults::setCategoryIndex(int index)
459+{
460+ if (m_categoryIndex != index) {
461+ m_categoryIndex = index;
462+ Q_EMIT categoryIndexChanged(m_categoryIndex);
463+ }
464+}
465+
466+QHash<int, QByteArray>
467+CategoryResults::roleNames() const
468+{
469+ return m_roles;
470+}
471+
472+QVariant
473+CategoryResults::data(const QModelIndex& index, int role) const
474+{
475+ if (!index.isValid()) {
476+ return QVariant();
477+ }
478+
479+ switch (role) {
480+ case RoleUri:
481+ return DeeListModel::data(index, ResultsColumn::URI);
482+ case RoleIconHint:
483+ return DeeListModel::data(index, ResultsColumn::ICON_HINT);
484+ case RoleCategory:
485+ return DeeListModel::data(index, ResultsColumn::CATEGORY);
486+ case RoleMimetype:
487+ return DeeListModel::data(index, ResultsColumn::MIMETYPE);
488+ case RoleTitle:
489+ return DeeListModel::data(index, ResultsColumn::TITLE);
490+ case RoleComment:
491+ return DeeListModel::data(index, ResultsColumn::COMMENT);
492+ case RoleDndUri:
493+ return DeeListModel::data(index, ResultsColumn::DND_URI);
494+ case RoleMetadata:
495+ return DeeListModel::data(index, ResultsColumn::METADATA);
496+ default:
497+ return QVariant();
498+ }
499+}
500
501=== added file 'plugins/Unity/categoryresults.h'
502--- plugins/Unity/categoryresults.h 1970-01-01 00:00:00 +0000
503+++ plugins/Unity/categoryresults.h 2013-07-05 12:05:30 +0000
504@@ -0,0 +1,67 @@
505+/*
506+ * Copyright (C) 2013 Canonical, Ltd.
507+ *
508+ * Authors:
509+ * Michal Hruby <michal.hruby@canonical.com>
510+ *
511+ * This program is free software; you can redistribute it and/or modify
512+ * it under the terms of the GNU General Public License as published by
513+ * the Free Software Foundation; version 3.
514+ *
515+ * This program is distributed in the hope that it will be useful,
516+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
517+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
518+ * GNU General Public License for more details.
519+ *
520+ * You should have received a copy of the GNU General Public License
521+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
522+ */
523+
524+
525+#ifndef CATEGORY_RESULTS_H
526+#define CATEGORY_RESULTS_H
527+
528+// dee-qt
529+#include "deelistmodel.h"
530+
531+class CategoryResults : public DeeListModel
532+{
533+ Q_OBJECT
534+
535+ Q_ENUMS(Roles)
536+
537+ Q_PROPERTY(int categoryIndex READ categoryIndex WRITE setCategoryIndex NOTIFY categoryIndexChanged)
538+
539+public:
540+ explicit CategoryResults(QObject* parent = 0);
541+
542+ enum Roles {
543+ RoleUri,
544+ RoleIconHint,
545+ RoleCategory,
546+ RoleMimetype,
547+ RoleTitle,
548+ RoleComment,
549+ RoleDndUri,
550+ RoleMetadata
551+ };
552+
553+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
554+
555+ QHash<int, QByteArray> roleNames() const;
556+
557+ /* getters */
558+ int categoryIndex() const;
559+
560+ /* setters */
561+ void setCategoryIndex(int index);
562+
563+Q_SIGNALS:
564+ void categoryIndexChanged(int index);
565+
566+private:
567+ QHash<int, QByteArray> m_roles;
568+ int m_categoryIndex;
569+};
570+
571+#endif // CATEGORY_RESULTS_H
572
573=== modified file 'plugins/Unity/plugin.cpp'
574--- plugins/Unity/plugin.cpp 2013-06-27 12:02:22 +0000
575+++ plugins/Unity/plugin.cpp 2013-07-05 12:05:30 +0000
576@@ -30,7 +30,7 @@
577 #include "scope.h"
578 #include "scopes.h"
579 #include "categories.h"
580-#include "categoryfilter.h"
581+#include "categoryresults.h"
582 #include "bottombarvisibilitycommunicatorshell.h"
583 #include "launchermodel.h"
584
585@@ -49,7 +49,7 @@
586 qmlRegisterType<Scope>(uri, 0, 1, "Scope");
587 qmlRegisterType<Scopes>(uri, 0, 1, "Scopes");
588 qmlRegisterType<Categories>(uri, 0, 1, "Categories");
589- qmlRegisterType<CategoryFilter>(uri, 0, 1, "CategoryFilter");
590+ qmlRegisterUncreatableType<CategoryResults>(uri, 0, 1, "CategoryResults", "Can't create new Category Results in QML. Get them from Categories instance.");
591 qmlRegisterType<DeeListModel>(uri, 0, 1, "DeeListModel");
592 qmlRegisterType<LauncherModel>(uri, 0, 1, "LauncherModel");
593 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", "Can't create new Launcher Items in QML. Get them from the LauncherModel.");
594
595=== modified file 'plugins/Unity/scope.cpp'
596--- plugins/Unity/scope.cpp 2013-07-02 08:33:11 +0000
597+++ plugins/Unity/scope.cpp 2013-07-05 12:05:30 +0000
598@@ -3,6 +3,7 @@
599 *
600 * Authors:
601 * Florian Boucault <florian.boucault@canonical.com>
602+ * Michal Hruby <michal.hruby@canonical.com>
603 *
604 * This program is free software; you can redistribute it and/or modify
605 * it under the terms of the GNU General Public License as published by
606@@ -38,10 +39,7 @@
607 Scope::Scope(QObject *parent) :
608 QObject(parent)
609 {
610- m_results = new DeeListModel(this);
611- m_categories = new Categories(this);
612-
613- m_categories->setResultModel(m_results);
614+ m_categories.reset(new Categories(this));
615 }
616
617 QString Scope::id() const
618@@ -84,14 +82,9 @@
619 return m_unityScope->connected();
620 }
621
622-DeeListModel* Scope::results() const
623-{
624- return m_results;
625-}
626-
627 Categories* Scope::categories() const
628 {
629- return m_categories;
630+ return m_categories.get();
631 }
632
633 QString Scope::searchQuery() const
634@@ -104,6 +97,11 @@
635 return m_noResultsHint;
636 }
637
638+QString Scope::formFactor() const
639+{
640+ return m_formFactor;
641+}
642+
643 void Scope::setSearchQuery(const QString& search_query)
644 {
645 /* Checking for m_searchQuery.isNull() which returns true only when the string
646@@ -125,6 +123,15 @@
647 }
648 }
649
650+void Scope::setFormFactor(const QString& form_factor) {
651+ if (form_factor != m_formFactor) {
652+ m_formFactor = form_factor;
653+ if (m_unityScope) {
654+ m_unityScope->form_factor = m_formFactor.toStdString();
655+ }
656+ Q_EMIT formFactorChanged();
657+ }
658+}
659
660 unity::dash::LocalResult Scope::createLocalResult(const QVariant &uri, const QVariant &icon_hint,
661 const QVariant &category, const QVariant &result_type,
662@@ -237,18 +244,9 @@
663 {
664 m_unityScope = scope;
665
666- if (QString::fromStdString(m_unityScope->results()->swarm_name) == QString(":local")) {
667- m_results->setModel(m_unityScope->results()->model());
668- } else {
669- m_results->setName(QString::fromStdString(m_unityScope->results()->swarm_name));
670- }
671-
672- if (QString::fromStdString(m_unityScope->categories()->swarm_name) == QString(":local")) {
673- m_categories->setModel(m_unityScope->categories()->model());
674- } else {
675- m_categories->setName(QString::fromStdString(m_unityScope->categories()->swarm_name));
676- }
677-
678+ m_categories->setUnityScope(m_unityScope);
679+
680+ m_unityScope->form_factor = m_formFactor.toStdString();
681 /* Property change signals */
682 m_unityScope->id.changed.connect(sigc::mem_fun(this, &Scope::idChanged));
683 m_unityScope->name.changed.connect(sigc::mem_fun(this, &Scope::nameChanged));
684@@ -258,12 +256,6 @@
685 m_unityScope->visible.changed.connect(sigc::mem_fun(this, &Scope::visibleChanged));
686 m_unityScope->shortcut.changed.connect(sigc::mem_fun(this, &Scope::shortcutChanged));
687 m_unityScope->connected.changed.connect(sigc::mem_fun(this, &Scope::connectedChanged));
688- m_unityScope->results.changed.connect(sigc::mem_fun(this, &Scope::onResultsChanged));
689- m_unityScope->results()->swarm_name.changed.connect(sigc::mem_fun(this, &Scope::onResultsSwarmNameChanged));
690- m_unityScope->results()->model.changed.connect(sigc::mem_fun(this, &Scope::onResultsModelChanged));
691- m_unityScope->categories()->model.changed.connect(sigc::mem_fun(this, &Scope::onCategoriesModelChanged));
692- m_unityScope->categories.changed.connect(sigc::mem_fun(this, &Scope::onCategoriesChanged));
693- m_unityScope->categories()->swarm_name.changed.connect(sigc::mem_fun(this, &Scope::onCategoriesSwarmNameChanged));
694 /* Signals forwarding */
695 connect(this, SIGNAL(searchFinished(const std::string &, unity::glib::HintsMap const &, unity::glib::Error const &)), SLOT(onSearchFinished(const std::string &, unity::glib::HintsMap const &)));
696
697@@ -294,36 +286,6 @@
698 }
699 }
700
701-void Scope::onResultsSwarmNameChanged(const std::string& /* swarm_name */)
702-{
703- m_results->setName(QString::fromStdString(m_unityScope->results()->swarm_name));
704-}
705-
706-void Scope::onResultsChanged(const unity::dash::Results::Ptr& /* results */)
707-{
708- m_results->setName(QString::fromStdString(m_unityScope->results()->swarm_name));
709-}
710-
711-void Scope::onResultsModelChanged(unity::glib::Object<DeeModel> /* model */)
712-{
713- m_results->setModel(m_unityScope->results()->model());
714-}
715-
716-void Scope::onCategoriesSwarmNameChanged(const std::string& /* swarm_name */)
717-{
718- m_categories->setName(QString::fromStdString(m_unityScope->categories()->swarm_name));
719-}
720-
721-void Scope::onCategoriesChanged(const unity::dash::Categories::Ptr& /* categories */)
722-{
723- m_categories->setName(QString::fromStdString(m_unityScope->categories()->swarm_name));
724-}
725-
726-void Scope::onCategoriesModelChanged(unity::glib::Object<DeeModel> model)
727-{
728- m_categories->setModel(model);
729-}
730-
731 void Scope::onSearchFinished(const std::string& /* query */, unity::glib::HintsMap const &hints)
732 {
733 QString hint;
734
735=== modified file 'plugins/Unity/scope.h'
736--- plugins/Unity/scope.h 2013-06-26 10:56:43 +0000
737+++ plugins/Unity/scope.h 2013-07-05 12:05:30 +0000
738@@ -32,7 +32,8 @@
739 // dee-qt
740 #include "deelistmodel.h"
741
742-class Categories;
743+#include "categories.h"
744+
745 class Preview;
746
747 class Scope : public QObject
748@@ -47,11 +48,11 @@
749 Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged)
750 Q_PROPERTY(QString shortcut READ shortcut NOTIFY shortcutChanged)
751 Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
752- Q_PROPERTY(DeeListModel* results READ results NOTIFY resultsChanged)
753 Q_PROPERTY(Categories* categories READ categories NOTIFY categoriesChanged)
754
755 Q_PROPERTY(QString searchQuery READ searchQuery WRITE setSearchQuery NOTIFY searchQueryChanged)
756 Q_PROPERTY(QString noResultsHint READ noResultsHint WRITE setNoResultsHint NOTIFY noResultsHintChanged)
757+ Q_PROPERTY(QString formFactor READ formFactor WRITE setFormFactor NOTIFY formFactorChanged)
758
759 public:
760 explicit Scope(QObject *parent = 0);
761@@ -65,14 +66,15 @@
762 bool visible() const;
763 QString shortcut() const;
764 bool connected() const;
765- DeeListModel* results() const;
766 Categories* categories() const;
767 QString searchQuery() const;
768 QString noResultsHint() const;
769+ QString formFactor() const;
770
771 /* setters */
772 void setSearchQuery(const QString& search_query);
773 void setNoResultsHint(const QString& hint);
774+ void setFormFactor(const QString& form_factor);
775
776 Q_INVOKABLE void activate(const QVariant &uri, const QVariant &icon_hint, const QVariant &category,
777 const QVariant &result_type, const QVariant &mimetype, const QVariant &title,
778@@ -94,11 +96,11 @@
779 void visibleChanged(bool);
780 void shortcutChanged(const std::string&);
781 void connectedChanged(bool);
782- void resultsChanged();
783 void categoriesChanged();
784 void searchFinished(const std::string&, unity::glib::HintsMap const&, unity::glib::Error const&);
785 void searchQueryChanged();
786 void noResultsHintChanged();
787+ void formFactorChanged();
788
789 // signals triggered by activate(..) or preview(..) requests.
790 void previewReady(Preview *preview);
791@@ -111,12 +113,6 @@
792 void onSearchFinished(const std::string &, unity::glib::HintsMap const &);
793
794 private:
795- void onResultsSwarmNameChanged(const std::string&);
796- void onResultsChanged(const unity::dash::Results::Ptr&);
797- void onResultsModelChanged(unity::glib::Object<DeeModel>);
798- void onCategoriesSwarmNameChanged(const std::string&);
799- void onCategoriesModelChanged(unity::glib::Object<DeeModel>);
800- void onCategoriesChanged(const unity::dash::Categories::Ptr&);
801 unity::dash::LocalResult createLocalResult(const QVariant &uri, const QVariant &icon_hint,
802 const QVariant &category, const QVariant &result_type,
803 const QVariant &mimetype, const QVariant &title,
804@@ -127,10 +123,10 @@
805 void fallbackActivate(const QString& uri);
806
807 unity::dash::Scope::Ptr m_unityScope;
808- DeeListModel* m_results;
809- Categories* m_categories;
810+ std::unique_ptr<Categories> m_categories;
811 QString m_searchQuery;
812 QString m_noResultsHint;
813+ QString m_formFactor;
814 };
815
816 Q_DECLARE_METATYPE(Scope*)
817
818=== modified file 'tests/mocks/Unity/CMakeLists.txt'
819--- tests/mocks/Unity/CMakeLists.txt 2013-06-12 15:03:07 +0000
820+++ tests/mocks/Unity/CMakeLists.txt 2013-07-05 12:05:30 +0000
821@@ -19,11 +19,10 @@
822 add_definitions(-DQT_NO_KEYWORDS)
823
824 set(UnityQML_SOURCES
825- ${CMAKE_SOURCE_DIR}/plugins/Unity/categories.cpp
826- ${CMAKE_SOURCE_DIR}/plugins/Unity/categoryfilter.cpp
827- ${CMAKE_SOURCE_DIR}/plugins/Utils/qsortfilterproxymodelqml.cpp
828+ ${CMAKE_SOURCE_DIR}/plugins/Unity/categoryresults.cpp
829 fake_scope.cpp
830 fake_scopes.cpp
831+ fake_categories.cpp
832 fake_unity_plugin.cpp
833 fake_launchermodel.cpp
834 )
835
836=== added file 'tests/mocks/Unity/fake_categories.cpp'
837--- tests/mocks/Unity/fake_categories.cpp 1970-01-01 00:00:00 +0000
838+++ tests/mocks/Unity/fake_categories.cpp 2013-07-05 12:05:30 +0000
839@@ -0,0 +1,178 @@
840+/*
841+ * Copyright (C) 2013 Canonical, Ltd.
842+ *
843+ * This program is free software; you can redistribute it and/or modify
844+ * it under the terms of the GNU General Public License as published by
845+ * the Free Software Foundation; version 3.
846+ *
847+ * This program is distributed in the hope that it will be useful,
848+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
849+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
850+ * GNU General Public License for more details.
851+ *
852+ * You should have received a copy of the GNU General Public License
853+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
854+ *
855+ * Author: Nick Dedekind <nick.dedekind@canonical.com>
856+ */
857+
858+// self
859+#include "fake_categories.h"
860+
861+// local
862+#include "categoryresults.h"
863+
864+#define CATEGORY_COLUMN 2
865+
866+Categories::Categories(QObject* parent)
867+ : DeeListModel(parent)
868+{
869+ // FIXME: need to clean up unused filters on countChanged
870+ m_roles[Categories::RoleId] = "id";
871+ m_roles[Categories::RoleName] = "name";
872+ m_roles[Categories::RoleIcon] = "icon";
873+ m_roles[Categories::RoleRenderer] = "renderer";
874+ m_roles[Categories::RoleContentType] = "content_type";
875+ m_roles[Categories::RoleHints] = "hints";
876+ m_roles[Categories::RoleResults] = "results";
877+ m_roles[Categories::RoleCount] = "count";
878+
879+ // TODO This should not be needed but accumulatting the count changes
880+ // makes the visualization more stable and also makes crashes on fast
881+ // change of the search term harder to reproduce
882+ m_timer.setSingleShot(true);
883+ m_timer.setInterval(50);
884+ connect(&m_timer, SIGNAL(timeout()), this, SLOT(onEmitCountChanged()));
885+}
886+
887+DeeListModel*
888+Categories::getFilter(int index) const
889+{
890+ if (!m_filters.contains(index)) {
891+ auto results = new CategoryResults ();
892+ results->setCategoryIndex(index);
893+ connect(results, SIGNAL(countChanged()), this, SLOT(onCountChanged()));
894+
895+ unsigned cat_index = static_cast<unsigned>(index);
896+ auto model = getResultsForCategory(cat_index);
897+ results->setModel(model);
898+
899+ m_filters.insert(index, results);
900+ }
901+
902+ return m_filters[index];
903+}
904+
905+void
906+Categories::onCountChanged()
907+{
908+ DeeListModel* filter = qobject_cast<DeeListModel*>(sender());
909+ if (filter) {
910+ m_timerFilters << filter;
911+ m_timer.start();
912+ }
913+}
914+
915+void
916+Categories::onEmitCountChanged()
917+{
918+ QVector<int> roles;
919+ roles.append(Categories::RoleCount);
920+ Q_FOREACH(DeeListModel* results, m_timerFilters) {
921+ auto cat_results = qobject_cast<CategoryResults*>(results);
922+ QModelIndex changedIndex = index(cat_results->categoryIndex());
923+ Q_EMIT dataChanged(changedIndex, changedIndex, roles);
924+ }
925+ m_timerFilters.clear();
926+}
927+
928+QHash<int, QByteArray>
929+Categories::roleNames() const
930+{
931+ return m_roles;
932+}
933+
934+void Categories::setResultModel(DeeModel* model)
935+{
936+ // FIXME: should ref it
937+ m_dee_results = model;
938+}
939+
940+
941+static void category_filter_map_func (DeeModel* orig_model,
942+ DeeFilterModel* filter_model,
943+ gpointer user_data)
944+{
945+ DeeModelIter* iter;
946+ DeeModelIter* end;
947+ unsigned index = GPOINTER_TO_UINT(user_data);
948+
949+ iter = dee_model_get_first_iter(orig_model);
950+ end = dee_model_get_last_iter(orig_model);
951+ while (iter != end) {
952+ unsigned category_index = dee_model_get_uint32(orig_model, iter, CATEGORY_COLUMN);
953+ if (index == category_index) {
954+ dee_filter_model_append_iter(filter_model, iter);
955+ }
956+ iter = dee_model_next(orig_model, iter);
957+ }
958+}
959+
960+static gboolean category_filter_notify_func (DeeModel* orig_model,
961+ DeeModelIter* orig_iter,
962+ DeeFilterModel* filter_model,
963+ gpointer user_data)
964+{
965+ unsigned index = GPOINTER_TO_UINT(user_data);
966+ unsigned category_index = dee_model_get_uint32(orig_model, orig_iter, CATEGORY_COLUMN);
967+
968+ if (index != category_index)
969+ return FALSE;
970+
971+ dee_filter_model_insert_iter_with_original_order(filter_model, orig_iter);
972+ return TRUE;
973+}
974+
975+DeeModel* Categories::getResultsForCategory(unsigned cat_index) const
976+{
977+ DeeFilter filter;
978+ filter.map_func = category_filter_map_func;
979+ filter.map_notify = category_filter_notify_func;
980+ filter.destroy = nullptr;
981+ filter.userdata = GUINT_TO_POINTER(cat_index);
982+
983+ DeeModel* filtered_model = dee_filter_model_new(m_dee_results, &filter);
984+ return filtered_model;
985+}
986+
987+QVariant
988+Categories::data(const QModelIndex& index, int role) const
989+{
990+ if (!index.isValid()) {
991+ return QVariant();
992+ }
993+
994+ switch (role) {
995+ case RoleId:
996+ return QVariant::fromValue(index.row());
997+ case RoleName:
998+ return DeeListModel::data(index, 1); //DISPLAY_NAME
999+ case RoleIcon:
1000+ return DeeListModel::data(index, 2); //ICON_HINT
1001+ case RoleRenderer:
1002+ return DeeListModel::data(index, 3); //RENDERER_NAME
1003+ case RoleContentType:
1004+ {
1005+ auto hints = DeeListModel::data(index, 4).toHash();
1006+ return hints.contains("content-type") ? hints["content-type"] : QVariant(QString("default"));
1007+ }
1008+ case RoleHints:
1009+ return DeeListModel::data(index, 4); //HINTS
1010+ case RoleResults:
1011+ return QVariant::fromValue(getFilter(index.row()));
1012+ case RoleCount:
1013+ return QVariant::fromValue(getFilter(index.row())->rowCount());
1014+ default:
1015+ return QVariant();
1016+ }
1017+}
1018
1019=== added file 'tests/mocks/Unity/fake_categories.h'
1020--- tests/mocks/Unity/fake_categories.h 1970-01-01 00:00:00 +0000
1021+++ tests/mocks/Unity/fake_categories.h 2013-07-05 12:05:30 +0000
1022@@ -0,0 +1,69 @@
1023+/*
1024+ * Copyright (C) 2013 Canonical, Ltd.
1025+ *
1026+ * This program is free software; you can redistribute it and/or modify
1027+ * it under the terms of the GNU General Public License as published by
1028+ * the Free Software Foundation; version 3.
1029+ *
1030+ * This program is distributed in the hope that it will be useful,
1031+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1032+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1033+ * GNU General Public License for more details.
1034+ *
1035+ * You should have received a copy of the GNU General Public License
1036+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1037+ */
1038+
1039+#ifndef FAKE_CATEGORIES_H
1040+#define FAKE_CATEGORIES_H
1041+
1042+// Qt
1043+#include <QObject>
1044+#include <QSet>
1045+#include <QTimer>
1046+
1047+#include <dee.h>
1048+#include <deelistmodel.h>
1049+
1050+class Categories : public DeeListModel
1051+{
1052+ Q_OBJECT
1053+
1054+ Q_ENUMS(Roles)
1055+
1056+public:
1057+ Categories(QObject* parent = 0);
1058+ enum Roles {
1059+ RoleId,
1060+ RoleName,
1061+ RoleIcon,
1062+ RoleRenderer,
1063+ RoleContentType,
1064+ RoleHints,
1065+ RoleResults,
1066+ RoleCount
1067+ };
1068+
1069+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
1070+
1071+ QHash<int, QByteArray> roleNames() const;
1072+
1073+ /* setters */
1074+ void setResultModel(DeeModel* model);
1075+
1076+private Q_SLOTS:
1077+ void onCountChanged();
1078+ void onEmitCountChanged();
1079+
1080+private:
1081+ DeeModel* getResultsForCategory(unsigned index) const;
1082+ DeeListModel* getFilter(int index) const;
1083+
1084+ DeeModel* m_dee_results;
1085+ QTimer m_timer;
1086+ QSet<DeeListModel*> m_timerFilters;
1087+ QHash<int, QByteArray> m_roles;
1088+ mutable QMap<int, DeeListModel*> m_filters;
1089+};
1090+
1091+#endif // FAKE_CATEGORIES_H
1092
1093=== modified file 'tests/mocks/Unity/fake_scope.cpp'
1094--- tests/mocks/Unity/fake_scope.cpp 2013-06-14 14:08:46 +0000
1095+++ tests/mocks/Unity/fake_scope.cpp 2013-07-05 12:05:30 +0000
1096@@ -26,32 +26,29 @@
1097 // TODO: Implement remaining pieces
1098
1099 Scope::Scope(QObject* parent)
1100-: QObject(parent),
1101- m_visible(false),
1102- m_categories(new Categories(this)),
1103- m_results(new DeeListModel(this))
1104+ : QObject(parent)
1105+ , m_visible(false)
1106+ , m_categories(new Categories(this))
1107+ , m_results(new DeeListModel(this))
1108 {
1109+ DeeModel* results_model = create_results_model(4, 30);
1110+ m_categories->setResultModel(results_model);
1111 m_categories->setModel(create_categories_model(4));
1112- m_results->setModel(create_results_model(4, 30));
1113-
1114- m_categories->setResultModel(m_results);
1115+ m_results->setModel(results_model);
1116 }
1117
1118-Scope::Scope(QString const& id,
1119- QString const& name,
1120- bool visible,
1121- QObject* parent)
1122-: QObject(parent),
1123- m_id(id),
1124- m_name(name),
1125- m_visible(visible),
1126- m_categories(new Categories(this)),
1127- m_results(new DeeListModel(this))
1128+Scope::Scope(QString const& id, QString const& name, bool visible, QObject* parent)
1129+ : QObject(parent)
1130+ , m_id(id)
1131+ , m_name(name)
1132+ , m_visible(visible)
1133+ , m_categories(new Categories(this))
1134+ , m_results(new DeeListModel(this))
1135 {
1136+ DeeModel* results_model = create_results_model(4, 30);
1137+ m_categories->setResultModel(results_model);
1138 m_categories->setModel(create_categories_model(4));
1139- m_results->setModel(create_results_model(4, 30));
1140-
1141- m_categories->setResultModel(m_results);
1142+ m_results->setModel(results_model);
1143 }
1144
1145 QString Scope::id() const {
1146@@ -66,6 +63,42 @@
1147 return m_searchQuery;
1148 }
1149
1150+QString Scope::iconHint() const {
1151+ return m_iconHint;
1152+}
1153+
1154+QString Scope::description() const {
1155+ return m_description;
1156+}
1157+
1158+QString Scope::searchHint() const {
1159+ return QString("");
1160+}
1161+
1162+QString Scope::shortcut() const {
1163+ return QString("");
1164+}
1165+
1166+bool Scope::connected() const {
1167+ return true;
1168+}
1169+
1170+Categories* Scope::categories() const {
1171+ return m_categories;
1172+}
1173+
1174+QString Scope::noResultsHint() const {
1175+ return m_noResultsHint;
1176+}
1177+
1178+QString Scope::formFactor() const {
1179+ return m_formFactor;
1180+}
1181+
1182+bool Scope::visible() const {
1183+ return m_visible;
1184+}
1185+
1186 void Scope::setName(const QString &str) {
1187 if (str != m_name) {
1188 m_name = str;
1189@@ -76,45 +109,47 @@
1190 void Scope::setSearchQuery(const QString &str) {
1191 if (str != m_searchQuery) {
1192 m_searchQuery = str;
1193- Q_EMIT searchQueryChanged(m_searchQuery);
1194- }
1195-}
1196-
1197-bool Scope::visible() const {
1198- return m_visible;
1199-}
1200-
1201-Categories* Scope::categories() const {
1202- return m_categories;
1203+ Q_EMIT searchQueryChanged();
1204+ }
1205+}
1206+
1207+void Scope::setFormFactor(const QString &str) {
1208+ if (str != m_formFactor) {
1209+ m_formFactor = str;
1210+ Q_EMIT formFactorChanged();
1211+ }
1212+}
1213+
1214+void Scope::setNoResultsHint(const QString& str) {
1215+ if (str != m_noResultsHint) {
1216+ m_noResultsHint = str;
1217+ Q_EMIT noResultsHintChanged();
1218+ }
1219 }
1220
1221 static const gchar * categories_model_schema[] = {
1222- "s", //ID
1223- "s", // DISPLAY_NAME
1224- "s", // ICON_HINT
1225- "s", // RENDERER_NAME
1226- "a{sv}" // HINTS
1227+ "s", //ID
1228+ "s", // DISPLAY_NAME
1229+ "s", // ICON_HINT
1230+ "s", // RENDERER_NAME
1231+ "a{sv}" // HINTS
1232 };
1233
1234
1235 DeeModel* create_categories_model(unsigned category_count) {
1236 DeeModel* category_model = dee_sequence_model_new();
1237 dee_model_set_schema_full(category_model, categories_model_schema, G_N_ELEMENTS(categories_model_schema));
1238-
1239- GVariantBuilder b;
1240- g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}"));
1241- GVariant *hints = g_variant_builder_end(&b);
1242+ GVariant* hints = g_variant_new_array(g_variant_type_element(G_VARIANT_TYPE_VARDICT), NULL, 0);
1243
1244 for(unsigned i = 0; i < category_count; ++i)
1245 {
1246- dee_model_append(category_model,
1247- std::to_string(i).c_str(),
1248- ("Category "+std::to_string(i)).c_str(),
1249- "gtk-apply",
1250- "grid",
1251- hints);
1252+ dee_model_append(category_model,
1253+ std::to_string(i).c_str(),
1254+ ("Category "+std::to_string(i)).c_str(),
1255+ "gtk-apply",
1256+ "grid",
1257+ hints);
1258 }
1259- g_variant_unref(hints);
1260 return category_model;
1261 }
1262
1263@@ -122,48 +157,44 @@
1264 /* Schema that is used in the DeeModel representing
1265 the results */
1266 static const gchar * results_model_schema[] = {
1267- "s", // URI
1268- "s", // ICON_HINT
1269- "u", // CATEGORY
1270- "u", // RESULT_TYPE
1271- "s", // MIMETYPE
1272- "s", // TITLE
1273- "s", // COMMENT
1274- "s", // DND_URI
1275- "a{sv}" // METADATA
1276+ "s", // URI
1277+ "s", // ICON_HINT
1278+ "u", // CATEGORY
1279+ "u", // RESULT_TYPE
1280+ "s", // MIMETYPE
1281+ "s", // TITLE
1282+ "s", // COMMENT
1283+ "s", // DND_URI
1284+ "a{sv}" // METADATA
1285 };
1286
1287 static const gchar * icons[] = {
1288- "Applications.png",
1289- "Home.png",
1290- "Music.png",
1291- "People.png",
1292- "Videos.png",
1293+ "Applications.png",
1294+ "Home.png",
1295+ "Music.png",
1296+ "People.png",
1297+ "Videos.png",
1298 };
1299
1300 DeeModel* create_results_model(unsigned category_count, unsigned result_count) {
1301 DeeModel* results_model = dee_sequence_model_new();
1302 dee_model_set_schema_full(results_model, results_model_schema, G_N_ELEMENTS(results_model_schema));
1303-
1304- GVariantBuilder b;
1305- g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}"));
1306- GVariant *hints = g_variant_builder_end(&b);
1307+ GVariant* hints = g_variant_new_array(g_variant_type_element(G_VARIANT_TYPE_VARDICT), NULL, 0);
1308
1309 for(unsigned i = 0; i < result_count; ++i)
1310 {
1311- unsigned category = i % category_count;
1312+ unsigned category = i % category_count;
1313
1314- dee_model_append(results_model,
1315- ("uri://result."+std::to_string(i)).c_str(),
1316- (shellAppDirectory() + "Dash/graphics/scopeIcons/" + (icons[i%G_N_ELEMENTS(icons)])).toLatin1().data(),
1317- category,
1318- 0,
1319- "application/x-desktop",
1320- ("Title."+std::to_string(i)).c_str(),
1321- ("Comment."+std::to_string(i)).c_str(),
1322- ("uri://result."+std::to_string(i)).c_str(),
1323- hints);
1324+ dee_model_append(results_model,
1325+ ("uri://result."+std::to_string(i)).c_str(),
1326+ (shellAppDirectory() + "Dash/graphics/scopeIcons/" + (icons[i%G_N_ELEMENTS(icons)])).toLatin1().data(),
1327+ category,
1328+ 0,
1329+ "application/x-desktop",
1330+ ("Title."+std::to_string(i)).c_str(),
1331+ ("Comment."+std::to_string(i)).c_str(),
1332+ ("uri://result."+std::to_string(i)).c_str(),
1333+ hints);
1334 }
1335- g_variant_unref(hints);
1336 return results_model;
1337- }
1338+}
1339
1340=== modified file 'tests/mocks/Unity/fake_scope.h'
1341--- tests/mocks/Unity/fake_scope.h 2013-06-12 15:03:07 +0000
1342+++ tests/mocks/Unity/fake_scope.h 2013-07-05 12:05:30 +0000
1343@@ -19,43 +19,75 @@
1344
1345 // Qt
1346 #include <QObject>
1347-#include "categories.h"
1348+#include "fake_categories.h"
1349
1350 class Scope : public QObject
1351 {
1352 Q_OBJECT
1353+
1354 Q_PROPERTY(QString id READ id NOTIFY idChanged)
1355- Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
1356- Q_PROPERTY(QString searchQuery READ searchQuery WRITE setSearchQuery NOTIFY searchQueryChanged)
1357+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
1358+ Q_PROPERTY(QString iconHint READ iconHint NOTIFY iconHintChanged)
1359+ Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
1360+ Q_PROPERTY(QString searchHint READ searchHint NOTIFY searchHintChanged)
1361 Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged)
1362+ Q_PROPERTY(QString shortcut READ shortcut NOTIFY shortcutChanged)
1363+ Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
1364 Q_PROPERTY(Categories* categories READ categories NOTIFY categoriesChanged)
1365
1366+ Q_PROPERTY(QString searchQuery READ searchQuery WRITE setSearchQuery NOTIFY searchQueryChanged)
1367+ Q_PROPERTY(QString noResultsHint READ noResultsHint WRITE setNoResultsHint NOTIFY noResultsHintChanged)
1368+ Q_PROPERTY(QString formFactor READ formFactor WRITE setFormFactor NOTIFY formFactorChanged)
1369+
1370 public:
1371 Scope(QObject* parent = 0);
1372 Scope(QString const& id, QString const& name, bool visible, QObject* parent = 0);
1373
1374+ /* getters */
1375 QString id() const;
1376 QString name() const;
1377- QString searchQuery() const;
1378+ QString iconHint() const;
1379+ QString description() const;
1380+ QString searchHint() const;
1381 bool visible() const;
1382-
1383- void setName(const QString &str);
1384- void setSearchQuery(const QString &str);
1385-
1386+ QString shortcut() const;
1387+ bool connected() const;
1388 Categories* categories() const;
1389+ QString searchQuery() const;
1390+ QString noResultsHint() const;
1391+ QString formFactor() const;
1392+
1393+ /* setters */
1394+ void setName(const QString& name);
1395+ void setSearchQuery(const QString& search_query);
1396+ void setNoResultsHint(const QString& hint);
1397+ void setFormFactor(const QString& form_factor);
1398
1399 Q_SIGNALS:
1400- void idChanged(QString);
1401- void nameChanged(QString);
1402- void searchQueryChanged(QString);
1403+ void idChanged(const QString&);
1404+ void nameChanged(const QString&);
1405+ void iconHintChanged(const QString&);
1406+ void descriptionChanged(const QString&);
1407+ void searchHintChanged(const QString&);
1408 void visibleChanged(bool);
1409+ void shortcutChanged(const QString&);
1410+ void connectedChanged(bool);
1411 void categoriesChanged();
1412+ void searchFinished(const QString&);
1413+ void searchQueryChanged();
1414+ void noResultsHintChanged();
1415+ void formFactorChanged();
1416
1417 private:
1418 QString m_id;
1419+ QString m_iconHint;
1420+ QString m_description;
1421 QString m_name;
1422 QString m_searchQuery;
1423+ QString m_noResultsHint;
1424+ QString m_formFactor;
1425 bool m_visible;
1426+
1427 Categories* m_categories;
1428 DeeListModel* m_results;
1429 };
1430
1431=== modified file 'tests/mocks/Unity/fake_unity_plugin.cpp'
1432--- tests/mocks/Unity/fake_unity_plugin.cpp 2013-06-12 15:03:07 +0000
1433+++ tests/mocks/Unity/fake_unity_plugin.cpp 2013-07-05 12:05:30 +0000
1434@@ -21,8 +21,8 @@
1435
1436 // local
1437 #include "fake_scopes.h"
1438-#include "categories.h"
1439-#include "categoryfilter.h"
1440+#include "fake_categories.h"
1441+#include "categoryresults.h"
1442 #include "fake_launchermodel.h"
1443
1444 // External
1445@@ -40,7 +40,7 @@
1446 qmlRegisterType<Scopes>(uri, 0, 1, "Scopes");
1447 qmlRegisterType<Scope>(uri, 0, 1, "Scope");
1448 qmlRegisterType<Categories>(uri, 0, 1, "Categories");
1449- qmlRegisterType<CategoryFilter>(uri, 0, 1, "CategoryFilter");
1450+ qmlRegisterUncreatableType<CategoryResults>(uri, 0, 1, "CategoryResults", "Can't create");
1451 qmlRegisterType<LauncherModel>(uri, 0, 1, "LauncherModel");
1452 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", "Can't create");
1453 }
1454
1455=== modified file 'tests/plugins/Unity/CMakeLists.txt'
1456--- tests/plugins/Unity/CMakeLists.txt 2013-06-28 14:01:56 +0000
1457+++ tests/plugins/Unity/CMakeLists.txt 2013-07-05 12:05:30 +0000
1458@@ -21,7 +21,7 @@
1459 add_executable(${_test}Exec ${_test}.cpp previewbindingstest.cpp)
1460 qt5_use_modules(${_test}Exec Test Core Qml)
1461 set_tests_properties(test${CLASSNAME}${_test}
1462- PROPERTIES ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Unity:${LIBUNITYPROTO_LIBRARY_DIRS})
1463+ PROPERTIES ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Unity:${LIBUNITYPROTO_LIBRARY_DIRS}")
1464
1465 target_link_libraries(${_test}Exec
1466 Unity-qml
1467
1468=== modified file 'tests/qmltests/Dash/qml/FakeScopeView.qml'
1469--- tests/qmltests/Dash/qml/FakeScopeView.qml 2013-06-13 13:15:36 +0000
1470+++ tests/qmltests/Dash/qml/FakeScopeView.qml 2013-07-05 12:05:30 +0000
1471@@ -107,10 +107,10 @@
1472 Image {
1473 width: units.gu(5)
1474 height: units.gu(5)
1475- source: column_1
1476+ source: icon
1477 anchors.horizontalCenter: parent.horizontalCenter
1478 }
1479- Text { text: column_4; anchors.horizontalCenter: parent.horizontalCenter }
1480+ Text { text: title; anchors.horizontalCenter: parent.horizontalCenter }
1481 }
1482 }
1483 }

Subscribers

People subscribed via source and target branches