Use virtual functions for OSD

This commit is contained in:
Jonas Kvinge
2020-08-09 01:37:00 +02:00
parent 184e9a5c93
commit ab7d383cf1
18 changed files with 281 additions and 198 deletions

View File

@@ -1,324 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#ifdef HAVE_DBUS
# include <dbus/notification.h>
#endif
#include <QObject>
#include <QCoreApplication>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QImage>
#include <QSettings>
#ifdef HAVE_DBUS
# include <QDBusPendingCall>
#endif
#include "osd.h"
#include "osdpretty.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/systemtrayicon.h"
#include "core/utilities.h"
#include "covermanager/currentalbumcoverloader.h"
const char *OSD::kSettingsGroup = "OSD";
OSD::OSD(SystemTrayIcon *tray_icon, Application *app, QObject *parent)
: QObject(parent),
app_(app),
tray_icon_(tray_icon),
app_name_(QCoreApplication::applicationName()),
timeout_msec_(5000),
behaviour_(Native),
show_on_volume_change_(false),
show_art_(true),
show_on_play_mode_change_(true),
show_on_pause_(true),
show_on_resume_(false),
use_custom_text_(false),
custom_text1_(QString()),
custom_text2_(QString()),
preview_mode_(false),
force_show_next_(false),
ignore_next_stopped_(false),
pretty_popup_(new OSDPretty(OSDPretty::Mode_Popup))
{
connect(app_->current_albumcover_loader(), SIGNAL(ThumbnailLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
ReloadSettings();
Init();
app_name_[0] = app_name_[0].toUpper();
}
OSD::~OSD() {
delete pretty_popup_;
}
void OSD::ReloadSettings() {
QSettings s;
s.beginGroup(kSettingsGroup);
behaviour_ = OSD::Behaviour(s.value("Behaviour", Native).toInt());
timeout_msec_ = s.value("Timeout", 5000).toInt();
show_on_volume_change_ = s.value("ShowOnVolumeChange", false).toBool();
show_art_ = s.value("ShowArt", true).toBool();
show_on_play_mode_change_ = s.value("ShowOnPlayModeChange", true).toBool();
show_on_pause_ = s.value("ShowOnPausePlayback", true).toBool();
show_on_resume_ = s.value("ShowOnResumePlayback", false).toBool();
use_custom_text_ = s.value(("CustomTextEnabled"), false).toBool();
custom_text1_ = s.value("CustomText1").toString();
custom_text2_ = s.value("CustomText2").toString();
s.endGroup();
if (!SupportsNativeNotifications() && behaviour_ == Native)
behaviour_ = Pretty;
if (!SupportsTrayPopups() && behaviour_ == TrayPopup)
behaviour_ = Disabled;
ReloadPrettyOSDSettings();
}
// Reload just Pretty OSD settings, not everything
void OSD::ReloadPrettyOSDSettings() {
pretty_popup_->set_popup_duration(timeout_msec_);
pretty_popup_->ReloadSettings();
}
void OSD::ReshowCurrentSong() {
force_show_next_ = true;
AlbumCoverLoaded(last_song_, last_image_uri_, last_image_);
}
void OSD::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
// Don't change tray icon details if it's a preview
if (!preview_mode_ && tray_icon_)
tray_icon_->SetNowPlaying(song, cover_url);
last_song_ = song;
last_image_ = image;
last_image_uri_ = cover_url;
QStringList message_parts;
QString summary;
if (!use_custom_text_) {
summary = song.PrettyTitle();
if (!song.artist().isEmpty())
summary = QString("%1 - %2").arg(song.artist(), summary);
if (!song.album().isEmpty())
message_parts << song.album();
if (song.disc() > 0)
message_parts << tr("disc %1").arg(song.disc());
if (song.track() > 0)
message_parts << tr("track %1").arg(song.track());
}
else {
summary = ReplaceMessage(custom_text1_, song);
message_parts << ReplaceMessage(custom_text2_, song);
}
if (show_art_) {
ShowMessage(summary, message_parts.join(", "), "notification-audio-play", image);
}
else {
ShowMessage(summary, message_parts.join(", "), "notification-audio-play", QImage());
}
// Reload the saved settings if they were changed for preview
if (preview_mode_) {
ReloadSettings();
preview_mode_ = false;
}
}
void OSD::Paused() {
if (show_on_pause_) {
ShowMessage(app_name_, tr("Paused"));
}
}
void OSD::Resumed() {
if (show_on_resume_) {
AlbumCoverLoaded(last_song_, last_image_uri_, last_image_);
}
}
void OSD::Stopped() {
if (tray_icon_) tray_icon_->ClearNowPlaying();
if (ignore_next_stopped_) {
ignore_next_stopped_ = false;
return;
}
ShowMessage(app_name_, tr("Stopped"));
}
void OSD::StopAfterToggle(bool stop) {
ShowMessage(app_name_, tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
}
void OSD::PlaylistFinished() {
// We get a PlaylistFinished followed by a Stopped from the player
ignore_next_stopped_ = true;
ShowMessage(app_name_, tr("Playlist finished"));
}
void OSD::VolumeChanged(int value) {
if (!show_on_volume_change_) return;
ShowMessage(app_name_, tr("Volume %1%").arg(value));
}
void OSD::ShowMessage(const QString &summary, const QString &message, const QString icon, const QImage &image) {
if (pretty_popup_->toggle_mode()) {
pretty_popup_->ShowMessage(summary, message, image);
}
else {
switch (behaviour_) {
case Native:
if (image.isNull()) {
ShowMessageNative(summary, message, icon, QImage());
}
else {
ShowMessageNative(summary, message, QString(), image);
}
break;
#ifndef Q_OS_MACOS
case TrayPopup:
if (tray_icon_) tray_icon_->ShowPopup(summary, message, timeout_msec_);
break;
#endif
case Disabled:
if (!force_show_next_) break;
force_show_next_ = false;
// fallthrough
case Pretty:
pretty_popup_->ShowMessage(summary, message, image);
break;
default:
break;
}
}
}
#if !defined(HAVE_X11)
#if defined(HAVE_DBUS)
void OSD::CallFinished(QDBusPendingCallWatcher*) {}
#else
void OSD::CallFinished() {}
#endif
#endif
void OSD::ShuffleModeChanged(PlaylistSequence::ShuffleMode mode) {
if (show_on_play_mode_change_) {
QString current_mode = QString();
switch (mode) {
case PlaylistSequence::Shuffle_Off: current_mode = tr("Don't shuffle"); break;
case PlaylistSequence::Shuffle_All: current_mode = tr("Shuffle all"); break;
case PlaylistSequence::Shuffle_InsideAlbum: current_mode = tr("Shuffle tracks in this album"); break;
case PlaylistSequence::Shuffle_Albums: current_mode = tr("Shuffle albums"); break;
}
ShowMessage(app_name_, current_mode);
}
}
void OSD::RepeatModeChanged(PlaylistSequence::RepeatMode mode) {
if (show_on_play_mode_change_) {
QString current_mode = QString();
switch (mode) {
case PlaylistSequence::Repeat_Off: current_mode = tr("Don't repeat"); break;
case PlaylistSequence::Repeat_Track: current_mode = tr("Repeat track"); break;
case PlaylistSequence::Repeat_Album: current_mode = tr("Repeat album"); break;
case PlaylistSequence::Repeat_Playlist: current_mode = tr("Repeat playlist"); break;
case PlaylistSequence::Repeat_OneByOne: current_mode = tr("Stop after every track"); break;
case PlaylistSequence::Repeat_Intro: current_mode = tr("Intro tracks"); break;
}
ShowMessage(app_name_, current_mode);
}
}
QString OSD::ReplaceMessage(const QString &message, const Song &song) {
QString newline = "<br/>";
if (message.indexOf("%newline%") != -1) {
// We need different strings depending on notification type
switch (behaviour_) {
case Native:
#ifdef Q_OS_MACOS
newline = "\n";
break;
#endif
#ifdef Q_OS_LINUX
break;
#endif
#ifdef Q_OS_WIN32
// Other OS don't support native notifications
qLog(Debug) << "New line not supported by this notification type under Windows";
newline = "";
break;
#endif
case TrayPopup:
qLog(Debug) << "New line not supported by this notification type";
newline = "";
break;
case Pretty:
default:
// When notifications are disabled, we force the PrettyOSD
break;
}
}
return Utilities::ReplaceMessage(message, song, newline);
}
void OSD::ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song) {
behaviour_ = type;
custom_text1_ = line1;
custom_text2_ = line2;
if (!use_custom_text_) use_custom_text_ = true;
// We want to reload the settings, but we can't do this here because the cover art loading is asynch
preview_mode_ = true;
AlbumCoverLoaded(song, QUrl(), QImage());
}
void OSD::SetPrettyOSDToggleMode(bool toggle) {
pretty_popup_->set_toggle_mode(toggle);
}

View File

@@ -1,137 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef OSD_H
#define OSD_H
#include "config.h"
#include <memory>
#include <QtGlobal>
#include <QObject>
#include <QString>
#include <QUrl>
#include <QDateTime>
#include <QImage>
#ifdef HAVE_DBUS
# include <QDBusArgument>
# include <QDBusPendingCall>
#endif
#include "core/song.h"
#include "playlist/playlistsequence.h"
class Application;
class OSDPretty;
class OrgFreedesktopNotificationsInterface;
class SystemTrayIcon;
class QDBusPendingCallWatcher;
class OSD : public QObject {
Q_OBJECT
public:
explicit OSD(SystemTrayIcon *tray_icon, Application *app, QObject *parent = nullptr);
~OSD() override;
static const char *kSettingsGroup;
enum Behaviour {
Disabled = 0,
Native,
TrayPopup,
Pretty,
};
// Implemented in the OS-specific files
static bool SupportsNativeNotifications();
static bool SupportsTrayPopups();
void ReloadPrettyOSDSettings();
void SetPrettyOSDToggleMode(bool toggle);
public slots:
void ReloadSettings();
void Paused();
void Resumed();
void Stopped();
void StopAfterToggle(bool stop);
void PlaylistFinished();
void VolumeChanged(int value);
void RepeatModeChanged(PlaylistSequence::RepeatMode mode);
void ShuffleModeChanged(PlaylistSequence::ShuffleMode mode);
void ReshowCurrentSong();
void ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song);
private:
void ShowMessage(const QString &summary, const QString &message = QString(), const QString icon = QString("strawberry"), const QImage &image = QImage());
QString ReplaceMessage(const QString &message, const Song &song);
// These are implemented in the OS-specific files
void Init();
void ShowMessageNative(const QString &summary, const QString &message, const QString &icon = QString(), const QImage &image = QImage());
private slots:
#ifdef HAVE_DBUS
void CallFinished(QDBusPendingCallWatcher *watcher);
#endif
void CallFinished();
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
private:
Application *app_;
SystemTrayIcon *tray_icon_;
QString app_name_;
int timeout_msec_;
Behaviour behaviour_;
bool show_on_volume_change_;
bool show_art_;
bool show_on_play_mode_change_;
bool show_on_pause_;
bool show_on_resume_;
bool use_custom_text_;
QString custom_text1_;
QString custom_text2_;
bool preview_mode_;
bool force_show_next_;
bool ignore_next_stopped_;
OSDPretty *pretty_popup_;
Song last_song_;
QUrl last_image_uri_;
QImage last_image_;
#ifdef HAVE_DBUS
std::unique_ptr<OrgFreedesktopNotificationsInterface> interface_;
uint notification_id_;
QDateTime last_notification_time_;
#endif
};
#endif // OSD_H

