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, QVector<int>() << RolePinned);
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  QModelIndex modelIndex = this->index(index);
163  Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
164  return;
165  }
166 
167  beginRemoveRows(QModelIndex(), index, index);
168  m_list.takeAt(index)->deleteLater();
169  endRemoveRows();
170 
171  storeAppList();
172 }
173 
174 void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
175 {
176  int index = findApplication(appId);
177  if (index < 0) {
178  return;
179  }
180 
181  LauncherItem *item = m_list.at(index);
182  QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
183  if (model) {
184  QString actionId = model->get(actionIndex).actionId();
185 
186  // Check if this is one of the launcher actions we handle ourselves
187  if (actionId == "pin_item") {
188  if (item->pinned()) {
189  requestRemove(appId);
190  } else {
191  pin(appId);
192  }
193 
194  // Nope, we don't know this action, let the backend forward it to the application
195  } else {
196  m_backend->triggerQuickListAction(appId, actionId);
197  }
198  }
199 }
200 
201 void LauncherModel::setUser(const QString &username)
202 {
203  m_backend->setUser(username);
204 }
205 
206 QString LauncherModel::getUrlForAppId(const QString &appId) const
207 {
208  // appId is either an appId or a legacy app name. Let's find out which
209  if (appId.isEmpty())
210  return QString();
211 
212  QString df = m_backend->desktopFile(appId + ".desktop");
213  if (!df.isEmpty())
214  return "application:///" + appId + ".desktop";
215 
216  QStringList parts = appId.split('_');
217  QString package = parts.value(0);
218  QString app = parts.value(1, "first-listed-app");
219  return "appid://" + package + "/" + app + "/current-user-version";
220 }
221 
222 ApplicationManagerInterface *LauncherModel::applicationManager() const
223 {
224  return m_appManager;
225 }
226 
227 void LauncherModel::setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager)
228 {
229  // Is there already another appmanager set?
230  if (m_appManager) {
231  // Disconnect any signals
232  disconnect(this, SLOT(applicationAdded(QModelIndex,int)));
233  disconnect(this, SLOT(applicationRemoved(QModelIndex,int)));
234  disconnect(this, SLOT(focusedAppIdChanged()));
235 
236  // remove any recent/running apps from the launcher
237  QList<int> recentAppIndices;
238  for (int i = 0; i < m_list.count(); ++i) {
239  if (m_list.at(i)->recent()) {
240  recentAppIndices << i;
241  }
242  }
243  int run = 0;
244  while (recentAppIndices.count() > 0) {
245  beginRemoveRows(QModelIndex(), recentAppIndices.first() - run, recentAppIndices.first() - run);
246  m_list.takeAt(recentAppIndices.first() - run)->deleteLater();
247  endRemoveRows();
248  recentAppIndices.takeFirst();
249  ++run;
250  }
251  }
252 
253  m_appManager = appManager;
254  connect(m_appManager, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(applicationAdded(QModelIndex,int)));
255  connect(m_appManager, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(applicationRemoved(QModelIndex,int)));
256  connect(m_appManager, SIGNAL(focusedApplicationIdChanged()), SLOT(focusedAppIdChanged()));
257 
258  Q_EMIT applicationManagerChanged();
259 
260  for (int i = 0; i < appManager->count(); ++i) {
261  applicationAdded(QModelIndex(), i);
262  }
263 }
264 
265 
266 void LauncherModel::storeAppList()
267 {
268  QStringList appIds;
269  Q_FOREACH(LauncherItem *item, m_list) {
270  if (item->pinned()) {
271  appIds << item->appId();
272  }
273  }
274  m_backend->setStoredApplications(appIds);
275 }
276 
277 int LauncherModel::findApplication(const QString &appId)
278 {
279  for (int i = 0; i < m_list.count(); ++i) {
280  LauncherItem *item = m_list.at(i);
281  if (item->appId() == appId) {
282  return i;
283  }
284  }
285  return -1;
286 }
287 
288 void LauncherModel::progressChanged(const QString &appId, int progress)
289 {
290  int idx = findApplication(appId);
291  if (idx >= 0) {
292  LauncherItem *item = m_list.at(idx);
293  item->setProgress(progress);
294  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
295  }
296 }
297 
298 
299 void LauncherModel::countChanged(const QString &appId, int count)
300 {
301  int idx = findApplication(appId);
302  if (idx >= 0) {
303  LauncherItem *item = m_list.at(idx);
304  item->setCount(count);
305  Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
306  }
307 }
308 
309 void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
310 {
311  Q_UNUSED(parent);
312 
313  ApplicationInfoInterface *app = m_appManager->get(row);
314  if (!app) {
315  qWarning() << "LauncherModel received an applicationAdded signal, but there's no such application!";
316  return;
317  }
318 
319  bool found = false;
320  Q_FOREACH(LauncherItem *item, m_list) {
321  if (app->appId() == item->appId()) {
322  found = true;
323  break;
324  }
325  }
326  if (found) {
327  // Shall we paint some running/recent app highlight? If yes, do it here.
328  } else {
329  LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString());
330  item->setRecent(true);
331  item->setFocused(app->focused());
332 
333  beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
334  m_list.append(item);
335  endInsertRows();
336  }
337 }
338 
339 void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
340 {
341  Q_UNUSED(parent)
342 
343  int appIndex = -1;
344  for (int i = 0; i < m_list.count(); ++i) {
345  if (m_list.at(i)->appId() == m_appManager->get(row)->appId()) {
346  appIndex = i;
347  break;
348  }
349  }
350 
351  if (appIndex > -1 && !m_list.at(appIndex)->pinned()) {
352  beginRemoveRows(QModelIndex(), appIndex, appIndex);
353  m_list.takeAt(appIndex)->deleteLater();
354  endRemoveRows();
355  }
356 }
357 
358 void LauncherModel::focusedAppIdChanged()
359 {
360  QString appId = m_appManager->focusedApplicationId();
361  for (int i = 0; i < m_list.count(); ++i) {
362  LauncherItem *item = m_list.at(i);
363  if (!item->focused() && item->appId() == appId) {
364  item->setFocused(true);
365  Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
366  } else if (item->focused() && item->appId() != appId) {
367  item->setFocused(false);
368  Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
369  }
370  }
371 }