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