Unity 8
 All Classes Functions
launchermodel.cpp
1 /*
2  * Copyright 2013 Canonical Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authors:
17  * Michael Zanetti <michael.zanetti@canonical.com>
18  */
19 
20 #include "launchermodel.h"
21 #include "launcheritem.h"
22 #include "gsettings.h"
23 #include "desktopfilehandler.h"
24 #include "dbusinterface.h"
25 
26 #include <unity/shell/application/ApplicationInfoInterface.h>
27 
28 #include <QDesktopServices>
29 #include <QDebug>
30 
31 using namespace unity::shell::application;
32 
33 LauncherModel::LauncherModel(QObject *parent):
34  LauncherModelInterface(parent),
35  m_settings(new GSettings(this)),
36  m_dbusIface(new DBusInterface(this)),
37  m_appManager(0)
38 {
39  connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
40  connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
41  connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
42 
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  }
49 
50  LauncherItem *item = new LauncherItem(entry,
51  desktopFile.displayName(),
52  desktopFile.icon(),
53  this);
54  item->setPinned(true);
55  m_list.append(item);
56  }
57 }
58 
59 LauncherModel::~LauncherModel()
60 {
61  while (!m_list.empty()) {
62  m_list.takeFirst()->deleteLater();
63  }
64 }
65 
66 int LauncherModel::rowCount(const QModelIndex &parent) const
67 {
68  Q_UNUSED(parent)
69  return m_list.count();
70 }
71 
72 QVariant LauncherModel::data(const QModelIndex &index, int role) const
73 {
74  LauncherItem *item = m_list.at(index.row());
75  switch(role) {
76  case RoleAppId:
77  return item->appId();
78  case RoleName:
79  return item->name();
80  case RoleIcon:
81  return item->icon();
82  case RolePinned:
83  return item->pinned();
84  case RoleCount:
85  return item->count();
86  case RoleCountVisible:
87  return item->countVisible();
88  case RoleProgress:
89  return item->progress();
90  case RoleFocused:
91  return item->focused();
92  }
93 
94  return QVariant();
95 }
96 
97 unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
98 {
99  if (index < 0 || index >= m_list.count()) {
100  return 0;
101  }
102  return m_list.at(index);
103 }
104 
105 void LauncherModel::move(int oldIndex, int newIndex)
106 {
107  // Make sure its not moved outside the lists
108  if (newIndex < 0) {
109  newIndex = 0;
110  }
111  if (newIndex >= m_list.count()) {
112  newIndex = m_list.count()-1;
113  }
114 
115  // Nothing to do?
116  if (oldIndex == newIndex) {
117  return;
118  }
119 
120  // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
121  // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
122  // adjust the model's index by +1 in case we're moving upwards
123  int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
124 
125  beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
126  m_list.move(oldIndex, newIndex);
127  endMoveRows();
128 
129  if (!m_list.at(newIndex)->pinned()) {
130  pin(m_list.at(newIndex)->appId());
131  } else {
132  storeAppList();
133  }
134 }
135 
136 void LauncherModel::pin(const QString &appId, int index)
137 {
138  int currentIndex = findApplication(appId);
139 
140  if (currentIndex >= 0) {
141  if (index == -1 || index == currentIndex) {
142  m_list.at(currentIndex)->setPinned(true);
143  QModelIndex modelIndex = this->index(currentIndex);
144  Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
145  } else {
146  move(currentIndex, index);
147  // move() will store the list to the backend itself, so just exit at this point.
148  return;
149  }
150  } else {
151  if (index == -1) {
152  index = m_list.count();
153  }
154 
155  DesktopFileHandler desktopFile(appId);
156  if (!desktopFile.isValid()) {
157  qWarning() << "Can't pin this application, there is no .destkop file available.";
158  return;
159  }
160 
161  beginInsertRows(QModelIndex(), index, index);
162  LauncherItem *item = new LauncherItem(appId,
163  desktopFile.displayName(),
164  desktopFile.icon(),
165  this);
166  item->setPinned(true);
167  m_list.insert(index, item);
168  endInsertRows();
169  }
170 
171  storeAppList();
172 }
173 
174 void LauncherModel::requestRemove(const QString &appId)
175 {
176  int index = findApplication(appId);
177  if (index < 0) {
178  return;
179  }
180 
181  if (m_appManager->findApplication(appId)) {
182  m_list.at(index)->setPinned(false);
183  QModelIndex modelIndex = this->index(index);
184  Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
185  return;
186  }
187 
188  beginRemoveRows(QModelIndex(), index, index);
189  m_list.takeAt(index)->deleteLater();
190  endRemoveRows();
191 
192  storeAppList();
193 }
194 
195 void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
196 {
197  int index = findApplication(appId);
198  if (index < 0) {
199  return;
200  }
201 
202  LauncherItem *item = m_list.at(index);
203  QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
204  if (model) {
205  QString actionId = model->get(actionIndex).actionId();
206 
207  // Check if this is one of the launcher actions we handle ourselves
208  if (actionId == "pin_item") {
209  if (item->pinned()) {
210  requestRemove(appId);
211  } else {
212  pin(appId);
213  }
214  } else if (actionId == "launch_item") {
215  QDesktopServices::openUrl(getUrlForAppId(appId));
216 
217  // Nope, we don't know this action, let the backend forward it to the application
218  } else {
219  // TODO: forward quicklist action to app, possibly via m_dbusIface
220  }
221  }
222 }
223 
224 void LauncherModel::setUser(const QString &username)
225 {
226  Q_UNUSED(username)
227  qWarning() << "This backend doesn't support multiple users";
228 }
229 
230 QString LauncherModel::getUrlForAppId(const QString &appId) const
231 {
232  // appId is either an appId or a legacy app name. Let's find out which
233  if (appId.isEmpty()) {
234  return QString();
235  }
236 
237  if (!appId.contains("_")) {
238  return "application:///" + appId + ".desktop";
239  }
240 
241  QStringList parts = appId.split('_');
242  QString package = parts.value(0);
243  QString app = parts.value(1, "first-listed-app");
244  return "appid://" + package + "/" + app + "/current-user-version";
245 }
246 
247 ApplicationManagerInterface *LauncherModel::applicationManager() const
248 {
249  return m_appManager;
250 }
251 
252 void LauncherModel::setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager)
253 {
254  // Is there already another appmanager set?
255  if (m_appManager) {
256  // Disconnect any signals
257  disconnect(this, SLOT(applicationAdded(QModelIndex,int)));
258  disconnect(this, SLOT(applicationRemoved(QModelIndex,int)));
259  disconnect(this, SLOT(focusedAppIdChanged()));
260 
261  // remove any recent/running apps from the launcher
262  QList<int> recentAppIndices;
263  for (int i = 0; i < m_list.count(); ++i) {
264  if (m_list.at(i)->recent()) {
265  recentAppIndices << i;
266  }
267  }
268  int run = 0;
269  while (recentAppIndices.count() > 0) {
270  beginRemoveRows(QModelIndex(), recentAppIndices.first() - run, recentAppIndices.first() - run);
271  m_list.takeAt(recentAppIndices.first() - run)->deleteLater();
272  endRemoveRows();
273  recentAppIndices.takeFirst();
274  ++run;
275  }
276  }
277 
278  m_appManager = appManager;
279  connect(m_appManager, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(applicationAdded(QModelIndex,int)));
280  connect(m_appManager, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(applicationRemoved(QModelIndex,int)));
281  connect(m_appManager, SIGNAL(focusedApplicationIdChanged()), SLOT(focusedAppIdChanged()));
282 
283  Q_EMIT applicationManagerChanged();
284 
285  for (int i = 0; i < appManager->count(); ++i) {
286  applicationAdded(QModelIndex(), i);
287  }
288 }
289 
290 
291 void LauncherModel::storeAppList()
292 {
293  QStringList appIds;
294  Q_FOREACH(LauncherItem *item, m_list) {
295  if (item->pinned()) {
296  appIds << item->appId();
297  }
298  }
299  m_settings->setStoredApplications(appIds);
300 }
301 
302 int LauncherModel::findApplication(const QString &appId)
303 {
304  for (int i = 0; i < m_list.count(); ++i) {
305  LauncherItem *item = m_list.at(i);
306  if (item->appId() == appId) {
307  return i;
308  }
309  }
310  return -1;
311 }
312 
313 void LauncherModel::progressChanged(const QString &appId, int progress)
314 {
315  int idx = findApplication(appId);
316  if (idx >= 0) {
317  LauncherItem *item = m_list.at(idx);
318  item->setProgress(progress);
319  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
320  }
321 }
322 
323 
324 void LauncherModel::countChanged(const QString &appId, int count)
325 {
326  int idx = findApplication(appId);
327  if (idx >= 0) {
328  LauncherItem *item = m_list.at(idx);
329  item->setCount(count);
330  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
331  }
332 }
333 
334 void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
335 {
336  int idx = findApplication(appId);
337  if (idx >= 0) {
338  LauncherItem *item = m_list.at(idx);
339  item->setCountVisible(countVisible);
340  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
341 
342  // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
343  if (!countVisible && !item->pinned() && !item->recent()) {
344  beginRemoveRows(QModelIndex(), idx, idx);
345  m_list.takeAt(idx)->deleteLater();
346  endRemoveRows();
347  }
348  } else {
349  // Need to create a new LauncherItem and show the highlight
350  DesktopFileHandler desktopFile(appId);
351  if (countVisible && desktopFile.isValid()) {
352  LauncherItem *item = new LauncherItem(appId,
353  desktopFile.displayName(),
354  desktopFile.icon());
355  item->setCountVisible(true);
356  beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
357  m_list.append(item);
358  endInsertRows();
359  }
360  }
361 }
362 
363 void LauncherModel::refresh()
364 {
365  QList<LauncherItem*> toBeRemoved;
366  Q_FOREACH (LauncherItem* item, m_list) {
367  DesktopFileHandler desktopFile(item->appId());
368  if (!desktopFile.isValid()) {
369  toBeRemoved << item;
370  } else {
371  int idx = m_list.indexOf(item);
372  item->setName(desktopFile.displayName());
373  item->setIcon(desktopFile.icon());
374  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
375  }
376  }
377 
378  Q_FOREACH (LauncherItem* item, toBeRemoved) {
379  requestRemove(item->appId());
380  }
381 }
382 
383 void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
384 {
385  Q_UNUSED(parent);
386 
387  ApplicationInfoInterface *app = m_appManager->get(row);
388  if (!app) {
389  qWarning() << "LauncherModel received an applicationAdded signal, but there's no such application!";
390  return;
391  }
392 
393  if (app->appId() == "unity8-dash") {
394  // Not adding the dash app
395  return;
396  }
397 
398  bool found = false;
399  Q_FOREACH(LauncherItem *item, m_list) {
400  if (app->appId() == item->appId()) {
401  found = true;
402  break;
403  }
404  }
405  if (found) {
406  // Shall we paint some running/recent app highlight? If yes, do it here.
407  } else {
408  LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
409  item->setRecent(true);
410  item->setFocused(app->focused());
411 
412  beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
413  m_list.append(item);
414  endInsertRows();
415  }
416 }
417 
418 void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
419 {
420  Q_UNUSED(parent)
421 
422  int appIndex = -1;
423  for (int i = 0; i < m_list.count(); ++i) {
424  if (m_list.at(i)->appId() == m_appManager->get(row)->appId()) {
425  appIndex = i;
426  break;
427  }
428  }
429 
430  if (appIndex > -1 && !m_list.at(appIndex)->pinned()) {
431  beginRemoveRows(QModelIndex(), appIndex, appIndex);
432  m_list.takeAt(appIndex)->deleteLater();
433  endRemoveRows();
434  }
435 }
436 
437 void LauncherModel::focusedAppIdChanged()
438 {
439  QString appId = m_appManager->focusedApplicationId();
440  for (int i = 0; i < m_list.count(); ++i) {
441  LauncherItem *item = m_list.at(i);
442  if (!item->focused() && item->appId() == appId) {
443  item->setFocused(true);
444  Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
445  } else if (item->focused() && item->appId() != appId) {
446  item->setFocused(false);
447  Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
448  }
449  }
450 }