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 "backend/launcherbackend.h"
23 
24 #include <unity/shell/application/ApplicationInfoInterface.h>
25 
26 #include <QDesktopServices>
27 #include <QDebug>
28 
29 using namespace unity::shell::application;
30 
31 LauncherModel::LauncherModel(QObject *parent):
32  LauncherModelInterface(parent),
33  m_backend(new LauncherBackend(this)),
34  m_appManager(0)
35 {
36  connect(m_backend, SIGNAL(countChanged(QString,int)), SLOT(countChanged(QString,int)));
37  connect(m_backend, SIGNAL(progressChanged(QString,int)), SLOT(progressChanged(QString,int)));
38 
39  Q_FOREACH (const QString &entry, m_backend->storedApplications()) {
40  LauncherItem *item = new LauncherItem(entry,
41  m_backend->displayName(entry),
42  m_backend->icon(entry),
43  this);
44  item->setPinned(true);
45  m_list.append(item);
46  }
47 }
48 
49 LauncherModel::~LauncherModel()
50 {
51  while (!m_list.empty()) {
52  m_list.takeFirst()->deleteLater();
53  }
54 }
55 
56 int LauncherModel::rowCount(const QModelIndex &parent) const
57 {
58  Q_UNUSED(parent)
59  return m_list.count();
60 }
61 
62 QVariant LauncherModel::data(const QModelIndex &index, int role) const
63 {
64  LauncherItem *item = m_list.at(index.row());
65  switch(role) {
66  case RoleAppId:
67  return item->appId();
68  case RoleName:
69  return item->name();
70  case RoleIcon:
71  return item->icon();
72  case RolePinned:
73  return item->pinned();
74  case RoleCount:
75  return item->count();
76  case RoleProgress:
77  return item->progress();
78  case RoleFocused:
79  return item->focused();
80  }
81 
82  return QVariant();
83 }
84 
85 unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
86 {
87  if (index < 0 || index >= m_list.count()) {
88  return 0;
89  }
90  return m_list.at(index);
91 }
92 
93 void LauncherModel::move(int oldIndex, int newIndex)
94 {
95  // Make sure its not moved outside the lists
96  if (newIndex < 0) {
97  newIndex = 0;
98  }
99  if (newIndex >= m_list.count()) {
100  newIndex = m_list.count()-1;
101  }
102 
103  // Nothing to do?
104  if (oldIndex == newIndex) {
105  return;
106  }
107 
108  // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
109  // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
110  // adjust the model's index by +1 in case we're moving upwards
111  int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
112 
113  beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
114  m_list.move(oldIndex, newIndex);
115  endMoveRows();
116 
117  if (!m_list.at(newIndex)->pinned()) {
118  pin(m_list.at(newIndex)->appId());
119  } else {
120  storeAppList();
121  }
122 }
123 
124 void LauncherModel::pin(const QString &appId, int index)
125 {
126  int currentIndex = findApplication(appId);
127 
128  if (currentIndex >= 0) {
129  if (index == -1 || index == currentIndex) {
130  m_list.at(currentIndex)->setPinned(true);
131  QModelIndex modelIndex = this->index(currentIndex);
132  Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
133  } else {
134  move(currentIndex, index);
135  // move() will store the list to the backend itself, so just exit at this point.
136  return;
137  }
138  } else {
139  if (index == -1) {
140  index = m_list.count();
141  }
142  beginInsertRows(QModelIndex(), index, index);
143  LauncherItem *item = new LauncherItem(appId,
144  m_backend->displayName(appId),
145  m_backend->icon(appId),
146  this);
147  item->setPinned(true);
148  m_list.insert(index, item);
149  endInsertRows();
150  }
151 
152  storeAppList();
153 }
154 
155 void LauncherModel::requestRemove(const QString &appId)
156 {
157  int index = findApplication(appId);
158  if (index < 0) {
159  return;
160  }
161 
162  if (m_appManager->findApplication(appId)) {
163  m_list.at(index)->setPinned(false);
164  QModelIndex modelIndex = this->index(index);
165  Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
166  return;
167  }
168 
169  beginRemoveRows(QModelIndex(), index, index);
170  m_list.takeAt(index)->deleteLater();
171  endRemoveRows();
172 
173  storeAppList();
174 }
175 
176 void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
177 {
178  int index = findApplication(appId);
179  if (index < 0) {
180  return;
181  }
182 
183  LauncherItem *item = m_list.at(index);
184  QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
185  if (model) {
186  QString actionId = model->get(actionIndex).actionId();
187 
188  // Check if this is one of the launcher actions we handle ourselves
189  if (actionId == "pin_item") {
190  if (item->pinned()) {
191  requestRemove(appId);
192  } else {
193  pin(appId);
194  }
195  } else if (actionId == "launch_item") {
196  QDesktopServices::openUrl(getUrlForAppId(appId));
197 
198  // Nope, we don't know this action, let the backend forward it to the application
199  } else {
200  m_backend->triggerQuickListAction(appId, actionId);
201  }
202  }
203 }
204 
205 void LauncherModel::setUser(const QString &username)
206 {
207  m_backend->setUser(username);
208 }
209 
210 QString LauncherModel::getUrlForAppId(const QString &appId) const
211 {
212  // appId is either an appId or a legacy app name. Let's find out which
213  if (appId.isEmpty())
214  return QString();
215 
216  QString df = m_backend->desktopFile(appId + ".desktop");
217  if (!df.isEmpty())
218  return "application:///" + appId + ".desktop";
219 
220  QStringList parts = appId.split('_');
221  QString package = parts.value(0);
222  QString app = parts.value(1, "first-listed-app");
223  return "appid://" + package + "/" + app + "/current-user-version";
224 }
225 
226 ApplicationManagerInterface *LauncherModel::applicationManager() const
227 {
228  return m_appManager;
229 }
230 
231 void LauncherModel::setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager)
232 {
233  // Is there already another appmanager set?
234  if (m_appManager) {
235  // Disconnect any signals
236  disconnect(this, SLOT(applicationAdded(QModelIndex,int)));
237  disconnect(this, SLOT(applicationRemoved(QModelIndex,int)));
238  disconnect(this, SLOT(focusedAppIdChanged()));
239 
240  // remove any recent/running apps from the launcher
241  QList<int> recentAppIndices;
242  for (int i = 0; i < m_list.count(); ++i) {
243  if (m_list.at(i)->recent()) {
244  recentAppIndices << i;
245  }
246  }
247  int run = 0;
248  while (recentAppIndices.count() > 0) {
249  beginRemoveRows(QModelIndex(), recentAppIndices.first() - run, recentAppIndices.first() - run);
250  m_list.takeAt(recentAppIndices.first() - run)->deleteLater();
251  endRemoveRows();
252  recentAppIndices.takeFirst();
253  ++run;
254  }
255  }
256 
257  m_appManager = appManager;
258  connect(m_appManager, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(applicationAdded(QModelIndex,int)));
259  connect(m_appManager, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(applicationRemoved(QModelIndex,int)));
260  connect(m_appManager, SIGNAL(focusedApplicationIdChanged()), SLOT(focusedAppIdChanged()));
261 
262  Q_EMIT applicationManagerChanged();
263 
264  for (int i = 0; i < appManager->count(); ++i) {
265  applicationAdded(QModelIndex(), i);
266  }
267 }
268 
269 
270 void LauncherModel::storeAppList()
271 {
272  QStringList appIds;
273  Q_FOREACH(LauncherItem *item, m_list) {
274  if (item->pinned()) {
275  appIds << item->appId();
276  }
277  }
278  m_backend->setStoredApplications(appIds);
279 }
280 
281 int LauncherModel::findApplication(const QString &appId)
282 {
283  for (int i = 0; i < m_list.count(); ++i) {
284  LauncherItem *item = m_list.at(i);
285  if (item->appId() == appId) {
286  return i;
287  }
288  }
289  return -1;
290 }
291 
292 void LauncherModel::progressChanged(const QString &appId, int progress)
293 {
294  int idx = findApplication(appId);
295  if (idx >= 0) {
296  LauncherItem *item = m_list.at(idx);
297  item->setProgress(progress);
298  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
299  }
300 }
301 
302 
303 void LauncherModel::countChanged(const QString &appId, int count)
304 {
305  int idx = findApplication(appId);
306  if (idx >= 0) {
307  LauncherItem *item = m_list.at(idx);
308  item->setCount(count);
309  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
310  }
311 }
312 
313 void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
314 {
315  Q_UNUSED(parent);
316 
317  ApplicationInfoInterface *app = m_appManager->get(row);
318  if (!app) {
319  qWarning() << "LauncherModel received an applicationAdded signal, but there's no such application!";
320  return;
321  }
322 
323  if (app->appId() == "unity8-dash") {
324  // Not adding the dash app
325  return;
326  }
327 
328  bool found = false;
329  Q_FOREACH(LauncherItem *item, m_list) {
330  if (app->appId() == item->appId()) {
331  found = true;
332  break;
333  }
334  }
335  if (found) {
336  // Shall we paint some running/recent app highlight? If yes, do it here.
337  } else {
338  LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
339  item->setRecent(true);
340  item->setFocused(app->focused());
341 
342  beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
343  m_list.append(item);
344  endInsertRows();
345  }
346 }
347 
348 void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
349 {
350  Q_UNUSED(parent)
351 
352  int appIndex = -1;
353  for (int i = 0; i < m_list.count(); ++i) {
354  if (m_list.at(i)->appId() == m_appManager->get(row)->appId()) {
355  appIndex = i;
356  break;
357  }
358  }
359 
360  if (appIndex > -1 && !m_list.at(appIndex)->pinned()) {
361  beginRemoveRows(QModelIndex(), appIndex, appIndex);
362  m_list.takeAt(appIndex)->deleteLater();
363  endRemoveRows();
364  }
365 }
366 
367 void LauncherModel::focusedAppIdChanged()
368 {
369  QString appId = m_appManager->focusedApplicationId();
370  for (int i = 0; i < m_list.count(); ++i) {
371  LauncherItem *item = m_list.at(i);
372  if (!item->focused() && item->appId() == appId) {
373  item->setFocused(true);
374  Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
375  } else if (item->focused() && item->appId() != appId) {
376  item->setFocused(false);
377  Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
378  }
379  }
380 }