1 /* This file is part of Clementine.
2 Copyright 2010, David Sansome <me@davidsansome.com>
3
4 Clementine is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19 #include "core/application.h"
20 #include "core/logging.h"
21 #include "covers/currentartloader.h"
22 #include "osd.h"
23 #include "osdpretty.h"
24 #include "ui/iconloader.h"
25 #include "ui/systemtrayicon.h"
26
27 #ifdef HAVE_DBUS
28 #include "dbus/notification.h"
29 #endif
30
31 #include <QCoreApplication>
32 #include <QtDebug>
33 #include <QSettings>
34
35 const char* OSD::kSettingsGroup = "OSD";
36
OSD(SystemTrayIcon * tray_icon,Application * app,QObject * parent)37 OSD::OSD(SystemTrayIcon* tray_icon, Application* app, QObject* parent)
38 : QObject(parent),
39 tray_icon_(tray_icon),
40 app_(app),
41 timeout_msec_(5000),
42 behaviour_(Native),
43 show_on_volume_change_(false),
44 show_art_(true),
45 show_on_play_mode_change_(true),
46 show_on_pause_(true),
47 use_custom_text_(false),
48 custom_text1_(QString()),
49 custom_text2_(QString()),
50 preview_mode_(false),
51 force_show_next_(false),
52 ignore_next_stopped_(false),
53 pretty_popup_(new OSDPretty(OSDPretty::Mode_Popup)) {
54 connect(app_->current_art_loader(),
55 SIGNAL(ThumbnailLoaded(Song, QString, QImage)),
56 SLOT(AlbumArtLoaded(Song, QString, QImage)));
57
58 ReloadSettings();
59 Init();
60 }
61
~OSD()62 OSD::~OSD() { delete pretty_popup_; }
63
ReloadSettings()64 void OSD::ReloadSettings() {
65 QSettings s;
66 s.beginGroup(kSettingsGroup);
67 behaviour_ = OSD::Behaviour(s.value("Behaviour", Native).toInt());
68 timeout_msec_ = s.value("Timeout", 5000).toInt();
69 show_on_volume_change_ = s.value("ShowOnVolumeChange", false).toBool();
70 show_art_ = s.value("ShowArt", true).toBool();
71 show_on_play_mode_change_ = s.value("ShowOnPlayModeChange", true).toBool();
72 show_on_pause_ = s.value("ShowOnPausePlayback", true).toBool();
73 use_custom_text_ = s.value(("CustomTextEnabled"), false).toBool();
74 custom_text1_ = s.value("CustomText1").toString();
75 custom_text2_ = s.value("CustomText2").toString();
76
77 if (!SupportsNativeNotifications() && behaviour_ == Native)
78 behaviour_ = Pretty;
79 if (!SupportsTrayPopups() && behaviour_ == TrayPopup) behaviour_ = Disabled;
80
81 ReloadPrettyOSDSettings();
82 }
83
84 // Reload just Pretty OSD settings, not everything
ReloadPrettyOSDSettings()85 void OSD::ReloadPrettyOSDSettings() {
86 pretty_popup_->set_popup_duration(timeout_msec_);
87 pretty_popup_->ReloadSettings();
88 }
89
ReshowCurrentSong()90 void OSD::ReshowCurrentSong() {
91 force_show_next_ = true;
92 AlbumArtLoaded(last_song_, last_image_uri_, last_image_);
93 }
94
AlbumArtLoaded(const Song & song,const QString & uri,const QImage & image)95 void OSD::AlbumArtLoaded(const Song& song, const QString& uri,
96 const QImage& image) {
97 // Don't change tray icon details if it's a preview
98 if (!preview_mode_ && tray_icon_) {
99 tray_icon_->SetNowPlaying(song, uri);
100 }
101
102 last_song_ = song;
103 last_image_ = image;
104 last_image_uri_ = uri;
105
106 QStringList message_parts;
107 QString summary;
108 if (!use_custom_text_) {
109 summary = song.PrettyTitle();
110 if (!song.artist().isEmpty())
111 summary = QString("%1 - %2").arg(song.artist(), summary);
112 if (!song.album().isEmpty()) message_parts << song.album();
113 if (song.disc() > 0) message_parts << tr("disc %1").arg(song.disc());
114 if (song.track() > 0) message_parts << tr("track %1").arg(song.track());
115 } else {
116 QRegExp variable_replacer("[%][a-z]+[%]");
117 summary = custom_text1_;
118 QString message(custom_text2_);
119
120 // Replace the first line
121 int pos = 0;
122 variable_replacer.indexIn(custom_text1_);
123 while ((pos = variable_replacer.indexIn(custom_text1_, pos)) != -1) {
124 QStringList captured = variable_replacer.capturedTexts();
125 summary.replace(captured[0], ReplaceVariable(captured[0], song));
126 pos += variable_replacer.matchedLength();
127 }
128
129 // Replace the second line
130 pos = 0;
131 variable_replacer.indexIn(custom_text2_);
132 while ((pos = variable_replacer.indexIn(custom_text2_, pos)) != -1) {
133 QStringList captured = variable_replacer.capturedTexts();
134 message.replace(captured[0], ReplaceVariable(captured[0], song));
135 pos += variable_replacer.matchedLength();
136 }
137 message_parts << message;
138 }
139
140 if (show_art_) {
141 ShowMessage(summary, message_parts.join(", "), "notification-audio-play",
142 image);
143 } else {
144 ShowMessage(summary, message_parts.join(", "), "notification-audio-play",
145 QImage());
146 }
147
148 // Reload the saved settings if they were changed for preview
149 if (preview_mode_) {
150 ReloadSettings();
151 preview_mode_ = false;
152 }
153 }
154
Paused()155 void OSD::Paused() {
156 if (show_on_pause_) {
157 ShowMessage(QCoreApplication::applicationName(), tr("Paused"));
158 }
159 }
160
Stopped()161 void OSD::Stopped() {
162 if (tray_icon_) tray_icon_->ClearNowPlaying();
163 if (ignore_next_stopped_) {
164 ignore_next_stopped_ = false;
165 return;
166 }
167
168 ShowMessage(QCoreApplication::applicationName(), tr("Stopped"));
169 }
170
StopAfterToggle(bool stop)171 void OSD::StopAfterToggle(bool stop) {
172 ShowMessage(
173 QCoreApplication::applicationName(),
174 tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
175 }
176
PlaylistFinished()177 void OSD::PlaylistFinished() {
178 // We get a PlaylistFinished followed by a Stopped from the player
179 ignore_next_stopped_ = true;
180
181 ShowMessage(QCoreApplication::applicationName(), tr("Playlist finished"));
182 }
183
VolumeChanged(int value)184 void OSD::VolumeChanged(int value) {
185 if (!show_on_volume_change_) return;
186
187 ShowMessage(QCoreApplication::applicationName(), tr("Volume %1%").arg(value));
188 }
189
MagnatuneDownloadFinished(const QStringList & albums)190 void OSD::MagnatuneDownloadFinished(const QStringList& albums) {
191 QString message;
192 if (albums.count() == 1)
193 message = albums[0];
194 else
195 message = tr("%1 albums").arg(albums.count());
196
197 ShowMessage(tr("Magnatune download finished"), message, QString(),
198 QImage(IconLoader::Load("magnatune",
199 IconLoader::Provider).pixmap(16).toImage()));
200 }
201
ShowMessage(const QString & summary,const QString & message,const QString & icon,const QImage & image)202 void OSD::ShowMessage(const QString& summary, const QString& message,
203 const QString& icon, const QImage& image) {
204 if (pretty_popup_->toggle_mode()) {
205 pretty_popup_->ShowMessage(summary, message, image);
206 } else {
207 switch (behaviour_) {
208 case Native:
209 if (image.isNull()) {
210 ShowMessageNative(summary, message, icon, QImage());
211 } else {
212 ShowMessageNative(summary, message, QString(), image);
213 }
214 break;
215
216 #ifndef Q_OS_DARWIN
217 case TrayPopup:
218 if (tray_icon_) tray_icon_->ShowPopup(summary, message, timeout_msec_);
219 break;
220 #endif
221
222 case Disabled:
223 if (!force_show_next_) break;
224 force_show_next_ = false;
225 // fallthrough
226 case Pretty:
227 pretty_popup_->ShowMessage(summary, message, image);
228 break;
229
230 default:
231 break;
232 }
233 }
234 }
235
236 #if !defined(HAVE_X11) && defined(HAVE_DBUS)
CallFinished(QDBusPendingCallWatcher *)237 void OSD::CallFinished(QDBusPendingCallWatcher*) {}
238 #endif
239
WiiremoteActived(int id)240 void OSD::WiiremoteActived(int id) {
241 ShowMessage(QString(tr("%1: Wiimotedev module"))
242 .arg(QCoreApplication::applicationName()),
243 tr("Wii Remote %1: activated").arg(QString::number(id)));
244 }
245
WiiremoteDeactived(int id)246 void OSD::WiiremoteDeactived(int id) {
247 ShowMessage(QString(tr("%1: Wiimotedev module"))
248 .arg(QCoreApplication::applicationName()),
249 tr("Wii Remote %1: disactived").arg(QString::number(id)));
250 }
251
WiiremoteConnected(int id)252 void OSD::WiiremoteConnected(int id) {
253 ShowMessage(QString(tr("%1: Wiimotedev module"))
254 .arg(QCoreApplication::applicationName()),
255 tr("Wii Remote %1: connected").arg(QString::number(id)));
256 }
257
WiiremoteDisconnected(int id)258 void OSD::WiiremoteDisconnected(int id) {
259 ShowMessage(QString(tr("%1: Wiimotedev module"))
260 .arg(QCoreApplication::applicationName()),
261 tr("Wii Remote %1: disconnected").arg(QString::number(id)));
262 }
263
WiiremoteLowBattery(int id,int live)264 void OSD::WiiremoteLowBattery(int id, int live) {
265 ShowMessage(QString(tr("%1: Wiimotedev module"))
266 .arg(QCoreApplication::applicationName()),
267 tr("Wii Remote %1: low battery (%2%)")
268 .arg(QString::number(id), QString::number(live)));
269 }
270
WiiremoteCriticalBattery(int id,int live)271 void OSD::WiiremoteCriticalBattery(int id, int live) {
272 ShowMessage(QString(tr("%1: Wiimotedev module"))
273 .arg(QCoreApplication::applicationName()),
274 tr("Wii Remote %1: critical battery (%2%) ")
275 .arg(QString::number(id), QString::number(live)));
276 }
277
ShuffleModeChanged(PlaylistSequence::ShuffleMode mode)278 void OSD::ShuffleModeChanged(PlaylistSequence::ShuffleMode mode) {
279 if (show_on_play_mode_change_) {
280 QString current_mode = QString();
281 switch (mode) {
282 case PlaylistSequence::Shuffle_Off:
283 current_mode = tr("Don't shuffle");
284 break;
285 case PlaylistSequence::Shuffle_All:
286 current_mode = tr("Shuffle all");
287 break;
288 case PlaylistSequence::Shuffle_InsideAlbum:
289 current_mode = tr("Shuffle tracks in this album");
290 break;
291 case PlaylistSequence::Shuffle_Albums:
292 current_mode = tr("Shuffle albums");
293 break;
294 }
295 ShowMessage(QCoreApplication::applicationName(), current_mode);
296 }
297 }
298
RepeatModeChanged(PlaylistSequence::RepeatMode mode)299 void OSD::RepeatModeChanged(PlaylistSequence::RepeatMode mode) {
300 if (show_on_play_mode_change_) {
301 QString current_mode = QString();
302 switch (mode) {
303 case PlaylistSequence::Repeat_Off:
304 current_mode = tr("Don't repeat");
305 break;
306 case PlaylistSequence::Repeat_Track:
307 current_mode = tr("Repeat track");
308 break;
309 case PlaylistSequence::Repeat_Album:
310 current_mode = tr("Repeat album");
311 break;
312 case PlaylistSequence::Repeat_Playlist:
313 current_mode = tr("Repeat playlist");
314 break;
315 case PlaylistSequence::Repeat_OneByOne:
316 current_mode = tr("Stop after every track");
317 break;
318 case PlaylistSequence::Repeat_Intro:
319 current_mode = tr("Intro tracks");
320 break;
321 }
322 ShowMessage(QCoreApplication::applicationName(), current_mode);
323 }
324 }
325
ReplaceVariable(const QString & variable,const Song & song)326 QString OSD::ReplaceVariable(const QString& variable, const Song& song) {
327 QString return_value;
328 if (variable == "%artist%") {
329 return song.artist();
330 } else if (variable == "%album%") {
331 return song.album();
332 } else if (variable == "%title%") {
333 return song.PrettyTitle();
334 } else if (variable == "%albumartist%") {
335 return song.effective_albumartist();
336 } else if (variable == "%year%") {
337 return song.PrettyYear();
338 } else if (variable == "%composer%") {
339 return song.composer();
340 } else if (variable == "%performer%") {
341 return song.performer();
342 } else if (variable == "%grouping%") {
343 return song.grouping();
344 } else if (variable == "%lyrics%") {
345 return song.lyrics();
346 } else if (variable == "%length%") {
347 return song.PrettyLength();
348 } else if (variable == "%disc%") {
349 return return_value.setNum(song.disc());
350 } else if (variable == "%track%") {
351 return return_value.setNum(song.track());
352 } else if (variable == "%genre%") {
353 return song.genre();
354 } else if (variable == "%playcount%") {
355 return return_value.setNum(song.playcount());
356 } else if (variable == "%skipcount%") {
357 return return_value.setNum(song.skipcount());
358 } else if (variable == "%filename%") {
359 return song.basefilename();
360 } else if (variable == "%rating%") {
361 return song.PrettyRating();
362 } else if (variable == "%score%") {
363 return QString::number(song.score());
364 } else if (variable == "%newline%") {
365 // We need different strings depending on notification type
366 switch (behaviour_) {
367 case Native:
368 #ifdef Q_OS_DARWIN
369 return "\n";
370 #endif
371 #ifdef Q_OS_LINUX
372 return "<br/>";
373 #endif
374 #ifdef Q_OS_WIN32
375 // Other OS don't support native notifications
376 qLog(Debug)
377 << "New line not supported by this notification type under Windows";
378 return "";
379 #endif
380 case TrayPopup:
381 qLog(Debug) << "New line not supported by this notification type";
382 return "";
383 case Pretty:
384 default:
385 // When notifications are disabled, we force the PrettyOSD
386 return "<br/>";
387 }
388 }
389
390 // if the variable is not recognized, just return it
391 return variable;
392 }
393
ShowPreview(const Behaviour type,const QString & line1,const QString & line2,const Song & song)394 void OSD::ShowPreview(const Behaviour type, const QString& line1,
395 const QString& line2, const Song& song) {
396 behaviour_ = type;
397 custom_text1_ = line1;
398 custom_text2_ = line2;
399 if (!use_custom_text_) use_custom_text_ = true;
400
401 // We want to reload the settings, but we can't do this here because the cover
402 // art loading is asynch
403 preview_mode_ = true;
404 AlbumArtLoaded(song, QString(), QImage());
405 }
406
SetPrettyOSDToggleMode(bool toggle)407 void OSD::SetPrettyOSDToggleMode(bool toggle) {
408 pretty_popup_->set_toggle_mode(toggle);
409 }
410