View File

@@ -1,70 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "osd.h"
#include <QBuffer>
#include <QByteArray>
#include <QFile>
#include <QtDebug>
#include "core/scoped_nsobject.h"
namespace {
bool NotificationCenterSupported() {
return NSClassFromString(@"NSUserNotificationCenter");
}
void SendNotificationCenterMessage(NSString* title, NSString* subtitle) {
Class clazz = NSClassFromString(@"NSUserNotificationCenter");
id notification_center = [clazz defaultUserNotificationCenter];
Class user_notification_class = NSClassFromString(@"NSUserNotification");
id notification = [[user_notification_class alloc] init];
[notification setTitle:title];
[notification setSubtitle:subtitle];
[notification_center deliverNotification:notification];
}
}
void OSD::Init() {}
bool OSD::SupportsNativeNotifications() {
return NotificationCenterSupported();
}
bool OSD::SupportsTrayPopups() { return false; }
void OSD::ShowMessageNative(const QString& summary, const QString& message, const QString& icon, const QImage& image) {
Q_UNUSED(icon);
Q_UNUSED(image);
if (NotificationCenterSupported()) {
scoped_nsobject<NSString> mac_message(
[[NSString alloc] initWithUTF8String:message.toUtf8().constData()]);
scoped_nsobject<NSString> mac_summary(
[[NSString alloc] initWithUTF8String:summary.toUtf8().constData()]);
SendNotificationCenterMessage(mac_summary.get(), mac_message.get());
}
}

