on 8a62abb help sign in
unity8/plugins/Unity/Launcher/launchermodel.cpp
447 lines 14 kB history raw
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 */
20#include "launchermodel.h"
21#include "launcheritem.h"
22#include "gsettings.h"
23#include "desktopfilehandler.h"
24#include "dbusinterface.h"
26#include <unity/shell/application/ApplicationInfoInterface.h>
28#include <QDebug>
30using namespace unity::shell::application;
32LauncherModel::LauncherModel(QObject *parent):
33 LauncherModelInterface(parent),
34 m_settings(new GSettings(this)),
35 m_dbusIface(new DBusInterface(this)),
36 m_appManager(0)
38 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
39 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
40 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
42 Q_FOREACH (const QString &entry, m_settings->storedApplications()) {
43 DesktopFileHandler desktopFile(entry);
44 if (!desktopFile.isValid()) {
45 qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
46 continue;
47 }
49 LauncherItem *item = new LauncherItem(entry,
50 desktopFile.displayName(),
51 desktopFile.icon(),
52 this);
53 item->setPinned(true);
54 m_list.append(item);
55 }
58LauncherModel::~LauncherModel()
60 while (!m_list.empty()) {
61 m_list.takeFirst()->deleteLater();
62 }
65int LauncherModel::rowCount(const QModelIndex &parent) const
67 Q_UNUSED(parent)
68 return m_list.count();
71QVariant LauncherModel::data(const QModelIndex &index, int role) const
73 LauncherItem *item = m_list.at(index.row());
74 switch(role) {
75 case RoleAppId:
76 return item->appId();
77 case RoleName:
78 return item->name();
79 case RoleIcon:
80 return item->icon();
81 case RolePinned:
82 return item->pinned();
83 case RoleCount:
84 return item->count();
85 case RoleCountVisible:
86 return item->countVisible();
87 case RoleProgress:
88 return item->progress();
89 case RoleFocused:
90 return item->focused();
91 }
93 return QVariant();
96unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
98 if (index < 0 || index >= m_list.count()) {
99 return 0;
100 }
101 return m_list.at(index);
104void LauncherModel::move(int oldIndex, int newIndex)
106 // Make sure its not moved outside the lists
107 if (newIndex < 0) {
108 newIndex = 0;
109 }
110 if (newIndex >= m_list.count()) {
111 newIndex = m_list.count()-1;
112 }
114 // Nothing to do?
115 if (oldIndex == newIndex) {
116 return;
117 }
119 // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
120 // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
121 // adjust the model's index by +1 in case we're moving upwards
122 int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
124 beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
125 m_list.move(oldIndex, newIndex);
126 endMoveRows();
128 if (!m_list.at(newIndex)->pinned()) {
129 pin(m_list.at(newIndex)->appId());
130 } else {
131 storeAppList();
132 }
135void LauncherModel::pin(const QString &appId, int index)
137 int currentIndex = findApplication(appId);
139 if (currentIndex >= 0) {
140 if (index == -1 || index == currentIndex) {
141 m_list.at(currentIndex)->setPinned(true);
142 QModelIndex modelIndex = this->index(currentIndex);
143 Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
144 } else {
145 move(currentIndex, index);
146 // move() will store the list to the backend itself, so just exit at this point.
147 return;
148 }
149 } else {
150 if (index == -1) {
151 index = m_list.count();
152 }
154 DesktopFileHandler desktopFile(appId);
155 if (!desktopFile.isValid()) {
156 qWarning() << "Can't pin this application, there is no .destkop file available.";
157 return;
158 }
160 beginInsertRows(QModelIndex(), index, index);
161 LauncherItem *item = new LauncherItem(appId,
162 desktopFile.displayName(),
163 desktopFile.icon(),
164 this);
165 item->setPinned(true);
166 m_list.insert(index, item);
167 endInsertRows();
168 }
170 storeAppList();
173void LauncherModel::requestRemove(const QString &appId)
175 int index = findApplication(appId);
176 if (index < 0) {
177 return;
178 }
180 if (m_appManager->findApplication(appId)) {
181 m_list.at(index)->setPinned(false);
182 QModelIndex modelIndex = this->index(index);
183 Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
184 return;
185 }
187 beginRemoveRows(QModelIndex(), index, index);
188 m_list.takeAt(index)->deleteLater();
189 endRemoveRows();
191 storeAppList();
194void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
196 int index = findApplication(appId);
197 if (index < 0) {
198 return;
199 }
201 LauncherItem *item = m_list.at(index);
202 QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
203 if (model) {
204 QString actionId = model->get(actionIndex).actionId();
206 // Check if this is one of the launcher actions we handle ourselves
207 if (actionId == "pin_item") {
208 if (item->pinned()) {
209 requestRemove(appId);
210 } else {
211 pin(appId);
212 }
214 // Nope, we don't know this action, let the backend forward it to the application
215 } else {
216 // TODO: forward quicklist action to app, possibly via m_dbusIface
217 }
218 }
221void LauncherModel::setUser(const QString &username)
223 Q_UNUSED(username)
224 qWarning() << "This backend doesn't support multiple users";
227QString LauncherModel::getUrlForAppId(const QString &appId) const
229 // appId is either an appId or a legacy app name. Let's find out which
230 if (appId.isEmpty()) {
231 return QString();
232 }
234 if (!appId.contains("_")) {
235 return "application:///" + appId + ".desktop";
236 }
238 QStringList parts = appId.split('_');
239 QString package = parts.value(0);
240 QString app = parts.value(1, "first-listed-app");
241 return "appid://" + package + "/" + app + "/current-user-version";
244ApplicationManagerInterface *LauncherModel::applicationManager() const
246 return m_appManager;
249void LauncherModel::setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager)
251 // Is there already another appmanager set?
252 if (m_appManager) {
253 // Disconnect any signals
254 disconnect(this, SLOT(applicationAdded(QModelIndex,int)));
255 disconnect(this, SLOT(applicationRemoved(QModelIndex,int)));
256 disconnect(this, SLOT(focusedAppIdChanged()));
258 // remove any recent/running apps from the launcher
259 QList<int> recentAppIndices;
260 for (int i = 0; i < m_list.count(); ++i) {
261 if (m_list.at(i)->recent()) {
262 recentAppIndices << i;
263 }
264 }
265 int run = 0;
266 while (recentAppIndices.count() > 0) {
267 beginRemoveRows(QModelIndex(), recentAppIndices.first() - run, recentAppIndices.first() - run);
268 m_list.takeAt(recentAppIndices.first() - run)->deleteLater();
269 endRemoveRows();
270 recentAppIndices.takeFirst();
271 ++run;
272 }
273 }
275 m_appManager = appManager;
276 connect(m_appManager, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(applicationAdded(QModelIndex,int)));
277 connect(m_appManager, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(applicationRemoved(QModelIndex,int)));
278 connect(m_appManager, SIGNAL(focusedApplicationIdChanged()), SLOT(focusedAppIdChanged()));
280 Q_EMIT applicationManagerChanged();
282 for (int i = 0; i < appManager->count(); ++i) {
283 applicationAdded(QModelIndex(), i);
284 }
288void LauncherModel::storeAppList()
290 QStringList appIds;
291 Q_FOREACH(LauncherItem *item, m_list) {
292 if (item->pinned()) {
293 appIds << item->appId();
294 }
295 }
296 m_settings->setStoredApplications(appIds);
299int LauncherModel::findApplication(const QString &appId)
301 for (int i = 0; i < m_list.count(); ++i) {
302 LauncherItem *item = m_list.at(i);
303 if (item->appId() == appId) {
304 return i;
305 }
306 }
307 return -1;
310void LauncherModel::progressChanged(const QString &appId, int progress)
312 int idx = findApplication(appId);
313 if (idx >= 0) {
314 LauncherItem *item = m_list.at(idx);
315 item->setProgress(progress);
316 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
317 }
321void LauncherModel::countChanged(const QString &appId, int count)
323 int idx = findApplication(appId);
324 if (idx >= 0) {
325 LauncherItem *item = m_list.at(idx);
326 item->setCount(count);
327 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
328 }
331void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
333 int idx = findApplication(appId);
334 if (idx >= 0) {
335 LauncherItem *item = m_list.at(idx);
336 item->setCountVisible(countVisible);
337 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
339 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
340 if (!countVisible && !item->pinned() && !item->recent()) {
341 beginRemoveRows(QModelIndex(), idx, idx);
342 m_list.takeAt(idx)->deleteLater();
343 endRemoveRows();
344 }
345 } else {
346 // Need to create a new LauncherItem and show the highlight
347 DesktopFileHandler desktopFile(appId);
348 if (countVisible && desktopFile.isValid()) {
349 LauncherItem *item = new LauncherItem(appId,
350 desktopFile.displayName(),
351 desktopFile.icon());
352 item->setCountVisible(true);
353 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
354 m_list.append(item);
355 endInsertRows();
356 }
357 }
360void LauncherModel::refresh()
362 QList<LauncherItem*> toBeRemoved;
363 Q_FOREACH (LauncherItem* item, m_list) {
364 DesktopFileHandler desktopFile(item->appId());
365 if (!desktopFile.isValid()) {
366 toBeRemoved << item;
367 } else {
368 int idx = m_list.indexOf(item);
369 item->setName(desktopFile.displayName());
370 item->setIcon(desktopFile.icon());
371 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
372 }
373 }
375 Q_FOREACH (LauncherItem* item, toBeRemoved) {
376 requestRemove(item->appId());
377 }
380void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
382 Q_UNUSED(parent);
384 ApplicationInfoInterface *app = m_appManager->get(row);
385 if (!app) {
386 qWarning() << "LauncherModel received an applicationAdded signal, but there's no such application!";
387 return;
388 }
390 if (app->appId() == "unity8-dash") {
391 // Not adding the dash app
392 return;
393 }
395 bool found = false;
396 Q_FOREACH(LauncherItem *item, m_list) {
397 if (app->appId() == item->appId()) {
398 found = true;
399 break;
400 }
401 }
402 if (found) {
403 // Shall we paint some running/recent app highlight? If yes, do it here.
404 } else {
405 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
406 item->setRecent(true);
407 item->setFocused(app->focused());
409 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
410 m_list.append(item);
411 endInsertRows();
412 }
415void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
417 Q_UNUSED(parent)
419 int appIndex = -1;
420 for (int i = 0; i < m_list.count(); ++i) {
421 if (m_list.at(i)->appId() == m_appManager->get(row)->appId()) {
422 appIndex = i;
423 break;
424 }
425 }
427 if (appIndex > -1 && !m_list.at(appIndex)->pinned()) {
428 beginRemoveRows(QModelIndex(), appIndex, appIndex);
429 m_list.takeAt(appIndex)->deleteLater();
430 endRemoveRows();
431 }
434void LauncherModel::focusedAppIdChanged()
436 QString appId = m_appManager->focusedApplicationId();
437 for (int i = 0; i < m_list.count(); ++i) {
438 LauncherItem *item = m_list.at(i);
439 if (!item->focused() && item->appId() == appId) {
440 item->setFocused(true);
441 Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
442 } else if (item->focused() && item->appId() != appId) {
443 item->setFocused(false);
444 Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
445 }
446 }