Replace Icon implementation by an extension of QIcon.

This commit is contained in:
Patrick von Reth 2015-09-22 15:04:03 +02:00
parent 553a7a0bb2
commit 2190957ba7
17 changed files with 116 additions and 373 deletions

View File

@ -3,12 +3,10 @@ set(SnoreNotify_SRCS ${SnoreNotify_SRCS}
notification/notification_p.cpp
notification/notificationaction.cpp
notification/icon.cpp
notification/icon_p.cpp
PARENT_SCOPE)
set(SnoreNotify_HDR
notification.h
notification_p.h
notificationaction.h
icon.h
)

View File

@ -20,20 +20,20 @@
#include "../snore.h"
#include "../snore_p.h"
#include "notification/icon_p.h"
#include <QApplication>
#include <QMutex>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QBuffer>
using namespace Snore;
QByteArray Icon::dataFromImage(const QImage &image)
{
QByteArray data;
QBuffer buffer(&data);
buffer.open(QBuffer::WriteOnly);
image.save(&buffer, "PNG");
return data;
}
QSet<QString> Icon::s_localImageCache;
QMap<QUrl,Icon> Icon::s_downloadImageCache;
Icon Icon::defaultIcon()
{
@ -41,73 +41,79 @@ Icon Icon::defaultIcon()
return icon;
}
Icon::Icon(const QImage &img):
d(new IconData(img))
Icon Icon::fromWebUrl(const QUrl &url, int maxTime)
{
Icon icon = defaultIcon();
snoreDebug(SNORE_DEBUG) << url;
if (!s_downloadImageCache.contains(url)) {
QTime timeout;
timeout.start();
QMutex isDownloading;
isDownloading.lock();
snoreDebug(SNORE_DEBUG) << "Downloading:" << url;
QNetworkAccessManager *manager = new QNetworkAccessManager();
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::downloadProgress, [&](qint64 bytesReceived, qint64 bytesTotal) {
snoreDebug(SNORE_DEBUG) << "Downloading:" << url << bytesReceived / double(bytesTotal) * 100.0 << "%";
});
QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [ & ](QNetworkReply::NetworkError code) {
snoreDebug(SNORE_WARNING) << "Error downloading" << url << ":" << code;
isDownloading.unlock();
});
QObject::connect(reply, &QNetworkReply::finished, [ & ]() {
if(reply->isOpen()){
QImage img(QImage::fromData(reply->readAll(), "PNG"));
icon = Icon(QPixmap::fromImage(img));
s_downloadImageCache.insert(url, icon);
snoreDebug(SNORE_DEBUG) << url << "added to cache.";
isDownloading.unlock();
} else {
snoreDebug(SNORE_DEBUG) << "Download of " << url << "timed out.";
}
});
while(!isDownloading.tryLock() && timeout.elapsed() < maxTime)
{
qApp->processEvents();
}
reply->close();
reply->deleteLater();
manager->deleteLater();
} else {
icon = s_downloadImageCache.value(url, defaultIcon());
snoreDebug(SNORE_DEBUG) << url << "from cache";
}
return icon;
}
Icon::Icon(const QIcon &icon):
d(new IconData(icon.pixmap(32).toImage()))
{}
Icon::Icon(const QString &url):
d(new IconData(url))
{
}
Icon::Icon(const Icon &other):
d(other.d)
{
}
Icon &Icon::operator=(const Icon &other)
{
d = other.d;
return *this;
}
Icon::~Icon()
Icon::Icon(const QPixmap &pixmap):
QIcon(pixmap)
{
}
const QImage &Icon::image() const
Icon::Icon(const QIcon &other):
QIcon(other)
{
return d->image();
}
QString Icon::localUrl()const
Icon::Icon(const QString &fileName):
QIcon(fileName)
{
return d->localUrl();
}
bool Icon::isLocalFile() const
QString Icon::localUrl(const QSize &size, Mode mode, State state)const
{
return d->m_isLocalFile;
QString localFileName = SnoreCorePrivate::tempPath() + QLatin1Char('/') + QString::number(cacheKey()) + QLatin1String("_") + QString::number(size.width())+ QLatin1String("x") + QString::number(size.height()) + QLatin1String(".png");
if(!s_localImageCache.contains(localFileName)){
QImage(pixmap(size,mode,state).toImage()).save(localFileName, "PNG");
s_localImageCache.insert(localFileName);
}
return localFileName;
}
bool Icon::isValid() const
{
return !(d->m_img.isNull() && d->m_url.isEmpty());
}
Icon Icon::scaled(const QSize &s) const
{
return Icon(image().scaled(s, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
QString Icon::url() const
{
return d->m_url;
}
bool Snore::Icon::isRemoteFile() const
{
return d->m_isRemoteFile;
}
QDebug operator<< (QDebug debug, const Snore::Icon &icon)
{
debug << "Snore::Icon(" << (icon.url().isEmpty() ? icon.d->m_localUrl : icon.url()) << ")" ;
return debug.maybeSpace();
}

View File

@ -20,106 +20,43 @@
#define NOTIFICATION_ICON_H
#include "libsnore/snore_exports.h"
#include <QIcon>
#include <QSharedData>
#include <QDebug>
namespace Snore
{
class Icon;
}
SNORE_EXPORT QDebug operator<< (QDebug, const Snore::Icon &);
namespace Snore
{
class IconData;
/**
* Icon contains an image for Notifications.
* Icon uses a shared datamodel, it's content is never copied and automatically released.
* @author Patrick von Reth \<vonreth at kde.org\>
*/
class SNORE_EXPORT Icon
class SNORE_EXPORT Icon : public QIcon
{
public:
static QByteArray dataFromImage(const QImage &image);
static Icon defaultIcon();
static Icon fromWebUrl(const QUrl& url, int maxTime = 5000);
/**
* Creates an Icon from an QImage
* @param img the image
*/
Icon(const QImage &img);
/**
* Creates an Icon from QIcon @p icon
*/
Icon(const QIcon &icon);
Icon(const QPixmap &pixmap);
Icon(const QIcon &other);
explicit Icon(const QString &fileName);
/**
* Creates an Icon from a url
* Valid urls are "file://home/foo/foo.png", "C:\\foo.png", ":/root/foo.png", "http://foo.com/foo.png"
* @param url the url
*/
explicit Icon(const QString &url);
/**
* Creates a copy of other
* @param other
*/
Icon(const Icon &other);
/**
* Creates a copy of other
* @param other
*/
Icon &operator=(const Icon &other);
~Icon();
/**
*
* @return a QImage from the Icon
*/
const QImage &image() const;
/**
*
* @return a local url to a file representing the Icon
*/
QString localUrl() const;
QString localUrl(const QSize &size, Mode mode = Normal, State state = Off) const;
/**
*
* @return the url of this Icon or an empty string if created from a QImage
*/
QString url() const;
/**
*
* @return whether the Icon was created from a local file
*/
bool isLocalFile() const;
/**
*
* @return whether the Icon was created from a remote file
*/
bool isRemoteFile() const;
/**
*
* @return whether this is a valid Icon
*/
bool isValid() const;
Icon scaled(const QSize &s) const;
private:
Icon() = delete;
QExplicitlySharedDataPointer<IconData> d;
friend SNORE_EXPORT QDebug(::operator<<)(QDebug, const Snore::Icon &);
static QSet<QString> s_localImageCache;
static QMap<QUrl,Icon> s_downloadImageCache;
};
}

View File

@ -1,120 +0,0 @@
/*
SnoreNotify is a Notification Framework based on Qt
Copyright (C) 2013-2014 Patrick von Reth <vonreth@kde.org>
SnoreNotify is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SnoreNotify 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with SnoreNotify. If not, see <http://www.gnu.org/licenses/>.
*/
#include "icon_p.h"
#include "../snore_p.h"
#include "../utils.h"
#include <QApplication>
#include <QFile>
#include <QNetworkAccessManager>
#include <QNetworkReply>
using namespace Snore;
QSet<QString> IconData::s_localImageCache;
IconData::IconData(const QString &url):
m_url(url),
m_hash(Utils::computeMD5Hash(m_url.toLatin1())),
m_localUrl(createLocalFileName(m_hash)),
m_isLocalFile(false),
m_isResource(m_url.startsWith(QLatin1String(":/")) || m_url.startsWith(QLatin1String("qrc:/")))
{
if (!m_isResource && QFile::exists(url)) {
m_isLocalFile = true;
m_localUrl = url;
}
m_isRemoteFile = !m_isLocalFile && !m_isResource;
if (!m_isLocalFile && !s_localImageCache.contains(m_localUrl)) {
if (m_isRemoteFile) {
m_isDownloading = true;
snoreDebug(SNORE_DEBUG) << "Downloading:" << m_url;
QNetworkAccessManager *manager = new QNetworkAccessManager();
QNetworkRequest request(QUrl::fromUserInput(m_url));
QNetworkReply *reply = manager->get(request);
// QObject::connect(reply, &QNetworkReply::downloadProgress, [&](qint64 bytesReceived, qint64 bytesTotal) {
// snoreDebug(SNORE_DEBUG) << "Downloading:" << m_localUrl << bytesReceived / double(bytesTotal) * 100.0 << "%";
// });
QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [ &, reply, manager](QNetworkReply::NetworkError code) {
snoreDebug(SNORE_WARNING) << "Error:" << code;
reply->deleteLater();
manager->deleteLater();
m_isDownloading = false;
});
QObject::connect(reply, &QNetworkReply::finished, [ &, reply, manager]() {
m_img = QImage::fromData(reply->readAll(), "PNG");
m_img.save(m_localUrl, "PNG");
s_localImageCache.insert(m_localUrl);
snoreDebug(SNORE_DEBUG) << m_localUrl << "added to cache";
reply->close();
reply->deleteLater();
manager->deleteLater();
m_isDownloading = false;
});
} else if (m_isResource) {
m_img = QImage(url);
m_img.save(m_localUrl, "PNG");
s_localImageCache.insert(m_localUrl);
snoreDebug(SNORE_DEBUG) << m_localUrl << "added to cache";
}
}
}
IconData::IconData(const QImage &img):
m_img(img),
m_hash(Utils::computeMD5Hash(Icon::dataFromImage(img))),
m_localUrl(createLocalFileName(m_hash)),
m_isLocalFile(false),
m_isResource(false),
m_isRemoteFile(false)
{
if (!s_localImageCache.contains(m_localUrl)) { //double check as image() could have called download
img.save(m_localUrl , "PNG");
s_localImageCache.insert(m_localUrl);
snoreDebug(SNORE_DEBUG) << m_localUrl << "added to cache";
}
}
IconData::~IconData()
{
}
const QImage &IconData::image()
{
if (m_img.isNull()) {
while (m_isDownloading) {
qApp->processEvents();
}
if (!m_isRemoteFile) {
m_img = QImage(m_url);
}
}
return m_img;
}
QString IconData::localUrl()
{
while (m_isDownloading) {
qApp->processEvents();
}
return m_localUrl;
}

View File

@ -1,65 +0,0 @@
/*
SnoreNotify is a Notification Framework based on Qt
Copyright (C) 2013-2014 Patrick von Reth <vonreth@kde.org>
SnoreNotify is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SnoreNotify 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with SnoreNotify. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ICONDATA_H
#define ICONDATA_H
#include "notification.h"
#include "snore_p.h"
#include <QImage>
#include <QSharedData>
#include <QSet>
namespace Snore
{
class IconData : public QSharedData
{
public:
IconData(const QString &url);
IconData(const QImage &img);
~IconData();
const QImage &image();
QString localUrl();
QImage m_img;
QString m_url;
QString m_hash;
QString m_localUrl;
bool m_isLocalFile;
bool m_isResource;
bool m_isRemoteFile;
bool m_isDownloading = false;
static QSet<QString> s_localImageCache;
private:
Q_DISABLE_COPY(IconData)
inline QString createLocalFileName(const QString &hash)
{
return SnoreCorePrivate::tempPath() + QLatin1Char('/') + hash + QLatin1String(".png");
}
};
}
#endif // ICONDATA_H

View File

@ -129,6 +129,7 @@ void NotificationData::stopTimeoutTimer()
{
if (m_timeoutTimer) {
m_timeoutTimer->deleteLater();
m_timeoutTimer = nullptr;
}
}

View File

@ -88,7 +88,7 @@ private:
QHash<int, Action> m_actions;
Hint m_hints;
Notification m_toReplace;
QPointer<QTimer> m_timeoutTimer;
QTimer *m_timeoutTimer = nullptr;
QSet<const QObject *> m_activeIn;
bool m_isBroadcasted = false;
SnorePlugin *m_source = nullptr;

View File

@ -108,6 +108,16 @@ void Utils::raiseWindowToFront(qlonglong wid)
return string;
}
QByteArray Utils::dataFromImage(const QImage &image)
{
QByteArray data;
QBuffer buffer(&data);
buffer.open(QBuffer::WriteOnly);
image.save(&buffer, "PNG");
return data;
}
#ifdef Q_OS_WIN
int Utils::attatchToActiveProcess()
{

View File

@ -21,7 +21,7 @@
#include "snore_exports.h"
#include "snoreglobals.h"
#include <QCryptographicHash>
#include <QImage>
#include <QObject>
namespace Snore
@ -105,14 +105,6 @@ public:
*/
static QString normalizeMarkup(QString string, MARKUP_FLAGS tags);
/**
* Computes a md5 hash of the provided data.
*/
static inline QString computeMD5Hash(const QByteArray &data)
{
return QString::fromUtf8(QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex());
}
/**
* Version number prefix for the settings.
*/
@ -136,6 +128,8 @@ public:
}
}
static QByteArray dataFromImage(const QImage &image);
private:
#ifdef Q_OS_WIN
static int attatchToActiveProcess();

