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