View File

@@ -1,43 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "osd.h"
#include "core/logging.h"
#include <QString>
#include <QImage>
#include <QtDebug>
void OSD::Init() {
}
bool OSD::SupportsNativeNotifications() {
return false;
}
bool OSD::SupportsTrayPopups() {
return true;
}
void OSD::ShowMessageNative(const QString&, const QString&, const QString&, const QImage&) {
qLog(Warning) << "not implemented";
}

View File

@@ -1,189 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <memory>
#ifdef HAVE_DBUS
# include <dbus/notification.h>
#endif
#include <QtGlobal>
#include <QObject>
#include <QByteArray>
#include <QDateTime>
#include <QMap>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QImage>
#ifdef HAVE_DBUS
# include <QCoreApplication>
# include <QDBusArgument>
# include <QDBusConnection>
# include <QDBusError>
# include <QDBusPendingCall>
# include <QDBusPendingReply>
#endif
#include <QJsonObject>
#include <QtDebug>
#include "core/logging.h"
#include "osd.h"
#ifdef HAVE_DBUS
QDBusArgument &operator<< (QDBusArgument &arg, const QImage &image);
const QDBusArgument &operator>> (const QDBusArgument &arg, QImage &image);
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image) {
if (image.isNull()) {
// Sometimes this gets called with a null QImage for no obvious reason.
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
// ABGR -> ARGB
QImage i = scaled.rgbSwapped();
#else
// ABGR -> GBAR
QImage i(scaled.size(), scaled.format());
for (int y = 0; y < i.height(); ++y) {
QRgb *p = (QRgb*)scaled.scanLine(y);
QRgb *q = (QRgb*)i.scanLine(y);
QRgb *end = p + scaled.width();
while (p < end) {
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
p++;
q++;
}
}
#endif
arg.beginStructure();
arg << i.width();
arg << i.height();
arg << i.bytesPerLine();
arg << i.hasAlphaChannel();
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
arg << i.depth() / channels;
arg << channels;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
arg << QByteArray(reinterpret_cast<const char*>(i.bits()), i.sizeInBytes());
#else
arg << QByteArray(reinterpret_cast<const char*>(i.bits()), i.byteCount());
#endif
arg.endStructure();
return arg;
}
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &image) {
Q_UNUSED(image);
// This is needed to link but shouldn't be called.
Q_ASSERT(0);
return arg;
}
#endif // HAVE_DBUS
void OSD::Init() {
#ifdef HAVE_DBUS
notification_id_ = 0;
interface_.reset(new OrgFreedesktopNotificationsInterface(OrgFreedesktopNotificationsInterface::staticInterfaceName(), "/org/freedesktop/Notifications", QDBusConnection::sessionBus()));
if (!interface_->isValid()) {
qLog(Warning) << "Error connecting to notifications service.";
}
#endif // HAVE_DBUS
}
bool OSD::SupportsNativeNotifications() {
#ifdef HAVE_DBUS
return true;
#else
return false;
#endif
}
bool OSD::SupportsTrayPopups() { return true; }
void OSD::ShowMessageNative(const QString &summary, const QString &message, const QString &icon, const QImage &image) {
#ifdef HAVE_DBUS
if (!interface_) return;
QVariantMap hints;
if (!image.isNull()) {
hints["image_data"] = QVariant(image);
}
hints["transient"] = QVariant(true);
int id = 0;
if (last_notification_time_.secsTo(QDateTime::currentDateTime()) * 1000 < timeout_msec_) {
// Reuse the existing popup if it's still open. The reason we don't always
// reuse the popup is because the notification daemon on KDE4 won't re-show the bubble if it's already gone to the tray. See issue #118
id = notification_id_;
}
QDBusPendingReply<uint> reply = interface_->Notify(QCoreApplication::applicationName(), id, icon, summary, message, QStringList(), hints, timeout_msec_);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(CallFinished(QDBusPendingCallWatcher*)));
#else // HAVE_DBUS
Q_UNUSED(summary)
Q_UNUSED(message)
Q_UNUSED(icon)
Q_UNUSED(image)
qLog(Warning) << "not implemented";
#endif // HAVE_DBUS
}
#ifdef HAVE_DBUS
void OSD::CallFinished(QDBusPendingCallWatcher *watcher) {
std::unique_ptr<QDBusPendingCallWatcher> w(watcher);
QDBusPendingReply<uint> reply = *w.get();
if (reply.isError()) {
qLog(Warning) << "Error sending notification" << reply.error().name();
return;
}
uint id = reply.value();
if (id != 0) {
notification_id_ = id;
last_notification_time_ = QDateTime::currentDateTime();
}
}
#endif
void OSD::CallFinished() {}

