Use virtual functions for OSD
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user