View File

@ -54,10 +54,9 @@ void FreedesktopBackend::slotNotify(Notification noti)
actions << QString::number(k) << noti.actions()[k].name();
}
QVariantMap hints;
if (noti.icon().isValid()) {
FreedesktopImageHint image(noti.icon().image());
hints.insert(QLatin1String("image_data"), QVariant::fromValue(image));
}
FreedesktopImageHint image(noti.icon().pixmap(QSize(128,128)).toImage());
hints.insert(QLatin1String("image_data"), QVariant::fromValue(image));
char urgency = 1;
if (noti.priority() < 0) {

View File

@ -85,7 +85,7 @@ void GrowlBackend::slotRegisterApplication(const Application &application)
settingsValue(QLatin1String("Password")).toString().toUtf8().constData(),
application.name().toUtf8().constData());
m_applications.insert(application.name(), growl);
growl->Register(alerts, application.icon().localUrl().toUtf8().constData());
growl->Register(alerts, application.icon().localUrl(QSize(128,128)).toUtf8().constData());
}
@ -108,9 +108,7 @@ void GrowlBackend::slotNotify(Notification notification)
notification.title().toUtf8().constData(),
notification.text().toUtf8().constData());
if (notification.icon().isValid()) {
data.setIcon(notification.icon().localUrl().toUtf8().constData());
}
data.setIcon(notification.icon().localUrl(QSize(128,128)).toUtf8().constData());
data.setCallbackData("1");
growl->Notify(data);

