ukui-notification/libukui-notification/popup-notification.cpp

433 lines
11 KiB
C++
Raw Normal View History

2023-07-31 18:01:44 +08:00
/*
* Copyright (C) 2023, KylinSoft Co., Ltd.
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*
* Authors: iaom <zhangpengfei@kylinos.cn>
*/
#include "popup-notification.h"
#include <QDBusArgument>
#include <QDebug>
#include <QImageReader>
namespace UkuiNotification {
class PopupNotificationPrivate
{
public:
PopupNotificationPrivate();
~PopupNotificationPrivate();
QImage parseImageHint(const QDBusArgument &arg);
void loadImageFromPath(const QString &path);
QSize maximumImageSize();
uint m_id = 0;
QString m_applicationName;
QString m_applicationIconName;
QString m_summary;
QString m_body;
QList<QPair<QString, QString>> m_actions;
bool m_hasDefaultAction = false;
QString m_defaultActionLabel;
QVariantMap m_hints = QVariantMap();
QString m_category;
QImage m_image;
QString m_icon;
QDateTime m_createdTime;
QString m_desktopEntry;
PopupNotification::Urgency m_urgency = PopupNotification::NormalUrgency;
int m_timeout = -1;
bool m_resident = false;
bool m_transient = false;
QString m_soundFile;
QString m_soundName;
bool m_suppressSound = false;
QString m_display;
QStringList m_actionState;
bool m_noFold = false;
int m_popupTimeout = 6000; //ms
2023-07-31 18:01:44 +08:00
};
}
using namespace UkuiNotification;
PopupNotificationPrivate::PopupNotificationPrivate()
{
}
PopupNotificationPrivate::~PopupNotificationPrivate() = default;
QImage PopupNotificationPrivate::parseImageHint(const QDBusArgument &arg)
{
//copy from plasma-workspace
int width, height, rowStride, hasAlpha, bitsPerSample, channels;
QByteArray pixels;
char *ptr;
char *end;
arg.beginStructure();
arg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> pixels;
arg.endStructure();
auto copyLineRGB32 = [](QRgb *dst, const char *src, int width) {
const char *end = src + width * 3;
for (; src != end; ++dst, src += 3) {
*dst = qRgb(src[0], src[1], src[2]);
}
};
auto copyLineARGB32 = [](QRgb *dst, const char *src, int width) {
const char *end = src + width * 4;
for (; src != end; ++dst, src += 4) {
*dst = qRgba(src[0], src[1], src[2], src[3]);
}
};
QImage::Format format = QImage::Format_Invalid;
void (*fcn)(QRgb *, const char *, int) = nullptr;
if (bitsPerSample == 8) {
if (channels == 4) {
format = QImage::Format_ARGB32;
fcn = copyLineARGB32;
} else if (channels == 3) {
format = QImage::Format_RGB32;
fcn = copyLineRGB32;
}
}
if (format == QImage::Format_Invalid) {
qWarning() << "Unsupported image format (hasAlpha:" << hasAlpha << "bitsPerSample:" << bitsPerSample
<< "channels:" << channels << ")";
return QImage();
}
QImage image(width, height, format);
ptr = pixels.data();
end = ptr + pixels.length();
for (int y = 0; y < height; ++y, ptr += rowStride) {
if (ptr + channels * width > end) {
qWarning() << "Image data is incomplete. y:" << y << "height:" << height;
break;
}
fcn((QRgb *)image.scanLine(y), ptr, width);
}
return image;
}
void PopupNotificationPrivate::loadImageFromPath(const QString &path)
{
//copy from plasma-workspace
QUrl imageUrl;
if (path.startsWith(QLatin1Char('/'))) {
imageUrl = QUrl::fromLocalFile(path);
} else if (path.contains(QLatin1Char('/'))) {
imageUrl = QUrl(path);
if (!imageUrl.isLocalFile()) {
qDebug() << "Refused to load image from" << path << "which isn't a valid local location.";
return;
}
}
if (!imageUrl.isValid()) {
m_icon = path;
return;
}
QImageReader reader(imageUrl.toLocalFile());
reader.setAutoTransform(true);
const QSize imageSize = reader.size();
if (imageSize.isValid() && (imageSize.width() > maximumImageSize().width() || imageSize.height() > maximumImageSize().height())) {
const QSize thumbnailSize = imageSize.scaled(maximumImageSize(), Qt::KeepAspectRatio);
reader.setScaledSize(thumbnailSize);
}
m_image = reader.read();
}
QSize PopupNotificationPrivate::maximumImageSize()
{
return QSize(256, 256);
}
PopupNotification::PopupNotification(uint id) : d(new PopupNotificationPrivate())
{
d->m_id = id;
}
PopupNotification::PopupNotification(const PopupNotification &other) : d(new PopupNotificationPrivate(*other.d))
{
}
PopupNotification &PopupNotification::operator=( const PopupNotification &other)
{
*d = *other.d;
return *this;
}
PopupNotification &PopupNotification::operator=(PopupNotification &&other) Q_DECL_NOEXCEPT
{
d = other.d;
other.d = nullptr;
return *this;
}
PopupNotification::~PopupNotification()
{
if(d) {
delete d;
d = nullptr;
}
}
uint PopupNotification::id() const
{
return d->m_id;
}
QString PopupNotification::applicationName() const
{
return d->m_applicationName;
}
void PopupNotification::setApplicationName(const QString &applicationName)
{
d->m_applicationName = applicationName;
}
QString PopupNotification::applicationIconName() const
{
return d->m_applicationIconName;
}
void PopupNotification::setApplicationIconName(const QString &applicationIconName)
{
d->m_applicationIconName = applicationIconName;
}
QString PopupNotification::summary() const
{
return d->m_summary;
}
void PopupNotification::setSummary(const QString &summary)
{
d->m_summary = summary;
}
QString PopupNotification::body() const
{
return d->m_body;
}
void PopupNotification::setBody(const QString &body)
{
d->m_body = body;
}
bool PopupNotification::hasDefaultAction() const
{
return d->m_hasDefaultAction;
}
QString PopupNotification::defaultActionLabel()
{
return d->m_defaultActionLabel;
}
void PopupNotification::setActions(const QStringList &actions)
{
if (actions.count() % 2 != 0) {
qWarning() << "List of actions must contain an even number of items, tried to set actions to" << actions;
return;
}
d->m_hasDefaultAction = false;
for (int i = 0; i < actions.count(); i += 2) {
const QString &key = actions.at(i);
const QString &label = actions.at(i + 1);
if (!d->m_hasDefaultAction && key == QLatin1String("default")) {
d->m_hasDefaultAction = true;
d->m_defaultActionLabel = label;
}
d->m_actions.append({key, label});
}
}
QList<QPair<QString, QString>> PopupNotification::actions() const
{
return d->m_actions;
}
QVariantMap PopupNotification::hints() const
{
return d->m_hints;
}
void PopupNotification::setHints(const QVariantMap &hints)
{
d->m_hints = hints;
d->m_desktopEntry = hints.value(QStringLiteral("desktop-entry")).toString();
bool ok;
int urgency = hints.value(QStringLiteral("urgency")).toInt(&ok);
if(ok) {
switch (urgency) {
default:
case 0: //低等级,不弹窗
setUrgency(Urgency::LowUrgency);
d->m_popupTimeout = 0;
break;
case 1: //中等级弹窗时间默认6秒
setUrgency(Urgency::NormalUrgency);
d->m_popupTimeout = 6000;
2023-07-31 18:01:44 +08:00
break;
case 2: //高等级,默认弹窗常驻,不可折叠
setUrgency(Urgency::CriticalUrgency);
d->m_popupTimeout = -1;
d->m_noFold = true;
break;
}
}
d->m_createdTime = QDateTime::fromString(hints.value(QStringLiteral("x-ukui-createdTime")).toString());
d->m_resident = hints.value(QStringLiteral("resident")).toBool();
d->m_transient = hints.value(QStringLiteral("transient")).toBool();
d->m_category = hints.value(QStringLiteral("category")).toString();
auto end = hints.end();
auto it = hints.find(QStringLiteral("image-data"));
if (it != end) {
d->m_image = d->parseImageHint(it->value<QDBusArgument>());
if (!d->m_image.isNull()) {
const QSize max = d->maximumImageSize();
if (d->m_image.size().width() > max.width() || d->m_image.size().height() > max.height()) {
d->m_image = d->m_image.scaled(max, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
}
}
if (d->m_image.isNull()) {
it = hints.find(QStringLiteral("image-path"));
if (it != end) {
d->loadImageFromPath(it->toString());
}
}
d->m_soundFile = hints.value(QStringLiteral("sound-file")).toString();
d->m_soundName = hints.value(QStringLiteral("sound-name")).toString();
d->m_suppressSound = hints.value(QStringLiteral("suppress-sound")).toBool();
d->m_display = hints.value(QStringLiteral("x-ukui-display")).toString();
d->m_actionState = hints.value(QStringLiteral("x-ukui-action-state")).toStringList();
QVariant tmp = hints.value(QStringLiteral("x-ukui-no-fold"));
if(!tmp.isNull()) {
d->m_noFold = tmp.toBool();
}
tmp = hints.value(QStringLiteral("x-ukui-popup-timeout"));
if(!tmp.isNull()) {
d->m_popupTimeout = tmp.toInt();
}
}
int PopupNotification::timeout() const
{
return d->m_timeout;
}
void PopupNotification::setTimeout(int timeout)
{
d->m_timeout = timeout;
}
QDateTime PopupNotification::createdTime() const
{
return d->m_createdTime;
}
bool PopupNotification::enableActionIcons() const
{
//TODO 可以支持action-icon
return false;
}
QString PopupNotification::category() const
{
return d->m_category;
}
QString PopupNotification::desktopEntry() const
{
return d->m_desktopEntry;
}
QImage PopupNotification::image() const
{
return d->m_image;
}
QString PopupNotification::icon() const
{
return d->m_icon;
}
bool PopupNotification::resident() const
{
return d->m_resident;
}
QString PopupNotification::soundFile() const
{
return d->m_soundFile;
}
QString PopupNotification::soundName() const
{
return d->m_soundName;
}
bool PopupNotification::suppressSound() const
{
return d->m_suppressSound;
}
bool PopupNotification::transient() const
{
return d->m_transient;
}
QString PopupNotification::display() const
{
return d->m_display;
}
void PopupNotification::setUrgency(PopupNotification::Urgency urgency)
{
d->m_urgency = urgency;
}
PopupNotification::Urgency PopupNotification::urgency() const
{
return d->m_urgency;
}
QStringList PopupNotification::actionState() const {
return d->m_actionState;
}
bool PopupNotification::noFold() const {
return d->m_noFold;
}
int PopupNotification::popupTimeout() const {
return d->m_popupTimeout;
}