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
22#include <QDebug>
23#include <QDateTime>
24#include <QtConcurrentRun>
25
26static std::shared_ptr<LauncherItem> makeSharedLauncherItem(
27 const QString &appId, const QString &name, const QString &icon, QObject * parent)
28{
29 return std::shared_ptr<LauncherItem>(
30 new LauncherItem(appId, name, icon, parent),
31 [] (LauncherItem *item) { item->deleteLater(); });
32}
33
34AppDrawerModel::AppDrawerModel(QObject *parent):
35 AppDrawerModelInterface(parent),
36 m_ual(new UalWrapper(this)),
37 m_xdgWatcher(new XdgWatcher(this)),
38 m_refreshing(false)
39{
40 connect(&m_refreshFutureWatcher, &QFutureWatcher<ItemList>::finished,
41 this, &AppDrawerModel::onRefreshFinished);
42
43 // keep this a queued connection as it's coming from another thread.
44 connect(m_xdgWatcher, &XdgWatcher::appAdded, this, &AppDrawerModel::appAdded, Qt::QueuedConnection);
45 connect(m_xdgWatcher, &XdgWatcher::appRemoved, this, &AppDrawerModel::appRemoved, Qt::QueuedConnection);
46 connect(m_xdgWatcher, &XdgWatcher::appInfoChanged, this, &AppDrawerModel::appInfoChanged, Qt::QueuedConnection);
47
48 refresh();
49}
50
51int AppDrawerModel::rowCount(const QModelIndex &parent) const
52{
53 Q_UNUSED(parent)
54 return m_list.count();
55}
56
57QVariant AppDrawerModel::data(const QModelIndex &index, int role) const
58{
59 switch (role) {
60 case RoleAppId:
61 return m_list.at(index.row())->appId();
62 case RoleName:
63 return m_list.at(index.row())->name();
64 case RoleIcon:
65 return m_list.at(index.row())->icon();
66 case RoleKeywords:
67 return m_list.at(index.row())->keywords();
68 case RoleUsage:
69 return m_list.at(index.row())->popularity();
70 }
71
72 return QVariant();
73}
74
75void AppDrawerModel::appAdded(const QString &appId)
76{
77 if (m_refreshing)
78 // Will be replaced by the refresh result anyway.
79 return;
80
81 UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
82 if (!info.valid) {
83 qWarning() << "App added signal received but failed to get app info for app" << appId;
84 return;
85 }
86
87 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
88 auto item = makeSharedLauncherItem(appId, info.name, info.icon, /* parent */ nullptr);
89 item->setKeywords(info.keywords);
90 item->setPopularity(info.popularity);
91 m_list.append(std::move(item));
92 endInsertRows();
93}
94
95void AppDrawerModel::appRemoved(const QString &appId)
96{
97 if (m_refreshing)
98 // Will be replaced by the refresh result anyway.
99 return;
100
101 int idx = -1;
102 for (int i = 0; i < m_list.count(); i++) {
103 if (m_list.at(i)->appId() == appId) {
104 idx = i;
105 break;
106 }
107 }
108 if (idx < 0) {
109 qWarning() << "App removed signal received but app doesn't seem to be in the drawer model";
110 return;
111 }
112 beginRemoveRows(QModelIndex(), idx, idx);
113 m_list.removeAt(idx);
114 endRemoveRows();
115}
116
117void AppDrawerModel::appInfoChanged(const QString &appId)
118{
119 if (m_refreshing)
120 // Will be replaced by the refresh result anyway.
121 return;
122
123 std::shared_ptr<LauncherItem> item;
124 int idx = -1;
125
126 for(int i = 0; i < m_list.count(); i++) {
127 if (m_list.at(i)->appId() == appId) {
128 item = m_list.at(i);
129 idx = i;
130 break;
131 }
132 }
133
134 if (!item) {
135 return;
136 }
137
138 UalWrapper::AppInfo info = m_ual->getApplicationInfo(appId);
139 item->setPopularity(info.popularity);
140 Q_EMIT dataChanged(index(idx), index(idx), {AppDrawerModelInterface::RoleUsage});
141}
142
143bool AppDrawerModel::refreshing() {
144 return m_refreshing;
145}
146
147void AppDrawerModel::refresh() {
148 if (m_refreshing)
149 return;
150
151 m_refreshFutureWatcher.setFuture(QtConcurrent::run([](QThread *thread) {
152 ItemList list;
153
154 Q_FOREACH (const QString &appId, UalWrapper::installedApps()) {
155 UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
156 if (!info.valid) {
157 qWarning() << "Failed to get app info for app" << appId;
158 continue;
159 }
160 // We don't pass parent in because this may run after the model is destroyed.
161 // (And, in fact, we can't, because the model is in a diferent thread.)
162 auto item = makeSharedLauncherItem(appId, info.name, info.icon, /* parent */ nullptr);
163 item->setKeywords(info.keywords);
164 item->setPopularity(info.popularity);
165 item->moveToThread(thread);
166 list.append(std::move(item));
167 }
168
169 return list;
170 }, this->thread()));
171
172 m_refreshing = true;
173 Q_EMIT refreshingChanged();
174}
175
176void AppDrawerModel::onRefreshFinished() {
177 if (m_refreshFutureWatcher.isCanceled())
178 // This is the result of setting canceled future below.
179 return;
180
181 beginResetModel();
182
183 m_list = m_refreshFutureWatcher.result();
184 // Remove the future & its result, so that future modifications won't
185 // create a copy.
186 m_refreshFutureWatcher.setFuture(QFuture<ItemList>());
187
188 endResetModel();
189
190 m_refreshing = false;
191 Q_EMIT refreshingChanged();
192}