View File

@ -174,7 +174,7 @@ void SnarlBackend::slotRegisterApplication(const Application &application)
QString password = settingsValue(QLatin1String("Password")).toString();
LONG32 result = snarlInterface->Register(appName.toUtf8().constData(),
application.name().toUtf8().constData(),
application.icon().localUrl().toUtf8().constData(),
application.icon().localUrl(QSize(128,128)).toUtf8().constData(),
password.isEmpty() ? 0 : password.toUtf8().constData(),
(HWND)m_eventLoop->winId(), SNORENOTIFIER_MESSAGE_ID);
snoreDebug(SNORE_DEBUG) << result;
@ -182,7 +182,7 @@ void SnarlBackend::slotRegisterApplication(const Application &application)
foreach (const Alert &alert, application.alerts()) {
snarlInterface->AddClass(alert.name().toUtf8().constData(),
alert.name().toUtf8().constData(),
0, 0, alert.icon().localUrl().toUtf8().constData());
0, 0, alert.icon().localUrl(QSize(128,128)).toUtf8().constData());
}
}
@ -224,8 +224,8 @@ void SnarlBackend::slotNotify(Notification notification)
notification.title().toUtf8().constData(),
notification.text().toUtf8().constData(),
notification.timeout(),
notification.icon().isLocalFile() ? notification.icon().localUrl().toUtf8().constData() : 0,
!notification.icon().isLocalFile() ? Icon::dataFromImage(notification.icon().image()).toBase64().constData() : 0,
nullptr,
Utils::dataFromImage(notification.icon().pixmap(QSize(128,128)).toImage()).toBase64().constData(),
priority);
foreach (const Action &a, notification.actions()) {
@ -241,8 +241,8 @@ void SnarlBackend::slotNotify(Notification notification)
notification.title().toUtf8().constData(),
notification.text().toUtf8().constData(),
notification.timeout(),
notification.icon().isLocalFile() ? notification.icon().localUrl().toUtf8().constData() : 0,
!notification.icon().isLocalFile() ? Icon::dataFromImage(notification.icon().image()).toBase64().constData() : 0,
nullptr,
Utils::dataFromImage(notification.icon().pixmap(QSize(128,128)).toImage()).toBase64().constData(),
priority);
}

