From 5bde427e9c68f7f832e0b2bc7083dc8a5ae68dad Mon Sep 17 00:00:00 2001
From: Lukas Holecek <hluk@email.cz>
Date: Mon, 12 Apr 2021 19:39:49 +0200
Subject: [PATCH] Allow to use non-native notifications
Avoid using native notifications on unsupported Windows 7.
---
shared/themes/notification.css | 15 +
src/CMakeLists.txt | 1 +
src/app/clipboardserver.cpp | 29 ++
src/common/appconfig.h | 25 ++
src/gui/actionhandler.cpp | 1 -
src/gui/configurationmanager.cpp | 5 +
src/gui/mainwindow.cpp | 1 -
src/gui/notification.h | 70 +---
src/gui/notificationbasic.cpp | 387 ++++++++++++++++++
src/gui/notificationbasic.h | 25 ++
src/gui/notificationdaemon.cpp | 163 +++++++-
src/gui/notificationdaemon.h | 38 ++
.../notificationnative.cpp} | 119 ++++--
.../notificationnative/notificationnative.h | 28 ++
src/gui/theme.cpp | 9 +
src/gui/theme.h | 2 +
src/notifications.cmake | 20 +-
src/scriptable/scriptable.cpp | 6 +-
src/scriptable/scriptableproxy.cpp | 7 +-
src/tests/tests.cpp | 2 +
src/ui/configtabappearance.ui | 16 +
src/ui/configtabnotifications.ui | 198 ++++++++-
utils/appveyor/after_build.sh | 20 +-
utils/appveyor/before_build.sh | 2 +-
24 files changed, 1078 insertions(+), 111 deletions(-)
create mode 100644 shared/themes/notification.css
create mode 100644 src/gui/notificationbasic.cpp
create mode 100644 src/gui/notificationbasic.h
rename src/gui/{notification.cpp => notificationnative/notificationnative.cpp} (69%)
create mode 100644 src/gui/notificationnative/notificationnative.h
diff --git a/shared/themes/notification.css b/shared/themes/notification.css
new file mode 100644
index 000000000..b8eff38fa
--- /dev/null
+++ b/shared/themes/notification.css
@@ -0,0 +1,15 @@
+#Notification, #Notification QWidget
+{
+ /* Resets notification opacity. It will be set in NotificationDaemon::setNotificationOpacity(). */
+ ;background: ${notification_bg + #000}
+}
+#Notification QWidget{
+ ;color: ${notification_fg}
+ ;${notification_font}
+}
+#Notification #NotificationTitle{
+ ;${scale=1.2}${notification_font}${scale=1}
+}
+#Notification #NotificationTip{
+ ;font-style: italic
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e841901f0..a708d74d8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,6 +4,7 @@ file(GLOB copyq_SOURCES
app/*.cpp
common/*.cpp
gui/*.cpp
+ gui/notification.h
item/*.cpp
scriptable/*.cpp
scriptable/scriptableproxy.h
diff --git a/src/app/clipboardserver.cpp b/src/app/clipboardserver.cpp
index c94bade1d..5f9df01e5 100644
--- a/src/app/clipboardserver.cpp
+++ b/src/app/clipboardserver.cpp
@@ -700,6 +700,35 @@ void ClipboardServer::loadSettings()
startMonitoring();
}
+ m_sharedData->notifications->setNativeNotificationsEnabled(
+ appConfig.option<Config::native_notifications>() );
+ m_sharedData->notifications->setNotificationOpacity(
+ m_sharedData->theme.color("notification_bg").alphaF() );
+ m_sharedData->notifications->setNotificationStyleSheet(
+ m_sharedData->theme.getNotificationStyleSheet() );
+
+ int id = appConfig.option<Config::notification_position>();
+ NotificationDaemon::Position position;
+ switch (id) {
+ case 0: position = NotificationDaemon::Top; break;
+ case 1: position = NotificationDaemon::Bottom; break;
+ case 2: position = NotificationDaemon::TopRight; break;
+ case 3: position = NotificationDaemon::BottomRight; break;
+ case 4: position = NotificationDaemon::BottomLeft; break;
+ default: position = NotificationDaemon::TopLeft; break;
+ }
+ m_sharedData->notifications->setPosition(position);
+
+ const int x = appConfig.option<Config::notification_horizontal_offset>();
+ const int y = appConfig.option<Config::notification_vertical_offset>();
+ m_sharedData->notifications->setOffset(x, y);
+
+ const int w = appConfig.option<Config::notification_maximum_width>();
+ const int h = appConfig.option<Config::notification_maximum_height>();
+ m_sharedData->notifications->setMaximumSize(w, h);
+
+ m_sharedData->notifications->updateNotificationWidgets();
+
m_updateThemeTimer.stop();
COPYQ_LOG("Configuration loaded");
diff --git a/src/common/appconfig.h b/src/common/appconfig.h
index 61f49c8c8..e81ad3f8f 100644
--- a/src/common/appconfig.h
+++ b/src/common/appconfig.h
@@ -68,16 +68,36 @@ struct item_popup_interval : Config<int> {
static QString name() { return "item_popup_interval"; }
};
+struct notification_position : Config<int> {
+ static QString name() { return "notification_position"; }
+ static Value defaultValue() { return 3; }
+};
+
struct clipboard_notification_lines : Config<int> {
static QString name() { return "clipboard_notification_lines"; }
static Value value(Value v) { return qBound(0, v, 10000); }
};
+struct notification_horizontal_offset : Config<int> {
+ static QString name() { return "notification_horizontal_offset"; }
+ static Value defaultValue() { return 10; }
+};
+
+struct notification_vertical_offset : Config<int> {
+ static QString name() { return "notification_vertical_offset"; }
+ static Value defaultValue() { return 10; }
+};
+
struct notification_maximum_width : Config<int> {
static QString name() { return "notification_maximum_width"; }
static Value defaultValue() { return 300; }
};
+struct notification_maximum_height : Config<int> {
+ static QString name() { return "notification_maximum_height"; }
+ static Value defaultValue() { return 100; }
+};
+
struct edit_ctrl_return : Config<bool> {
static QString name() { return "edit_ctrl_return"; }
static Value defaultValue() { return true; }
@@ -412,6 +432,11 @@ struct style : Config<QString> {
static QString name() { return "style"; }
};
+struct native_notifications : Config<bool> {
+ static QString name() { return "native_notifications"; }
+ static Value defaultValue() { return true; }
+};
+
} // namespace Config
class AppConfig final
diff --git a/src/gui/actionhandler.cpp b/src/gui/actionhandler.cpp
index aea309cfc..b6898f5d3 100644
--- a/src/gui/actionhandler.cpp
+++ b/src/gui/actionhandler.cpp
@@ -188,5 +188,4 @@ void ActionHandler::showActionErrors(Action *action, const QString &message, ush
notification->setTitle(title);
notification->setMessage(msg, Qt::PlainText);
notification->setIcon(icon);
- notification->show();
}
diff --git a/src/gui/configurationmanager.cpp b/src/gui/configurationmanager.cpp
index 9fb574e0e..4f148e0e8 100644
--- a/src/gui/configurationmanager.cpp
+++ b/src/gui/configurationmanager.cpp
@@ -259,8 +259,13 @@ void ConfigurationManager::initOptions()
bind<Config::expire_tab>(m_tabHistory->spinBoxExpireTab);
bind<Config::editor>(m_tabHistory->lineEditEditor);
bind<Config::item_popup_interval>(m_tabNotifications->spinBoxNotificationPopupInterval);
+ bind<Config::notification_position>(m_tabNotifications->comboBoxNotificationPosition);
bind<Config::clipboard_notification_lines>(m_tabNotifications->spinBoxClipboardNotificationLines);
+ bind<Config::notification_horizontal_offset>(m_tabNotifications->spinBoxNotificationHorizontalOffset);
+ bind<Config::notification_vertical_offset>(m_tabNotifications->spinBoxNotificationVerticalOffset);
bind<Config::notification_maximum_width>(m_tabNotifications->spinBoxNotificationMaximumWidth);
+ bind<Config::notification_maximum_height>(m_tabNotifications->spinBoxNotificationMaximumHeight);
+ bind<Config::native_notifications>(m_tabNotifications->checkBoxUseNativeNotifications);
bind<Config::edit_ctrl_return>(m_tabHistory->checkBoxEditCtrlReturn);
bind<Config::show_simple_items>(m_tabHistory->checkBoxShowSimpleItems);
bind<Config::number_search>(m_tabHistory->checkBoxNumberSearch);
diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp
index 486a7c2a2..bf3d66bd8 100644
--- a/src/gui/mainwindow.cpp
+++ b/src/gui/mainwindow.cpp
@@ -2305,7 +2305,6 @@ void MainWindow::showError(const QString &msg)
notification->setTitle( tr("CopyQ Error", "Notification error message title") );
notification->setMessage(msg);
notification->setIcon(IconTimesCircle);
- notification->show();
}
Notification *MainWindow::createNotification(const QString &id)
diff --git a/src/gui/notification.h b/src/gui/notification.h
index f9acac82f..4c2f65226 100644
--- a/src/gui/notification.h
+++ b/src/gui/notification.h
@@ -17,75 +17,37 @@
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef NOTIFICATION_H
-#define NOTIFICATION_H
+#pragma once
-#include "gui/notificationbutton.h"
-
-#include <QColor>
-#include <QTimer>
#include <QObject>
-#include <QPixmap>
-#include <QPointer>
-#include <memory>
+#include "gui/notificationbutton.h"
class KNotification;
class QWidget;
-class Notification final : public QObject
+class Notification : public QObject
{
Q_OBJECT
public:
- static void initConfiguration();
-
- explicit Notification(const QColor &iconColor, QObject *parent = nullptr);
-
- ~Notification();
-
- void setTitle(const QString &title);
- void setMessage(const QString &msg, Qt::TextFormat format = Qt::PlainText);
- void setPixmap(const QPixmap &pixmap);
- void setIcon(const QString &icon);
- void setIcon(ushort icon);
- void setIconColor(const QColor &color);
- void setInterval(int msec);
- void setButtons(const NotificationButtons &buttons);
-
- void show();
-
- void close();
+ explicit Notification(QObject *parent) : QObject(parent) {}
+ virtual void setTitle(const QString &title) = 0;
+ virtual void setMessage(const QString &msg, Qt::TextFormat format = Qt::PlainText) = 0;
+ virtual void setPixmap(const QPixmap &pixmap) = 0;
+ virtual void setIcon(const QString &icon) = 0;
+ virtual void setIcon(ushort icon) = 0;
+ virtual void setInterval(int msec) = 0;
+ virtual void setOpacity(qreal opacity) = 0;
+ virtual void setButtons(const NotificationButtons &buttons) = 0;
+ virtual void adjust() = 0;
+ virtual QWidget *widget() = 0;
+ virtual void show() = 0;
+ virtual void close() = 0;
signals:
/** Emitted if notification needs to be closed. */
void closeNotification(Notification *self);
void buttonClicked(const NotificationButton &button);
-
-private:
- void onButtonClicked(unsigned int id);
- void onDestroyed();
- void onClosed();
- void onIgnored();
- void onActivated();
- void update();
-
- void notificationLog(const char *message);
-
- KNotification *dropNotification();
-
- QPointer<KNotification> m_notification;
- NotificationButtons m_buttons;
-
- QColor m_iconColor;
- QTimer m_timer;
- int m_intervalMsec = -1;
- QString m_title;
- QString m_message;
- QString m_icon;
- ushort m_iconId;
- QPixmap m_pixmap;
};
-
-#endif // NOTIFICATION_H
diff --git a/src/gui/notificationbasic.cpp b/src/gui/notificationbasic.cpp
new file mode 100644
index 000000000..0079cc076
--- /dev/null
+++ b/src/gui/notificationbasic.cpp
@@ -0,0 +1,387 @@
+/*
+ Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
+
+ This file is part of CopyQ.
+
+ CopyQ 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.
+
+ CopyQ 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 CopyQ. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "gui/notificationbasic.h"
+#include "gui/notification.h"
+
+#include "common/common.h"
+#include "common/display.h"
+#include "common/textdata.h"
+#include "common/timer.h"
+#include "gui/iconfactory.h"
+#include "gui/icons.h"
+#include "gui/pixelratio.h"
+
+#include <QApplication>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QGridLayout>
+#include <QHBoxLayout>
+#include <QIcon>
+#include <QLabel>
+#include <QMap>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QPushButton>
+#include <QTextEdit>
+
+#include <memory>
+
+namespace {
+
+class NotificationButtonWidget final : public QPushButton
+{
+ Q_OBJECT
+
+public:
+ NotificationButtonWidget(const NotificationButton &button, QWidget *parent)
+ : QPushButton(button.name, parent)
+ , m_button(button)
+ {
+ connect( this, &NotificationButtonWidget::clicked,
+ this, &NotificationButtonWidget::onClicked );
+ }
+
+signals:
+ void clickedButton(const NotificationButton &button);
+
+private:
+ void onClicked()
+ {
+ emit clickedButton(m_button);
+ }
+
+ NotificationButton m_button;
+};
+
+class NotificationBasic;
+
+class NotificationBasicWidget final : public QWidget
+{
+ Q_OBJECT
+
+public:
+ NotificationBasicWidget(NotificationBasic *parent);
+
+ void setTitle(const QString &title);
+ void setMessage(const QString &msg, Qt::TextFormat format = Qt::AutoText);
+ void setPixmap(const QPixmap &pixmap);
+ void setIcon(const QString &icon);
+ void setIcon(ushort icon);
+ void setInterval(int msec);
+ void setOpacity(qreal opacity);
+ void setButtons(const NotificationButtons &buttons);
+
+ void updateIcon();
+
+ void adjust();
+
+ void mousePressEvent(QMouseEvent *event) override;
+ void enterEvent(QEvent *event) override;
+ void leaveEvent(QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+ void showEvent(QShowEvent *event) override;
+ void hideEvent(QHideEvent *event) override;
+
+private:
+ void onTimeout();
+ void onButtonClicked(const NotificationButton &button);
+
+ NotificationBasic *m_parent;
+ QGridLayout *m_layout = nullptr;
+ QHBoxLayout *m_buttonLayout = nullptr;
+ QLabel *m_titleLabel = nullptr;
+ QLabel *m_iconLabel = nullptr;
+ QLabel *m_msgLabel = nullptr;
+ QTimer m_timer;
+ bool m_autoclose = false;
+ qreal m_opacity = 1.0;
+ QString m_icon;
+};
+
+class NotificationBasic final : public Notification
+{
+ Q_OBJECT
+
+ friend class NotificationBasicWidget;
+
+public:
+ NotificationBasic(QObject *parent)
+ : Notification(parent)
+ , m_widget(this)
+ {
+ m_widget.setObjectName("Notification");
+ }
+
+ void setTitle(const QString &title) override {
+ m_widget.setTitle(title);
+ }
+ void setMessage(const QString &msg, Qt::TextFormat format = Qt::AutoText) override {
+ m_widget.setMessage(msg, format);
+ }
+ void setPixmap(const QPixmap &pixmap) override {
+ m_widget.setPixmap(pixmap);
+ }
+ void setIcon(const QString &icon) override {
+ m_widget.setIcon(icon);
+ }
+ void setIcon(ushort icon) override {
+ m_widget.setIcon(icon);
+ }
+ void setInterval(int msec) override {
+ m_widget.setInterval(msec);
+ }
+ void setOpacity(qreal opacity) override {
+ m_widget.setOpacity(opacity);
+ }
+ void setButtons(const NotificationButtons &buttons) override {
+ m_widget.setButtons(buttons);
+ }
+
+ void adjust() override {
+ m_widget.updateIcon();
+ m_widget.adjust();
+ }
+
+ QWidget *widget() override {
+ m_widget.updateIcon();
+ m_widget.adjust();
+ return &m_widget;
+ }
+
+ void show() override {
+ m_widget.show();
+ }
+
+ void close() override {
+ m_widget.close();
+ }
+
+private:
+ NotificationBasicWidget m_widget;
+};
+
+} // namespace
+
+NotificationBasicWidget::NotificationBasicWidget(NotificationBasic *parent)
+ : m_parent(parent)
+{
+ m_layout = new QGridLayout(this);
+ m_layout->setMargin(8);
+
+ m_iconLabel = new QLabel(this);
+ m_iconLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+
+ m_msgLabel = new QLabel(this);
+ m_msgLabel->setAlignment(Qt::AlignTop | Qt::AlignAbsolute);
+
+ setTitle(QString());
+
+ setWindowFlags(Qt::ToolTip);
+ setWindowOpacity(m_opacity);
+ setAttribute(Qt::WA_ShowWithoutActivating);
+
+ initSingleShotTimer( &m_timer, 0, this, &NotificationBasicWidget::onTimeout );
+}
+
+void NotificationBasicWidget::setTitle(const QString &title)
+{
+ if ( !title.isEmpty() ) {
+ if (!m_titleLabel)
+ m_titleLabel = new QLabel(this);
+
+ m_titleLabel->setObjectName("NotificationTitle");
+ m_titleLabel->setTextFormat(Qt::PlainText);
+ m_titleLabel->setText(title);
+
+ m_layout->addWidget(m_iconLabel, 0, 0);
+ m_layout->addWidget(m_titleLabel, 0, 1, Qt::AlignCenter);
+ m_layout->addWidget(m_msgLabel, 1, 0, 1, 2);
+ } else {
+ if (m_titleLabel) {
+ m_titleLabel->deleteLater();
+ m_titleLabel = nullptr;
+ }
+
+ m_layout->addWidget(m_iconLabel, 0, 0, Qt::AlignTop);
+ m_layout->addWidget(m_msgLabel, 0, 1);
+ }
+}
+
+void NotificationBasicWidget::setMessage(const QString &msg, Qt::TextFormat format)
+{
+ m_msgLabel->setTextFormat(format);
+ m_msgLabel->setText(msg);
+ m_msgLabel->setVisible( !msg.isEmpty() );
+}
+
+void NotificationBasicWidget::setPixmap(const QPixmap &pixmap)
+{
+ m_msgLabel->setPixmap(pixmap);
+}
+
+void NotificationBasicWidget::setIcon(const QString &icon)
+{
+ m_icon = icon;
+}
+
+void NotificationBasicWidget::setIcon(ushort icon)
+{
+ m_icon = QString(QChar(icon));
+}
+
+void NotificationBasicWidget::setInterval(int msec)
+{
+ if (msec >= 0) {
+ m_autoclose = true;
+ m_timer.setInterval(msec);
+ if (isVisible())
+ m_timer.start();
+ } else {
+ m_autoclose = false;
+ }
+}
+
+void NotificationBasicWidget::setOpacity(qreal opacity)
+{
+ m_opacity = opacity;
+ setWindowOpacity(m_opacity);
+}
+
+void NotificationBasicWidget::setButtons(const NotificationButtons &buttons)
+{
+ for (const auto &buttonWidget : findChildren<NotificationButtonWidget*>())
+ buttonWidget->deleteLater();
+
+ if ( !buttons.isEmpty() ) {
+ if (!m_buttonLayout)
+ m_buttonLayout = new QHBoxLayout();
+
+ m_buttonLayout->addStretch();
+ m_layout->addLayout(m_buttonLayout, 2, 0, 1, 2);
+
+ for (const auto &button : buttons) {
+ const auto buttonWidget = new NotificationButtonWidget(button, this);
+ connect( buttonWidget, &NotificationButtonWidget::clickedButton,
+ this, &NotificationBasicWidget::onButtonClicked );
+ m_buttonLayout->addWidget(buttonWidget);
+ }
+ } else if (m_buttonLayout) {
+ m_buttonLayout->deleteLater();
+ m_buttonLayout = nullptr;
+ }
+}
+
+void NotificationBasicWidget::updateIcon()
+{
+ const QColor color = getDefaultIconColor(*this);
+ const auto height = static_cast<int>( m_msgLabel->fontMetrics().lineSpacing() * 1.2 );
+ const auto iconId = toIconId(m_icon);
+
+ const auto ratio = pixelRatio(this);
+
+ auto pixmap = iconId == 0
+ ? QPixmap(m_icon)
+ : createPixmap(iconId, color, static_cast<int>(height * ratio));
+
+ pixmap.setDevicePixelRatio(ratio);
+
+ m_iconLabel->setPixmap(pixmap);
+ m_iconLabel->resize(pixmap.size());
+}
+
+void NotificationBasicWidget::adjust()
+{
+ m_msgLabel->setMaximumSize(maximumSize());
+ if ( !m_msgLabel->isVisible() && m_msgLabel->sizeHint().width() > maximumWidth() ) {
+ m_msgLabel->setWordWrap(true);
+ m_msgLabel->adjustSize();
+ }
+ adjustSize();
+}
+
+void NotificationBasicWidget::mousePressEvent(QMouseEvent *)
+{
+ m_timer.stop();
+
+ emit m_parent->closeNotification(m_parent);
+}
+
+void NotificationBasicWidget::enterEvent(QEvent *event)
+{
+ setWindowOpacity(1.0);
+ m_timer.stop();
+ QWidget::enterEvent(event);
+}
+
+void NotificationBasicWidget::leaveEvent(QEvent *event)
+{
+ setWindowOpacity(m_opacity);
+ m_timer.start();
+ QWidget::leaveEvent(event);
+}
+
+void NotificationBasicWidget::paintEvent(QPaintEvent *event)
+{
+ QWidget::paintEvent(event);
+
+ QPainter p(this);
+
+ // black outer border
+ p.setPen(Qt::black);
+ p.drawRect(rect().adjusted(0, 0, -1, -1));
+
+ // light inner border
+ p.setPen( palette().color(QPalette::Window).lighter(300) );
+ p.drawRect(rect().adjusted(1, 1, -2, -2));
+}
+
+void NotificationBasicWidget::showEvent(QShowEvent *event)
+{
+ m_timer.start();
+
+ // QTBUG-33078: Window opacity must be set after show event.
+ setWindowOpacity(m_opacity);
+ QWidget::showEvent(event);
+}
+
+void NotificationBasicWidget::hideEvent(QHideEvent *event)
+{
+ QWidget::hideEvent(event);
+ emit m_parent->closeNotification(m_parent);
+}
+
+void NotificationBasicWidget::onTimeout()
+{
+ if (m_autoclose)
+ emit m_parent->closeNotification(m_parent);
+}
+
+void NotificationBasicWidget::onButtonClicked(const NotificationButton &button)
+{
+ emit m_parent->buttonClicked(button);
+ emit m_parent->closeNotification(m_parent);
+}
+
+Notification *createNotificationBasic(QObject *parent)
+{
+ return new NotificationBasic(parent);
+}
+
+#include "notificationbasic.moc"
diff --git a/src/gui/notificationbasic.h b/src/gui/notificationbasic.h
new file mode 100644
index 000000000..ef63f3c18
--- /dev/null
+++ b/src/gui/notificationbasic.h
@@ -0,0 +1,25 @@
+/*
+ Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
+
+ This file is part of CopyQ.
+
+ CopyQ 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.
+
+ CopyQ 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 CopyQ. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+class Notification;
+class QObject;
+
+Notification *createNotificationBasic(QObject *parent);
diff --git a/src/gui/notificationdaemon.cpp b/src/gui/notificationdaemon.cpp
index 52de0a2fd..e06d463df 100644
--- a/src/gui/notificationdaemon.cpp
+++ b/src/gui/notificationdaemon.cpp
@@ -20,20 +20,98 @@
#include "gui/notificationdaemon.h"
#include "common/common.h"
+#include "common/display.h"
+#include "common/timer.h"
#include "gui/notification.h"
+#include "gui/notificationbasic.h"
#include "gui/screen.h"
+#ifdef WITH_NATIVE_NOTIFICATIONS
+# include "gui/notificationnative/notificationnative.h"
+# include <QSysInfo>
+#endif
+
#include <QApplication>
#include <QPixmap>
+#include <QPoint>
+#include <QVariant>
+#include <QWidget>
+
+namespace {
+
+const int notificationMarginPoints = 10;
+
+int notificationMargin()
+{
+ return pointsToPixels(notificationMarginPoints);
+}
+
+bool hasNativeNotifications()
+{
+#ifdef Q_OS_WIN
+ static const bool supportsNotifications =
+ !QSysInfo::productVersion().startsWith(QLatin1String("7"));
+ return supportsNotifications;
+#else
+ return true;
+#endif
+}
+
+} // namespace
NotificationDaemon::NotificationDaemon(QObject *parent)
: QObject(parent)
+ , m_position(BottomRight)
+ , m_notifications()
+ , m_opacity(1.0)
+ , m_horizontalOffsetPoints(0)
+ , m_verticalOffsetPoints(0)
+ , m_maximumWidthPoints(300)
+ , m_maximumHeightPoints(100)
{
- Notification::initConfiguration();
+#ifdef WITH_NATIVE_NOTIFICATIONS
+ if (hasNativeNotifications())
+ initNotificationNativeConfiguration();
+#endif
+
+ initSingleShotTimer( &m_timerUpdate, 100, this, &NotificationDaemon::doUpdateNotificationWidgets );
}
NotificationDaemon::~NotificationDaemon() = default;
+void NotificationDaemon::setPosition(NotificationDaemon::Position position)
+{
+ m_position = position;
+}
+
+void NotificationDaemon::setOffset(int horizontalPoints, int verticalPoints)
+{
+ m_horizontalOffsetPoints = horizontalPoints;
+ m_verticalOffsetPoints = verticalPoints;
+}
+
+void NotificationDaemon::setMaximumSize(int maximumWidthPoints, int maximumHeightPoints)
+{
+ m_maximumWidthPoints = maximumWidthPoints;
+ m_maximumHeightPoints = maximumHeightPoints;
+}
+
+void NotificationDaemon::updateNotificationWidgets()
+{
+ if ( !m_timerUpdate.isActive() )
+ m_timerUpdate.start();
+}
+
+void NotificationDaemon::setNotificationOpacity(qreal opacity)
+{
+ m_opacity = opacity;
+}
+
+void NotificationDaemon::setNotificationStyleSheet(const QString &styleSheet)
+{
+ m_styleSheet = styleSheet;
+}
+
void NotificationDaemon::setIconColor(const QColor &color)
{
m_iconColor = color;
@@ -55,9 +133,68 @@ void NotificationDaemon::onNotificationClose(Notification *notification)
}
}
+ if (notification->widget() != nullptr)
+ updateNotificationWidgets();
+
notification->deleteLater();
}
+void NotificationDaemon::doUpdateNotificationWidgets()
+{
+ const QPoint cursor = QCursor::pos();
+
+ // Postpone update if mouse cursor is over a notification.
+ for (auto ¬ificationData : m_notifications) {
+ auto notification = notificationData.notification;
+ QWidget *w = notification->widget();
+ if ( w != nullptr && w->isVisible() && w->geometry().contains(cursor) ) {
+ m_timerUpdate.start();
+ return;
+ }
+ }
+
+ const QRect screen = screenGeometry(0);
+
+ int y = (m_position & Top) ? offsetY() : screen.bottom() - offsetY();
+
+ for (auto ¬ificationData : m_notifications) {
+ auto notification = notificationData.notification;
+ QWidget *w = notification->widget();
+ if (w == nullptr)
+ continue;
+
+ notification->setOpacity(m_opacity);
+ w->setStyleSheet(m_styleSheet);
+ w->setMaximumSize( pointsToPixels(m_maximumWidthPoints), pointsToPixels(m_maximumHeightPoints) );
+ notification->adjust();
+
+ // Avoid positioning a notification under mouse cursor.
+ QRect rect = w->geometry();
+ do {
+ int x;
+ if (m_position & Left)
+ x = offsetX();
+ else if (m_position & Right)
+ x = screen.right() - rect.width() - offsetX();
+ else
+ x = screen.right() / 2 - rect.width() / 2;
+
+ if (m_position & Bottom)
+ y -= rect.height();
+
+ if (m_position & Top)
+ y += rect.height() + notificationMargin();
+ else
+ y -= notificationMargin();
+
+ rect.moveTo(x, y);
+ } while( rect.contains(cursor) );
+
+ w->move(rect.topLeft());
+ notification->show();
+ }
+}
+
Notification *NotificationDaemon::findNotification(const QString &id)
{
for (auto ¬ificationData : m_notifications) {
@@ -75,7 +212,16 @@ Notification *NotificationDaemon::createNotification(const QString &id)
notification = findNotification(id);
if (notification == nullptr) {
- notification = new Notification(m_iconColor, this);
+#ifdef WITH_NATIVE_NOTIFICATIONS
+ if (m_nativeNotificationsEnabled && hasNativeNotifications()) {
+ notification = createNotificationNative(m_iconColor, this);
+ QTimer::singleShot(0, notification, &Notification::show);
+ } else {
+ notification = createNotificationBasic(this);
+ }
+#else
+ notification = createNotificationBasic(this);
+#endif
connect(this, &QObject::destroyed, notification, &QObject::deleteLater);
connect( notification, &Notification::closeNotification,
@@ -86,5 +232,18 @@ Notification *NotificationDaemon::createNotification(const QString &id)
m_notifications.append(NotificationData{id, notification});
}
+ if (notification->widget() != nullptr)
+ updateNotificationWidgets();
+
return notification;
}
+
+int NotificationDaemon::offsetX() const
+{
+ return pointsToPixels(m_horizontalOffsetPoints);
+}
+
+int NotificationDaemon::offsetY() const
+{
+ return pointsToPixels(m_verticalOffsetPoints);
+}
diff --git a/src/gui/notificationdaemon.h b/src/gui/notificationdaemon.h
index d8215c5f5..0ca7b49cc 100644
--- a/src/gui/notificationdaemon.h
+++ b/src/gui/notificationdaemon.h
@@ -37,6 +37,17 @@ class NotificationDaemon final : public QObject
{
Q_OBJECT
public:
+ enum Position {
+ Top = 0x2,
+ Bottom = 0x4,
+ Right = 0x8,
+ Left = 0x10,
+ TopRight = Top | Right,
+ BottomRight = Bottom | Right,
+ BottomLeft = Bottom | Left,
+ TopLeft = Top | Left
+ };
+
explicit NotificationDaemon(QObject *parent = nullptr);
~NotificationDaemon();
@@ -44,8 +55,22 @@ public:
Notification *createNotification(const QString &id = QString());
Notification *findNotification(const QString &id);
+ void setPosition(Position position);
+
+ void setOffset(int horizontalPoints, int verticalPoints);
+
+ void setMaximumSize(int maximumWidthPoints, int maximumHeightPoints);
+
+ void updateNotificationWidgets();
+
+ void setNotificationOpacity(qreal opacity);
+
+ void setNotificationStyleSheet(const QString &styleSheet);
+
void setIconColor(const QColor &color);
+ void setNativeNotificationsEnabled(bool enable) { m_nativeNotificationsEnabled = enable; }
+
void removeNotification(const QString &id);
signals:
@@ -53,6 +78,7 @@ signals:
private:
void onNotificationClose(Notification *notification);
+ void doUpdateNotificationWidgets();
struct NotificationData {
QString id;
@@ -61,8 +87,20 @@ private:
Notification *findNotification(Notification *notification);
+ int offsetX() const;
+ int offsetY() const;
+
+ Position m_position;
QList<NotificationData> m_notifications;
QColor m_iconColor;
+ qreal m_opacity;
+ int m_horizontalOffsetPoints;
+ int m_verticalOffsetPoints;
+ int m_maximumWidthPoints;
+ int m_maximumHeightPoints;
+ QString m_styleSheet;
+ QTimer m_timerUpdate;
+ bool m_nativeNotificationsEnabled = true;
};
#endif // NOTIFICATIONDAEMON_H
diff --git a/src/gui/notification.cpp b/src/gui/notificationnative/notificationnative.cpp
similarity index 69%
rename from src/gui/notification.cpp
rename to src/gui/notificationnative/notificationnative.cpp
index f9602fce2..5a48b46d5 100644
--- a/src/gui/notification.cpp
+++ b/src/gui/notificationnative/notificationnative.cpp
@@ -17,12 +17,13 @@
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "gui/notification.h"
+#include "notificationnative.h"
#include "common/log.h"
#include "common/timer.h"
#include "gui/iconfactory.h"
#include "gui/icons.h"
+#include "gui/notification.h"
#include <KNotification>
#include <QApplication>
@@ -36,6 +37,7 @@
#include <QMap>
#include <QMouseEvent>
#include <QPainter>
+#include <QPointer>
#include <QPushButton>
#include <QStandardPaths>
#include <QTextEdit>
@@ -64,9 +66,57 @@ QPixmap defaultIcon()
return pixmap;
}
+class NotificationNative final : public Notification
+{
+ Q_OBJECT
+
+public:
+ explicit NotificationNative(const QColor &iconColor, QObject *parent = nullptr);
+
+ ~NotificationNative();
+
+ void setTitle(const QString &title) override;
+ void setMessage(const QString &msg, Qt::TextFormat format = Qt::PlainText) override;
+ void setPixmap(const QPixmap &pixmap) override;
+ void setIcon(const QString &icon) override;
+ void setIcon(ushort icon) override;
+ void setInterval(int msec) override;
+ void setOpacity(qreal) override {}
+ void setButtons(const NotificationButtons &buttons) override;
+ void adjust() override {}
+ QWidget *widget() override { return nullptr; }
+ void show() override;
+ void close() override;
+
+private:
+ void onButtonClicked(unsigned int id);
+ void onDestroyed();
+ void onClosed();
+ void onIgnored();
+ void onActivated();
+ void update();
+
+ void notificationLog(const char *message);
+
+ KNotification *dropNotification();
+
+ QPointer<KNotification> m_notification;
+ NotificationButtons m_buttons;
+
+ QColor m_iconColor;
+ QTimer m_timer;
+ int m_intervalMsec = -1;
+ QString m_title;
+ QString m_message;
+ QString m_icon;
+ ushort m_iconId;
+ QPixmap m_pixmap;
+ bool m_closed = false;
+};
+
} // namespace
-void Notification::initConfiguration()
+void initNotificationNativeConfiguration()
{
const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
QDir dir(dataDir);
@@ -98,14 +148,14 @@ void Notification::initConfiguration()
configFile.close();
}
-Notification::Notification(const QColor &iconColor, QObject *parent)
- : QObject(parent)
+NotificationNative::NotificationNative(const QColor &iconColor, QObject *parent)
+ : Notification(parent)
, m_iconColor(iconColor)
{
- initSingleShotTimer( &m_timer, 0, this, &Notification::close );
+ initSingleShotTimer( &m_timer, 0, this, &NotificationNative::close );
}
-Notification::~Notification()
+NotificationNative::~NotificationNative()
{
auto notification = dropNotification();
if (notification) {
@@ -114,12 +164,12 @@ Notification::~Notification()
}
}
-void Notification::setTitle(const QString &title)
+void NotificationNative::setTitle(const QString &title)
{
m_title = title;
}
-void Notification::setMessage(const QString &msg, Qt::TextFormat format)
+void NotificationNative::setMessage(const QString &msg, Qt::TextFormat format)
{
if (format == Qt::PlainText)
m_message = msg.toHtmlEscaped();
@@ -127,14 +177,14 @@ void Notification::setMessage(const QString &msg, Qt::TextFormat format)
m_message = msg;
}
-void Notification::setPixmap(const QPixmap &pixmap)
+void NotificationNative::setPixmap(const QPixmap &pixmap)
{
m_icon.clear();
m_iconId = 0;
m_pixmap = pixmap;
}
-void Notification::setIcon(const QString &icon)
+void NotificationNative::setIcon(const QString &icon)
{
m_iconId = toIconId(icon);
if (m_iconId == 0)
@@ -144,25 +194,28 @@ void Notification::setIcon(const QString &icon)
m_pixmap = QPixmap();
}
-void Notification::setIcon(ushort icon)
+void NotificationNative::setIcon(ushort icon)
{
m_icon.clear();
m_iconId = icon;
m_pixmap = QPixmap();
}
-void Notification::setInterval(int msec)
+void NotificationNative::setInterval(int msec)
{
m_intervalMsec = msec;
}
-void Notification::setButtons(const NotificationButtons &buttons)
+void NotificationNative::setButtons(const NotificationButtons &buttons)
{
m_buttons = buttons;
}
-void Notification::show()
+void NotificationNative::show()
{
+ if (m_closed)
+ return;
+
notificationLog("Update");
if (m_notification) {
@@ -178,20 +231,20 @@ void Notification::show()
m_notification->setComponentName(componentName);
connect( m_notification.data(), static_cast<void (KNotification::*)(unsigned int)>(&KNotification::activated),
- this, &Notification::onButtonClicked );
+ this, &NotificationNative::onButtonClicked );
connect( m_notification.data(), &KNotification::closed,
- this, &Notification::onClosed );
+ this, &NotificationNative::onClosed );
connect( m_notification.data(), &KNotification::ignored,
- this, &Notification::onIgnored );
+ this, &NotificationNative::onIgnored );
#if KNOTIFICATIONS_VERSION < QT_VERSION_CHECK(5,67,0)
connect( m_notification.data(), static_cast<void (KNotification::*)()>(&KNotification::activated),
- this, &Notification::onActivated );
+ this, &NotificationNative::onActivated );
#else
connect( m_notification.data(), &KNotification::defaultActivated,
- this, &Notification::onActivated );
+ this, &NotificationNative::onActivated );
#endif
connect( m_notification.data(), &QObject::destroyed,
- this, &Notification::onDestroyed );
+ this, &NotificationNative::onDestroyed );
update();
if (m_notification)
@@ -200,7 +253,7 @@ void Notification::show()
notificationLog("Created");
}
-void Notification::close()
+void NotificationNative::close()
{
notificationLog("Close");
@@ -213,7 +266,7 @@ void Notification::close()
emit closeNotification(this);
}
-void Notification::onButtonClicked(unsigned int id)
+void NotificationNative::onButtonClicked(unsigned int id)
{
notificationLog("onButtonClicked");
@@ -223,31 +276,31 @@ void Notification::onButtonClicked(unsigned int id)
}
}
-void Notification::onDestroyed()
+void NotificationNative::onDestroyed()
{
notificationLog("Destroyed");
dropNotification();
}
-void Notification::onClosed()
+void NotificationNative::onClosed()
{
notificationLog("onClosed");
dropNotification();
}
-void Notification::onIgnored()
+void NotificationNative::onIgnored()
{
notificationLog("onIgnored");
dropNotification();
}
-void Notification::onActivated()
+void NotificationNative::onActivated()
{
notificationLog("onActivated");
dropNotification();
}
-void Notification::update()
+void NotificationNative::update()
{
if (!m_notification)
return;
@@ -298,7 +351,7 @@ void Notification::update()
}
}
-void Notification::notificationLog(const char *message)
+void NotificationNative::notificationLog(const char *message)
{
COPYQ_LOG_VERBOSE(
QString("Notification [%1:%2]: %3")
@@ -307,8 +360,9 @@ void Notification::notificationLog(const char *message)
.arg(message) );
}
-KNotification *Notification::dropNotification()
+KNotification *NotificationNative::dropNotification()
{
+ m_closed = true;
if (!m_notification)
return nullptr;
@@ -317,3 +371,10 @@ KNotification *Notification::dropNotification()
notification->disconnect(this);
return notification;
}
+
+Notification *createNotificationNative(const QColor &iconColor, QObject *parent)
+{
+ return new NotificationNative(iconColor, parent);
+}
+
+#include "notificationnative.moc"
diff --git a/src/gui/notificationnative/notificationnative.h b/src/gui/notificationnative/notificationnative.h
new file mode 100644
index 000000000..eda2f9222
--- /dev/null
+++ b/src/gui/notificationnative/notificationnative.h
@@ -0,0 +1,28 @@
+/*
+ Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
+
+ This file is part of CopyQ.
+
+ CopyQ 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.
+
+ CopyQ 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 CopyQ. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+class Notification;
+class QColor;
+class QObject;
+
+void initNotificationNativeConfiguration();
+
+Notification *createNotificationNative(const QColor &iconColor, QObject *parent);
diff --git a/src/gui/theme.cpp b/src/gui/theme.cpp
index 740b695c9..d22853538 100644
--- a/src/gui/theme.cpp
+++ b/src/gui/theme.cpp
@@ -364,6 +364,12 @@ QString Theme::getToolTipStyleSheet() const
return getStyleSheet(cssTemplate);
}
+QString Theme::getNotificationStyleSheet() const
+{
+ const QString cssTemplate = value("css_template_notification").toString();
+ return getStyleSheet(cssTemplate);
+}
+
Qt::ScrollBarPolicy Theme::scrollbarPolicy() const
{
return value("show_scrollbars").toBool()
@@ -405,6 +411,7 @@ void Theme::resetTheme()
m_theme["find_fg"] = Option("#000", "VALUE", ui ? ui->pushButtonColorFoundFg : nullptr);
m_theme["notes_bg"] = Option(defaultColorVarToolTipBase, "VALUE", ui ? ui->pushButtonColorNotesBg : nullptr);
m_theme["notes_fg"] = Option(defaultColorVarToolTipText, "VALUE", ui ? ui->pushButtonColorNotesFg : nullptr);
+ m_theme["notification_bg"] = Option("#333", "VALUE", ui ? ui->pushButtonColorNotificationBg : nullptr);
m_theme["notification_fg"] = Option("#ddd", "VALUE", ui ? ui->pushButtonColorNotificationFg : nullptr);
m_theme["font"] = Option("", "VALUE", ui ? ui->pushButtonFont : nullptr);
@@ -412,6 +419,7 @@ void Theme::resetTheme()
m_theme["find_font"] = Option("", "VALUE", ui ? ui->pushButtonFoundFont : nullptr);
m_theme["num_font"] = Option("", "VALUE", ui ? ui->pushButtonNumberFont : nullptr);
m_theme["notes_font"] = Option("", "VALUE", ui ? ui->pushButtonNotesFont : nullptr);
+ m_theme["notification_font"] = Option("", "VALUE", ui ? ui->pushButtonNotificationFont : nullptr);
m_theme["show_number"] = Option(true, "checked", ui ? ui->checkBoxShowNumber : nullptr);
m_theme["show_scrollbars"] = Option(true, "checked", ui ? ui->checkBoxScrollbars : nullptr);
@@ -527,6 +535,7 @@ void Theme::resetTheme()
m_theme["css_template_items"] = Option("items");
m_theme["css_template_main_window"] = Option("main_window");
+ m_theme["css_template_notification"] = Option("notification");
m_theme["css_template_tooltip"] = Option("tooltip");
}
diff --git a/src/gui/theme.h b/src/gui/theme.h
index 2fa5116a0..85293554f 100644
--- a/src/gui/theme.h
+++ b/src/gui/theme.h
@@ -79,6 +79,8 @@ public:
/** Return stylesheet for tooltips. */
QString getToolTipStyleSheet() const;
+ QString getNotificationStyleSheet() const;
+
Qt::ScrollBarPolicy scrollbarPolicy() const;
bool useSystemIcons() const;
diff --git a/src/notifications.cmake b/src/notifications.cmake
index 47909254b..f3422f15c 100644
--- a/src/notifications.cmake
+++ b/src/notifications.cmake
@@ -1,8 +1,18 @@
-set(KF5_MIN_VERSION "5.18.0")
+OPTION(WITH_NATIVE_NOTIFICATIONS "Build with native notification support" ON)
-find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
-list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
+if (WITH_NATIVE_NOTIFICATIONS)
+ set(KF5_MIN_VERSION "5.18.0")
-find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Notifications)
+ find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
+ list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
-list(APPEND copyq_LIBRARIES KF5::Notifications)
+ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Notifications)
+
+ list(APPEND copyq_LIBRARIES KF5::Notifications)
+
+ list(APPEND copyq_SOURCES
+ gui/notificationnative/notificationnative.cpp
+ gui/notificationnative/notificationdaemonnative.cpp)
+
+ list(APPEND copyq_DEFINITIONS WITH_NATIVE_NOTIFICATIONS)
+endif()
diff --git a/src/scriptable/scriptable.cpp b/src/scriptable/scriptable.cpp
index 780315084..a7efaa97a 100644
--- a/src/scriptable/scriptable.cpp
+++ b/src/scriptable/scriptable.cpp
@@ -65,7 +65,9 @@
#include <QThread>
#include <QTimer>
-#include <knotifications_version.h>
+#ifdef WITH_NATIVE_NOTIFICATIONS
+# include <knotifications_version.h>
+#endif
Q_DECLARE_METATYPE(QByteArray*)
Q_DECLARE_METATYPE(QFile*)
@@ -824,7 +826,9 @@ QJSValue Scriptable::version()
m_skipArguments = 0;
return tr("CopyQ Clipboard Manager") + " " COPYQ_VERSION "\n"
+ "Qt: " QT_VERSION_STR "\n"
+#ifdef WITH_NATIVE_NOTIFICATIONS
+ "KNotifications: " KNOTIFICATIONS_VERSION_STRING "\n"
+#endif
+ "Compiler: "
#if defined(Q_CC_GNU)
"GCC"
diff --git a/src/scriptable/scriptableproxy.cpp b/src/scriptable/scriptableproxy.cpp
index 7f77b00f4..52eb0ead5 100644
--- a/src/scriptable/scriptableproxy.cpp
+++ b/src/scriptable/scriptableproxy.cpp
@@ -716,7 +716,6 @@ public:
notification->setMessage(popupMessage);
notification->setIcon(IconKeyboard);
notification->setInterval(2000);
- notification->show();
if ( keys.startsWith(":") ) {
const auto text = keys.mid(1);
@@ -1265,7 +1264,6 @@ void ScriptableProxy::showMessage(const QString &title,
notification->setIcon(icon);
notification->setInterval(msec);
notification->setButtons(buttons);
- notification->show();
}
QVariantMap ScriptableProxy::nextItem(const QString &tabName, int where)
@@ -2124,7 +2122,9 @@ void ScriptableProxy::showDataNotification(const QVariantMap &data)
const QStringList formats = data.keys();
const int imageIndex = formats.indexOf(QRegularExpression("^image/.*"));
- const QFont &font = qApp->font();
+ const QFont &font = notification->widget()
+ ? notification->widget()->font()
+ : qApp->font();
const bool isHidden = data.contains(mimeHidden);
QString title;
@@ -2161,7 +2161,6 @@ void ScriptableProxy::showDataNotification(const QVariantMap &data)
}
notification->setTitle(title);
- notification->show();
}
bool ScriptableProxy::enableMenuItem(int actionId, int currentRun, int menuItemMatchCommandIndex, const QVariantMap &menuItem)
diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp
index 13b253284..7cace81d5 100644
--- a/src/tests/tests.cpp
+++ b/src/tests/tests.cpp
@@ -189,6 +189,8 @@ bool testStderr(const QByteArray &stderrData, TestInterface::ReadStderrFlag flag
plain("ERROR: QtCritical: QWindowsPipeWriter::write failed. (The pipe is being closed.)"),
plain("ERROR: QtCritical: QWindowsPipeWriter: asynchronous write failed. (The pipe has been ended.)"),
+ plain("[kf.notifications] QtWarning: Received a response for an unknown notification."),
+
regex("QtWarning: QTemporaryDir: Unable to remove .* most likely due to the presence of read-only files."),
// Windows Qt 5.15.2
diff --git a/src/ui/configtabappearance.ui b/src/ui/configtabappearance.ui
index 7ece0900e..129bb6e0f 100644
--- a/src/ui/configtabappearance.ui
+++ b/src/ui/configtabappearance.ui
@@ -252,6 +252,13 @@
</property>
</widget>
</item>
+ <item row="8" column="1">
+ <widget class="QPushButton" name="pushButtonColorNotificationBg">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
<item row="8" column="2">
<widget class="QPushButton" name="pushButtonColorNotificationFg">
<property name="text">
@@ -259,6 +266,13 @@
</property>
</widget>
</item>
+ <item row="8" column="3">
+ <widget class="QPushButton" name="pushButtonNotificationFont">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
@@ -402,7 +416,9 @@
<tabstop>pushButtonNotesFont</tabstop>
<tabstop>pushButtonColorNumberFg</tabstop>
<tabstop>pushButtonNumberFont</tabstop>
+ <tabstop>pushButtonColorNotificationBg</tabstop>
<tabstop>pushButtonColorNotificationFg</tabstop>
+ <tabstop>pushButtonNotificationFont</tabstop>
<tabstop>checkBoxShowNumber</tabstop>
<tabstop>checkBoxScrollbars</tabstop>
<tabstop>checkBoxSystemIcons</tabstop>
diff --git a/src/ui/configtabnotifications.ui b/src/ui/configtabnotifications.ui
index 129df1268..7c9b6f223 100644
--- a/src/ui/configtabnotifications.ui
+++ b/src/ui/configtabnotifications.ui
@@ -59,7 +59,71 @@
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
- <item row="0" column="0">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>&Notification position:</string>
+ </property>
+ <property name="buddy">
+ <cstring>comboBoxNotificationPosition</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <item>
+ <widget class="QComboBox" name="comboBoxNotificationPosition">
+ <property name="toolTip">
+ <string>Position on screen for notifications</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>Top</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Bottom</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Top Right</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Bottom Right</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Bottom Left</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Top Left</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Int&erval in seconds to display notifications:</string>
@@ -69,7 +133,7 @@
</property>
</widget>
</item>
- <item row="0" column="1">
+ <item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QSpinBox" name="spinBoxNotificationPopupInterval">
@@ -103,7 +167,7 @@ Set to -1 to keep visible until clicked.</string>
</item>
</layout>
</item>
- <item row="1" column="0">
+ <item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Num&ber of lines for clipboard notification:</string>
@@ -113,7 +177,7 @@ Set to -1 to keep visible until clicked.</string>
</property>
</widget>
</item>
- <item row="1" column="1">
+ <item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QSpinBox" name="spinBoxClipboardNotificationLines">
@@ -142,6 +206,13 @@ Set to 0 to disable.</string>
</item>
</layout>
</item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="checkBoxUseNativeNotifications">
+ <property name="text">
+ <string>&Use native notifications</string>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
@@ -151,6 +222,86 @@ Set to 0 to disable.</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Hori&zontal offset:</string>
+ </property>
+ <property name="buddy">
+ <cstring>spinBoxNotificationHorizontalOffset</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_19">
+ <item>
+ <widget class="QSpinBox" name="spinBoxNotificationHorizontalOffset">
+ <property name="toolTip">
+ <string>Notification distance from left or right screen edge in screen points</string>
+ </property>
+ <property name="minimum">
+ <number>-99999</number>
+ </property>
+ <property name="maximum">
+ <number>99999</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_19">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>&Vertical offset:</string>
+ </property>
+ <property name="buddy">
+ <cstring>spinBoxNotificationVerticalOffset</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_20">
+ <item>
+ <widget class="QSpinBox" name="spinBoxNotificationVerticalOffset">
+ <property name="toolTip">
+ <string>Notification distance from top or bottom screen edge in screen points</string>
+ </property>
+ <property name="minimum">
+ <number>-99999</number>
+ </property>
+ <property name="maximum">
+ <number>99999</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_20">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Maximum &width:</string>
@@ -160,7 +311,7 @@ Set to 0 to disable.</string>
</property>
</widget>
</item>
- <item row="0" column="1">
+ <item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_21">
<item>
<widget class="QSpinBox" name="spinBoxNotificationMaximumWidth">
@@ -187,6 +338,43 @@ Set to 0 to disable.</string>
</item>
</layout>
</item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>Ma&ximum height:</string>
+ </property>
+ <property name="buddy">
+ <cstring>spinBoxNotificationMaximumHeight</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_22">
+ <item>
+ <widget class="QSpinBox" name="spinBoxNotificationMaximumHeight">
+ <property name="toolTip">
+ <string>Maximum height for notification in screen points</string>
+ </property>
+ <property name="maximum">
+ <number>9999</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_22">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
diff --git a/utils/appveyor/after_build.sh b/utils/appveyor/after_build.sh
index 6112e19e7..0667a263e 100644
--- a/utils/appveyor/after_build.sh
+++ b/utils/appveyor/after_build.sh
@@ -77,15 +77,19 @@ export COPYQ_TESTS_RERUN_FAILED=1
"$Executable" write text/html "<p><b>Rich text</b> <i>item</i></p>"
"$Executable" write image/png - < "$Source/src/images/icon_128x128.png"
-# FIXME: This does not show notifications.
+# FIXME: This does not show native notifications.
# Maybe a user interaction, like mouse move, is required.
-"$Executable" popup "Popup title" "Popup message..."
-"$Executable" notification \
- .title "Notification title" \
- .message "Notification message..." \
- .button OK cmd data \
- .button Close cmd data
-
+for native in "true" "false"; do
+ "$Executable" config native_notifications "$native"
+ "$Executable" popup "Popup title" "Popup message..."
+ "$Executable" notification \
+ .title "Notification title" \
+ .message "Notification message..." \
+ .button OK cmd data \
+ .button Close cmd data
+done
+
+"$Executable" sleep 1000
"$Executable" screenshot > screenshot.png
"$Executable" exit
diff --git a/utils/appveyor/before_build.sh b/utils/appveyor/before_build.sh
index 57873130f..d67b8a1c1 100644
--- a/utils/appveyor/before_build.sh
+++ b/utils/appveyor/before_build.sh
@@ -10,7 +10,7 @@ export PATH=$PATH:$INSTALL_PREFIX/bin
"$build" snoretoast "v$SNORETOAST_VERSION" "$SNORETOAST_BASE_URL"
"$build" extra-cmake-modules
-"$build" kconfig "" "" "-DKCONFIG_USE_GUI=OFF -DKCONFIG_USE_DBUS=OFF"
+"$build" kconfig "" "" "-DKCONFIG_USE_DBUS=OFF" "-DKCONFIG_USE_GUI=OFF"
"$build" kwindowsystem
"$build" kcoreaddons
"$build" knotifications
--
GitLab