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
=== modified file 'debian/control'
--- debian/control 2014-08-14 15:31:11 +0000
+++ debian/control 2014-08-25 17:04:42 +0000
@@ -4,7 +4,7 @@
4Build-Depends: cmake,4Build-Depends: cmake,
5 debhelper (>= 9),5 debhelper (>= 9),
6 libunity-api-dev (>= 7.88),6 libunity-api-dev (>= 7.88),
7 libunity-scopes-dev (>= 0.6.2~),7 libunity-scopes-dev (>= 0.6.3~),
8 libgsettings-qt-dev (>= 0.1),8 libgsettings-qt-dev (>= 0.1),
9 libqtdbustest1-dev (>= 0.2),9 libqtdbustest1-dev (>= 0.2),
10 libqtdbusmock1-dev (>= 0.2),10 libqtdbusmock1-dev (>= 0.2),
@@ -15,6 +15,7 @@
15 qtdeclarative5-dev,15 qtdeclarative5-dev,
16 qtdeclarative5-dev-tools,16 qtdeclarative5-dev-tools,
17 qtdeclarative5-qtquick2-plugin,17 qtdeclarative5-qtquick2-plugin,
18 unity-schemas (>= 7.3.1),
18Standards-Version: 3.9.519Standards-Version: 3.9.5
19Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>20Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
20Homepage: https://launchpad.net/unity-scopes-shell21Homepage: https://launchpad.net/unity-scopes-shell
@@ -30,6 +31,7 @@
30Pre-Depends: ${misc:Pre-Depends}31Pre-Depends: ${misc:Pre-Depends}
31Depends: ${misc:Depends},32Depends: ${misc:Depends},
32 ${shlibs:Depends},33 ${shlibs:Depends},
34 unity-schemas (>= 7.3.1),
33Provides: unity-scopes-impl,35Provides: unity-scopes-impl,
34 unity-scopes-impl-0,36 unity-scopes-impl-0,
35 unity-scopes-impl-1,37 unity-scopes-impl-1,
3638
=== modified file 'src/Unity/CMakeLists.txt'
--- src/Unity/CMakeLists.txt 2014-08-14 15:31:11 +0000
+++ src/Unity/CMakeLists.txt 2014-08-25 17:04:42 +0000
@@ -3,7 +3,7 @@
33
4# Dependencies4# Dependencies
5pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=4)5pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=4)
6pkg_check_modules(SCOPESLIB REQUIRED libunity-scopes>=0.6.2)6pkg_check_modules(SCOPESLIB REQUIRED libunity-scopes>=0.6.3)
7pkg_check_modules(GSETTINGSQT REQUIRED gsettings-qt)7pkg_check_modules(GSETTINGSQT REQUIRED gsettings-qt)
8pkg_check_modules(UBUNTU_LOCATION_SERVICE REQUIRED ubuntu-location-service)8pkg_check_modules(UBUNTU_LOCATION_SERVICE REQUIRED ubuntu-location-service)
99
1010
=== modified file 'src/Unity/overviewcategories.cpp'
--- src/Unity/overviewcategories.cpp 2014-07-22 13:49:50 +0000
+++ src/Unity/overviewcategories.cpp 2014-08-25 17:04:42 +0000
@@ -53,7 +53,7 @@
53 , m_isSurfacing(true)53 , m_isSurfacing(true)
54{54{
55 m_allScopes.reset(new OverviewResultsModel(this));55 m_allScopes.reset(new OverviewResultsModel(this));
56 m_favouriteScopes.reset(new OverviewResultsModel(this));56 m_favoriteScopes.reset(new OverviewResultsModel(this));
5757
58 m_surfaceCategories.append(QSharedPointer<ScopesCategoryData>(new ScopesCategoryData("favorites", CATEGORY_JSON)));58 m_surfaceCategories.append(QSharedPointer<ScopesCategoryData>(new ScopesCategoryData("favorites", CATEGORY_JSON)));
59 m_surfaceCategories.append(QSharedPointer<ScopesCategoryData>(new ScopesCategoryData("all", CATEGORY_JSON)));59 m_surfaceCategories.append(QSharedPointer<ScopesCategoryData>(new ScopesCategoryData("all", CATEGORY_JSON)));
@@ -84,10 +84,10 @@
84 QModelIndex changedIndex(index(1));84 QModelIndex changedIndex(index(1));
85 dataChanged(changedIndex, changedIndex, roles);85 dataChanged(changedIndex, changedIndex, roles);
86}86}
87 87
88void OverviewCategories::setFavouriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes)88void OverviewCategories::setFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes)
89{89{
90 m_favouriteScopes->setResults(scopes);90 m_favoriteScopes->setResults(scopes);
9191
92 if (!m_isSurfacing) return;92 if (!m_isSurfacing) return;
9393
@@ -98,6 +98,11 @@
98 dataChanged(changedIndex, changedIndex, roles);98 dataChanged(changedIndex, changedIndex, roles);
99}99}
100100
101void OverviewCategories::updateFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes)
102{
103 m_favoriteScopes->setResults(scopes);
104}
105
101int OverviewCategories::rowCount(const QModelIndex& parent) const106int OverviewCategories::rowCount(const QModelIndex& parent) const
102{107{
103 if (m_isSurfacing) {108 if (m_isSurfacing) {
@@ -115,7 +120,7 @@
115 }120 }
116121
117 ScopesCategoryData* catData = m_surfaceCategories.at(index.row()).data();122 ScopesCategoryData* catData = m_surfaceCategories.at(index.row()).data();
118 OverviewResultsModel* results = index.row() == 0 ? m_favouriteScopes.data() : m_allScopes.data();123 OverviewResultsModel* results = index.row() == 0 ? m_favoriteScopes.data() : m_allScopes.data();
119124
120 switch (role) {125 switch (role) {
121 case RoleCategoryId:126 case RoleCategoryId:
122127
=== modified file 'src/Unity/overviewcategories.h'
--- src/Unity/overviewcategories.h 2014-07-18 09:21:24 +0000
+++ src/Unity/overviewcategories.h 2014-08-25 17:04:42 +0000
@@ -45,14 +45,15 @@
45 int rowCount(const QModelIndex& parent = QModelIndex()) const override;45 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
4646
47 void setAllScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);47 void setAllScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);
48 void setFavouriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);48 void setFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);
49 void updateFavoriteScopes(const QList<unity::scopes::ScopeMetadata::SPtr>& scopes);
4950
50private:51private:
51 bool m_isSurfacing;52 bool m_isSurfacing;
5253
53 QList<QSharedPointer<ScopesCategoryData>> m_surfaceCategories;54 QList<QSharedPointer<ScopesCategoryData>> m_surfaceCategories;
54 QScopedPointer<OverviewResultsModel> m_allScopes;55 QScopedPointer<OverviewResultsModel> m_allScopes;
55 QScopedPointer<OverviewResultsModel> m_favouriteScopes;56 QScopedPointer<OverviewResultsModel> m_favoriteScopes;
56};57};
5758
58} // namespace scopes_ng59} // namespace scopes_ng
5960
=== modified file 'src/Unity/overviewresults.cpp'
--- src/Unity/overviewresults.cpp 2014-08-25 17:04:42 +0000
+++ src/Unity/overviewresults.cpp 2014-08-25 17:04:42 +0000
@@ -25,6 +25,7 @@
25#include "utils.h"25#include "utils.h"
2626
27#include <unity/scopes/Result.h>27#include <unity/scopes/Result.h>
28#include <QSet>
2829
29namespace scopes_ng {30namespace scopes_ng {
3031
@@ -68,9 +69,50 @@
6869
69void OverviewResultsModel::setResults(const QList<unity::scopes::ScopeMetadata::SPtr>& results)70void OverviewResultsModel::setResults(const QList<unity::scopes::ScopeMetadata::SPtr>& results)
70{71{
71 beginResetModel();72 if (m_results.empty()) {
72 m_results = results;73 beginResetModel();
73 endResetModel();74 m_results = results;
75 endResetModel();
76 Q_EMIT countChanged();
77 return;
78 }
79
80 QSet<QString> newResult;
81 for (auto const res: results) {
82 newResult.insert(QString::fromStdString(res->scope_id()));
83 }
84
85 // itearate over old results, remove rows that are not present in new results
86 int row = 0;
87 for (auto it = m_results.begin(); it != m_results.end();)
88 {
89 if (!newResult.contains(QString::fromStdString((*it)->scope_id()))) {
90 beginRemoveRows(QModelIndex(), row, row);
91 it = m_results.erase(it);
92 endRemoveRows();
93 } else {
94 ++it;
95 ++row;
96 }
97 }
98
99 QSet<QString> oldResult;
100 for (auto const res: m_results) {
101 oldResult.insert(QString::fromStdString(res->scope_id()));
102 }
103
104 // iterate over new results, insert rows if not in previous model
105 row = 0;
106 for (auto const newRes: results)
107 {
108 if (!oldResult.contains(QString::fromStdString(newRes->scope_id())))
109 {
110 beginInsertRows(QModelIndex(), row, row);
111 m_results.insert(row, newRes);
112 endInsertRows();
113 }
114 ++row;
115 }
74 Q_EMIT countChanged();116 Q_EMIT countChanged();
75}117}
76118
77119
=== modified file 'src/Unity/overviewscope.cpp'
--- src/Unity/overviewscope.cpp 2014-08-01 11:43:58 +0000
+++ src/Unity/overviewscope.cpp 2014-08-25 17:04:42 +0000
@@ -66,11 +66,11 @@
66 }66 }
6767
68 QMap<QString, scopes::ScopeMetadata::SPtr> allMetadata = m_scopesInstance->getAllMetadata();68 QMap<QString, scopes::ScopeMetadata::SPtr> allMetadata = m_scopesInstance->getAllMetadata();
69 QList<scopes::ScopeMetadata::SPtr> favourites;69 QList<scopes::ScopeMetadata::SPtr> favorites;
70 Q_FOREACH(QString id, m_scopesInstance->getFavoriteIds()) {70 Q_FOREACH(QString id, m_scopesInstance->getFavoriteIds()) {
71 auto it = allMetadata.find(id);71 auto it = allMetadata.find(id);
72 if (it != allMetadata.end()) {72 if (it != allMetadata.end()) {
73 favourites.append(it.value());73 favorites.append(it.value());
74 }74 }
75 }75 }
7676
@@ -88,7 +88,7 @@
8888
89 // FIXME: filter invisible scopes?89 // FIXME: filter invisible scopes?
90 categories->setAllScopes(allScopes);90 categories->setAllScopes(allScopes);
91 categories->setFavouriteScopes(favourites);91 categories->setFavoriteScopes(favorites);
92}92}
9393
94QString OverviewScope::id() const94QString OverviewScope::id() const
@@ -106,6 +106,27 @@
106 }106 }
107}107}
108108
109void OverviewScope::updateFavorites(const QStringList& favorites)
110{
111 QList<scopes::ScopeMetadata::SPtr> favs;
112 auto allMetadata = m_scopesInstance->getAllMetadata();
113 for (auto const id: favorites)
114 {
115 auto it = allMetadata.find(id);
116 if (it != allMetadata.end()) {
117 favs.append(it.value());
118 }
119 }
120
121 OverviewCategories* categories = qobject_cast<OverviewCategories*>(m_categories.data());
122 if (!categories) {
123 qWarning("Unable to cast m_categories to OverviewCategories");
124 return;
125 }
126
127 categories->updateFavoriteScopes(favs);
128}
129
109void OverviewScope::dispatchSearch()130void OverviewScope::dispatchSearch()
110{131{
111 OverviewCategories* categories = qobject_cast<OverviewCategories*>(m_categories.data());132 OverviewCategories* categories = qobject_cast<OverviewCategories*>(m_categories.data());
112133
=== modified file 'src/Unity/overviewscope.h'
--- src/Unity/overviewscope.h 2014-08-01 11:43:58 +0000
+++ src/Unity/overviewscope.h 2014-08-25 17:04:42 +0000
@@ -38,6 +38,8 @@
3838
39 void dispatchSearch() override;39 void dispatchSearch() override;
4040
41 void updateFavorites(const QStringList& favorites);
42
41 unity::scopes::ScopeProxy proxy_for_result(unity::scopes::Result::SPtr const& result) const override;43 unity::scopes::ScopeProxy proxy_for_result(unity::scopes::Result::SPtr const& result) const override;
4244
43private Q_SLOTS:45private Q_SLOTS:
4446
=== modified file 'src/Unity/scope.cpp'
--- src/Unity/scope.cpp 2014-08-18 18:41:23 +0000
+++ src/Unity/scope.cpp 2014-08-25 17:04:42 +0000
@@ -74,6 +74,7 @@
74 , m_delayedClear(false)74 , m_delayedClear(false)
75 , m_hasNavigation(false)75 , m_hasNavigation(false)
76 , m_hasAltNavigation(false)76 , m_hasAltNavigation(false)
77 , m_favorite(false)
77 , m_searchController(new CollectionController)78 , m_searchController(new CollectionController)
78 , m_activationController(new CollectionController)79 , m_activationController(new CollectionController)
79 , m_status(Status::Okay)80 , m_status(Status::Okay)
@@ -753,7 +754,7 @@
753754
754bool Scope::favorite() const755bool Scope::favorite() const
755{756{
756 return true;757 return m_favorite;
757}758}
758759
759QString Scope::shortcut() const760QString Scope::shortcut() const
@@ -993,9 +994,12 @@
993994
994void Scope::setFavorite(const bool value)995void Scope::setFavorite(const bool value)
995{996{
996 Q_UNUSED(value);997 if (value != m_favorite)
997998 {
998 qWarning("Unimplemented: %s", __func__);999 m_favorite = value;
1000 Q_EMIT favoriteChanged(value);
1001 m_scopesInstance->setFavorite(id(), value);
1002 }
999}1003}
10001004
1001void Scope::activate(QVariant const& result_var)1005void Scope::activate(QVariant const& result_var)
10021006
=== modified file 'src/Unity/scope.h'
--- src/Unity/scope.h 2014-08-13 08:36:17 +0000
+++ src/Unity/scope.h 2014-08-25 17:04:42 +0000
@@ -111,6 +111,8 @@
111111
112 virtual bool event(QEvent* ev) override;112 virtual bool event(QEvent* ev) override;
113113
114 Q_PROPERTY(bool favorite READ favorite WRITE setFavorite NOTIFY favoriteChanged)
115
114 /* getters */116 /* getters */
115 QString id() const override;117 QString id() const override;
116 QString name() const override;118 QString name() const override;
@@ -162,6 +164,7 @@
162164
163Q_SIGNALS:165Q_SIGNALS:
164 void resultsDirtyChanged();166 void resultsDirtyChanged();
167 void favoriteChanged(bool);
165168
166private Q_SLOTS:169private Q_SLOTS:
167 void flushUpdates();170 void flushUpdates();
@@ -207,6 +210,7 @@
207 bool m_delayedClear;210 bool m_delayedClear;
208 bool m_hasNavigation;211 bool m_hasNavigation;
209 bool m_hasAltNavigation;212 bool m_hasAltNavigation;
213 bool m_favorite;
210214
211 std::unique_ptr<CollectionController> m_searchController;215 std::unique_ptr<CollectionController> m_searchController;
212 std::unique_ptr<CollectionController> m_activationController;216 std::unique_ptr<CollectionController> m_activationController;
213217
=== modified file 'src/Unity/scopes.cpp'
--- src/Unity/scopes.cpp 2014-08-01 13:12:54 +0000
+++ src/Unity/scopes.cpp 2014-08-25 17:04:42 +0000
@@ -82,6 +82,7 @@
82}82}
8383
84int Scopes::LIST_DELAY = -1;84int Scopes::LIST_DELAY = -1;
85const int Scopes::SCOPE_DELETE_DELAY = 3;
8586
86class Scopes::Priv : public QObject {87class Scopes::Priv : public QObject {
87 Q_OBJECT88 Q_OBJECT
@@ -111,6 +112,12 @@
111112
112 QDBusConnection::sessionBus().connect(QString(), QString("/com/canonical/unity/scopes"), QString("com.canonical.unity.scopes"), QString("InvalidateResults"), this, SLOT(invalidateScopeResults(QString)));113 QDBusConnection::sessionBus().connect(QString(), QString("/com/canonical/unity/scopes"), QString("com.canonical.unity.scopes"), QString("InvalidateResults"), this, SLOT(invalidateScopeResults(QString)));
113114
115 m_dashSettings = QGSettings::isSchemaInstalled("com.canonical.Unity.Dash") ? new QGSettings("com.canonical.Unity.Dash", QByteArray(), this) : nullptr;
116 if (m_dashSettings)
117 {
118 QObject::connect(m_dashSettings, &QGSettings::changed, this, &Scopes::dashSettingsChanged);
119 }
120
114 m_overviewScope = new OverviewScope(this);121 m_overviewScope = new OverviewScope(this);
115 m_locationService.reset(new UbuntuLocationService());122 m_locationService.reset(new UbuntuLocationService());
116}123}
@@ -159,31 +166,8 @@
159 std::bind(&Scopes::Priv::safeInvalidateScopeResults,166 std::bind(&Scopes::Priv::safeInvalidateScopeResults,
160 m_priv.get(), SCOPES_SCOPE_ID))));167 m_priv.get(), SCOPES_SCOPE_ID))));
161168
162 // FIXME: use a dconf setting for this
163 QByteArray enabledScopes = qgetenv("UNITY_SCOPES_LIST");
164
165 beginResetModel();169 beginResetModel();
166170
167 if (!enabledScopes.isNull()) {
168 QList<QByteArray> scopeList = enabledScopes.split(';');
169 for (int i = 0; i < scopeList.size(); i++) {
170 std::string scope_name(scopeList[i].constData());
171 auto it = scopes.find(scope_name);
172 if (it != scopes.end()) {
173 auto scope = new Scope(this);
174 scope->setScopeData(it->second);
175 m_scopes.append(scope);
176 }
177 }
178 } else {
179 // add all the scopes
180 for (auto it = scopes.begin(); it != scopes.end(); ++it) {
181 auto scope = new Scope(this);
182 scope->setScopeData(it->second);
183 m_scopes.append(scope);
184 }
185 }
186
187 // HACK! deal with the overview scope171 // HACK! deal with the overview scope
188 {172 {
189 auto it = scopes.find(SCOPES_SCOPE_ID);173 auto it = scopes.find(SCOPES_SCOPE_ID);
@@ -199,6 +183,7 @@
199 m_cachedMetadata[QString::fromStdString(it->first)] = std::make_shared<unity::scopes::ScopeMetadata>(it->second);183 m_cachedMetadata[QString::fromStdString(it->first)] = std::make_shared<unity::scopes::ScopeMetadata>(it->second);
200 }184 }
201185
186 processFavoriteScopes();
202 endResetModel();187 endResetModel();
203188
204 m_loaded = true;189 m_loaded = true;
@@ -210,6 +195,104 @@
210 m_listThread = nullptr;195 m_listThread = nullptr;
211}196}
212197
198void Scopes::processFavoriteScopes()
199{
200 //
201 // read the favoriteScopes array value from gsettings.
202 // process it and turn its values into scope ids.
203 // create new Scope objects or remove existing according to the list of favorities.
204 // notify about scopes model changes accordingly.
205 if (m_dashSettings) {
206 QStringList newFavorites;
207 QSet<QString> favScopesLut;
208 for (auto const& fv: m_dashSettings->get("favoriteScopes").toList())
209 {
210 try
211 {
212 auto const query = unity::scopes::CannedQuery::from_uri(fv.toString().toStdString());
213 const QString id = QString::fromStdString(query.scope_id());
214 newFavorites.push_back(id);
215 favScopesLut.insert(id);
216 }
217 catch (const InvalidArgumentException &e)
218 {
219 qWarning() << "Invalid canned query '" << fv.toString() << "'" << QString::fromStdString(e.what());
220 }
221 }
222
223 // this prevents further processing if we get called back when calling scope->setFavorite() below
224 if (m_favoriteScopes == newFavorites)
225 return;
226
227 m_favoriteScopes = newFavorites;
228
229 QSet<QString> oldScopes;
230 int row = 0;
231 // remove un-favorited scopes
232 for (auto it = m_scopes.begin(); it != m_scopes.end();)
233 {
234 if (!favScopesLut.contains((*it)->id()))
235 {
236 beginRemoveRows(QModelIndex(), row, row);
237 (*it)->setFavorite(false);
238 //
239 // we need to delay actual deletion of Scope object so that shell can animate it
240 QTimer::singleShot(1000 * SCOPE_DELETE_DELAY, (*it), SLOT(deleteLater));
241 it = m_scopes.erase(it);
242 endRemoveRows();
243 }
244 else
245 {
246 oldScopes.insert((*it)->id());
247 ++it;
248 ++row;
249 }
250 }
251
252 // add new favorites
253 row = 0;
254 for (auto favIt = m_favoriteScopes.begin(); favIt != m_favoriteScopes.end(); )
255 {
256 auto const fav = *favIt;
257 if (!oldScopes.contains(fav))
258 {
259 auto it = m_cachedMetadata.find(fav);
260 if (it != m_cachedMetadata.end())
261 {
262 auto scope = new Scope(this);
263 scope->setScopeData(*(it.value()));
264 scope->setFavorite(true);
265 beginInsertRows(QModelIndex(), row, row);
266 m_scopes.insert(row, scope);
267 endInsertRows();
268 }
269 else
270 {
271 qWarning() << "No such scope:" << fav;
272 favIt = m_favoriteScopes.erase(favIt);
273 continue;
274 }
275 }
276 ++row;
277 ++favIt;
278 }
279 }
280}
281
282void Scopes::dashSettingsChanged(QString const& key)
283{
284 if (key != "favoriteScopes") {
285 return;
286 }
287
288 processFavoriteScopes();
289
290 if (m_overviewScope)
291 {
292 m_overviewScope->updateFavorites(m_favoriteScopes);
293 }
294}
295
213void Scopes::refreshFinished()296void Scopes::refreshFinished()
214{297{
215 ScopeListWorker* thread = qobject_cast<ScopeListWorker*>(sender());298 ScopeListWorker* thread = qobject_cast<ScopeListWorker*>(sender());
@@ -288,13 +371,39 @@
288371
289QStringList Scopes::getFavoriteIds() const372QStringList Scopes::getFavoriteIds() const
290{373{
291 QStringList ids;374 return m_favoriteScopes;
292375}
293 Q_FOREACH(Scope* scope, m_scopes) {376
294 ids << scope->id();377void Scopes::setFavorite(QString const& scopeId, bool value)
378{
379 if (m_dashSettings)
380 {
381 QStringList cannedQueries;
382 bool changed = false;
383
384 for (auto const& fav: m_favoriteScopes)
385 {
386 if (value == false && fav == scopeId) {
387 changed = true;
388 continue; // skip it
389 }
390 // TODO: use CannedQuery::to_uri() when we really support them
391 const QString query = "scope://" + fav;
392 cannedQueries.push_back(query);
393 }
394
395 if (value && !m_favoriteScopes.contains(scopeId)) {
396 const QString query = "scope://" + scopeId;
397 cannedQueries.push_back(query);
398 changed = true;
399 }
400
401 if (changed) {
402 // update gsettings entry
403 // note: this will trigger notification, so that new favorites are processed by processFavoriteScopes
404 m_dashSettings->set("favoriteScopes", QVariant(cannedQueries));
405 }
295 }406 }
296
297 return ids;
298}407}
299408
300QMap<QString, unity::scopes::ScopeMetadata::SPtr> Scopes::getAllMetadata() const409QMap<QString, unity::scopes::ScopeMetadata::SPtr> Scopes::getAllMetadata() const
301410
=== modified file 'src/Unity/scopes.h'
--- src/Unity/scopes.h 2014-08-01 13:12:54 +0000
+++ src/Unity/scopes.h 2014-08-25 17:04:42 +0000
@@ -29,6 +29,7 @@
29#include <QThread>29#include <QThread>
30#include <QStringList>30#include <QStringList>
31#include <QSharedPointer>31#include <QSharedPointer>
32#include <QGSettings>
3233
33#include <unity/scopes/Runtime.h>34#include <unity/scopes/Runtime.h>
34#include <unity/scopes/Registry.h>35#include <unity/scopes/Registry.h>
@@ -40,6 +41,7 @@
40{41{
4142
42class Scope;43class Scope;
44class OverviewScope;
4345
44class Q_DECL_EXPORT Scopes : public unity::shell::scopes::ScopesInterface46class Q_DECL_EXPORT Scopes : public unity::shell::scopes::ScopesInterface
45{47{
@@ -58,6 +60,7 @@
58 unity::scopes::ScopeMetadata::SPtr getCachedMetadata(QString const& scopeId) const;60 unity::scopes::ScopeMetadata::SPtr getCachedMetadata(QString const& scopeId) const;
59 QMap<QString, unity::scopes::ScopeMetadata::SPtr> getAllMetadata() const;61 QMap<QString, unity::scopes::ScopeMetadata::SPtr> getAllMetadata() const;
60 QStringList getFavoriteIds() const;62 QStringList getFavoriteIds() const;
63 void setFavorite(QString const& scopeId, bool value);
6164
62 void refreshScopeMetadata();65 void refreshScopeMetadata();
6366
@@ -71,6 +74,8 @@
71 void metadataRefreshed();74 void metadataRefreshed();
7275
73private Q_SLOTS:76private Q_SLOTS:
77 void dashSettingsChanged(QString const &key);
78 void processFavoriteScopes();
74 void populateScopes();79 void populateScopes();
75 void discoveryFinished();80 void discoveryFinished();
76 void refreshFinished();81 void refreshFinished();
@@ -78,11 +83,14 @@
7883
79private:84private:
80 static int LIST_DELAY;85 static int LIST_DELAY;
86 static const int SCOPE_DELETE_DELAY;
81 class Priv;87 class Priv;
8288
83 QList<Scope*> m_scopes;89 QList<Scope*> m_scopes;
90 QStringList m_favoriteScopes;
91 QGSettings* m_dashSettings;
84 QMap<QString, unity::scopes::ScopeMetadata::SPtr> m_cachedMetadata;92 QMap<QString, unity::scopes::ScopeMetadata::SPtr> m_cachedMetadata;
85 Scope* m_overviewScope;93 OverviewScope* m_overviewScope;
86 QThread* m_listThread;94 QThread* m_listThread;
87 bool m_loaded;95 bool m_loaded;
8896
8997
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2014-08-13 17:16:21 +0000
+++ tests/CMakeLists.txt 2014-08-25 17:04:42 +0000
@@ -36,6 +36,7 @@
36 add_executable(${_test}Exec36 add_executable(${_test}Exec
37 ${_test}.cpp 37 ${_test}.cpp
38 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h38 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
39 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/ScopeInterface.h
39 )40 )
40 qt5_use_modules(${_test}Exec Test Core Qml DBus)41 qt5_use_modules(${_test}Exec Test Core Qml DBus)
41 set_tests_properties(test${CLASSNAME}${_test}42 set_tests_properties(test${CLASSNAME}${_test}
@@ -57,6 +58,7 @@
5758
58run_tests(59run_tests(
59 departmentstest60 departmentstest
61 favoritestest
60 locationtest62 locationtest
61 overviewtest63 overviewtest
62 previewtest64 previewtest
6365
=== modified file 'tests/departmentstest.cpp'
--- tests/departmentstest.cpp 2014-08-04 15:06:55 +0000
+++ tests/departmentstest.cpp 2014-08-25 17:04:42 +0000
@@ -64,6 +64,10 @@
6464
65 void init()65 void init()
66 {66 {
67 QStringList favs;
68 favs << "scope://mock-scope-departments" << "scope://mock-scope-double-nav";
69 setFavouriteScopes(favs);
70
67 m_scopes.reset(new Scopes(nullptr));71 m_scopes.reset(new Scopes(nullptr));
68 // no scopes on startup72 // no scopes on startup
69 QCOMPARE(m_scopes->rowCount(), 0);73 QCOMPARE(m_scopes->rowCount(), 0);
7074
=== added file 'tests/favoritestest.cpp'
--- tests/favoritestest.cpp 1970-01-01 00:00:00 +0000
+++ tests/favoritestest.cpp 2014-08-25 17:04:42 +0000
@@ -0,0 +1,168 @@
1/*
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Pawel Stolowski <pawel.stolowski@canonical.com>
18 */
19
20#include <QObject>
21#include <QTest>
22#include <QScopedPointer>
23#include <QSignalSpy>
24
25#include <scopes.h>
26#include <scope.h>
27#include <overviewresults.h>
28#include <unity/shell/scopes/ScopeInterface.h>
29
30#include "registry-spawner.h"
31#include "test-utils.h"
32
33using namespace scopes_ng;
34using namespace unity::shell::scopes;
35
36class FavoritesTest: public QObject
37{
38 Q_OBJECT
39private:
40 QScopedPointer<Scopes> m_scopes;
41 Scope* m_overviewScope;
42 QScopedPointer<RegistrySpawner> m_registry;
43
44private Q_SLOTS:
45 void initTestCase()
46 {
47 m_registry.reset(new RegistrySpawner);
48 }
49
50 void cleanupTestCase()
51 {
52 m_registry.reset();
53 }
54
55 void init()
56 {
57 setFavouriteScopes(QStringList());
58
59 m_scopes.reset(new Scopes(nullptr));
60
61 // no scopes on startup
62 QCOMPARE(m_scopes->rowCount(), 0);
63 QCOMPARE(m_scopes->loaded(), false);
64 QSignalSpy spy(m_scopes.data(), SIGNAL(loadedChanged()));
65
66 // wait till the registry spawns
67 QVERIFY(spy.wait());
68 QCOMPARE(m_scopes->loaded(), true);
69
70 // no scopes after startup, since favorites empty
71 QCOMPARE(m_scopes->rowCount(), 0);
72
73 // get overview scope proxy
74 m_overviewScope = qobject_cast<scopes_ng::Scope*>(m_scopes->overviewScope());
75 QVERIFY(m_overviewScope!= nullptr);
76 m_overviewScope->setActive(true);
77 }
78
79 void testFavoritePropertyUpdates()
80 {
81 QStringList favs;
82 favs << "scope://mock-scope-departments";
83 setFavouriteScopes(favs);
84
85 // should have one scope now
86 QTRY_COMPARE(m_scopes->rowCount(), 1);
87 auto scope1 = qobject_cast<scopes_ng::Scope*>(m_scopes->getScope(QString("mock-scope-departments")));
88 QVERIFY(scope1 != nullptr);
89 QCOMPARE(scope1->favorite(), true);
90
91 favs << "scope://mock-scope-double-nav";
92 setFavouriteScopes(favs);
93
94 // should have two scopes now
95 QTRY_COMPARE(m_scopes->rowCount(), 2);
96 auto scope2 = qobject_cast<scopes_ng::Scope*>(m_scopes->getScope(QString("mock-scope-double-nav")));
97 QVERIFY(scope2 != nullptr);
98 QCOMPARE(scope2->favorite(), true);
99
100 // unfavorite 1st scope
101 QSignalSpy spy(scope1, SIGNAL(favoriteChanged(bool)));
102 scope1->setFavorite(false);
103 QTRY_COMPARE(spy.count(), 1);
104 QTRY_COMPARE(m_scopes->rowCount(), 1);
105 QVERIFY(m_scopes->getScopeById("mock-scope-departments") == nullptr);
106 QCOMPARE(m_scopes->data(m_scopes->index(0), Scopes::RoleId), QVariant(QString("mock-scope-double-nav")));
107
108 // favorite a scope
109 auto overviewScope = m_scopes->overviewScope();
110 QVERIFY(overviewScope != nullptr);
111 connect(overviewScope, &unity::shell::scopes::ScopeInterface::openScope, [](unity::shell::scopes::ScopeInterface* scope) {
112 QCOMPARE(scope->favorite(), false);
113 scope->setFavorite(true);
114 });
115
116 QVERIFY(m_scopes->getScopeById("mock-scope") == nullptr);
117 overviewScope->performQuery("scope://mock-scope");
118 QTRY_VERIFY(m_scopes->getScopeById("mock-scope") != nullptr);
119 }
120
121 void testFavoritesOverviewUpdates()
122 {
123 auto categories = m_overviewScope->categories();
124 QVERIFY(categories->rowCount() > 0);
125 QCOMPARE(categories->data(categories->index(0), Categories::Roles::RoleCategoryId), QVariant(QString("favorites")));
126
127 QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
128 QVERIFY(results_var.canConvert<OverviewResultsModel*>());
129 OverviewResultsModel* results = results_var.value<OverviewResultsModel*>();
130 QCOMPARE(results->rowCount(), 0);
131
132 // favorite one scope, check if it appears in the favorites model
133 QStringList favs;
134 favs << "scope://mock-scope-departments";
135 setFavouriteScopes(favs);
136
137 QTRY_COMPARE(results->rowCount(), 1);
138
139 // unfavorite it, verify it disappears from favorites model
140 setFavouriteScopes(QStringList());
141 QTRY_COMPARE(results->rowCount(), 0);
142 }
143
144 void testGSettingsUpdates()
145 {
146 QStringList favs;
147 favs << "scope://mock-scope-departments" << "scope://mock-scope-double-nav";
148 setFavouriteScopes(favs);
149
150 // should have two scopes
151 QTRY_COMPARE(m_scopes->rowCount(), 2);
152 auto scope1 = qobject_cast<scopes_ng::Scope*>(m_scopes->getScope(QString("mock-scope-departments")));
153 QVERIFY(scope1 != nullptr);
154
155 // un-facorite one scope
156 scope1->setFavorite(false);
157 QTRY_COMPARE(getFavoriteScopes().size(), 1);
158 QCOMPARE(getFavoriteScopes().at(0), QString("scope://mock-scope-double-nav"));
159 }
160
161 void cleanup()
162 {
163 m_scopes.reset();
164 }
165};
166
167QTEST_GUILESS_MAIN(FavoritesTest)
168#include <favoritestest.moc>
0169
=== modified file 'tests/overviewtest.cpp'
--- tests/overviewtest.cpp 2014-07-31 11:23:44 +0000
+++ tests/overviewtest.cpp 2014-08-25 17:04:42 +0000
@@ -60,6 +60,10 @@
6060
61 void init()61 void init()
62 {62 {
63 QStringList favs;
64 favs << "scope://mock-scope-departments" << "scope://mock-scope-double-nav";
65 setFavouriteScopes(favs);
66
63 m_scopes.reset(new Scopes(nullptr));67 m_scopes.reset(new Scopes(nullptr));
64 // no scopes on startup68 // no scopes on startup
65 QCOMPARE(m_scopes->rowCount(), 0);69 QCOMPARE(m_scopes->rowCount(), 0);
6670
=== modified file 'tests/previewtest.cpp'
--- tests/previewtest.cpp 2014-08-06 08:35:20 +0000
+++ tests/previewtest.cpp 2014-08-25 17:04:42 +0000
@@ -58,6 +58,9 @@
5858
59 void init()59 void init()
60 {60 {
61 const QStringList favs {"scope://mock-scope-departments", "scope://mock-scope-double-nav", "scope://mock-scope"};
62 setFavouriteScopes(favs);
63
61 m_scopes.reset(new Scopes(nullptr));64 m_scopes.reset(new Scopes(nullptr));
62 // no scopes on startup65 // no scopes on startup
63 QCOMPARE(m_scopes->rowCount(), 0);66 QCOMPARE(m_scopes->rowCount(), 0);
6467
=== modified file 'tests/resultstest.cpp'
--- tests/resultstest.cpp 2014-08-14 10:23:34 +0000
+++ tests/resultstest.cpp 2014-08-25 17:04:42 +0000
@@ -104,6 +104,9 @@
104104
105 void init()105 void init()
106 {106 {
107 const QStringList favs {"scope://mock-scope", "scope://mock-scope-ttl", "scope://mock-scope-info"};
108 setFavouriteScopes(favs);
109
107 m_scopes.reset(new Scopes(nullptr));110 m_scopes.reset(new Scopes(nullptr));
108 // no scopes on startup111 // no scopes on startup
109 QCOMPARE(m_scopes->rowCount(), 0);112 QCOMPARE(m_scopes->rowCount(), 0);
110113
=== modified file 'tests/settingsendtoendtest.cpp'
--- tests/settingsendtoendtest.cpp 2014-08-06 08:35:20 +0000
+++ tests/settingsendtoendtest.cpp 2014-08-25 17:04:42 +0000
@@ -26,6 +26,7 @@
2626
27#include <unity/shell/scopes/SettingsModelInterface.h>27#include <unity/shell/scopes/SettingsModelInterface.h>
2828
29#include "test-utils.h"
29#include "registry-spawner.h"30#include "registry-spawner.h"
3031
31using namespace scopes_ng;32using namespace scopes_ng;
@@ -52,6 +53,9 @@
5253
53 void init()54 void init()
54 {55 {
56 const QStringList favs {"scope://mock-scope-departments", "scope://mock-scope-double-nav", "scope://mock-scope"};
57 setFavouriteScopes(favs);
58
55 m_scopes.reset(new Scopes(nullptr));59 m_scopes.reset(new Scopes(nullptr));
56 // no scopes on startup60 // no scopes on startup
57 QCOMPARE(m_scopes->rowCount(), 0);61 QCOMPARE(m_scopes->rowCount(), 0);
5862
=== modified file 'tests/test-utils.h'
--- tests/test-utils.h 2014-07-22 13:49:50 +0000
+++ tests/test-utils.h 2014-08-25 17:04:42 +0000
@@ -98,3 +98,21 @@
9898
99 return true;99 return true;
100}100}
101
102void setFavouriteScopes(const QStringList& cannedQueries)
103{
104 setenv("GSETTINGS_BACKEND", "memory", 1);
105 QGSettings settings("com.canonical.Unity.Dash", QByteArray(), nullptr);
106 settings.set("favoriteScopes", QVariant(cannedQueries));
107}
108
109QStringList getFavoriteScopes()
110{
111 setenv("GSETTINGS_BACKEND", "memory", 1);
112 QGSettings settings("com.canonical.Unity.Dash", QByteArray(), nullptr);
113 QStringList favs;
114 for (auto const favvar: settings.get("favoriteScopes").toList()) {
115 favs.push_back(favvar.toString());
116 }
117 return favs;
118}

Subscribers

People subscribed via source and target branches

to all changes: