on xenial help sign in
make the launcher model update on dconf changes
commiteb180d10adcad16607e78a74e3ca4a371003e620 authorMichael Zanetti <michael.zanetti@canonical.com> dateTue 30 Sep 2014, 17:56 (12 years ago) parent8580e14
8 files changed+181−38
plugins/Unity/Launcher/desktopfilehandler.cpp+2−1 plugins/Unity/Launcher/gsettings.cpp+11−7 plugins/Unity/Launcher/gsettings.h+10−1 plugins/Unity/Launcher/launchermodel.cpp+54−14 plugins/Unity/Launcher/launchermodel.h+2 tests/plugins/Unity/Launcher/gsettings.cpp+3−4 tests/plugins/Unity/Launcher/gsettings.h+6 tests/plugins/Unity/Launcher/launchermodeltest.cpp+93−11
plugins/Unity/Launcher/desktopfilehandler.cpp+2−1
@@ -24,6 +24,7 @@
2424#include <QDir>
2525#include <QSettings>
2626#include <QLocale>
27+#include <QDebug>
2728
2829#include <libintl.h>
2930
@@ -70,7 +71,7 @@ void DesktopFileHandler::load()
7071
7172 QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
7273#ifdef LAUNCHER_TESTING
73− searchDirs << "";
74+ searchDirs << ".";
7475#endif
7576
7677 QString path;
plugins/Unity/Launcher/gsettings.cpp+11−7
@@ -19,22 +19,20 @@
1919
2020#include "gsettings.h"
2121
22−#include <QGSettings>
2322#include <QVariant>
2423
2524GSettings::GSettings(QObject *parent):
2625 QObject(parent)
2726{
28−
27+ m_gSettings = new QGSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/", this);
28+ connect(m_gSettings, &QGSettings::changed, this, &GSettings::settingsChanged);
2929}
3030
3131QStringList GSettings::storedApplications() const
3232{
3333 QStringList storedApps;
3434
35− QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
36−
37− Q_FOREACH(const QString &entry, gSettings.get("items").toStringList()) {
35+ Q_FOREACH(const QString &entry, m_gSettings->get("items").toStringList()) {
3836 if (entry.startsWith("application:///")) {
3937 // convert legacy entries to new world appids
4038 QString appId = entry;
@@ -57,10 +55,16 @@ QStringList GSettings::storedApplications() const
5755
5856void GSettings::setStoredApplications(const QStringList &storedApplications)
5957{
60− QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
6158 QStringList gSettingsList;
6259 Q_FOREACH(const QString &entry, storedApplications) {
6360 gSettingsList << QString("appid://%1").arg(entry);
6461 }
65− gSettings.set("items", gSettingsList);
62+ m_gSettings->set("items", gSettingsList);
63+}
64+
65+void GSettings::settingsChanged(const QString &key)
66+{
67+ if (key == "items") {
68+ Q_EMIT changed();
69+ }
6670}
plugins/Unity/Launcher/gsettings.h+10−1
@@ -22,7 +22,7 @@
2222
2323#include <QObject>
2424#include <QStringList>
25−
25+#include <QGSettings>
2626
2727class GSettings: public QObject
2828{
@@ -32,6 +32,15 @@ public:
3232
3333 QStringList storedApplications() const;
3434 void setStoredApplications(const QStringList &storedApplications);
35+
36+Q_SIGNALS:
37+ void changed();
38+
39+private Q_SLOTS:
40+ void settingsChanged(const QString &key);
41+
42+private:
43+ QGSettings *m_gSettings;
3544};
3645
3746#endif
plugins/Unity/Launcher/launchermodel.cpp+54−14
@@ -40,20 +40,9 @@ LauncherModel::LauncherModel(QObject *parent):
4040 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
4141 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
4242
43− Q_FOREACH (const QString &entry, m_settings->storedApplications()) {
44− DesktopFileHandler desktopFile(entry);
45− if (!desktopFile.isValid()) {
46− qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
47− continue;
48− }
43+ connect(m_settings, &GSettings::changed, this, &LauncherModel::refresh);
4944
50− LauncherItem *item = new LauncherItem(entry,
51− desktopFile.displayName(),
52− desktopFile.icon(),
53− this);
54− item->setPinned(true);
55− m_list.append(item);
56− }
45+ refresh();
5746}
5847
5948LauncherModel::~LauncherModel()
@@ -320,7 +309,6 @@ void LauncherModel::progressChanged(const QString &appId, int progress)
320309 }
321310}
322311
323−
324312void LauncherModel::countChanged(const QString &appId, int count)
325313{
326314 int idx = findApplication(appId);
@@ -362,10 +350,15 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
362350
363351void LauncherModel::refresh()
364352{
353+ // First walk through all the existing items and see if we need to remove something
365354 QList<LauncherItem*> toBeRemoved;
366355 Q_FOREACH (LauncherItem* item, m_list) {
367356 DesktopFileHandler desktopFile(item->appId());
368357 if (!desktopFile.isValid()) {
358+ // Desktop file not available for this app => drop it!
359+ toBeRemoved << item;
360+ } else if (!m_settings->storedApplications().contains(item->appId())) {
361+ // Item not in settings any more => drop it!
369362 toBeRemoved << item;
370363 } else {
371364 int idx = m_list.indexOf(item);
@@ -378,6 +371,53 @@ void LauncherModel::refresh()
378371 Q_FOREACH (LauncherItem* item, toBeRemoved) {
379372 requestRemove(item->appId());
380373 }
374+
375+ // This brings the Launcher into sync with the settings backend again. There's an issue though:
376+ // If we can't find a .desktop file for an entry we need to skip it. That makes our settingsIndex
377+ // go out of sync with the actual index of items. So let's also use an addedIndex which reflects
378+ // the settingsIndex minus the skipped items.
379+ int addedIndex = 0;
380+
381+ // Now walk through settings and see if we need to add something
382+ for (int settingsIndex = 0; settingsIndex < m_settings->storedApplications().count(); ++settingsIndex) {
383+ QString entry = m_settings->storedApplications().at(settingsIndex);
384+ int itemIndex = -1;
385+ for (int i = 0; i < m_list.count(); ++i) {
386+ if (m_list.at(i)->appId() == entry) {
387+ itemIndex = i;
388+ break;
389+ }
390+ }
391+
392+ if (itemIndex == -1) {
393+ // Need to add it. Just add it into the addedIndex to keep same ordering as the list
394+ // in the settings.
395+ DesktopFileHandler desktopFile(entry);
396+ if (!desktopFile.isValid()) {
397+ qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
398+ continue;
399+ }
400+
401+ LauncherItem *item = new LauncherItem(entry,
402+ desktopFile.displayName(),
403+ desktopFile.icon(),
404+ this);
405+ item->setPinned(true);
406+ beginInsertRows(QModelIndex(), addedIndex, addedIndex);
407+ m_list.insert(addedIndex, item);
408+ endInsertRows();
409+ } else if (itemIndex != addedIndex) {
410+ // The item is already there, but it is in a different place then in the settings.
411+ // Move it to the addedIndex
412+ beginMoveRows(QModelIndex(), itemIndex, itemIndex, QModelIndex(), addedIndex);
413+ m_list.move(itemIndex, addedIndex);
414+ endMoveRows();
415+ }
416+
417+ // Just like settingsIndex, this will increase with every item, except the ones we
418+ // skipped with the "continue" call above.
419+ addedIndex++;
420+ }
381421}
382422
383423void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
plugins/Unity/Launcher/launchermodel.h+2
@@ -79,6 +79,8 @@ private:
7979 DBusInterface *m_dbusIface;
8080
8181 ApplicationManagerInterface *m_appManager;
82+
83+ friend class LauncherModelTest;
8284};
8385
8486#endif // LAUNCHERMODEL_H
tests/plugins/Unity/Launcher/gsettings.cpp+3−4
@@ -19,8 +19,6 @@
1919
2020#include "gsettings.h"
2121
22−#include <QDebug>
23−
2422// This is a mock implementation to not touch GSettings for real during tests
2523
2624GSettings::GSettings(QObject *parent):
@@ -31,10 +29,11 @@ GSettings::GSettings(QObject *parent):
3129
3230QStringList GSettings::storedApplications() const
3331{
34− return QStringList();
32+ return m_entries;
3533}
3634
3735void GSettings::setStoredApplications(const QStringList &storedApplications)
3836{
39− Q_UNUSED(storedApplications)
37+ m_entries = storedApplications;
38+ Q_EMIT changed();
4039}
tests/plugins/Unity/Launcher/gsettings.h+6
@@ -33,6 +33,12 @@ public:
3333
3434 QStringList storedApplications() const;
3535 void setStoredApplications(const QStringList &storedApplications);
36+
37+Q_SIGNALS:
38+ void changed();
39+
40+private:
41+ QStringList m_entries;
3642};
3743
3844#endif
tests/plugins/Unity/Launcher/launchermodeltest.cpp+93−11
@@ -24,6 +24,7 @@
2424#include "launcheritem.h"
2525#include "launchermodel.h"
2626#include "dbusinterface.h"
27+#include "gsettings.h"
2728
2829#include <QtTest>
2930#include <QDBusInterface>
@@ -81,7 +82,15 @@ public:
8182 return nullptr;
8283 }
8384 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) { return nullptr; }
84− bool stopApplication(const QString &) { return false; }
85+ bool stopApplication(const QString &appId) {
86+ Q_FOREACH(MockApp* app, m_list) {
87+ if (app->appId() == appId) {
88+ removeApplication(m_list.indexOf(app));
89+ return true;
90+ }
91+ }
92+ return false;
93+ }
8594 bool focusApplication(const QString &appId) {
8695 Q_FOREACH(MockApp* app, m_list) {
8796 app->setFocused(app->appId() == appId);
@@ -135,6 +144,8 @@ private Q_SLOTS:
135144
136145 appManager->addApplication(new MockApp("no-icon"));
137146 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
147+
148+ launcherModel->m_settings->setStoredApplications(QStringList());
138149 }
139150
140151 // Removing apps from appmanager and launcher as pinned ones would stick
@@ -170,14 +181,15 @@ private Q_SLOTS:
170181 launcherModel->pin(launcherModel->get(0)->appId());
171182 QCOMPARE(launcherModel->get(0)->pinned(), true);
172183 QCOMPARE(launcherModel->get(1)->pinned(), false);
173− QCOMPARE(spy.count(), 1);
184+ QCOMPARE(spy.count() > 0, true);
174185 QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
175186
187+ spy.clear();
176188 launcherModel->requestRemove(launcherModel->get(0)->appId());
177189 QCOMPARE(launcherModel->get(0)->pinned(), false);
178190 QCOMPARE(launcherModel->get(1)->pinned(), false);
179− QCOMPARE(spy.count(), 2);
180− QCOMPARE(spy.at(1).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
191+ QCOMPARE(spy.count(), 1);
192+ QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
181193 }
182194
183195 void testRemove_data() {
@@ -186,7 +198,7 @@ private Q_SLOTS:
186198 QTest::addColumn<bool>("running");
187199
188200 QTest::newRow("non-pinned, running") << false << true;
189− QTest::newRow("pinned, running") << true << false;
201+ QTest::newRow("pinned, running") << true << true;
190202 QTest::newRow("pinned, non-running") << true << false;
191203 }
192204
@@ -199,23 +211,24 @@ private Q_SLOTS:
199211
200212 // pin one if required
201213 if (pinned) {
202− launcherModel->pin(launcherModel->get(1)->appId());
214+ launcherModel->pin("abs-icon");
203215 }
204216
205217 // stop it if required
206218 if (!running) {
207− appManager->removeApplication(1);
219+ appManager->stopApplication("abs-icon");
208220 }
209221
210222 // Now remove it
211− launcherModel->requestRemove(launcherModel->get(1)->appId());
223+ launcherModel->requestRemove("abs-icon");
212224
213225 if (running) {
214226 // both apps are running, both apps must still be here
215227 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
216228
217229 // Item must be unpinned now
218− QCOMPARE(launcherModel->get(1)->pinned(), false);
230+ int index = launcherModel->findApplication("abs-icon");
231+ QCOMPARE(launcherModel->get(index)->pinned(), false);
219232
220233 } else if (pinned) {
221234 // Item 1 must go away, item 0 is here to stay
@@ -224,7 +237,7 @@ private Q_SLOTS:
224237
225238 // done our checks. now stop the app if was still running
226239 if (running) {
227− appManager->removeApplication(1);
240+ appManager->stopApplication("abs-icon");
228241 }
229242
230243 // It needs to go away in any case now
@@ -369,12 +382,81 @@ private Q_SLOTS:
369382 QCOMPARE(launcherModel->get(index)->count(), 55);
370383 }
371384
372− void testRefresh() {
385+ void testRefreshAfterDeletedDesktopFiles_data() {
386+ QTest::addColumn<bool>("deleted");
387+ QTest::newRow("have .desktop files") << false;
388+ QTest::newRow("deleted .desktop files") << true;
389+ }
390+
391+ void testRefreshAfterDeletedDesktopFiles() {
392+ QFETCH(bool, deleted);
393+
394+ // pin both apps
395+ launcherModel->pin("abs-icon");
396+ launcherModel->pin("no-icon");
397+ // close both apps
398+ appManager->removeApplication(0);
399+ appManager->removeApplication(0);
400+
401+ // "delete" the .desktop files
402+ QString oldCurrent = QDir::currentPath();
403+ if (deleted) {
404+ // In testing mode, the launcher searches the current dir for the sample .desktop file
405+ // We can make that fail by changing the current dir
406+ QDir::setCurrent("..");
407+ }
408+
409+ // Call refresh
373410 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher");
374411 QDBusReply<void> reply = interface.call("Refresh");
375412
376413 // Make sure the call to Refresh returned without error.
377414 QCOMPARE(reply.isValid(), true);
415+
416+ QCOMPARE(launcherModel->rowCount(), deleted ? 0 : 2);
417+
418+ // Restoring current dir
419+ QDir::setCurrent(oldCurrent);
420+ }
421+
422+ void testSettings_data() {
423+ QTest::addColumn<QStringList>("storedList");
424+
425+ QTest::newRow("2 stored") << (QStringList() << "abs-icon" << "no-icon");
426+ QTest::newRow("1 stored") << (QStringList() << "abs-icon");
427+ }
428+
429+ void testSettings() {
430+ GSettings *settings = launcherModel->m_settings;
431+
432+ // Nothing pinned at startup
433+ QCOMPARE(settings->storedApplications().count(), 0);
434+
435+ // pin both apps
436+ launcherModel->pin("abs-icon");
437+ launcherModel->pin("no-icon");
438+
439+ // Now settings should have 2 apps
440+ QCOMPARE(settings->storedApplications().count(), 2);
441+
442+ // close both apps
443+ appManager->removeApplication(0);
444+ appManager->removeApplication(0);
445+
446+ // Now settings should have 2 apps
447+ QCOMPARE(settings->storedApplications().count(), 2);
448+
449+ // Now remove 1 app through the backend, make sure one is still there
450+ settings->setStoredApplications(QStringList() << "abs-icon");
451+ QCOMPARE(settings->storedApplications().count(), 1);
452+
453+ // Check if it disappeared from the frontend too
454+ QCOMPARE(launcherModel->rowCount(), 1);
455+
456+ // Add them back:
457+ settings->setStoredApplications(QStringList() << "abs-icon" << "no-icon");
458+ QCOMPARE(launcherModel->rowCount(), 2);
459+
378460 }
379461};
380462