View File

@ -36,12 +36,10 @@ void SnoreToast::slotNotify(Notification notification)
arguements << QLatin1String("-t")
<< notification.title()
<< QLatin1String("-m")
<< notification.text();
if (notification.icon().isValid()) {
arguements << QLatin1String("-p")
<< QDir::toNativeSeparators(notification.icon().localUrl());
}
arguements << QLatin1String("-w")
<< notification.text()
<< QLatin1String("-p")
<< QDir::toNativeSeparators(notification.icon().localUrl(QSize(1024,1024)))
<< QLatin1String("-w")
<< QLatin1String("-appID")
<< appId(notification.application())
<< QLatin1String("-id")

View File

@ -76,15 +76,7 @@ uint FreedesktopFrontend::Notify(const QString &app_name, uint replaces_id,
Notification::Prioritys priotity = Notification::NORMAL;
if (!SnoreCore::instance().aplications().contains(app_name)) {
qDebug() << QIcon::themeSearchPaths();
QIcon qicon = QIcon::fromTheme(app_icon, QIcon(QLatin1String(":/root/snore.png")));
QSize max;
foreach (const QSize &s, qicon.availableSizes()) {
if (s.width()*s.height() > max.width()*max.height()) {
max = s;
}
}
Icon appIcon(qicon.pixmap(max).toImage());
Icon appIcon(QIcon::fromTheme(app_icon, QIcon(QLatin1String(":/root/snore.png"))));
app = Application(app_name, appIcon);
app.hints().setValue("use-markup", true);
SnoreCore::instance().registerApplication(app);
@ -96,7 +88,7 @@ uint FreedesktopFrontend::Notify(const QString &app_name, uint replaces_id,
if (hints.contains(QLatin1String("image_data"))) {
FreedesktopImageHint image;
hints.value(QLatin1String("image_data")).value<QDBusArgument>() >> image;
icon = Icon(image.toQImage());
icon = Icon(QPixmap::fromImage(image.toQImage()));
}
if (hints.contains(QLatin1String("urgency"))) {

View File

@ -242,9 +242,9 @@ void PushoverFrontend::getMessages()
Application app = SnoreCore::instance().aplications().value(appName);
if (!app.isValid()) {
Icon icon(QLatin1String("https://api.pushover.net/icons/") +
Icon icon(Icon::fromWebUrl(QUrl::fromEncoded((QLatin1String("https://api.pushover.net/icons/") +
notification.value(QLatin1String("icon")).toString() +
QLatin1String(".png"));
QLatin1String(".png")).toUtf8().constData())));
app = Application(appName, icon);
SnoreCore::instance().registerApplication(app);
}

View File

@ -48,6 +48,7 @@ protected:
public Q_SLOTS:
void slotActionInvoked(Snore::Notification notification);
void connectToService();
Q_SIGNALS:
void loggedInChanged(bool isLoggedIn);
@ -62,7 +63,6 @@ private:
QString secret();
QString device();
void connectToService();
void disconnectService();
void registerDevice(const QString &secret, const QString &deviceName);

View File

@ -54,14 +54,9 @@ void Toasty::slotNotify(Notification notification)
QHttpPart icon;
Icon sIcon = notification.icon();
QSize iconSize = notification.icon().image().size();
if (iconSize.height() > 128 || iconSize.width() > 128) {
sIcon = sIcon.scaled(QSize(128, 128));
}
icon.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QLatin1String("form-data; name=\"image\"; filename=\"") + sIcon.localUrl() + QLatin1Char('"')));
icon.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QLatin1String("form-data; name=\"image\"; filename=\"") + notification.icon().localUrl(QSize(128,128)) + QLatin1Char('"')));
icon.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("image/png")));
QFile *file = new QFile(sIcon.localUrl());
QFile *file = new QFile(notification.icon().localUrl(QSize(128,128)));
file->open(QIODevice::ReadOnly);
icon.setBodyDevice(file);
mp->append(icon);