View File

@@ -1,586 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QtGlobal>
#include <QApplication>
#include <QGuiApplication>
#include <QWindow>
#include <QScreen>
#include <QWidget>
#include <QList>
#include <QVariant>
#include <QString>
#include <QImage>
#include <QPixmap>
#include <QBitmap>
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
#include <QPalette>
#include <QColor>
#include <QBrush>
#include <QCursor>
#include <QPen>
#include <QRect>
#include <QPoint>
#include <QFont>
#include <QTimer>
#include <QTimeLine>
#include <QTransform>
#include <QLayout>
#include <QBoxLayout>
#include <QLinearGradient>
#include <QSettings>
#include <QFlags>
#include <QtEvents>
#ifdef HAVE_X11
# include <QX11Info>
#endif
#ifdef Q_OS_WIN
# include <QtWin>
#endif
#include "osdpretty.h"
#include "ui_osdpretty.h"
#ifdef Q_OS_WIN
# include <windows.h>
#endif
const char *OSDPretty::kSettingsGroup = "OSDPretty";
const int OSDPretty::kDropShadowSize = 13;
const int OSDPretty::kBorderRadius = 10;
const int OSDPretty::kMaxIconSize = 100;
const int OSDPretty::kSnapProximity = 20;
const QRgb OSDPretty::kPresetBlue = qRgb(102, 150, 227);
const QRgb OSDPretty::kPresetRed = qRgb(202, 22, 16);
OSDPretty::OSDPretty(Mode mode, QWidget *parent)
: QWidget(parent),
ui_(new Ui_OSDPretty),
mode_(mode),
background_color_(kPresetBlue),
background_opacity_(0.85),
popup_screen_(nullptr),
font_(QFont()),
disable_duration_(false),
timeout_(new QTimer(this)),
fading_enabled_(false),
fader_(new QTimeLine(300, this)),
toggle_mode_(false) {
Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint;
setWindowFlags(flags);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_X11NetWmWindowTypeNotification, true);
setAttribute(Qt::WA_ShowWithoutActivating, true);
ui_->setupUi(this);
#ifdef Q_OS_WIN
// Don't show the window in the taskbar. Qt::ToolTip does this too, but it adds an extra ugly shadow.
int ex_style = GetWindowLong((HWND) winId(), GWL_EXSTYLE);
ex_style |= WS_EX_NOACTIVATE;
SetWindowLong((HWND) winId(), GWL_EXSTYLE, ex_style);
#endif
// Mode settings
switch (mode_) {
case Mode_Popup:
setCursor(QCursor(Qt::ArrowCursor));
break;
case Mode_Draggable:
setCursor(QCursor(Qt::OpenHandCursor));
break;
}
// Timeout
timeout_->setSingleShot(true);
timeout_->setInterval(5000);
connect(timeout_, SIGNAL(timeout()), SLOT(hide()));
ui_->icon->setMaximumSize(kMaxIconSize, kMaxIconSize);
// Fader
connect(fader_, SIGNAL(valueChanged(qreal)), SLOT(FaderValueChanged(qreal)));
connect(fader_, SIGNAL(finished()), SLOT(FaderFinished()));
#ifdef Q_OS_WIN
set_fading_enabled(true);
#endif
// Load the show edges and corners
QImage shadow_edge(":/pictures/osd_shadow_edge.png");
QImage shadow_corner(":/pictures/osd_shadow_corner.png");
for (int i = 0; i < 4; ++i) {
QTransform rotation = QTransform().rotate(90 * i);
shadow_edge_[i] = QPixmap::fromImage(shadow_edge.transformed(rotation));
shadow_corner_[i] = QPixmap::fromImage(shadow_corner.transformed(rotation));
}
background_ = QPixmap(":/pictures/osd_background.png");
// Set the margins to allow for the drop shadow
QBoxLayout *l = qobject_cast<QBoxLayout*>(layout());
QMargins margin = l->contentsMargins();
margin.setTop(margin.top() + kDropShadowSize);
margin.setBottom(margin.bottom() + kDropShadowSize);
margin.setLeft(margin.left() + kDropShadowSize);
margin.setRight(margin.right() + kDropShadowSize);
l->setContentsMargins(margin);
connect(qApp, SIGNAL(screenAdded(QScreen*)), this, SLOT(ScreenAdded(QScreen*)));
connect(qApp, SIGNAL(screenRemoved(QScreen*)), this, SLOT(ScreenRemoved(QScreen*)));
}
OSDPretty::~OSDPretty() {
delete ui_;
}
void OSDPretty::showEvent(QShowEvent *e) {
screens_.clear();
for(QScreen *screen : qApp->screens()) {
screens_.insert(screen->name(), screen);
}
// Get current screen resolution
QScreen *screen = current_screen();
if (screen) {
QRect resolution = screen->availableGeometry();
// Leave 200 px for icon
ui_->summary->setMaximumWidth(resolution.width() - 200);
ui_->message->setMaximumWidth(resolution.width() - 200);
// Set maximum size for the OSD, a little margin here too
setMaximumSize(resolution.width() - 100, resolution.height() - 100);
}
setWindowOpacity(fading_enabled_ ? 0.0 : 1.0);
QWidget::showEvent(e);
Load();
Reposition();
if (fading_enabled_) {
fader_->setDirection(QTimeLine::Forward);
fader_->start(); // Timeout will be started in FaderFinished
}
else if (mode_ == Mode_Popup) {
if (!disable_duration())
timeout_->start();
// Ensures it is above when showing the preview
raise();
}
}
void OSDPretty::ScreenAdded(QScreen *screen) {
screens_.insert(screen->name(), screen);
}
void OSDPretty::ScreenRemoved(QScreen *screen) {
if (screens_.contains(screen->name())) screens_.remove(screen->name());
if (screen == popup_screen_) popup_screen_ = current_screen();
}
bool OSDPretty::IsTransparencyAvailable() {
#if defined(HAVE_X11)
return QX11Info::isCompositingManagerRunning();
#endif
return true;
}
void OSDPretty::Load() {
QSettings s;
s.beginGroup(kSettingsGroup);
foreground_color_ = QColor(s.value("foreground_color", 0).toInt());
background_color_ = QColor(s.value("background_color", kPresetBlue).toInt());
background_opacity_ = s.value("background_opacity", 0.85).toDouble();
font_.fromString(s.value("font", "Verdana,9,-1,5,50,0,0,0,0,0").toString());
disable_duration_ = s.value("disable_duration", false).toBool();
if (s.contains("popup_screen")) {
popup_screen_name_ = s.value("popup_screen").toString();
if (screens_.contains(popup_screen_name_)) {
popup_screen_ = screens_[popup_screen_name_];
}
else {
popup_screen_ = current_screen();
if (current_screen()) popup_screen_name_ = current_screen()->name();
else popup_screen_name_.clear();
}
}
else {
popup_screen_ = current_screen();
if (current_screen()) popup_screen_name_ = current_screen()->name();
}
if (s.contains("popup_pos")) {
popup_pos_ = s.value("popup_pos").toPoint();
}
else {
if (popup_screen_) {
QRect geometry = popup_screen_->availableGeometry();
popup_pos_.setX(geometry.width() - width());
popup_pos_.setY(0);
}
else {
popup_pos_.setX(0);
popup_pos_.setY(0);
}
}
set_font(font());
set_foreground_color(foreground_color());
s.endGroup();
}
void OSDPretty::ReloadSettings() {
Load();
if (isVisible()) update();
}
QRect OSDPretty::BoxBorder() const {
return rect().adjusted(kDropShadowSize, kDropShadowSize, -kDropShadowSize, -kDropShadowSize);
}
void OSDPretty::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
QRect box(BoxBorder());
// Shadow corners
const int kShadowCornerSize = kDropShadowSize + kBorderRadius;
p.drawPixmap(0, 0, shadow_corner_[0]);
p.drawPixmap(width() - kShadowCornerSize, 0, shadow_corner_[1]);
p.drawPixmap(width() - kShadowCornerSize, height() - kShadowCornerSize, shadow_corner_[2]);
p.drawPixmap(0, height() - kShadowCornerSize, shadow_corner_[3]);
// Shadow edges
p.drawTiledPixmap(kShadowCornerSize, 0, width() - kShadowCornerSize*2, kDropShadowSize, shadow_edge_[0]);
p.drawTiledPixmap(width() - kDropShadowSize, kShadowCornerSize, kDropShadowSize, height() - kShadowCornerSize*2, shadow_edge_[1]);
p.drawTiledPixmap(kShadowCornerSize, height() - kDropShadowSize, width() - kShadowCornerSize*2, kDropShadowSize, shadow_edge_[2]);
p.drawTiledPixmap(0, kShadowCornerSize, kDropShadowSize, height() - kShadowCornerSize*2, shadow_edge_[3]);
// Box background
p.setBrush(background_color_);
p.setPen(QPen());
p.setOpacity(background_opacity_);
p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
// Background pattern
QPainterPath background_path;
background_path.addRoundedRect(box, kBorderRadius, kBorderRadius);
p.setClipPath(background_path);
p.setOpacity(1.0);
p.drawPixmap(box.right() - background_.width(), box.bottom() - background_.height(), background_);
p.setClipping(false);
// Gradient overlay
QLinearGradient gradient(0, 0, 0, height());
gradient.setColorAt(0, QColor(255, 255, 255, 130));
gradient.setColorAt(1, QColor(255, 255, 255, 50));
p.setBrush(gradient);
p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
// Box border
p.setBrush(QBrush());
p.setPen(QPen(background_color_.darker(150), 2));
p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
}
void OSDPretty::SetMessage(const QString& summary, const QString& message, const QImage &image) {
if (!image.isNull()) {
QImage scaled_image = image.scaled(kMaxIconSize, kMaxIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
ui_->icon->setPixmap(QPixmap::fromImage(scaled_image));
ui_->icon->show();
}
else {
ui_->icon->hide();
}
ui_->summary->setText(summary);
ui_->message->setText(message);
if (isVisible()) Reposition();
}
// Set the desired message and then show the OSD
void OSDPretty::ShowMessage(const QString &summary, const QString &message, const QImage &image) {
SetMessage(summary, message, image);
if (isVisible() && mode_ == Mode_Popup) {
// The OSD is already visible, toggle or restart the timer
if (toggle_mode()) {
set_toggle_mode(false);
// If timeout is disabled, timer hadn't been started
if (!disable_duration())
timeout_->stop();
hide();
}
else {
if (!disable_duration())
timeout_->start(); // Restart the timer
}
}
else {
if (toggle_mode())
set_toggle_mode(false);
// The OSD is not visible, show it
show();
}
}
void OSDPretty::setVisible(bool visible) {
if (!visible && fading_enabled_ && fader_->direction() == QTimeLine::Forward) {
fader_->setDirection(QTimeLine::Backward);
fader_->start();
}
else {
QWidget::setVisible(visible);
}
}
void OSDPretty::FaderFinished() {
if (fader_->direction() == QTimeLine::Backward)
hide();
else if (mode_ == Mode_Popup && !disable_duration())
timeout_->start();
}
void OSDPretty::FaderValueChanged(qreal value) {
setWindowOpacity(value);
}
void OSDPretty::Reposition() {
// Make the OSD the proper size
layout()->activate();
resize(sizeHint());
// Work out where to place the OSD. -1 for x or y means "on the right or bottom edge".
if (popup_screen_) {
QRect geometry = popup_screen_->availableGeometry();
int x = popup_pos_.x() < 0 ? geometry.right() - width() : geometry.left() + popup_pos_.x();
int y = popup_pos_.y() < 0 ? geometry.bottom() - height() : geometry.top() + popup_pos_.y();
#ifndef Q_OS_WIN
x = qBound(0, x, geometry.right() - width());
y = qBound(0, y, geometry.bottom() - height());
#endif
move(x, y);
}
// Create a mask for the actual area of the OSD
QBitmap mask(size());
mask.clear();
QPainter p(&mask);
p.setBrush(Qt::color1);
p.drawRoundedRect(BoxBorder().adjusted(-1, -1, 0, 0), kBorderRadius, kBorderRadius);
p.end();
// If there's no compositing window manager running then we have to set an XShape mask.
if (IsTransparencyAvailable())
clearMask();
else {
setMask(mask);
}
#ifdef Q_OS_WIN
// On windows, enable blurbehind on the masked area
QtWin::enableBlurBehindWindow(this, QRegion(mask));
#endif
}
void OSDPretty::enterEvent(QEvent *) {
if (mode_ == Mode_Popup)
setWindowOpacity(0.25);
}
void OSDPretty::leaveEvent(QEvent *) {
setWindowOpacity(1.0);
}
void OSDPretty::mousePressEvent(QMouseEvent *e) {
if (mode_ == Mode_Popup)
hide();
else {
original_window_pos_ = pos();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
drag_start_pos_ = e->globalPosition().toPoint();
#else
drag_start_pos_ = e->globalPos();
#endif
}
}
void OSDPretty::mouseMoveEvent(QMouseEvent *e) {
if (mode_ == Mode_Draggable) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QPoint delta = e->globalPosition().toPoint() - drag_start_pos_;
#else
QPoint delta = e->globalPos() - drag_start_pos_;
#endif
QPoint new_pos = original_window_pos_ + delta;
// Keep it to the bounds of the desktop
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QScreen *screen = current_screen(e->globalPosition().toPoint());
#else
QScreen *screen = current_screen(e->globalPos());
#endif
if (!screen) return;
QRect geometry = screen->availableGeometry();
new_pos.setX(qBound(geometry.left(), new_pos.x(), geometry.right() - width()));
new_pos.setY(qBound(geometry.top(), new_pos.y(), geometry.bottom() - height()));
// Snap to center
int snap_x = geometry.center().x() - width() / 2;
if (new_pos.x() > snap_x - kSnapProximity && new_pos.x() < snap_x + kSnapProximity) {
new_pos.setX(snap_x);
}
move(new_pos);
popup_screen_ = screen;
popup_screen_name_ = screen->name();
}
}
void OSDPretty::mouseReleaseEvent(QMouseEvent *) {
if (current_screen() && mode_ == Mode_Draggable) {
popup_screen_ = current_screen();
popup_screen_name_ = current_screen()->name();
popup_pos_ = current_pos();
emit PositionChanged();
}
}
QScreen *OSDPretty::current_screen(const QPoint &pos) const {
QScreen *screen(nullptr);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
screen = QGuiApplication::screenAt(pos);
#else
Q_UNUSED(pos)
if (window() && window()->windowHandle()) screen = window()->windowHandle()->screen();
#endif
if (!screen) screen = QGuiApplication::primaryScreen();
return screen;
}
QScreen *OSDPretty::current_screen() const { return current_screen(pos()); }
QPoint OSDPretty::current_pos() const {
if (current_screen()) {
QRect geometry = current_screen()->availableGeometry();
int x = pos().x() >= geometry.right() - width() ? -1 : pos().x() - geometry.left();
int y = pos().y() >= geometry.bottom() - height() ? -1 : pos().y() - geometry.top();
return QPoint(x, y);
}
return QPoint(0, 0);
}
void OSDPretty::set_background_color(QRgb color) {
background_color_ = color;
if (isVisible()) update();
}
void OSDPretty::set_background_opacity(qreal opacity) {
background_opacity_ = opacity;
if (isVisible()) update();
}
void OSDPretty::set_foreground_color(QRgb color) {
foreground_color_ = QColor(color);
QPalette p;
p.setColor(QPalette::WindowText, foreground_color_);
ui_->summary->setPalette(p);
ui_->message->setPalette(p);
}
void OSDPretty::set_popup_duration(int msec) {
timeout_->setInterval(msec);
}
void OSDPretty::set_font(QFont font) {
font_ = font;
// Update the UI
ui_->summary->setFont(font);
ui_->message->setFont(font);
// Now adjust OSD size so everything fits
ui_->verticalLayout->activate();
resize(sizeHint());
// Update the position after font change
Reposition();
}

