on xenial help sign in
rebase on top of lp:~macslow/unity8/use-set-progress-api
ef55e4dLukáš Tinkl
debian/control+3−3
@@ -30,7 +30,7 @@ Build-Depends: cmake,
30 libqt5xmlpatterns5-dev,
30 libqt5xmlpatterns5-dev,
31 libsystemsettings-dev,
31 libsystemsettings-dev,
32 libudev-dev,
32 libudev-dev,
33− libunity-api-dev (>= 7.97),
33+ libunity-api-dev (>= 7.98),
34 libusermetricsoutput1-dev,
34 libusermetricsoutput1-dev,
35 libxcb1-dev,
35 libxcb1-dev,
36 pkg-config,
36 pkg-config,
@@ -101,7 +101,7 @@ Depends: gsettings-desktop-schemas,
101 qtdeclarative5-ubuntu-telephony0.1,
101 qtdeclarative5-ubuntu-telephony0.1,
102 qtdeclarative5-ubuntu-web-plugin,
102 qtdeclarative5-ubuntu-web-plugin,
103 ubuntu-system-settings,
103 ubuntu-system-settings,
104− unity-launcher-impl-4,
104+ unity-launcher-impl-7,
105 unity8-common (= ${source:Version}),
105 unity8-common (= ${source:Version}),
106 unity8-private (= ${binary:Version}),
106 unity8-private (= ${binary:Version}),
107 unity8-private | unity-launcher-impl,
107 unity8-private | unity-launcher-impl,
@@ -191,7 +191,7 @@ Depends: accountsservice-ubuntu-schemas (>= 0.0.3),
191 ${misc:Depends},
191 ${misc:Depends},
192 ${shlibs:Depends},
192 ${shlibs:Depends},
193Provides: unity-launcher-impl,
193Provides: unity-launcher-impl,
194− unity-launcher-impl-4,
194+ unity-launcher-impl-7,
195Description: Unity 8 private libs
195Description: Unity 8 private libs
196 The Unity 8 shell is the primary user interface for Ubuntu devices.
196 The Unity 8 shell is the primary user interface for Ubuntu devices.
197 .
197 .
plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4
4
plugins/Greeter/Unity/Launcher/launcheritem.cpp+14
@@ -31,6 +31,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
31 m_count(0),
31 m_count(0),
32 m_countVisible(false),
32 m_countVisible(false),
33 m_focused(false),
33 m_focused(false),
34+ m_alerting(false),
34 m_quickList(new QuickListModel(this))
35 m_quickList(new QuickListModel(this))
35{
36{
36 QuickListEntry nameAction;
37 QuickListEntry nameAction;
@@ -165,6 +166,19 @@ void LauncherItem::setFocused(bool focused)
165 }
166 }
166}
167}
167
168
169+bool LauncherItem::alerting() const
170+{
171+ return m_alerting;
172+}
173+
174+void LauncherItem::setAlerting(bool alerting)
175+{
176+ if (m_alerting != alerting) {
177+ m_alerting = alerting;
178+ Q_EMIT alertingChanged(alerting);
179+ }
180+}
181+
168unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
182unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
169{
183{
170 return m_quickList;
184 return m_quickList;
plugins/Greeter/Unity/Launcher/launcheritem.h+4
@@ -41,6 +41,7 @@ public:
41 int count() const override;
41 int count() const override;
42 bool countVisible() const override;
42 bool countVisible() const override;
43 bool focused() const override;
43 bool focused() const override;
44+ bool alerting() const override;
44
45
45 unity::shell::launcher::QuickListModelInterface *quickList() const override;
46 unity::shell::launcher::QuickListModelInterface *quickList() const override;
46
47
@@ -54,6 +55,8 @@ private:
54 void setCount(int count);
55 void setCount(int count);
55 void setCountVisible(bool countVisible);
56 void setCountVisible(bool countVisible);
56 void setFocused(bool focused);
57 void setFocused(bool focused);
58+ void setAlerting(bool alerting);
59+
57
60
58private:
61private:
59 QString m_appId;
62 QString m_appId;
@@ -66,6 +69,7 @@ private:
66 int m_count;
69 int m_count;
67 bool m_countVisible;
70 bool m_countVisible;
68 bool m_focused;
71 bool m_focused;
72+ bool m_alerting;
69 QuickListModel *m_quickList;
73 QuickListModel *m_quickList;
70
74
71 friend class LauncherModel;
75 friend class LauncherModel;
plugins/Greeter/Unity/Launcher/launchermodelas.cpp+26−14
@@ -74,6 +74,13 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
74 return QVariant();
74 return QVariant();
75}
75}
76
76
77+void LauncherModel::setAlerting(const QString &appId, bool alerting)
78+{
79+ Q_UNUSED(appId)
80+ Q_UNUSED(alerting)
81+ qWarning() << "This is a read only implementation. Cannot set alert-state of items.";
82+}
83+
77unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
84unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
78{
85{
79 if (index < 0 || index >= m_list.count()) {
86 if (index < 0 || index >= m_list.count()) {
@@ -202,17 +209,20 @@ void LauncherModel::refresh()
202 Q_FOREACH (LauncherItem* item, m_list) {
209 Q_FOREACH (LauncherItem* item, m_list) {
203 bool found = false;
210 bool found = false;
204 Q_FOREACH(const QVariant &asItem, items) {
211 Q_FOREACH(const QVariant &asItem, items) {
212+ QVariantMap cachedMap = asItem.toMap();
205− if (asItem.toMap().value("id").toString() == item->appId()) {
213+ if (cachedMap.value("id").toString() == item->appId()) {
206 // Only keep and update it if we either show unpinned or it is pinned
214 // Only keep and update it if we either show unpinned or it is pinned
207− if (!m_onlyPinned || asItem.toMap().value("pinned").toBool()) {
215+ if (!m_onlyPinned || cachedMap.value("pinned").toBool()) {
208 found = true;
216 found = true;
209− item->setName(asItem.toMap().value("name").toString());
217+ item->setName(cachedMap.value("name").toString());
210− item->setIcon(asItem.toMap().value("icon").toString());
218+ item->setIcon(cachedMap.value("icon").toString());
211− item->setCount(asItem.toMap().value("count").toInt());
219+ item->setCount(cachedMap.value("count").toInt());
212− item->setCountVisible(asItem.toMap().value("countVisible").toBool());
220+ item->setCountVisible(cachedMap.value("countVisible").toBool());
221+ item->setProgress(cachedMap.value("progress").toInt());
213− item->setRunning(asItem.toMap().value("running").toBool());
222+ item->setRunning(cachedMap.value("running").toBool());
223+
214 int idx = m_list.indexOf(item);
224 int idx = m_list.indexOf(item);
215− Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleCount, RoleCountVisible, RoleRunning});
225+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleCount, RoleCountVisible, RoleRunning, RoleProgress});
216 }
226 }
217 break;
227 break;
218 }
228 }
@@ -250,15 +260,17 @@ void LauncherModel::refresh()
250 }
260 }
251
261
252 if (itemIndex == -1) {
262 if (itemIndex == -1) {
263+ QVariantMap cachedMap = entry.toMap();
253 // Need to add it. Just add it into the addedIndex to keep same ordering as the list in AS.
264 // Need to add it. Just add it into the addedIndex to keep same ordering as the list in AS.
254− LauncherItem *item = new LauncherItem(entry.toMap().value("id").toString(),
265+ LauncherItem *item = new LauncherItem(cachedMap.value("id").toString(),
255− entry.toMap().value("name").toString(),
266+ cachedMap.value("name").toString(),
256− entry.toMap().value("icon").toString(),
267+ cachedMap.value("icon").toString(),
257 this);
268 this);
258 item->setPinned(true);
269 item->setPinned(true);
259− item->setCount(entry.toMap().value("count").toInt());
270+ item->setCount(cachedMap.value("count").toInt());
260− item->setCountVisible(entry.toMap().value("countVisible").toBool());
271+ item->setCountVisible(cachedMap.value("countVisible").toBool());
272+ item->setProgress(cachedMap.value("progress").toInt());
261− item->setRunning(entry.toMap().value("running").toBool());
273+ item->setRunning(cachedMap.value("running").toBool());
262 beginInsertRows(QModelIndex(), newPosition, newPosition);
274 beginInsertRows(QModelIndex(), newPosition, newPosition);
263 m_list.insert(newPosition, item);
275 m_list.insert(newPosition, item);
264 endInsertRows();
276 endInsertRows();
plugins/Greeter/Unity/Launcher/launchermodelas.h+1
@@ -41,6 +41,7 @@ public:
41
41
42 QVariant data(const QModelIndex &index, int role) const override;
42 QVariant data(const QModelIndex &index, int role) const override;
43
43
44+ Q_INVOKABLE void setAlerting(const QString &appId, bool alerting) override;
44 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
45 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
45 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
46 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
46 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
47 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
plugins/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4
4
plugins/Unity/Launcher/asadapter.cpp+1
@@ -57,5 +57,6 @@ QVariantMap ASAdapter::itemToVariant(LauncherItem *item) const
57 details.insert("countVisible", item->countVisible());
57 details.insert("countVisible", item->countVisible());
58 details.insert("pinned", item->pinned());
58 details.insert("pinned", item->pinned());
59 details.insert("running", item->running());
59 details.insert("running", item->running());
60+ details.insert("progress", item->progress());
60 return details;
61 return details;
61}
62}
plugins/Unity/Launcher/dbusinterface.cpp+39−13
@@ -62,6 +62,8 @@ QString DBusInterface::introspect(const QString &path) const
62 "<interface name=\"com.canonical.Unity.Launcher.Item\">"
62 "<interface name=\"com.canonical.Unity.Launcher.Item\">"
63 "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
63 "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
64 "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
64 "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
65+ "<property name=\"progress\" type=\"i\" access=\"readwrite\" />"
66+ "<method name=\"Alert\" />"
65 "</interface>";
67 "</interface>";
66 return nodeiface;
68 return nodeiface;
67}
69}
@@ -122,6 +124,20 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
122 return false;
124 return false;
123 }
125 }
124
126
127+ /* Break down the path to just the app id */
128+ bool validpath = true;
129+ QString pathtemp = message.path();
130+ if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
131+ validpath = false;
132+ }
133+ pathtemp.remove("/com/canonical/Unity/Launcher/");
134+ if (pathtemp.indexOf('/') >= 0) {
135+ validpath = false;
136+ }
137+
138+ /* Find ourselves an appid */
139+ QString appid = decodeAppId(pathtemp);
140+
125 // First handle methods of the Launcher interface
141 // First handle methods of the Launcher interface
126 if (message.interface() == "com.canonical.Unity.Launcher") {
142 if (message.interface() == "com.canonical.Unity.Launcher") {
127 if (message.member() == "Refresh") {
143 if (message.member() == "Refresh") {
@@ -129,6 +145,13 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
129 Q_EMIT refreshCalled();
145 Q_EMIT refreshCalled();
130 return connection.send(reply);
146 return connection.send(reply);
131 }
147 }
148+ } else if (message.interface() == "com.canonical.Unity.Launcher.Item") {
149+ // Handle methods of the Launcher-Item interface
150+ if (message.member() == "Alert" && validpath) {
151+ QDBusMessage reply = message.createReply();
152+ Q_EMIT alertCalled(appid);
153+ return connection.send(reply);
154+ }
132 }
155 }
133
156
134 // Now handle dynamic properties (for launcher emblems)
157 // Now handle dynamic properties (for launcher emblems)
@@ -140,50 +163,53 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
140 return false;
163 return false;
141 }
164 }
142
165
143− /* Break down the path to just the app id */
166+ if (!validpath) {
144− QString pathtemp = message.path();
145− if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
146− return false;
147− }
148− pathtemp.remove("/com/canonical/Unity/Launcher/");
149− if (pathtemp.indexOf('/') >= 0) {
150 return false;
167 return false;
151 }
168 }
152
169
153− /* Find ourselves an appid */
154− QString appid = decodeAppId(pathtemp);
155 int index = m_launcherModel->findApplication(appid);
170 int index = m_launcherModel->findApplication(appid);
156 LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
171 LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
157
172
158 QVariantList retval;
173 QVariantList retval;
159 if (message.member() == "Get") {
174 if (message.member() == "Get") {
175+ QString cachedString = message.arguments()[1].toString();
160 if (!item) {
176 if (!item) {
161 return false;
177 return false;
162 }
178 }
163− if (message.arguments()[1].toString() == "count") {
179+ if (cachedString == "count") {
164 retval.append(QVariant::fromValue(QDBusVariant(item->count())));
180 retval.append(QVariant::fromValue(QDBusVariant(item->count())));
165− } else if (message.arguments()[1].toString() == "countVisible") {
181+ } else if (cachedString == "countVisible") {
166 retval.append(QVariant::fromValue(QDBusVariant(item->countVisible())));
182 retval.append(QVariant::fromValue(QDBusVariant(item->countVisible())));
183+ } else if (cachedString == "progress") {
184+ retval.append(QVariant::fromValue(QDBusVariant(item->progress())));
167 }
185 }
168 } else if (message.member() == "Set") {
186 } else if (message.member() == "Set") {
169− if (message.arguments()[1].toString() == "count") {
187+ QString cachedString = message.arguments()[1].toString();
188+ if (cachedString == "count") {
170 int newCount = message.arguments()[2].value<QDBusVariant>().variant().toInt();
189 int newCount = message.arguments()[2].value<QDBusVariant>().variant().toInt();
171 if (!item || newCount != item->count()) {
190 if (!item || newCount != item->count()) {
172 Q_EMIT countChanged(appid, newCount);
191 Q_EMIT countChanged(appid, newCount);
173 notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "count", QVariant(newCount));
192 notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "count", QVariant(newCount));
174 }
193 }
175− } else if (message.arguments()[1].toString() == "countVisible") {
194+ } else if (cachedString == "countVisible") {
176 bool newVisible = message.arguments()[2].value<QDBusVariant>().variant().toBool();
195 bool newVisible = message.arguments()[2].value<QDBusVariant>().variant().toBool();
177 if (!item || newVisible != item->countVisible()) {
196 if (!item || newVisible != item->countVisible()) {
178 Q_EMIT countVisibleChanged(appid, newVisible);
197 Q_EMIT countVisibleChanged(appid, newVisible);
179 notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "countVisible", newVisible);
198 notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "countVisible", newVisible);
180 }
199 }
200+ } else if (cachedString == "progress") {
201+ int newProgress = message.arguments()[2].value<QDBusVariant>().variant().toInt();
202+ if (!item || newProgress != item->progress()) {
203+ Q_EMIT progressChanged(appid, newProgress);
204+ notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "progress", QVariant(newProgress));
205+ }
181 }
206 }
182 } else if (message.member() == "GetAll") {
207 } else if (message.member() == "GetAll") {
183 if (item) {
208 if (item) {
184 QVariantMap all;
209 QVariantMap all;
185 all.insert("count", item->count());
210 all.insert("count", item->count());
186 all.insert("countVisible", item->countVisible());
211 all.insert("countVisible", item->countVisible());
212+ all.insert("progress", item->progress());
187 retval.append(all);
213 retval.append(all);
188 }
214 }
189 } else {
215 } else {
plugins/Unity/Launcher/dbusinterface.h+2
@@ -36,7 +36,9 @@ public:
36Q_SIGNALS:
36Q_SIGNALS:
37 void countChanged(const QString &appId, int count);
37 void countChanged(const QString &appId, int count);
38 void countVisibleChanged(const QString &appId, bool countVisible);
38 void countVisibleChanged(const QString &appId, bool countVisible);
39+ void progressChanged(const QString &appId, int progress);
39 void refreshCalled();
40 void refreshCalled();
41+ void alertCalled(const QString &appId);
40
42
41private:
43private:
42 static QString decodeAppId(const QString& path);
44 static QString decodeAppId(const QString& path);
plugins/Unity/Launcher/launcheritem.cpp+23
@@ -34,6 +34,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
34 m_count(0),
34 m_count(0),
35 m_countVisible(false),
35 m_countVisible(false),
36 m_focused(false),
36 m_focused(false),
37+ m_alerting(false),
37 m_quickList(new QuickListModel(this))
38 m_quickList(new QuickListModel(this))
38{
39{
39 QuickListEntry nameAction;
40 QuickListEntry nameAction;
@@ -160,6 +161,9 @@ void LauncherItem::setCount(int count)
160 if (m_count != count) {
161 if (m_count != count) {
161 m_count = count;
162 m_count = count;
162 Q_EMIT countChanged(count);
163 Q_EMIT countChanged(count);
164+ if (m_countVisible) {
165+ setAlerting(true);
166+ }
163 }
167 }
164}
168}
165
169
@@ -173,6 +177,9 @@ void LauncherItem::setCountVisible(bool countVisible)
173 if (m_countVisible != countVisible) {
177 if (m_countVisible != countVisible) {
174 m_countVisible = countVisible;
178 m_countVisible = countVisible;
175 Q_EMIT countVisibleChanged(countVisible);
179 Q_EMIT countVisibleChanged(countVisible);
180+ if (countVisible) {
181+ setAlerting(true);
182+ }
176 }
183 }
177}
184}
178
185
@@ -185,10 +192,26 @@ void LauncherItem::setFocused(bool focused)
185{
192{
186 if (m_focused != focused) {
193 if (m_focused != focused) {
187 m_focused = focused;
194 m_focused = focused;
195+ if (focused) {
196+ setAlerting(false);
197+ }
188 Q_EMIT focusedChanged(focused);
198 Q_EMIT focusedChanged(focused);
189 }
199 }
190}
200}
191
201
202+bool LauncherItem::alerting() const
203+{
204+ return m_alerting;
205+}
206+
207+void LauncherItem::setAlerting(bool alerting)
208+{
209+ if (m_alerting != alerting) {
210+ m_alerting = alerting;
211+ Q_EMIT alertingChanged(alerting);
212+ }
213+}
214+
192unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
215unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
193{
216{
194 return m_quickList;
217 return m_quickList;
plugins/Unity/Launcher/launcheritem.h+3
@@ -44,6 +44,7 @@ public:
44 int count() const override;
44 int count() const override;
45 bool countVisible() const override;
45 bool countVisible() const override;
46 bool focused() const override;
46 bool focused() const override;
47+ bool alerting() const override;
47
48
48 unity::shell::launcher::QuickListModelInterface *quickList() const override;
49 unity::shell::launcher::QuickListModelInterface *quickList() const override;
49
50
@@ -57,6 +58,7 @@ private:
57 void setCount(int count);
58 void setCount(int count);
58 void setCountVisible(bool countVisible);
59 void setCountVisible(bool countVisible);
59 void setFocused(bool focused);
60 void setFocused(bool focused);
61+ void setAlerting(bool alerting);
60
62
61private:
63private:
62 QString m_appId;
64 QString m_appId;
@@ -69,6 +71,7 @@ private:
69 int m_count;
71 int m_count;
70 bool m_countVisible;
72 bool m_countVisible;
71 bool m_focused;
73 bool m_focused;
74+ bool m_alerting;
72 QuickListModel *m_quickList;
75 QuickListModel *m_quickList;
73 QuickListEntry m_quitAction;
76 QuickListEntry m_quitAction;
74
77
plugins/Unity/Launcher/launchermodel.cpp+32
@@ -40,7 +40,9 @@ LauncherModel::LauncherModel(QObject *parent):
40{
40{
41 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
41 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
42 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
42 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
43+ connect(m_dbusIface, &DBusInterface::progressChanged, this, &LauncherModel::progressChanged);
43 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
44 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
45+ connect(m_dbusIface, &DBusInterface::alertCalled, this, &LauncherModel::alert);
44
46
45 connect(m_settings, &GSettings::changed, this, &LauncherModel::refresh);
47 connect(m_settings, &GSettings::changed, this, &LauncherModel::refresh);
46
48
@@ -82,6 +84,8 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
82 return item->progress();
84 return item->progress();
83 case RoleFocused:
85 case RoleFocused:
84 return item->focused();
86 return item->focused();
87+ case RoleAlerting:
88+ return item->alerting();
85 case RoleRunning:
89 case RoleRunning:
86 return item->running();
90 return item->running();
87 default:
91 default:
@@ -92,6 +96,18 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
92 return QVariant();
96 return QVariant();
93}
97}
94
98
99+void LauncherModel::setAlerting(const QString &appId, bool alerting) {
100+ int index = findApplication(appId);
101+ if (index >= 0) {
102+ QModelIndex modelIndex = this->index(index);
103+ LauncherItem *item = m_list.at(index);
104+ if (!item->focused()) {
105+ item->setAlerting(alerting);
106+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleAlerting);
107+ }
108+ }
109+}
110+
95unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
111unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
96{
112{
97 if (index < 0 || index >= m_list.count()) {
113 if (index < 0 || index >= m_list.count()) {
@@ -342,6 +358,9 @@ void LauncherModel::countChanged(const QString &appId, int count)
342 if (idx >= 0) {
358 if (idx >= 0) {
343 LauncherItem *item = m_list.at(idx);
359 LauncherItem *item = m_list.at(idx);
344 item->setCount(count);
360 item->setCount(count);
361+ if (item->countVisible()) {
362+ setAlerting(item->appId(), true);
363+ }
345 m_asAdapter->syncItems(m_list);
364 m_asAdapter->syncItems(m_list);
346 Q_EMIT dataChanged(index(idx), index(idx), {RoleCount});
365 Q_EMIT dataChanged(index(idx), index(idx), {RoleCount});
347 }
366 }
@@ -353,6 +372,9 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
353 if (idx >= 0) {
372 if (idx >= 0) {
354 LauncherItem *item = m_list.at(idx);
373 LauncherItem *item = m_list.at(idx);
355 item->setCountVisible(countVisible);
374 item->setCountVisible(countVisible);
375+ if (countVisible) {
376+ setAlerting(item->appId(), true);
377+ }
356 Q_EMIT dataChanged(index(idx), index(idx), {RoleCountVisible});
378 Q_EMIT dataChanged(index(idx), index(idx), {RoleCountVisible});
357
379
358 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
380 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
@@ -462,6 +484,16 @@ void LauncherModel::refresh()
462 m_asAdapter->syncItems(m_list);
484 m_asAdapter->syncItems(m_list);
463}
485}
464
486
487+void LauncherModel::alert(const QString &appId)
488+{
489+ int idx = findApplication(appId);
490+ if (idx >= 0) {
491+ LauncherItem *item = m_list.at(idx);
492+ setAlerting(item->appId(), true);
493+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleAlerting);
494+ }
495+}
496+
465void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
497void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
466{
498{
467 Q_UNUSED(parent);
499 Q_UNUSED(parent);
plugins/Unity/Launcher/launchermodel.h+2
@@ -45,6 +45,7 @@ public:
45
45
46 QVariant data(const QModelIndex &index, int role) const override;
46 QVariant data(const QModelIndex &index, int role) const override;
47
47
48+ Q_INVOKABLE void setAlerting(const QString &appId, bool alerting) override;
48 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
49 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const override;
49 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
50 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
50 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
51 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
@@ -63,6 +64,7 @@ public:
63public Q_SLOTS:
64public Q_SLOTS:
64 void requestRemove(const QString &appId) override;
65 void requestRemove(const QString &appId) override;
65 Q_INVOKABLE void refresh();
66 Q_INVOKABLE void refresh();
67+ Q_INVOKABLE void alert(const QString &appId);
66
68
67private:
69private:
68 void storeAppList();
70 void storeAppList();
qml/Launcher/LauncherDelegate.qml+101−15
@@ -15,7 +15,7 @@
15 */
15 */
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18−import Ubuntu.Components 0.1
18+import Ubuntu.Components 1.1
19
19
20Item {
20Item {
21 id: root
21 id: root
@@ -27,6 +27,8 @@ Item {
27 property bool itemFocused: false
27 property bool itemFocused: false
28 property real maxAngle: 0
28 property real maxAngle: 0
29 property bool inverted: false
29 property bool inverted: false
30+ property bool alerting: false
31+ readonly property alias wiggling: wiggleAnim.running
30
32
31 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
33 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
32 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
34 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
@@ -39,6 +41,82 @@ Item {
39 property real offset: 0
41 property real offset: 0
40 property real itemOpacity: 1
42 property real itemOpacity: 1
41 property real brightness: 0
43 property real brightness: 0
44+ property double maxWiggleAngle: 5.0
45+
46+ QtObject {
47+ id: priv
48+
49+ readonly property int wiggleDuration: UbuntuAnimation.SnapDuration
50+ property real wiggleAngle: 0
51+ }
52+
53+ SequentialAnimation {
54+ id: wiggleAnim
55+
56+ running: alerting
57+ loops: 1
58+ alwaysRunToEnd: true
59+
60+ NumberAnimation {
61+ target: priv
62+ property: "wiggleAngle"
63+ from: 0
64+ to: maxWiggleAngle
65+ duration: priv.wiggleDuration
66+ easing.type: Easing.InQuad
67+ }
68+
69+ NumberAnimation {
70+ target: priv
71+ property: "wiggleAngle"
72+ from: maxWiggleAngle
73+ to: -maxWiggleAngle
74+ duration: priv.wiggleDuration
75+ easing.type: Easing.InOutQuad
76+ }
77+
78+ NumberAnimation {
79+ target: priv
80+ property: "wiggleAngle"
81+ from: -maxWiggleAngle
82+ to: maxWiggleAngle
83+ duration: priv.wiggleDuration
84+ easing.type: Easing.InOutQuad
85+ }
86+
87+ NumberAnimation {
88+ target: priv
89+ property: "wiggleAngle"
90+ from: maxWiggleAngle
91+ to: -maxWiggleAngle
92+ duration: priv.wiggleDuration
93+ easing.type: Easing.InOutQuad
94+ }
95+
96+ NumberAnimation {
97+ target: priv
98+ property: "wiggleAngle"
99+ from: -maxWiggleAngle
100+ to: maxWiggleAngle
101+ duration: priv.wiggleDuration
102+ easing.type: Easing.InOutQuad
103+ }
104+
105+ NumberAnimation {
106+ target: priv
107+ property: "wiggleAngle"
108+ from: maxWiggleAngle
109+ to: 0
110+ duration: priv.wiggleDuration
111+ easing.type: Easing.OutQuad
112+ }
113+
114+ UbuntuNumberAnimation {
115+ target: root
116+ property: "alerting"
117+ to: 0
118+ }
119+ }
42
120
43 Item {
121 Item {
44 id: iconItem
122 id: iconItem
@@ -75,7 +153,7 @@ Item {
75 objectName: "countEmblem"
153 objectName: "countEmblem"
76 anchors {
154 anchors {
77 right: parent.right
155 right: parent.right
78− top: parent.top
156+ bottom: parent.bottom
79 margins: units.dp(3)
157 margins: units.dp(3)
80 }
158 }
81 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
159 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
@@ -100,23 +178,21 @@ Item {
100 }
178 }
101 }
179 }
102
180
103− BorderImage {
181+ UbuntuShape {
104 id: progressOverlay
182 id: progressOverlay
105 objectName: "progressOverlay"
183 objectName: "progressOverlay"
184+
106 anchors {
185 anchors {
107 left: iconItem.left
186 left: iconItem.left
108 right: iconItem.right
187 right: iconItem.right
109− bottom: iconItem.bottom
188+ verticalCenter: parent.verticalCenter
110− leftMargin: units.gu(1)
189+ leftMargin: units.gu(1.5)
111− rightMargin: units.gu(1)
190+ rightMargin: units.gu(1.5)
112− bottomMargin: units.gu(1)
113 }
191 }
114− height: units.gu(1.5)
192+ height: units.gu(1)
115 visible: root.progress > -1
193 visible: root.progress > -1
116− source: "graphics/progressbar-trough.sci"
194+ color: UbuntuColors.darkGrey
117−
195+ borderSource: "none"
118− // For fill calculation we need to remove the 2 units of border defined in .sci file
119− property int adjustedWidth: width - units.gu(2)
120
196
121 Item {
197 Item {
122 anchors {
198 anchors {
@@ -124,20 +200,22 @@ Item {
124 top: parent.top
200 top: parent.top
125 bottom: parent.bottom
201 bottom: parent.bottom
126 }
202 }
127− width: Math.min(100, root.progress) / 100 * parent.adjustedWidth + units.gu(1)
203+ width: Math.min(100, root.progress) / 100 * parent.width
128 clip: true
204 clip: true
129
205
130− BorderImage {
206+ UbuntuShape {
131 anchors {
207 anchors {
132 left: parent.left
208 left: parent.left
133 top: parent.top
209 top: parent.top
134 bottom: parent.bottom
210 bottom: parent.bottom
135 }
211 }
212+ color: "white"
213+ borderSource: "none"
136 width: progressOverlay.width
214 width: progressOverlay.width
137− source: "graphics/progressbar-fill.sci"
138 }
215 }
139 }
216 }
140 }
217 }
218+
141 Image {
219 Image {
142 objectName: "focusedHighlight"
220 objectName: "focusedHighlight"
143 anchors {
221 anchors {
@@ -167,6 +245,14 @@ Item {
167 }
245 }
168
246
169 transform: [
247 transform: [
248+ // The rotation about the icon's center/z-axis for the wiggle
249+ // needs to happen here too, because there's no other way to
250+ // align the wiggle with the icon-folding otherwise
251+ Rotation {
252+ axis { x: 0; y: 0; z: 1 }
253+ origin { x: iconItem.width / 2; y: iconItem.height / 2; z: 0 }
254+ angle: priv.wiggleAngle
255+ },
170 // Rotating 3 times at top/bottom because that increases the perspective.
256 // Rotating 3 times at top/bottom because that increases the perspective.
171 // This is a hack, but as QML does not support real 3D coordinates
257 // This is a hack, but as QML does not support real 3D coordinates
172 // getting a higher perspective can only be done by a hack. This is the most
258 // getting a higher perspective can only be done by a hack. This is the most
qml/Launcher/LauncherPanel.qml+71
@@ -86,6 +86,7 @@ Rectangle {
86 height: parent.height - dashItem.height - parent.spacing*2
86 height: parent.height - dashItem.height - parent.spacing*2
87
87
88 Item {
88 Item {
89+ id: launcherListViewItem
89 anchors.fill: parent
90 anchors.fill: parent
90 clip: true
91 clip: true
91
92
@@ -109,12 +110,21 @@ Rectangle {
109 preferredHighlightBegin: (height - itemHeight) / 2
110 preferredHighlightBegin: (height - itemHeight) / 2
110 preferredHighlightEnd: (height + itemHeight) / 2
111 preferredHighlightEnd: (height + itemHeight) / 2
111
112
113+ // for the single peeking icon, when alert-state is set on delegate
114+ property int peekingIndex: -1
115+
112 // The size of the area the ListView is extended to make sure items are not
116 // The size of the area the ListView is extended to make sure items are not
113 // destroyed when dragging them outside the list. This needs to be at least
117 // destroyed when dragging them outside the list. This needs to be at least
114 // itemHeight to prevent folded items from disappearing and DragArea limits
118 // itemHeight to prevent folded items from disappearing and DragArea limits
115 // need to be smaller than this size to avoid breakage.
119 // need to be smaller than this size to avoid breakage.
116 property int extensionSize: 0
120 property int extensionSize: 0
117
121
122+ // These properties are needed to facilitate implicitly unfolding the
123+ // launcher to the peeking icon, if it is not already fully flat
124+ // and in view for the user
125+ property real moveToIndexYFrom
126+ property real moveToIndexYTo
127+
118 // Setting extensionSize after the list has been populated because it has
128 // Setting extensionSize after the list has been populated because it has
119 // the potential to mess up with the intial positioning in combination
129 // the potential to mess up with the intial positioning in combination
120 // with snapping to the center of the list. This catches all the cases
130 // with snapping to the center of the list. This catches all the cases
@@ -128,6 +138,14 @@ Rectangle {
128 flick(0, clickFlickSpeed)
138 flick(0, clickFlickSpeed)
129 }
139 }
130
140
141+ UbuntuNumberAnimation {
142+ id: moveToIndexAnimation
143+ target: launcherListView
144+ property: "contentY"
145+ from: launcherListView.moveToIndexYFrom
146+ to: launcherListView.moveToIndexYTo
147+ }
148+
131 // The height of the area where icons start getting folded
149 // The height of the area where icons start getting folded
132 property int foldingStartHeight: units.gu(6.5)
150 property int foldingStartHeight: units.gu(6.5)
133 // The height of the area where the items reach the final folding angle
151 // The height of the area where the items reach the final folding angle
@@ -180,10 +198,63 @@ Rectangle {
180 progress: model.progress
198 progress: model.progress
181 itemFocused: model.focused
199 itemFocused: model.focused
182 inverted: root.inverted
200 inverted: root.inverted
201+ alerting: model.alerting
183 z: -Math.abs(offset)
202 z: -Math.abs(offset)
184 maxAngle: 55
203 maxAngle: 55
185 property bool dragging: false
204 property bool dragging: false
186
205
206+ SequentialAnimation {
207+ id: peekingAnimation
208+
209+ // revealing
210+ PropertyAction { target: root; property: "visible"; value: (launcher.visibleWidth === 0) ? 1 : 0 }
211+ PropertyAction { target: launcherListViewItem; property: "clip"; value: 0 }
212+
213+ UbuntuNumberAnimation {
214+ target: launcherDelegate
215+ alwaysRunToEnd: true
216+ loops: 1
217+ properties: "x"
218+ to: (units.gu(.5) + launcherListView.width * .5) * (root.inverted ? -1 : 1)
219+ duration: UbuntuAnimation.BriskDuration
220+ }
221+
222+ // hiding
223+ UbuntuNumberAnimation {
224+ target: launcherDelegate
225+ alwaysRunToEnd: true
226+ loops: 1
227+ properties: "x"
228+ to: 0
229+ duration: UbuntuAnimation.BriskDuration
230+ }
231+
232+ PropertyAction { target: launcherListViewItem; property: "clip"; value: 1 }
233+ PropertyAction { target: root; property: "visible"; value: (launcher.visibleWidth === 0) ? 0 : 1 }
234+ }
235+
236+ onAlertingChanged: {
237+ if(alerting) {
238+ if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
239+ launcherListView.moveToIndexYFrom = launcherListView.contentY
240+ launcherListView.positionViewAtIndex(index, ListView.Center)
241+ launcherListView.moveToIndexYTo = launcherListView.contentY
242+ moveToIndexAnimation.start()
243+ if (!dragging && launcher.state !== "visible") {
244+ peekingAnimation.start()
245+ }
246+ }
247+
248+ if (launcherListView.peekingIndex === -1) {
249+ launcherListView.peekingIndex = index
250+ }
251+ } else {
252+ if (launcherListView.peekingIndex === index) {
253+ launcherListView.peekingIndex = -1
254+ }
255+ }
256+ }
257+
187 ThinDivider {
258 ThinDivider {
188 id: dropIndicator
259 id: dropIndicator
189 objectName: "dropIndicator"
260 objectName: "dropIndicator"
qml/Launcher/graphics/progressbar-fill@30.pngdeleted

Binary file not shown.

qml/Launcher/graphics/progressbar-fill@30.scideleted−5
@@ -1,5 +0,0 @@
1−border.left: 36
2−border.top: 16
3−border.bottom: 28
4−border.right: 36
5−source: progressbar-fill@30.png
qml/Launcher/graphics/progressbar-trough@30.pngdeleted

Binary file not shown.

qml/Launcher/graphics/progressbar-trough@30.scideleted−5
@@ -1,5 +0,0 @@
1−border.left: 36
2−border.top: 16
3−border.bottom: 26
4−border.right: 36
5−source: progressbar-trough@30.png
tests/mocks/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2
2
3include_directories(
3include_directories(
4 ${CMAKE_CURRENT_SOURCE_DIR}
4 ${CMAKE_CURRENT_SOURCE_DIR}
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+19
@@ -37,6 +37,7 @@ MockLauncherItem::MockLauncherItem(const QString &appId, const QString& desktopF
37 m_count(0),
37 m_count(0),
38 m_countVisible(false),
38 m_countVisible(false),
39 m_focused(false),
39 m_focused(false),
40+ m_alerting(false),
40 m_quickList(new MockQuickListModel(this))
41 m_quickList(new MockQuickListModel(this))
41{
42{
42}
43}
@@ -132,6 +133,9 @@ void MockLauncherItem::setCount(int count)
132 {
133 {
133 m_count = count;
134 m_count = count;
134 Q_EMIT countChanged(count);
135 Q_EMIT countChanged(count);
136+ if (m_countVisible) {
137+ setAlerting(true);
138+ }
135 }
139 }
136}
140}
137
141
@@ -145,6 +149,9 @@ void MockLauncherItem::setCountVisible(bool countVisible)
145 if (m_countVisible != countVisible) {
149 if (m_countVisible != countVisible) {
146 m_countVisible = countVisible;
150 m_countVisible = countVisible;
147 Q_EMIT countVisibleChanged(countVisible);
151 Q_EMIT countVisibleChanged(countVisible);
152+ if (countVisible) {
153+ setAlerting(true);
154+ }
148 }
155 }
149}
156}
150
157
@@ -162,6 +169,18 @@ void MockLauncherItem::setFocused(bool focused)
162 }
169 }
163}
170}
164
171
172+bool MockLauncherItem::alerting() const
173+{
174+ return m_alerting;
175+}
176+
177+void MockLauncherItem::setAlerting(bool alerting)
178+{
179+ if (m_alerting != alerting) {
180+ m_alerting = alerting;
181+ Q_EMIT alertingChanged(alerting);
182+ }
183+}
165
184
166unity::shell::launcher::QuickListModelInterface *MockLauncherItem::quickList() const
185unity::shell::launcher::QuickListModelInterface *MockLauncherItem::quickList() const
167{
186{
tests/mocks/Unity/Launcher/MockLauncherItem.h+3
@@ -45,6 +45,7 @@ public:
45 int count() const override;
45 int count() const override;
46 bool countVisible() const override;
46 bool countVisible() const override;
47 bool focused() const override;
47 bool focused() const override;
48+ bool alerting() const override;
48
49
49 unity::shell::launcher::QuickListModelInterface *quickList() const override;
50 unity::shell::launcher::QuickListModelInterface *quickList() const override;
50
51
@@ -56,6 +57,7 @@ private:
56 void setCount(int count);
57 void setCount(int count);
57 void setCountVisible(bool countVisible);
58 void setCountVisible(bool countVisible);
58 void setFocused(bool focused);
59 void setFocused(bool focused);
60+ void setAlerting(bool alerting);
59
61
60 QString m_appId;
62 QString m_appId;
61 QString m_desktopFile;
63 QString m_desktopFile;
@@ -68,6 +70,7 @@ private:
68 int m_count;
70 int m_count;
69 bool m_countVisible;
71 bool m_countVisible;
70 bool m_focused;
72 bool m_focused;
73+ bool m_alerting;
71 MockQuickListModel *m_quickList;
74 MockQuickListModel *m_quickList;
72
75
73 friend class MockLauncherModel;
76 friend class MockLauncherModel;
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+50
@@ -34,6 +34,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
34 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
34 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
35 item->setProgress(50);
35 item->setProgress(50);
36 item->setCountVisible(true);
36 item->setCountVisible(true);
37+ item->setAlerting(false);
37 m_list.append(item);
38 m_list.append(item);
38 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
39 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
39 m_list.append(item);
40 m_list.append(item);
@@ -44,25 +45,30 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
44 item->setCount(1);
45 item->setCount(1);
45 item->setCountVisible(true);
46 item->setCountVisible(true);
46 item->setRunning(true);
47 item->setRunning(true);
48+ item->setAlerting(false);
47 m_list.append(item);
49 m_list.append(item);
48 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
50 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
49 item->setCount(12);
51 item->setCount(12);
50 item->setCountVisible(true);
52 item->setCountVisible(true);
53+ item->setAlerting(false);
51 item->setPinned(true);
54 item->setPinned(true);
52 m_list.append(item);
55 m_list.append(item);
53 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
56 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
54 item->setCount(123);
57 item->setCount(123);
55 item->setCountVisible(true);
58 item->setCountVisible(true);
59+ item->setAlerting(false);
56 m_list.append(item);
60 m_list.append(item);
57 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
61 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
58 item->setCount(1234567890);
62 item->setCount(1234567890);
59 item->setCountVisible(true);
63 item->setCountVisible(true);
64+ item->setAlerting(false);
60 item->setPinned(true);
65 item->setPinned(true);
61 m_list.append(item);
66 m_list.append(item);
62 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
67 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
63 item->setProgress(50);
68 item->setProgress(50);
64 item->setCount(5);
69 item->setCount(5);
65 item->setCountVisible(true);
70 item->setCountVisible(true);
71+ item->setAlerting(false);
66 item->setFocused(true);
72 item->setFocused(true);
67 item->setPinned(true);
73 item->setPinned(true);
68 m_list.append(item);
74 m_list.append(item);
@@ -106,6 +112,8 @@ QVariant MockLauncherModel::data(const QModelIndex& index, int role) const
106 return item->countVisible();
112 return item->countVisible();
107 case RoleFocused:
113 case RoleFocused:
108 return item->focused();
114 return item->focused();
115+ case RoleAlerting:
116+ return item->alerting();
109 }
117 }
110
118
111 return QVariant();
119 return QVariant();
@@ -120,6 +128,18 @@ unity::shell::launcher::LauncherItemInterface *MockLauncherModel::get(int index)
120 return m_list.at(index);
128 return m_list.at(index);
121}
129}
122
130
131+void MockLauncherModel::setAlerting(const QString &appId, bool alerting) {
132+ int index = findApp(appId);
133+ if (index >= 0) {
134+ QModelIndex modelIndex = this->index(index);
135+ MockLauncherItem *item = m_list.at(index);
136+ if (!item->focused()) {
137+ item->setAlerting(alerting);
138+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleAlerting);
139+ }
140+ }
141+}
142+
123void MockLauncherModel::move(int oldIndex, int newIndex)
143void MockLauncherModel::move(int oldIndex, int newIndex)
124{
144{
125 // Make sure its not moved outside the lists
145 // Make sure its not moved outside the lists
@@ -198,6 +218,16 @@ int MockLauncherModel::findApp(const QString &appId)
198 return -1;
218 return -1;
199}
219}
200
220
221+void MockLauncherModel::setProgress(const QString &appId, int progress)
222+{
223+ int index = findApp(appId);
224+ if (index >= 0) {
225+ m_list.at(index)->setProgress(progress);
226+ QModelIndex modelIndex = this->index(index);
227+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleProgress);
228+ }
229+}
230+
201void MockLauncherModel::setUser(const QString &username)
231void MockLauncherModel::setUser(const QString &username)
202{
232{
203 Q_UNUSED(username)
233 Q_UNUSED(username)
@@ -229,6 +259,26 @@ void MockLauncherModel::emitHint()
229 Q_EMIT hint();
259 Q_EMIT hint();
230}
260}
231
261
262+void MockLauncherModel::setCount(const QString &appId, int count)
263+{
264+ int index = findApp(appId);
265+ if (index >= 0) {
266+ m_list.at(index)->setCount(count);
267+ QModelIndex modelIndex = this->index(index);
268+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleCount);
269+ }
270+}
271+
272+void MockLauncherModel::setCountVisible(const QString &appId, bool countVisible)
273+{
274+ int index = findApp(appId);
275+ if (index >= 0) {
276+ m_list.at(index)->setCountVisible(countVisible);
277+ QModelIndex modelIndex = this->index(index);
278+ Q_EMIT dataChanged(modelIndex, modelIndex);
279+ }
280+}
281+
232unity::shell::application::ApplicationManagerInterface *MockLauncherModel::applicationManager() const
282unity::shell::application::ApplicationManagerInterface *MockLauncherModel::applicationManager() const
233{
283{
234 return nullptr;
284 return nullptr;
tests/mocks/Unity/Launcher/MockLauncherModel.h+4
@@ -38,6 +38,7 @@ public:
38
38
39 QVariant data(const QModelIndex& index, int role) const override;
39 QVariant data(const QModelIndex& index, int role) const override;
40
40
41+ Q_INVOKABLE void setAlerting(const QString &appId, bool alerting) override;
41 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface *get(int index) const override;
42 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface *get(int index) const override;
42 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
43 Q_INVOKABLE void move(int oldIndex, int newIndex) override;
43 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
44 Q_INVOKABLE void pin(const QString &appId, int index = -1) override;
@@ -54,6 +55,9 @@ public:
54
55
55 // For testing
56 // For testing
56 Q_INVOKABLE void emitHint();
57 Q_INVOKABLE void emitHint();
58+ Q_INVOKABLE void setProgress(const QString &appId, int progress);
59+ Q_INVOKABLE void setCount(const QString &appId, int count);
60+ Q_INVOKABLE void setCountVisible(const QString &appId, bool countVisible);
57
61
58Q_SIGNALS:
62Q_SIGNALS:
59 void quickListTriggered(const QString &appId, int index);
63 void quickListTriggered(const QString &appId, int index);
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3
3
4include_directories(
4include_directories(
tests/plugins/Greeter/Unity/Launcher/launchermodelastest.cpp+3
@@ -43,6 +43,7 @@ private Q_SLOTS:
43 item.insert("name", "Item 1");
43 item.insert("name", "Item 1");
44 item.insert("icon", "fake.svg");
44 item.insert("icon", "fake.svg");
45 item.insert("count", 0);
45 item.insert("count", 0);
46+ item.insert("progress", 0);
46 item.insert("countVisible", false);
47 item.insert("countVisible", false);
47 item.insert("pinned", true);
48 item.insert("pinned", true);
48 list.append(item);
49 list.append(item);
@@ -67,6 +68,7 @@ private Q_SLOTS:
67 inSync &= model->get(i)->name() == list.at(i).value("name").toString();
68 inSync &= model->get(i)->name() == list.at(i).value("name").toString();
68 inSync &= model->get(i)->icon() == list.at(i).value("icon").toString();
69 inSync &= model->get(i)->icon() == list.at(i).value("icon").toString();
69 inSync &= model->get(i)->count() == list.at(i).value("count").toInt();
70 inSync &= model->get(i)->count() == list.at(i).value("count").toInt();
71+ inSync &= model->get(i)->progress() == list.at(i).value("progress").toInt();
70 inSync &= model->get(i)->countVisible() == list.at(i).value("countVisible").toBool();
72 inSync &= model->get(i)->countVisible() == list.at(i).value("countVisible").toBool();
71 }
73 }
72 return inSync;
74 return inSync;
@@ -111,6 +113,7 @@ private Q_SLOTS:
111 newEntry.insert("name", "New app");
113 newEntry.insert("name", "New app");
112 newEntry.insert("icon", "some-icon.svg");
114 newEntry.insert("icon", "some-icon.svg");
113 newEntry.insert("count", 0);
115 newEntry.insert("count", 0);
116+ newEntry.insert("progress", 42);
114 newEntry.insert("countVisible", false);
117 newEntry.insert("countVisible", false);
115 newEntry.insert("pinned", true);
118 newEntry.insert("pinned", true);
116 newList.append(newEntry);
119 newList.append(newEntry);
tests/plugins/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,5 +1,5 @@
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
3
3
4include_directories(
4include_directories(
5 ${CMAKE_CURRENT_SOURCE_DIR}
5 ${CMAKE_CURRENT_SOURCE_DIR}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+23−7
@@ -409,11 +409,18 @@ private Q_SLOTS:
409 }
409 }
410
410
411 void testCountEmblems() {
411 void testCountEmblems() {
412+ QSignalSpy spy(launcherModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
413+
412 // Call GetAll on abs-icon
414 // Call GetAll on abs-icon
413 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
415 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
414 QDBusReply<QVariantMap> reply = interface.call("GetAll");
416 QDBusReply<QVariantMap> reply = interface.call("GetAll");
415 QVariantMap map = reply.value();
417 QVariantMap map = reply.value();
416
418
419+ // Check that the alerting-status is still false, and the item on the upper side of the API
420+ int index = launcherModel->findApplication("abs-icon");
421+ QCOMPARE(index >= 0, true);
422+ QVERIFY(launcherModel->get(index)->alerting() == false);
423+
417 // Make sure GetAll returns a map with count and countVisible props
424 // Make sure GetAll returns a map with count and countVisible props
418 QCOMPARE(map.contains("count"), true);
425 QCOMPARE(map.contains("count"), true);
419 QCOMPARE(map.contains("countVisible"), true);
426 QCOMPARE(map.contains("countVisible"), true);
@@ -434,13 +441,8 @@ private Q_SLOTS:
434 QCOMPARE(map.value("count").toInt(), 55);
441 QCOMPARE(map.value("count").toInt(), 55);
435 QCOMPARE(map.value("countVisible").toBool(), true);
442 QCOMPARE(map.value("countVisible").toBool(), true);
436
443
437− // Now the item on the upper side of the API
444+ // Finally check, that the change to "count" implicitly also set the alerting-state to true
438− int index = launcherModel->findApplication("abs-icon");
439− QCOMPARE(index >= 0, true);
440−
441− // And make sure values have changed there as well
442− QCOMPARE(launcherModel->get(index)->countVisible(), true);
445+ QVERIFY(launcherModel->get(index)->alerting() == true);
443− QCOMPARE(launcherModel->get(index)->count(), 55);
444 }
446 }
445
447
446 void testCountEmblemAddsRemovesItem_data() {
448 void testCountEmblemAddsRemovesItem_data() {
@@ -500,6 +502,20 @@ private Q_SLOTS:
500 QCOMPARE(index == -1, !isRunning && !isPinned && !startWhenVisible);
502 QCOMPARE(index == -1, !isRunning && !isPinned && !startWhenVisible);
501 }
503 }
502
504
505+ void testAlert() {
506+ // Check that the alerting-status is still false
507+ int index = launcherModel->findApplication("abs-icon");
508+ QCOMPARE(index >= 0, true);
509+ QVERIFY(launcherModel->get(index)->alerting() == false);
510+
511+ // Call Alert() on "abs-icon"
512+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "com.canonical.Unity.Launcher.Item");
513+ interface.call("Alert");
514+
515+ // Check that the alerting-status is now true
516+ QVERIFY(launcherModel->get(index)->alerting() == true);
517+ }
518+
503 void testRefreshAfterDeletedDesktopFiles_data() {
519 void testRefreshAfterDeletedDesktopFiles_data() {
504 QTest::addColumn<bool>("deleted");
520 QTest::addColumn<bool>("deleted");
505 QTest::newRow("have .desktop files") << false;
521 QTest::newRow("have .desktop files") << false;
tests/qmltests/Launcher/tst_Launcher.qml+181
@@ -93,6 +93,57 @@ Item {
93 onClicked: launcherLoader.item.inverted = !launcherLoader.item.inverted
93 onClicked: launcherLoader.item.inverted = !launcherLoader.item.inverted
94 Layout.fillWidth: true
94 Layout.fillWidth: true
95 }
95 }
96+
97+ Row {
98+ spacing: units.gu(1)
99+
100+ Button {
101+ text: "35% bar"
102+ onClicked: LauncherModel.setProgress(LauncherModel.get(parseInt(appIdEntryBar.displayText)).appId, 35)
103+ Layout.fillWidth: true
104+ }
105+
106+ TextArea {
107+ id: appIdEntryBar
108+ anchors.verticalCenter: parent.verticalCenter
109+ width: units.gu(4)
110+ height: units.gu(4)
111+ autoSize: true
112+ text: "2"
113+ maximumLineCount: 1
114+ }
115+
116+ Button {
117+ text: "no bar"
118+ onClicked: LauncherModel.setProgress(LauncherModel.get(parseInt(appIdEntryBar.displayText)).appId, -1)
119+ Layout.fillWidth: true
120+ }
121+ }
122+
123+ Row {
124+ spacing: units.gu(1)
125+
126+ Button {
127+ text: "set alert"
128+ onClicked: LauncherModel.setAlerting(LauncherModel.get(parseInt(appIdEntryAlert.displayText)).appId, true)
129+ }
130+
131+ TextArea {
132+ id: appIdEntryAlert
133+ anchors.verticalCenter: parent.verticalCenter
134+ width: units.gu(5)
135+ height: units.gu(4)
136+ autoSize: true
137+ text: "2"
138+ maximumLineCount: 1
139+ Layout.fillWidth: true
140+ }
141+
142+ Button {
143+ text: "unset alert"
144+ onClicked: LauncherModel.setAlerting(LauncherModel.get(parseInt(appIdEntryAlert.displayText)).appId, false)
145+ }
146+ }
96 }
147 }
97
148
98 SignalSpy {
149 SignalSpy {
@@ -169,6 +220,16 @@ Item {
169 tryCompare(panel, "x", -panel.width, 1000);
220 tryCompare(panel, "x", -panel.width, 1000);
170 }
221 }
171
222
223+ function waitForWiggleToStart(appIcon) {
224+ verify(appIcon != undefined)
225+ tryCompare(appIcon, "wiggling", true, 1000, "wiggle-anim should not be in stopped state")
226+ }
227+
228+ function waitForWiggleToStop(appIcon) {
229+ verify(appIcon != undefined)
230+ tryCompare(appIcon, "wiggling", false, 1000, "wiggle-anim should not be in running state")
231+ }
232+
172 function positionLauncherListAtBeginning() {
233 function positionLauncherListAtBeginning() {
173 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
234 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
174 listView.contentY = -listView.topMargin;
235 listView.contentY = -listView.topMargin;
@@ -658,5 +719,125 @@ Item {
658 tryCompare(launcher, "state", "", 1000, "Launcher didn't hide after moving mouse away from it");
719 tryCompare(launcher, "state", "", 1000, "Launcher didn't hide after moving mouse away from it");
659 waitUntilLauncherDisappears();
720 waitUntilLauncherDisappears();
660 }
721 }
722+
723+ function test_progressChangeViaModel() {
724+ dragLauncherIntoView();
725+ var item = findChild(launcher, "launcherDelegate0")
726+ verify(item != undefined)
727+ LauncherModel.setProgress(LauncherModel.get(0).appId, -1)
728+ compare(findChild(item, "progressOverlay").visible, false)
729+ LauncherModel.setProgress(LauncherModel.get(0).appId, 20)
730+ compare(findChild(item, "progressOverlay").visible, true)
731+ LauncherModel.setProgress(LauncherModel.get(0).appId, 0)
732+ }
733+
734+ function test_alertPeekingIcon() {
735+ var listView = findChild(launcher, "launcherListView")
736+ verify(listView != undefined)
737+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
738+ tryCompare(listView, "peekingIndex", 5, 1000, "Wrong appId set as peeking-index")
739+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
740+ tryCompare(listView, "peekingIndex", -1, 1000, "peeking-index should be -1")
741+ }
742+
743+ function test_alertHidingIcon() {
744+ var listView = findChild(launcher, "launcherListView")
745+ verify(listView != undefined)
746+ var appIcon6 = findChild(launcher, "launcherDelegate6")
747+ verify(appIcon6 != undefined)
748+ LauncherModel.setAlerting(LauncherModel.get(6).appId, true)
749+ waitForWiggleToStart(appIcon6)
750+ LauncherModel.setAlerting(LauncherModel.get(6).appId, false)
751+ waitForWiggleToStop(appIcon6)
752+ tryCompare(appIcon6, "x", 0, 1000, "x-value of appId #6 should not be non-zero")
753+ waitForRendering(listView)
754+ }
755+
756+ function test_alertIgnoreFocusedApp() {
757+ LauncherModel.setAlerting(LauncherModel.get(0).appId, true)
758+ compare(LauncherModel.get(0).alerting, false, "Focused app should not have the alert-state set")
759+ }
760+
761+ function test_alertOnlyOnePeekingIcon() {
762+ var listView = findChild(launcher, "launcherListView")
763+ verify(listView != undefined)
764+ LauncherModel.setAlerting(LauncherModel.get(3).appId, true)
765+ LauncherModel.setAlerting(LauncherModel.get(1).appId, true)
766+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
767+ tryCompare(listView, "peekingIndex", 3, 1000, "Wrong appId set as peeking-index")
768+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
769+ LauncherModel.setAlerting(LauncherModel.get(3).appId, false)
770+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
771+ tryCompare(listView, "peekingIndex", -1, 1000, "peeking-index should be -1")
772+ waitForRendering(listView)
773+ }
774+
775+ function test_alertMultipleApps() {
776+ LauncherModel.setAlerting(LauncherModel.get(1).appId, true)
777+ LauncherModel.setAlerting(LauncherModel.get(3).appId, true)
778+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
779+ LauncherModel.setAlerting(LauncherModel.get(7).appId, true)
780+ compare(LauncherModel.get(1).alerting, true, "Alert-state of appId #1 should not be false")
781+ compare(LauncherModel.get(3).alerting, true, "Alert-state of appId #3 should not be false")
782+ compare(LauncherModel.get(5).alerting, true, "Alert-state of appId #5 should not be false")
783+ compare(LauncherModel.get(7).alerting, true, "Alert-state of appId #7 should not be false")
784+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
785+ LauncherModel.setAlerting(LauncherModel.get(3).appId, false)
786+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
787+ LauncherModel.setAlerting(LauncherModel.get(7).appId, false)
788+ compare(LauncherModel.get(1).alerting, false, "Alert-state of appId #1 should not be true")
789+ compare(LauncherModel.get(3).alerting, false, "Alert-state of appId #1 should not be true")
790+ compare(LauncherModel.get(5).alerting, false, "Alert-state of appId #1 should not be true")
791+ compare(LauncherModel.get(7).alerting, false, "Alert-state of appId #1 should not be true")
792+ }
793+
794+ function test_alertMoveIconIntoView() {
795+ dragLauncherIntoView();
796+ var appIcon1 = findChild(launcher, "launcherDelegate1");
797+ var appIcon7 = findChild(launcher, "launcherDelegate7");
798+ LauncherModel.setAlerting(LauncherModel.get(1).appId, true)
799+ tryCompare(appIcon1, "angle", 0, 1000, "angle of appId #1 should not be non-zero")
800+ waitForWiggleToStart(appIcon1)
801+ LauncherModel.setAlerting(LauncherModel.get(7).appId, true)
802+ tryCompare(appIcon7, "angle", 0, 1000, "angle of appId #7 should not be non-zero")
803+ waitForWiggleToStart(appIcon7)
804+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
805+ waitForWiggleToStop(appIcon1)
806+ LauncherModel.setAlerting(LauncherModel.get(7).appId, false)
807+ waitForWiggleToStop(appIcon7)
808+ }
809+
810+ function test_alertWigglePeekDrag() {
811+ var appIcon5 = findChild(launcher, "launcherDelegate5");
812+ var listView = findChild(launcher, "launcherListView")
813+ verify(listView != undefined)
814+ LauncherModel.setAlerting(LauncherModel.get(5).appId, true)
815+ tryCompare(listView, "peekingIndex", 5, 1000, "Wrong appId set as peeking-index")
816+ waitForWiggleToStart(appIcon5)
817+ tryCompare(appIcon5, "wiggling", true, 1000, "appId #6 should not be still")
818+ dragLauncherIntoView();
819+ tryCompare(listView, "peekingIndex", -1, 1000, "peeking-index should be -1")
820+ LauncherModel.setAlerting(LauncherModel.get(5).appId, false)
821+ waitForWiggleToStop(appIcon5)
822+ tryCompare(appIcon5, "wiggling", false, 1000, "appId #1 should not be wiggling")
823+ }
824+
825+ function test_alertViaCountAndCountVisible() {
826+ dragLauncherIntoView();
827+ var appIcon1 = findChild(launcher, "launcherDelegate1")
828+ var oldCount = LauncherModel.get(1).count
829+ LauncherModel.setCount(LauncherModel.get(1).appId, 42)
830+ tryCompare(appIcon1, "wiggling", false, 1000, "appId #1 should be still")
831+ LauncherModel.setCountVisible(LauncherModel.get(1).appId, 1)
832+ tryCompare(appIcon1, "wiggling", true, 1000, "appId #1 should not be still")
833+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
834+ waitForWiggleToStop(appIcon1)
835+ LauncherModel.setCount(LauncherModel.get(1).appId, 4711)
836+ tryCompare(appIcon1, "wiggling", true, 1000, "appId #1 should not be still")
837+ LauncherModel.setAlerting(LauncherModel.get(1).appId, false)
838+ waitForWiggleToStop(appIcon1)
839+ LauncherModel.setCountVisible(LauncherModel.get(1).appId, 0)
840+ LauncherModel.setCount(LauncherModel.get(1).appId, oldCount)
841+ }
661 }
842 }
662}
843}
tests/scripts/READMEnew file+9
@@ -0,0 +1,9 @@
1+This directory holds a few handy shell-scripts to simplify runtime-testing of DBus-APIs (requires qdbus-qt5 to be installed).
2+
3+launcher-icon related:
4+ * list-launcher-icons.sh - get all icons of unity8-launcher
5+ * alert-launcher-icon.sh - trigger alert/wiggle of unfocused launcher-icon
6+ * get-progress.sh - get progress-value of a launcher-icon
7+ * set-progress.sh - set progress-value of a launcher-icon
8+ * set-count.sh - set the count of a launcher-icon
9+ * set-count-visible.sh - set the count-visible flag of a launcher-icon
tests/scripts/alert-launcher-icon.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity.Launcher /com/canonical/Unity/Launcher/$1 com.canonical.Unity.Launcher.Item.Alert
tests/scripts/get-progress.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity.Launcher /com/canonical/Unity/Launcher/$1 org.freedesktop.DBus.Properties.Get com.canonical.Unity.Launcher.Item progress
tests/scripts/list-launcher-icons.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity | grep Launcher | cut -f6 -d/
tests/scripts/set-count-visible.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity.Launcher /com/canonical/Unity/Launcher/$1 org.freedesktop.DBus.Properties.Set com.canonical.Unity.Launcher.Item countVisible $2
tests/scripts/set-count.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity.Launcher /com/canonical/Unity/Launcher/$1 org.freedesktop.DBus.Properties.Set com.canonical.Unity.Launcher.Item count $2
tests/scripts/set-progress.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+qdbus com.canonical.Unity.Launcher /com/canonical/Unity/Launcher/$1 org.freedesktop.DBus.Properties.Set com.canonical.Unity.Launcher.Item progress $2