1 /*
2 * Cantata
3 *
4 * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5 *
6 * ----
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "currentcover.h"
25 #include "covers.h"
26 #include "config.h"
27 #include "widgets/icons.h"
28 #include "support/utils.h"
29 #include "support/globalstatic.h"
30 #include "online/onlineservice.h"
31 #include <QFile>
32 #include <QTimer>
33
34 #if !defined Q_OS_WIN && !defined Q_OS_MAC
themes(const QString & theme,QStringList & iconThemes)35 static void themes(const QString &theme, QStringList &iconThemes)
36 {
37 if (iconThemes.contains(theme)) {
38 return;
39 }
40 QString lower=theme.toLower();
41 iconThemes << theme;
42 if (lower!=theme) {
43 iconThemes << lower;
44 }
45 QStringList paths=QIcon::themeSearchPaths();
46 QString key("Inherits=");
47 for (const QString &p: paths) {
48 QString index(p+"/"+theme+"/index.theme");
49 QFile f(index);
50 if (f.open(QIODevice::ReadOnly|QIODevice::Text)) {
51 while (!f.atEnd()) {
52 QString line=QString::fromUtf8(f.readLine()).trimmed().simplified();
53 if (line.startsWith(key)) {
54 QStringList inherited=line.mid(key.length()).split(",", QString::SkipEmptyParts);
55 for (const QString &i: inherited) {
56 themes(i, iconThemes);
57 }
58 return;
59 }
60 }
61 }
62 }
63 }
64
initIconThemes()65 void CurrentCover::initIconThemes()
66 {
67 if (iconThemes.isEmpty()) {
68 themes(QIcon::themeName(), iconThemes);
69 }
70 }
71
findIcon(const QStringList & names)72 QString CurrentCover::findIcon(const QStringList &names)
73 {
74 initIconThemes();
75 QList<int> sizes=QList<int>() << 256 << 128 << 64 << 48 << 32 << 24 << 22;
76 QStringList paths=QIcon::themeSearchPaths();
77 QStringList sections=QStringList() << "apps" << "categories" << "devices" << "mimetypes" << "mimes";
78 for (const QString &theme: iconThemes) {
79 for (const QString &n: names) {
80 for (const QString &p: paths) {
81 QMap<int, QString> files;
82 for (int s: sizes) {
83 for (const QString §: sections) {
84 QString f(p+"/"+theme+"/"+QString::number(s)+"x"+QString::number(s)+"/"+sect+"/"+n+".png");
85 if (QFile::exists(f)) {
86 files.insert(s, f);
87 break;
88 }
89 f=QString(p+"/"+theme+"/"+QString::number(s)+"x"+QString::number(s)+"/"+sect+"/"+n+".svg");
90 if (QFile::exists(f)) {
91 files.insert(s, f);
92 break;
93 }
94 f=QString(p+"/"+theme+"/"+sect+"/"+QString::number(s)+"/"+n+".svg");
95 if (QFile::exists(f)) {
96 files.insert(s, f);
97 break;
98 }
99 }
100 }
101
102 if (!files.isEmpty()) {
103 for (int s: sizes) {
104 if (files.contains(s)) {
105 return files[s];
106 }
107 }
108 }
109 }
110 }
111 }
112 return QString();
113 }
114 #endif
115
GLOBAL_STATIC(CurrentCover,instance)116 GLOBAL_STATIC(CurrentCover, instance)
117
118 CurrentCover::CurrentCover()
119 : QObject(nullptr)
120 , enabled(false)
121 , valid(false)
122 , timer(nullptr)
123 {
124 img=stdImage(current);
125 coverFileName=noCoverFileName;
126 }
127
~CurrentCover()128 CurrentCover::~CurrentCover()
129 {
130 }
131
stdImage(const Song & s)132 const QImage & CurrentCover::stdImage(const Song &s)
133 {
134 bool podcast=s.isFromOnlineService() && OnlineService::isPodcasts(s.onlineService());
135 bool stream=s.isStandardStream() || OnlineService::showLogoAsCover(s);
136
137 QImage &img=podcast
138 ? noPodcastCover
139 : stream
140 ? noStreamCover
141 : noCover;
142
143 if (img.isNull()) {
144 int iconSize=Icon::stdSize(Utils::scaleForDpi(128));
145 const QIcon &icon=podcast
146 ? Icons::self()->podcastIcon
147 : stream
148 ? Icons::self()->streamIcon
149 : Icons::self()->albumIcon(iconSize);
150 img=icon.pixmap(iconSize, iconSize).toImage();
151
152 QString &file=podcast
153 ? noPodcastCoverFileName
154 : stream
155 ? noStreamCoverFileName
156 : noCoverFileName;
157
158 if (file.isEmpty()) {
159 if (podcast) {
160 QString iconFile=QString(CANTATA_SYS_ICONS_DIR+"podcasts.png");
161 if (QFile::exists(iconFile)) {
162 file=iconFile;
163 }
164 } else if (stream) {
165 QString iconFile=QString(CANTATA_SYS_ICONS_DIR+"stream.png");
166 if (QFile::exists(iconFile)) {
167 file=iconFile;
168 }
169 }
170 }
171 #if !defined Q_OS_WIN && !defined Q_OS_MAC
172 if (file.isEmpty()) {
173 QStringList iconNames=podcast
174 ? QStringList{ QStringLiteral("x-media-podcast"), QStringLiteral("news-feed"), QStringLiteral("application-rss+xml"), QStringLiteral("application-atom+xml") }
175 : stream
176 ? QStringList{ QStringLiteral("applications-internet") }
177 : QStringList{ QStringLiteral("media-optical"), QStringLiteral("media-optical-audio") };
178 file=findIcon(iconNames);
179 }
180 #endif
181 }
182 return img;
183 }
184
setEnabled(bool e)185 void CurrentCover::setEnabled(bool e)
186 {
187 if (enabled==e) {
188 return;
189 }
190
191 enabled=e;
192 if (enabled) {
193 img=stdImage(Song());
194 connect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &)));
195 connect(Covers::self(), SIGNAL(coverUpdated(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &)));
196 } else {
197 disconnect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &)));
198 disconnect(Covers::self(), SIGNAL(coverUpdated(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &)));
199 current=Song();
200 }
201 }
202
update(const Song & s)203 void CurrentCover::update(const Song &s)
204 {
205 if (!enabled) {
206 return;
207 }
208
209 if (s.albumArtist()!=current.albumArtist() || s.album!=current.album || s.isStream()!=current.isStream() ||
210 s.onlineService()!=current.onlineService()) {
211 current=s;
212 if (timer) {
213 timer->stop();
214 }
215 if (!s.isUnknownAlbum() && ((!s.albumArtist().isEmpty() && !s.album.isEmpty() && !current.isStandardStream()) || OnlineService::showLogoAsCover(s))) {
216 Covers::Image cImg=Covers::self()->requestImage(s, true);
217 valid=!cImg.img.isNull();
218 if (valid) {
219 coverFileName=cImg.fileName;
220 img=cImg.img;
221 emit coverFile(cImg.fileName);
222 if (current.isFromOnlineService()) {
223 if (coverFileName.startsWith(CANTATA_SYS_ICONS_DIR)) {
224 emit coverImage(QImage());
225 } else {
226 emit coverImage(cImg.img);
227 }
228 } else {
229 emit coverImage(cImg.img);
230 }
231 } else {
232 // We need to set the image here, so that TrayItem gets the correct 'noCover' image
233 // ...but if Covers does eventually download a cover, we dont want valid->noCover->valid
234 img=stdImage(current);
235 // Start a timer - in case cover retrieval fails...
236 if (!timer) {
237 timer=new QTimer(this);
238 timer->setSingleShot(true);
239 connect(timer, SIGNAL(timeout()), this, SLOT(setDefault()));
240 }
241 timer->start(750);
242 }
243 } else {
244 valid=true;
245 img=stdImage(current);
246 setDefault();
247 }
248 }
249 }
250
coverRetrieved(const Song & s,const QImage & newImage,const QString & file)251 void CurrentCover::coverRetrieved(const Song &s, const QImage &newImage, const QString &file)
252 {
253 if (!s.isArtistImageRequest() && s.albumArtist()==current.albumArtist() && s.album==current.album) {
254 if (timer) {
255 timer->stop();
256 }
257 valid=!newImage.isNull();
258 if (valid) {
259 coverFileName=file;
260 img=newImage;
261 emit coverFile(file);
262 emit coverImage(newImage);
263 } else {
264 setDefault();
265 }
266 }
267 }
268
setDefault()269 void CurrentCover::setDefault()
270 {
271 bool podcast=current.isFromOnlineService() && OnlineService::isPodcasts(current.onlineService());
272 bool stream=current.isStandardStream() || OnlineService::showLogoAsCover(current);
273
274 coverFileName=podcast
275 ? noPodcastCoverFileName
276 : stream
277 ? noStreamCoverFileName
278 : noCoverFileName;
279
280 emit coverFile(coverFileName);
281 emit coverImage(QImage());
282 }
283
284 #include "moc_currentcover.cpp"
285