Merge lp:~stolowski/unity-scopes-shell/favorite-scopes into lp:unity-scopes-shell

Proposed by Paweł Stołowski
Status: Merged
Approved by: Paweł Stołowski
Approved revision: 148
Merged at revision: 130
Proposed branch: lp:~stolowski/unity-scopes-shell/favorite-scopes
Merge into: lp:unity-scopes-shell
Prerequisite: lp:~aacid/unity-scopes-shell/resetMeansCountChanged
Diff against target: 879 lines (+453/-49)
19 files modified
debian/control (+3/-1)
src/Unity/CMakeLists.txt (+1/-1)
src/Unity/overviewcategories.cpp (+10/-5)
src/Unity/overviewcategories.h (+3/-2)
src/Unity/overviewresults.cpp (+45/-3)
src/Unity/overviewscope.cpp (+24/-3)
src/Unity/overviewscope.h (+2/-0)
src/Unity/scope.cpp (+8/-4)
src/Unity/scope.h (+4/-0)
src/Unity/scopes.cpp (+138/-29)
src/Unity/scopes.h (+9/-1)
tests/CMakeLists.txt (+2/-0)
tests/departmentstest.cpp (+4/-0)
tests/favoritestest.cpp (+168/-0)
tests/overviewtest.cpp (+4/-0)
tests/previewtest.cpp (+3/-0)
tests/resultstest.cpp (+3/-0)
tests/settingsendtoendtest.cpp (+4/-0)
tests/test-utils.h (+18/-0)
To merge this branch: bzr merge lp:~stolowski/unity-scopes-shell/favorite-scopes
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Albert Astals Cid (community) Approve
Michał Sawicz Pending
Review via email: mp+232050@code.launchpad.net

This proposal supersedes a proposal from 2014-08-14.

Commit message

Support scope favouriting.

Description of the change

Support scope favouriting.

Note: the only way to test it atm is to update gsettings key, e.g.

gsettings set com.canonical.Unity.Dash favorite-scopes "['scope://clickscope', 'scope://musicaggregator', 'scope://videoaggregator', 'scope://com.ubuntu.scopes.youtube_youtube', 'scope://com.canonical.scopes.amazon']"

The change should be reflected in the overview ("Favorites") and in the Dash (cards of available scopes).

To post a comment you must log in.
Revision history for this message
Pete Woods (pete-woods) wrote : Posted in a previous version of this proposal

A quick question before reading in detail. Do the tests mess around with the your real settings for the favourite scopes?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote : Posted in a previous version of this proposal

> A quick question before reading in detail. Do the tests mess around with the
> your real settings for the favourite scopes?

No, the tests use a gsettings memory backend and the intention of that is to use a temporary value for the duration of the tests.

Revision history for this message
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

Looking good, few minor comments.

All in all I feel like this required too much effort... Especially how you have to maintain two models :/

We should consider:
a) making the Scopes model a model canned queries, with dash requesting Scope objects as needed
b) using the same model for overview favorites and Scopes

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote : Posted in a previous version of this proposal

Ok, pushed a bunch of fixes and added comments inline.

As discussed on IRC, (a) and (b) suggested above are correct observations, but they require more refactoring in the existing code, so it's fot later.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

I seem to be losing the scope too early when I unfavorite it, causing the transition to be quite jarring :/

We'll need to defer destroying them for a while, or maybe move ownership to QML. Or stop supplying scopes in the model and relying on getScope() instead.

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

+1, thanks!

review: Approve
Revision history for this message
Albert Astals Cid (aacid) wrote :

Michał approved the other branch, this one adds r147 that looks good

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:147
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~stolowski/unity-scopes-shell/favorite-scopes/+merge/232050/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-scopes-shell-ci/207/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-scopes-shell-utopic-amd64-ci/104/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-scopes-shell-utopic-armhf-ci/104/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-scopes-shell-utopic-i386-ci/104/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/unity-scopes-shell-ci/207/rebuild

review: Needs Fixing (continuous-integration)
149. By Paweł Stołowski

