Lomiri
Loading...
Searching...
No Matches
appdrawermodel.cpp
1/*
2 * Copyright (C) 2016 Canonical Ltd.
3 * Copyright (C) 2020 UBports Foundation
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "appdrawermodel.h"
19#include "ualwrapper.h"
20#include "xdgwatcher.h"
21#include "iconcachewatcher.h"
22
23#include <QDebug>
24#include <QDateTime>
25#include <QtConcurrentRun>
26
27static std::shared_ptr<LauncherItem> makeSharedLauncherItem(
28 const QString &appId, const QString &name, const QString &icon, QObject * parent)
29{
30 return std::shared_ptr<LauncherItem>(
31 new LauncherItem(appId, name, icon, parent),
32 [] (LauncherItem *item) { item->deleteLater(); });
33}
34
35AppDrawerModel::AppDrawerModel(QObject *parent):
36 AppDrawerModelInterface(parent),
37 m_ual(new UalWrapper(this)),
38 m_xdgWatcher(new XdgWatcher(this)),
39 m_iconCacheWatcher(new IconCacheWatcher(this)),
40 m_refreshing(false)
41{
42 connect(&m_refreshFutureWatcher, &QFutureWatcher<ItemList>::finished,
43 this, &AppDrawerModel::onRefreshFinished);
44
45 // keep this a queued connection as it's coming from another thread.
46 connect(m_xdgWatcher, &XdgWatcher::appAdded, this, &AppDrawerModel::appAdded, Qt::QueuedConnection);
47 connect(m_xdgWatcher, &XdgWatcher::appRemoved, this, &AppDrawerModel::appRemoved, Qt::QueuedConnection);
48 connect(m_xdgWatcher, &XdgWatcher::appInfoChanged, this, &AppDrawerModel::appInfoChanged, Qt::QueuedConnection);
49
50 // Refresh app icons when icon cache changes
51 connect(m_iconCacheWatcher, &IconCacheWatcher::iconCacheChanged, this, &AppDrawerModel::refresh, Qt::QueuedConnection);
52
53 refresh();
54}
55
56int AppDrawerModel::rowCount(const QModelIndex &parent) const
57{
58 Q_UNUSED(parent)
59 return m_list.count();
60}
61
62QVariant AppDrawerModel::data(const QModelIndex &index, int role) const
63{
64 switch (role) {
65 case RoleAppId:
66 return m_list.at(index.row())->appId();
67 case RoleName:
68 return m_list.at(index.row())->name();
69 case RoleIcon:
70 return m_list.at(index.row())->icon();
71 case RoleKeywords:
72 return m_list.at(index.row())->keywords();
73 case RoleUsage:
74 return m_list.at(index.row())->popularity();
75 }
76
77 return QVariant();
78}
79
80void AppDrawerModel::appAdded(const QString &appId)
81{
82 if (m_refreshing)
83 // Will be replaced by the refresh result anyway.
84 return;
85
86 UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
87 if (!info.valid) {
88 qWarning() << "App added signal received but failed to get app info for app" << appId;
89 return;
90 }
91
92 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
93 auto item = makeSharedLauncherItem(appId, info.name, info.icon, /* parent */ nullptr);
94 item->setKeywords(info.keywords);
95 item->setPopularity(info.popularity);
96 m_list.append(std::move(item));
97 endInsertRows();
98}
99
100void AppDrawerModel::appRemoved(const QString &appId)
101{
102 if (m_refreshing)
103 // Will be replaced by the refresh result anyway.
104 return;
105
106 int idx = -1;
107 for (int i = 0; i < m_list.count(); i++) {
108 if (m_list.at(i)->appId() == appId) {
109 idx = i;
110 break;
111 }
112 }
113 if (idx < 0) {
114 qWarning() << "App removed signal received but app doesn't seem to be in the drawer model";
115 return;
116 }
117 beginRemoveRows(QModelIndex(), idx, idx);
118 m_list.removeAt(idx);
119 endRemoveRows();
120}
121
122void AppDrawerModel::appInfoChanged(const QString &appId)
123{
124 if (m_refreshing)
125 // Will be replaced by the refresh result anyway.
126 return;
127
128 std::shared_ptr<LauncherItem> item;
129 int idx = -1;
130
131 for(int i = 0; i < m_list.count(); i++) {
132 if (m_list.at(i)->appId() == appId) {
133 item = m_list.at(i);
134 idx = i;
135 break;
136 }
137 }
138
139 if (!item) {
140 return;
141 }
142
143 UalWrapper::AppInfo info = m_ual->getApplicationInfo(appId);
144 item->setPopularity(info.popularity);
145 Q_EMIT dataChanged(index(idx), index(idx), {AppDrawerModelInterface::RoleUsage});
146}
147
148bool AppDrawerModel::refreshing() {
149 return m_refreshing;
150}
151
152void AppDrawerModel::refresh() {
153 if (m_refreshing)
154 return;
155
156 m_refreshFutureWatcher.setFuture(QtConcurrent::run([](QThread *thread) {
157 ItemList list;
158
159 Q_FOREACH (const QString &appId, UalWrapper::installedApps()) {
160 UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
161 if (!info.valid) {
162 qWarning() << "Failed to get app info for app" << appId;
163 continue;
164 }
165 // We don't pass parent in because this may run after the model is destroyed.
166 // (And, in fact, we can't, because the model is in a diferent thread.)
167 auto item = makeSharedLauncherItem(appId, info.name, info.icon, /* parent */ nullptr);
168 item->setKeywords(info.keywords);
169 item->setPopularity(info.popularity);
170 item->moveToThread(thread);
171 list.append(std::move(item));
172 }
173
174 return list;
175 }, this->thread()));
176
177 m_refreshing = true;
178 Q_EMIT refreshingChanged();
179}
180
181void AppDrawerModel::onRefreshFinished() {
182 if (m_refreshFutureWatcher.isCanceled())
183 // This is the result of setting canceled future below.
184 return;
185
186 beginResetModel();
187
188 m_list = m_refreshFutureWatcher.result();
189 // Remove the future & its result, so that future modifications won't
190 // create a copy.
191 m_refreshFutureWatcher.setFuture(QFuture<ItemList>());
192
193 endResetModel();
194
195 m_refreshing = false;
196 Q_EMIT refreshingChanged();
197}