View File

@@ -1,176 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef OSDPRETTY_H
#define OSDPRETTY_H
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QMap>
#include <QString>
#include <QImage>
#include <QPixmap>
#include <QColor>
#include <QFont>
#include <QPoint>
#include <QRect>
#include <QRgb>
class QScreen;
class QTimer;
class QTimeLine;
class QEvent;
class QMouseEvent;
class QPaintEvent;
class QShowEvent;
class Ui_OSDPretty;
class OSDPretty : public QWidget {
Q_OBJECT
public:
enum Mode {
Mode_Popup,
Mode_Draggable,
};
explicit OSDPretty(Mode mode, QWidget *parent = nullptr);
~OSDPretty() override;
static const char *kSettingsGroup;
static const int kDropShadowSize;
static const int kBorderRadius;
static const int kMaxIconSize;
static const int kSnapProximity;
static const QRgb kPresetBlue;
static const QRgb kPresetRed;
static bool IsTransparencyAvailable();
void SetMessage(const QString &summary, const QString& message, const QImage &image);
void ShowMessage(const QString &summary, const QString& message, const QImage &image);
// Controls the fader. This is enabled by default on Windows.
void set_fading_enabled(bool enabled) { fading_enabled_ = enabled; }
// Popup duration in seconds. Only used in Mode_Popup.
void set_popup_duration(int msec);
// These will get overwritten when ReloadSettings() is called
void set_foreground_color(QRgb color);
void set_background_color(QRgb color);
void set_background_opacity(qreal opacity);
void set_font(QFont font);
QRgb foreground_color() const { return foreground_color_.rgb(); }
QRgb background_color() const { return background_color_.rgb(); }
qreal background_opacity() const { return background_opacity_; }
QString popup_screen() const { return popup_screen_name_; }
QPoint popup_pos() const { return popup_pos_; }
QFont font() const { return font_; }
bool disable_duration() const { return disable_duration_; }
// When the user has been moving the popup, use these to get its current position and screen.
// Note that these return invalid values if the popup is hidden.
QScreen *current_screen() const;
QScreen *current_screen(const QPoint &pos) const;
QPoint current_pos() const;
// QWidget
void setVisible(bool visible) override;
bool toggle_mode() const { return toggle_mode_; }
void set_toggle_mode(bool toggle_mode) { toggle_mode_ = toggle_mode; }
signals:
void PositionChanged();
public slots:
void ReloadSettings();
protected:
void paintEvent(QPaintEvent *e) override;
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void showEvent(QShowEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
void Reposition();
void Load();
QRect BoxBorder() const;
private slots:
void FaderValueChanged(qreal value);
void FaderFinished();
void ScreenAdded(QScreen *screen);
void ScreenRemoved(QScreen *screen);
private:
Ui_OSDPretty *ui_;
Mode mode_;
// Settings loaded from QSettings
QColor foreground_color_;
QColor background_color_;
float background_opacity_;
QString popup_screen_name_;
QPoint popup_pos_;
QScreen *popup_screen_;
QFont font_;
// The OSD is kept always on top until you click (no timer)
bool disable_duration_;
// Cached pixmaps
QPixmap shadow_edge_[4];
QPixmap shadow_corner_[4];
QPixmap background_;
// For dragging the OSD
QPoint original_window_pos_;
QPoint drag_start_pos_;
// For timeout of notification
QTimer *timeout_;
// For fading
bool fading_enabled_;
QTimeLine *fader_;
// Toggling requested, we have to show or hide the OSD
bool toggle_mode_;
QMap<QString, QScreen*> screens_;
};
#endif // OSDPRETTY_H

View File

@@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OSDPretty</class>
<widget class="QWidget" name="OSDPretty">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>80</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">OSDPretty {
background-color: transparent;
}
#summary {
font-weight: bold;
font-size: larger;
}
</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QLabel" name="icon"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="summary">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="message">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>