Fix test failure.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-08-14 15:31:11 +0000
3+++ debian/control 2014-08-25 17:04:42 +0000
4@@ -4,7 +4,7 @@
5 Build-Depends: cmake,
6 debhelper (>= 9),
7 libunity-api-dev (>= 7.88),
8- libunity-scopes-dev (>= 0.6.2~),
9+ libunity-scopes-dev (>= 0.6.3~),
10 libgsettings-qt-dev (>= 0.1),
11 libqtdbustest1-dev (>= 0.2),
12 libqtdbusmock1-dev (>= 0.2),
13@@ -15,6 +15,7 @@
14 qtdeclarative5-dev,
15 qtdeclarative5-dev-tools,
16 qtdeclarative5-qtquick2-plugin,
17+ unity-schemas (>= 7.3.1),
18 Standards-Version: 3.9.5
19 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
20 Homepage: https://launchpad.net/unity-scopes-shell
21@@ -30,6 +31,7 @@
22 Pre-Depends: ${misc:Pre-Depends}
23 Depends: ${misc:Depends},
24 ${shlibs:Depends},
25+ unity-schemas (>= 7.3.1),
26 Provides: unity-scopes-impl,
27 unity-scopes-impl-0,
28 unity-scopes-impl-1,
29
30=== modified file 'src/Unity/CMakeLists.txt'
31--- src/Unity/CMakeLists.txt 2014-08-14 15:31:11 +0000
32+++ src/Unity/CMakeLists.txt 2014-08-25 17:04:42 +0000
33@@ -3,7 +3,7 @@
34
35 # Dependencies
36 pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=4)
37-pkg_check_modules(SCOPESLIB REQUIRED libunity-scopes>=0.6.2)
38+pkg_check_modules(SCOPESLIB REQUIRED libunity-scopes>=0.6.3)
39 pkg_check_modules(GSETTINGSQT REQUIRED gsettings-qt)
40 pkg_check_modules(UBUNTU_LOCATION_SERVICE REQUIRED ubuntu-location-service)
41
42
43=== modified file 'src/Unity/overviewcategories.cpp'
44--- src/Unity/overviewcategories.cpp 2014-07-22 13:49:50 +0000
45+++ src/Unity/overviewcategories.cpp 2014-08-25 17:04:42 +0000
46@@ -53,7 +53,7 @@
47 , m_isSurfacing(true)
48 {
49 m_allScopes.reset(new OverviewResultsModel(this));
50- m_favouriteScopes.reset(new OverviewResultsModel(this));
51+ m_favoriteScopes.reset(new OverviewResultsModel(this));
52
53 m_surfaceCategories.append(QSharedPointer<ScopesCategoryData>(new ScopesCategoryData("favorites", CATEGORY_JSON)));
54 m_surfaceCategories.append(QSharedPointer<ScopesCategoryData>(new ScopesCategoryData("all", CATEGORY_JSON)));
55@@ -84,10 +84,10 @@
56 QModelIndex changedIndex(index(1));
57 dataChanged(changedIndex, changedIndex, roles);
58 }
59-
60-void OverviewCategories::setFavouriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes)
61+
62+void OverviewCategories::setFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes)
63 {
64- m_favouriteScopes->setResults(scopes);
65+ m_favoriteScopes->setResults(scopes);
66
67 if (!m_isSurfacing) return;
68
69@@ -98,6 +98,11 @@
70 dataChanged(changedIndex, changedIndex, roles);
71 }
72
73+void OverviewCategories::updateFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes)
74+{
75+ m_favoriteScopes->setResults(scopes);
76+}
77+
78 int OverviewCategories::rowCount(const QModelIndex& parent) const
79 {
80 if (m_isSurfacing) {
81@@ -115,7 +120,7 @@
82 }
83
84 ScopesCategoryData* catData = m_surfaceCategories.at(index.row()).data();
85- OverviewResultsModel* results = index.row() == 0 ? m_favouriteScopes.data() : m_allScopes.data();
86+ OverviewResultsModel* results = index.row() == 0 ? m_favoriteScopes.data() : m_allScopes.data();
87
88 switch (role) {
89 case RoleCategoryId:
90
91=== modified file 'src/Unity/overviewcategories.h'
92--- src/Unity/overviewcategories.h 2014-07-18 09:21:24 +0000
93+++ src/Unity/overviewcategories.h 2014-08-25 17:04:42 +0000
94@@ -45,14 +45,15 @@
95 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
96
97 void setAllScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);
98- void setFavouriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);
99+ void setFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);
100+ void updateFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);
101
102 private:
103 bool m_isSurfacing;
104
105 QList<QSharedPointer<ScopesCategoryData>> m_surfaceCategories;
106 QScopedPointer<OverviewResultsModel> m_allScopes;
107- QScopedPointer<OverviewResultsModel> m_favouriteScopes;
108+ QScopedPointer<OverviewResultsModel> m_favoriteScopes;
109 };
110
111 } // namespace scopes_ng
112
113=== modified file 'src/Unity/overviewresults.cpp'
114--- src/Unity/overviewresults.cpp 2014-08-25 17:04:42 +0000
115+++ src/Unity/overviewresults.cpp 2014-08-25 17:04:42 +0000
116@@ -25,6 +25,7 @@
117 #include "utils.h"
118
119 #include <unity/scopes/Result.h>
120+#include <QSet>
121
122 namespace scopes_ng {
123
124@@ -68,9 +69,50 @@
125
126 void OverviewResultsModel::setResults(const QList<unity::scopes::ScopeMetadata::SPtr>& results)
127 {
128- beginResetModel();
129- m_results = results;
130- endResetModel();
131+ if (m_results.empty()) {
132+ beginResetModel();
133+ m_results = results;
134+ endResetModel();
135+ Q_EMIT countChanged();
136+ return;
137+ }
138+
139+ QSet<QString> newResult;
140+ for (auto const res: results) {
141+ newResult.insert(QString::fromStdString(res->scope_id()));
142+ }
143+
144+ // itearate over old results, remove rows that are not present in new results
145+ int row = 0;
146+ for (auto it = m_results.begin(); it != m_results.end();)
147+ {
148+ if (!newResult.contains(QString::fromStdString((*it)->scope_id()))) {
149+ beginRemoveRows(QModelIndex(), row, row);
150+ it = m_results.erase(it);
151+ endRemoveRows();
152+ } else {
153+ ++it;
154+ ++row;
155+ }
156+ }
157+
158+ QSet<QString> oldResult;
159+ for (auto const res: m_results) {
160+ oldResult.insert(QString::fromStdString(res->scope_id()));
161+ }
162+
163+ // iterate over new results, insert rows if not in previous model
164+ row = 0;
165+ for (auto const newRes: results)
166+ {
167+ if (!oldResult.contains(QString::fromStdString(newRes->scope_id())))
168+ {
169+ beginInsertRows(QModelIndex(), row, row);
170+ m_results.insert(row, newRes);
171+ endInsertRows();
172+ }
173+ ++row;
174+ }
175 Q_EMIT countChanged();
176 }
177
178
179=== modified file 'src/Unity/overviewscope.cpp'
180--- src/Unity/overviewscope.cpp 2014-08-01 11:43:58 +0000
181+++ src/Unity/overviewscope.cpp 2014-08-25 17:04:42 +0000
182@@ -66,11 +66,11 @@
183 }
184
185 QMap<QString, scopes::ScopeMetadata::SPtr> allMetadata = m_scopesInstance->getAllMetadata();
186- QList<scopes::ScopeMetadata::SPtr> favourites;
187+ QList<scopes::ScopeMetadata::SPtr> favorites;
188 Q_FOREACH(QString id, m_scopesInstance->getFavoriteIds()) {
189 auto it = allMetadata.find(id);
190 if (it != allMetadata.end()) {
191- favourites.append(it.value());
192+ favorites.append(it.value());
193 }
194 }
195
196@@ -88,7 +88,7 @@
197
198 // FIXME: filter invisible scopes?
199 categories->setAllScopes(allScopes);
200- categories->setFavouriteScopes(favourites);
201+ categories->setFavoriteScopes(favorites);
202 }
203
204 QString OverviewScope::id() const
205@@ -106,6 +106,27 @@
206 }
207 }
208
209+void OverviewScope::updateFavorites(const QStringList& favorites)
210+{
211+ QList<scopes::ScopeMetadata::SPtr> favs;
212+ auto allMetadata = m_scopesInstance->getAllMetadata();
213+ for (auto const id: favorites)
214+ {
215+ auto it = allMetadata.find(id);
216+ if (it != allMetadata.end()) {
217+ favs.append(it.value());
218+ }
219+ }
220+
221+ OverviewCategories* categories = qobject_cast<OverviewCategories*>(m_categories.data());
222+ if (!categories) {
223+ qWarning("Unable to cast m_categories to OverviewCategories");
224+ return;
225+ }
226+
227+ categories->updateFavoriteScopes(favs);
228+}
229+
230 void OverviewScope::dispatchSearch()
231 {
232 OverviewCategories* categories = qobject_cast<OverviewCategories*>(m_categories.data());
233
234=== modified file 'src/Unity/overviewscope.h'
235--- src/Unity/overviewscope.h 2014-08-01 11:43:58 +0000
236+++ src/Unity/overviewscope.h 2014-08-25 17:04:42 +0000
237@@ -38,6 +38,8 @@
238
239 void dispatchSearch() override;
240
241+ void updateFavorites(const QStringList& favorites);
242+
243 unity::scopes::ScopeProxy proxy_for_result(unity::scopes::Result::SPtr const& result) const override;
244
245 private Q_SLOTS:
246
247=== modified file 'src/Unity/scope.cpp'
248--- src/Unity/scope.cpp 2014-08-18 18:41:23 +0000
249+++ src/Unity/scope.cpp 2014-08-25 17:04:42 +0000
250@@ -74,6 +74,7 @@
251 , m_delayedClear(false)
252 , m_hasNavigation(false)
253 , m_hasAltNavigation(false)
254+ , m_favorite(false)
255 , m_searchController(new CollectionController)
256 , m_activationController(new CollectionController)
257 , m_status(Status::Okay)
258@@ -753,7 +754,7 @@
259
260 bool Scope::favorite() const
261 {
262- return true;
263+ return m_favorite;
264 }
265
266 QString Scope::shortcut() const
267@@ -993,9 +994,12 @@
268
269 void Scope::setFavorite(const bool value)
270 {
271- Q_UNUSED(value);
272-
273- qWarning("Unimplemented: %s", __func__);
274+ if (value != m_favorite)
275+ {
276+ m_favorite = value;
277+ Q_EMIT favoriteChanged(value);
278+ m_scopesInstance->setFavorite(id(), value);
279+ }
280 }
281
282 void Scope::activate(QVariant const& result_var)
283
284=== modified file 'src/Unity/scope.h'
285--- src/Unity/scope.h 2014-08-13 08:36:17 +0000
286+++ src/Unity/scope.h 2014-08-25 17:04:42 +0000
287@@ -111,6 +111,8 @@
288
289 virtual bool event(QEvent* ev) override;
290
291+ Q_PROPERTY(bool favorite READ favorite WRITE setFavorite NOTIFY favoriteChanged)
292+
293 /* getters */
294 QString id() const override;
295 QString name() const override;
296@@ -162,6 +164,7 @@
297
298 Q_SIGNALS:
299 void resultsDirtyChanged();
300+ void favoriteChanged(bool);
301
302 private Q_SLOTS:
303 void flushUpdates();
304@@ -207,6 +210,7 @@
305 bool m_delayedClear;
306 bool m_hasNavigation;
307 bool m_hasAltNavigation;
308+ bool m_favorite;
309
310 std::unique_ptr<CollectionController> m_searchController;
311 std::unique_ptr<CollectionController> m_activationController;
312
313=== modified file 'src/Unity/scopes.cpp'
314--- src/Unity/scopes.cpp 2014-08-01 13:12:54 +0000
315+++ src/Unity/scopes.cpp 2014-08-25 17:04:42 +0000
316@@ -82,6 +82,7 @@
317 }
318
319 int Scopes::LIST_DELAY = -1;
320+const int Scopes::SCOPE_DELETE_DELAY = 3;
321
322 class Scopes::Priv : public QObject {
323 Q_OBJECT
324@@ -111,6 +112,12 @@
325
326 QDBusConnection::sessionBus().connect(QString(), QString("/com/canonical/unity/scopes"), QString("com.canonical.unity.scopes"), QString("InvalidateResults"), this, SLOT(invalidateScopeResults(QString)));
327
328+ m_dashSettings = QGSettings::isSchemaInstalled("com.canonical.Unity.Dash") ? new QGSettings("com.canonical.Unity.Dash", QByteArray(), this) : nullptr;
329+ if (m_dashSettings)
330+ {
331+ QObject::connect(m_dashSettings, &QGSettings::changed, this, &Scopes::dashSettingsChanged);
332+ }
333+
334 m_overviewScope = new OverviewScope(this);
335 m_locationService.reset(new UbuntuLocationService());
336 }
337@@ -159,31 +166,8 @@
338 std::bind(&Scopes::Priv::safeInvalidateScopeResults,
339 m_priv.get(), SCOPES_SCOPE_ID))));
340
341- // FIXME: use a dconf setting for this
342- QByteArray enabledScopes = qgetenv("UNITY_SCOPES_LIST");
343-
344 beginResetModel();
345
346- if (!enabledScopes.isNull()) {
347- QList<QByteArray> scopeList = enabledScopes.split(';');
348- for (int i = 0; i < scopeList.size(); i++) {
349- std::string scope_name(scopeList[i].constData());
350- auto it = scopes.find(scope_name);
351- if (it != scopes.end()) {
352- auto scope = new Scope(this);
353- scope->setScopeData(it->second);
354- m_scopes.append(scope);
355- }
356- }
357- } else {
358- // add all the scopes
359- for (auto it = scopes.begin(); it != scopes.end(); ++it) {
360- auto scope = new Scope(this);
361- scope->setScopeData(it->second);
362- m_scopes.append(scope);
363- }
364- }
365-
366 // HACK! deal with the overview scope
367 {
368 auto it = scopes.find(SCOPES_SCOPE_ID);
369@@ -199,6 +183,7 @@
370 m_cachedMetadata[QString::fromStdString(it->first)] = std::make_shared<unity::scopes::ScopeMetadata>(it->second);
371 }
372
373+ processFavoriteScopes();
374 endResetModel();
375
376 m_loaded = true;
377@@ -210,6 +195,104 @@
378 m_listThread = nullptr;
379 }
380
381+void Scopes::processFavoriteScopes()
382+{
383+ //
384+ // read the favoriteScopes array value from gsettings.
385+ // process it and turn its values into scope ids.
386+ // create new Scope objects or remove existing according to the list of favorities.
387+ // notify about scopes model changes accordingly.
388+ if (m_dashSettings) {
389+ QStringList newFavorites;
390+ QSet<QString> favScopesLut;
391+ for (auto const& fv: m_dashSettings->get("favoriteScopes").toList())
392+ {
393+ try
394+ {
395+ auto const query = unity::scopes::CannedQuery::from_uri(fv.toString().toStdString());
396+ const QString id = QString::fromStdString(query.scope_id());
397+ newFavorites.push_back(id);
398+ favScopesLut.insert(id);
399+ }
400+ catch (const InvalidArgumentException &e)
401+ {
402+ qWarning() << "Invalid canned query '" << fv.toString() << "'" << QString::fromStdString(e.what());
403+ }
404+ }
405+
406+ // this prevents further processing if we get called back when calling scope->setFavorite() below
407+ if (m_favoriteScopes == newFavorites)
408+ return;
409+
410+ m_favoriteScopes = newFavorites;
411+
412+ QSet<QString> oldScopes;
413+ int row = 0;
414+ // remove un-favorited scopes
415+ for (auto it = m_scopes.begin(); it != m_scopes.end();)
416+ {
417+ if (!favScopesLut.contains((*it)->id()))
418+ {
419+ beginRemoveRows(QModelIndex(), row, row);
420+ (*it)->setFavorite(false);
421+ //
422+ // we need to delay actual deletion of Scope object so that shell can animate it
423+ QTimer::singleShot(1000 * SCOPE_DELETE_DELAY, (*it), SLOT(deleteLater));
424+ it = m_scopes.erase(it);
425+ endRemoveRows();
426+ }
427+ else
428+ {
429+ oldScopes.insert((*it)->id());
430+ ++it;
431+ ++row;
432+ }
433+ }
434+
435+ // add new favorites
436+ row = 0;
437+ for (auto favIt = m_favoriteScopes.begin(); favIt != m_favoriteScopes.end(); )
438+ {
439+ auto const fav = *favIt;
440+ if (!oldScopes.contains(fav))
441+ {
442+ auto it = m_cachedMetadata.find(fav);
443+ if (it != m_cachedMetadata.end())
444+ {
445+ auto scope = new Scope(this);
446+ scope->setScopeData(*(it.value()));
447+ scope->setFavorite(true);
448+ beginInsertRows(QModelIndex(), row, row);
449+ m_scopes.insert(row, scope);
450+ endInsertRows();
451+ }
452+ else
453+ {
454+ qWarning() << "No such scope:" << fav;
455+ favIt = m_favoriteScopes.erase(favIt);
456+ continue;
457+ }
458+ }
459+ ++row;
460+ ++favIt;
461+ }
462+ }
463+}
464+
465+void Scopes::dashSettingsChanged(QString const& key)
466+{
467+ if (key != "favoriteScopes") {
468+ return;
469+ }
470+
471+ processFavoriteScopes();
472+
473+ if (m_overviewScope)
474+ {
475+ m_overviewScope->updateFavorites(m_favoriteScopes);
476+ }
477+}
478+
479 void Scopes::refreshFinished()
480 {
481 ScopeListWorker* thread = qobject_cast<ScopeListWorker*>(sender());
482@@ -288,13 +371,39 @@
483
484 QStringList Scopes::getFavoriteIds() const
485 {
486- QStringList ids;
487-
488- Q_FOREACH(Scope* scope, m_scopes) {
489- ids << scope->id();
490+ return m_favoriteScopes;
491+}
492+
493+void Scopes::setFavorite(QString const& scopeId, bool value)
494+{
495+ if (m_dashSettings)
496+ {
497+ QStringList cannedQueries;
498+ bool changed = false;
499+
500+ for (auto const& fav: m_favoriteScopes)
501+ {
502+ if (value == false && fav == scopeId) {
503+ changed = true;
504+ continue; // skip it
505+ }
506+ // TODO: use CannedQuery::to_uri() when we really support them
507+ const QString query = "scope://" + fav;
508+ cannedQueries.push_back(query);
509+ }
510+
511+ if (value && !m_favoriteScopes.contains(scopeId)) {
512+ const QString query = "scope://" + scopeId;
513+ cannedQueries.push_back(query);
514+ changed = true;
515+ }
516+
517+ if (changed) {
518+ // update gsettings entry
519+ // note: this will trigger notification, so that new favorites are processed by processFavoriteScopes
520+ m_dashSettings->set("favoriteScopes", QVariant(cannedQueries));
521+ }
522 }
523-
524- return ids;
525 }
526
527 QMap<QString, unity::scopes::ScopeMetadata::SPtr> Scopes::getAllMetadata() const
528
529=== modified file 'src/Unity/scopes.h'
530--- src/Unity/scopes.h 2014-08-01 13:12:54 +0000
531+++ src/Unity/scopes.h 2014-08-25 17:04:42 +0000
532@@ -29,6 +29,7 @@
533 #include <QThread>
534 #include <QStringList>
535 #include <QSharedPointer>
536+#include <QGSettings>
537
538 #include <unity/scopes/Runtime.h>
539 #include <unity/scopes/Registry.h>
540@@ -40,6 +41,7 @@
541 {
542
543 class Scope;
544+class OverviewScope;
545
546 class Q_DECL_EXPORT Scopes : public unity::shell::scopes::ScopesInterface
547 {
548@@ -58,6 +60,7 @@
549 unity::scopes::ScopeMetadata::SPtr getCachedMetadata(QString const& scopeId) const;
550 QMap<QString, unity::scopes::ScopeMetadata::SPtr> getAllMetadata() const;
551 QStringList getFavoriteIds() const;
552+ void setFavorite(QString const& scopeId, bool value);
553
554 void refreshScopeMetadata();
555
556@@ -71,6 +74,8 @@
557 void metadataRefreshed();
558
559 private Q_SLOTS:
560+ void dashSettingsChanged(QString const &key);
561+ void processFavoriteScopes();
562 void populateScopes();
563 void discoveryFinished();
564 void refreshFinished();
565@@ -78,11 +83,14 @@
566
567 private:
568 static int LIST_DELAY;
569+ static const int SCOPE_DELETE_DELAY;
570 class Priv;
571
572 QList<Scope*> m_scopes;
573+ QStringList m_favoriteScopes;
574+ QGSettings* m_dashSettings;
575 QMap<QString, unity::scopes::ScopeMetadata::SPtr> m_cachedMetadata;
576- Scope* m_overviewScope;
577+ OverviewScope* m_overviewScope;
578 QThread* m_listThread;
579 bool m_loaded;
580
581
582=== modified file 'tests/CMakeLists.txt'
583--- tests/CMakeLists.txt 2014-08-13 17:16:21 +0000
584+++ tests/CMakeLists.txt 2014-08-25 17:04:42 +0000
585@@ -36,6 +36,7 @@
586 add_executable(${_test}Exec
587 ${_test}.cpp
588 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
589+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopeInterface.h
590 )
591 qt5_use_modules(${_test}Exec Test Core Qml DBus)
592 set_tests_properties(test${CLASSNAME}${_test}
593@@ -57,6 +58,7 @@
594
595 run_tests(
596 departmentstest
597+ favoritestest
598 locationtest
599 overviewtest
600 previewtest
601
602=== modified file 'tests/departmentstest.cpp'
603--- tests/departmentstest.cpp 2014-08-04 15:06:55 +0000
604+++ tests/departmentstest.cpp 2014-08-25 17:04:42 +0000
605@@ -64,6 +64,10 @@
606
607 void init()
608 {
609+ QStringList favs;
610+ favs << "scope://mock-scope-departments" << "scope://mock-scope-double-nav";
611+ setFavouriteScopes(favs);
612+
613 m_scopes.reset(new Scopes(nullptr));
614 // no scopes on startup
615 QCOMPARE(m_scopes->rowCount(), 0);
616
617=== added file 'tests/favoritestest.cpp'
618--- tests/favoritestest.cpp 1970-01-01 00:00:00 +0000
619+++ tests/favoritestest.cpp 2014-08-25 17:04:42 +0000
620@@ -0,0 +1,168 @@
621+/*
622+ * Copyright (C) 2014 Canonical, Ltd.
623+ *
624+ * This program is free software; you can redistribute it and/or modify
625+ * it under the terms of the GNU General Public License as published by
626+ * the Free Software Foundation; version 3.
627+ *
628+ * This program is distributed in the hope that it will be useful,
629+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
630+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
631+ * GNU General Public License for more details.
632+ *
633+ * You should have received a copy of the GNU General Public License
634+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
635+ *
636+ * Authors:
637+ * Pawel Stolowski <pawel.stolowski@canonical.com>
638+ */
639+
640+#include <QObject>
641+#include <QTest>
642+#include <QScopedPointer>
643+#include <QSignalSpy>
644+
645+#include <scopes.h>
646+#include <scope.h>
647+#include <overviewresults.h>
648+#include <unity/shell/scopes/ScopeInterface.h>
649+
650+#include "registry-spawner.h"
651+#include "test-utils.h"
652+
653+using namespace scopes_ng;
654+using namespace unity::shell::scopes;
655+
656+class FavoritesTest: public QObject
657+{
658+ Q_OBJECT
659+private:
660+ QScopedPointer<Scopes> m_scopes;
661+ Scope* m_overviewScope;
662+ QScopedPointer<RegistrySpawner> m_registry;
663+
664+private Q_SLOTS:
665+ void initTestCase()
666+ {
667+ m_registry.reset(new RegistrySpawner);
668+ }
669+
670+ void cleanupTestCase()
671+ {
672+ m_registry.reset();
673+ }
674+
675+ void init()
676+ {
677+ setFavouriteScopes(QStringList());
678+
679+ m_scopes.reset(new Scopes(nullptr));
680+
681+ // no scopes on startup
682+ QCOMPARE(m_scopes->rowCount(), 0);
683+ QCOMPARE(m_scopes->loaded(), false);
684+ QSignalSpy spy(m_scopes.data(), SIGNAL(loadedChanged()));
685+
686+ // wait till the registry spawns
687+ QVERIFY(spy.wait());
688+ QCOMPARE(m_scopes->loaded(), true);
689+
690+ // no scopes after startup, since favorites empty
691+ QCOMPARE(m_scopes->rowCount(), 0);
692+
693+ // get overview scope proxy
694+ m_overviewScope = qobject_cast<scopes_ng::Scope*>(m_scopes->overviewScope());
695+ QVERIFY(m_overviewScope!= nullptr);
696+ m_overviewScope->setActive(true);
697+ }
698+
699+ void testFavoritePropertyUpdates()
700+ {
701+ QStringList favs;
702+ favs << "scope://mock-scope-departments";
703+ setFavouriteScopes(favs);
704+
705+ // should have one scope now
706+ QTRY_COMPARE(m_scopes->rowCount(), 1);
707+ auto scope1 = qobject_cast<scopes_ng::Scope*>(m_scopes->getScope(QString("mock-scope-departments")));
708+ QVERIFY(scope1 != nullptr);
709+ QCOMPARE(scope1->favorite(), true);
710+
711+ favs << "scope://mock-scope-double-nav";
712+ setFavouriteScopes(favs);
713+
714+ // should have two scopes now
715+ QTRY_COMPARE(m_scopes->rowCount(), 2);
716+ auto scope2 = qobject_cast<scopes_ng::Scope*>(m_scopes->getScope(QString("mock-scope-double-nav")));
717+ QVERIFY(scope2 != nullptr);
718+ QCOMPARE(scope2->favorite(), true);
719+
720+ // unfavorite 1st scope
721+ QSignalSpy spy(scope1, SIGNAL(favoriteChanged(bool)));
722+ scope1->setFavorite(false);
723+ QTRY_COMPARE(spy.count(), 1);
724+ QTRY_COMPARE(m_scopes->rowCount(), 1);
725+ QVERIFY(m_scopes->getScopeById("mock-scope-departments") == nullptr);
726+ QCOMPARE(m_scopes->data(m_scopes->index(0), Scopes::RoleId), QVariant(QString("mock-scope-double-nav")));
727+
728+ // favorite a scope
729+ auto overviewScope = m_scopes->overviewScope();
730+ QVERIFY(overviewScope != nullptr);
731+ connect(overviewScope, &unity::shell::scopes::ScopeInterface::openScope, [](unity::shell::scopes::ScopeInterface* scope) {
732+ QCOMPARE(scope->favorite(), false);
733+ scope->setFavorite(true);
734+ });
735+
736+ QVERIFY(m_scopes->getScopeById("mock-scope") == nullptr);
737+ overviewScope->performQuery("scope://mock-scope");
738+ QTRY_VERIFY(m_scopes->getScopeById("mock-scope") != nullptr);
739+ }
740+
741+ void testFavoritesOverviewUpdates()
742+ {
743+ auto categories = m_overviewScope->categories();
744+ QVERIFY(categories->rowCount() > 0);
745+ QCOMPARE(categories->data(categories->index(0), Categories::Roles::RoleCategoryId), QVariant(QString("favorites")));
746+
747+ QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
748+ QVERIFY(results_var.canConvert<OverviewResultsModel*>());
749+ OverviewResultsModel* results = results_var.value<OverviewResultsModel*>();
750+ QCOMPARE(results->rowCount(), 0);
751+
752+ // favorite one scope, check if it appears in the favorites model
753+ QStringList favs;
754+ favs << "scope://mock-scope-departments";
755+ setFavouriteScopes(favs);
756+
757+ QTRY_COMPARE(results->rowCount(), 1);
758+
759+ // unfavorite it, verify it disappears from favorites model
760+ setFavouriteScopes(QStringList());
761+ QTRY_COMPARE(results->rowCount(), 0);
762+ }
763+
764+ void testGSettingsUpdates()
765+ {
766+ QStringList favs;
767+ favs << "scope://mock-scope-departments" << "scope://mock-scope-double-nav";
768+ setFavouriteScopes(favs);
769+
770+ // should have two scopes
771+ QTRY_COMPARE(m_scopes->rowCount(), 2);
772+ auto scope1 = qobject_cast<scopes_ng::Scope*>(m_scopes->getScope(QString("mock-scope-departments")));
773+ QVERIFY(scope1 != nullptr);
774+
775+ // un-facorite one scope
776+ scope1->setFavorite(false);
777+ QTRY_COMPARE(getFavoriteScopes().size(), 1);
778+ QCOMPARE(getFavoriteScopes().at(0), QString("scope://mock-scope-double-nav"));
779+ }
780+
781+ void cleanup()
782+ {
783+ m_scopes.reset();
784+ }
785+};
786+
787+QTEST_GUILESS_MAIN(FavoritesTest)
788+#include <favoritestest.moc>
789
790=== modified file 'tests/overviewtest.cpp'
791--- tests/overviewtest.cpp 2014-07-31 11:23:44 +0000
792+++ tests/overviewtest.cpp 2014-08-25 17:04:42 +0000
793@@ -60,6 +60,10 @@
794
795 void init()
796 {
797+ QStringList favs;
798+ favs << "scope://mock-scope-departments" << "scope://mock-scope-double-nav";
799+ setFavouriteScopes(favs);
800+
801 m_scopes.reset(new Scopes(nullptr));
802 // no scopes on startup
803 QCOMPARE(m_scopes->rowCount(), 0);
804
805=== modified file 'tests/previewtest.cpp'
806--- tests/previewtest.cpp 2014-08-06 08:35:20 +0000
807+++ tests/previewtest.cpp 2014-08-25 17:04:42 +0000
808@@ -58,6 +58,9 @@
809
810 void init()
811 {
812+ const QStringList favs {"scope://mock-scope-departments", "scope://mock-scope-double-nav", "scope://mock-scope"};
813+ setFavouriteScopes(favs);
814+
815 m_scopes.reset(new Scopes(nullptr));
816 // no scopes on startup
817 QCOMPARE(m_scopes->rowCount(), 0);
818
819=== modified file 'tests/resultstest.cpp'
820--- tests/resultstest.cpp 2014-08-14 10:23:34 +0000
821+++ tests/resultstest.cpp 2014-08-25 17:04:42 +0000
822@@ -104,6 +104,9 @@
823
824 void init()
825 {
826+ const QStringList favs {"scope://mock-scope", "scope://mock-scope-ttl", "scope://mock-scope-info"};
827+ setFavouriteScopes(favs);
828+
829 m_scopes.reset(new Scopes(nullptr));
830 // no scopes on startup
831 QCOMPARE(m_scopes->rowCount(), 0);
832
833=== modified file 'tests/settingsendtoendtest.cpp'
834--- tests/settingsendtoendtest.cpp 2014-08-06 08:35:20 +0000
835+++ tests/settingsendtoendtest.cpp 2014-08-25 17:04:42 +0000
836@@ -26,6 +26,7 @@
837
838 #include <unity/shell/scopes/SettingsModelInterface.h>
839
840+#include "test-utils.h"
841 #include "registry-spawner.h"
842
843 using namespace scopes_ng;
844@@ -52,6 +53,9 @@
845
846 void init()
847 {
848+ const QStringList favs {"scope://mock-scope-departments", "scope://mock-scope-double-nav", "scope://mock-scope"};
849+ setFavouriteScopes(favs);
850+
851 m_scopes.reset(new Scopes(nullptr));
852 // no scopes on startup
853 QCOMPARE(m_scopes->rowCount(), 0);
854
855=== modified file 'tests/test-utils.h'
856--- tests/test-utils.h 2014-07-22 13:49:50 +0000
857+++ tests/test-utils.h 2014-08-25 17:04:42 +0000
858@@ -98,3 +98,21 @@
859
860 return true;
861 }
862+
863+void setFavouriteScopes(const QStringList& cannedQueries)
864+{
865+ setenv("GSETTINGS_BACKEND", "memory", 1);
866+ QGSettings settings("com.canonical.Unity.Dash", QByteArray(), nullptr);
867+ settings.set("favoriteScopes", QVariant(cannedQueries));
868+}
869+
870+QStringList getFavoriteScopes()
871+{
872+ setenv("GSETTINGS_BACKEND", "memory", 1);
873+ QGSettings settings("com.canonical.Unity.Dash", QByteArray(), nullptr);
874+ QStringList favs;
875+ for (auto const favvar: settings.get("favoriteScopes").toList()) {
876+ favs.push_back(favvar.toString());
877+ }
878+ return favs;
879+}

Subscribers

People subscribed via source and target branches

to all changes: