diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index 9fa32ab8b..3b9857df2 100644 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -61,8 +61,8 @@ #define RELEASES_LIST "https://api.github.com/repos/martinrotter/rssguard/releases" #define MSG_FILTERING_HELP APP_URL_DOCUMENTATION "#fltr" -#define URL_REGEXP \ - "^(http|https|feed|ftp):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/" \ +#define URL_REGEXP \ + "^(http|https|feed|ftp):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/" \ "~\\+#])?$" #define SCRIPT_SOURCE_TYPE_REGEXP "^.+#.*$" #define TEXT_TITLE_LIMIT 30 @@ -99,6 +99,8 @@ #define DEFAULT_NOTIFICATION_VOLUME 50 #define MAX_THREADPOOL_THREADS 32 #define WEB_BROWSER_SCROLL_STEP 50.0 +#define NOTIFICATIONS_MARGIN 16 +#define NOTIFICATIONS_WIDTH 256 #define GOOGLE_SEARCH_URL "https://www.google.com/search?q=%1&ie=utf-8&oe=utf-8" #define GOOGLE_SUGGEST_URL "http://suggestqueries.google.com/complete/search?output=toolbar&hl=en&q=%1" @@ -178,13 +180,13 @@ #define ZOOM_FACTOR_STEP 0.05f #if defined(USE_WEBENGINE) -#define HTTP_COMPLETE_USERAGENT \ - (qApp->web()->engineProfile()->httpUserAgent().toLocal8Bit() + QByteArrayLiteral(" ") + \ +#define HTTP_COMPLETE_USERAGENT \ + (qApp->web()->engineProfile()->httpUserAgent().toLocal8Bit() + QByteArrayLiteral(" ") + \ QByteArrayLiteral(APP_USERAGENT)) #else -#define HTTP_COMPLETE_USERAGENT \ - (QByteArrayLiteral("Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " \ - "QtWebEngine/5.15.2 Chrome/83.0.4103.122 Safari/537.36 ") + \ +#define HTTP_COMPLETE_USERAGENT \ + (QByteArrayLiteral("Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " \ + "QtWebEngine/5.15.2 Chrome/83.0.4103.122 Safari/537.36 ") + \ QByteArrayLiteral(APP_USERAGENT)) #endif diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 7a4d49d0d..43be3a731 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -1090,7 +1090,18 @@ void FormMain::showAddAccountDialog() { } void FormMain::reportABug() { - qApp->web()->openUrlInExternalBrowser(QSL(APP_URL_ISSUES_NEW)); + qApp + ->showGuiMessage(Notification::Event::GeneralEvent, + GuiMessage(QDateTime::currentDateTime().toString(), + "Quisque ullamcorper ut purus nec tempus. Vivamus eros dolor, sagittis ultrices augue " + "ut, posuere fringilla lorem. Donec posuere, enim sit amet fermentum dignissim, tellus " + "lectus laoreet lectus, vestibulum laoreet felis tortor eget nunc. Curabitur sagittis " + "quam in scelerisque placerat. Vivamus vel porta tortor. Vivamus nec volutpat sem", + QSystemTrayIcon::MessageIcon::Information), + GuiMessageDestination(), + GuiAction("test", []() {})); + + // qApp->web()->openUrlInExternalBrowser(QSL(APP_URL_ISSUES_NEW)); } void FormMain::donate() { diff --git a/src/librssguard/gui/notifications/basetoastnotification.cpp b/src/librssguard/gui/notifications/basetoastnotification.cpp index e37fd5c83..eef31be48 100644 --- a/src/librssguard/gui/notifications/basetoastnotification.cpp +++ b/src/librssguard/gui/notifications/basetoastnotification.cpp @@ -4,9 +4,12 @@ #include "miscellaneous/iconfactory.h" +#include + BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent) { setAttribute(Qt::WidgetAttribute::WA_ShowWithoutActivating); - setAttribute(Qt::WidgetAttribute::WA_TranslucentBackground); + setFixedWidth(NOTIFICATIONS_WIDTH); + setFocusPolicy(Qt::FocusPolicy::NoFocus); setWindowFlags( #ifdef Q_OS_MAC @@ -15,10 +18,27 @@ BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent) Qt::WindowType::Tool | #endif Qt::WindowType::FramelessWindowHint | Qt::WindowType::WindowStaysOnTopHint | Qt::WindowType::WindowSystemMenuHint); + + installEventFilter(this); } BaseToastNotification::~BaseToastNotification() {} void BaseToastNotification::setupCloseButton(QAbstractButton* btn) { btn->setIcon(qApp->icons()->fromTheme(QSL("dialog-close"), QSL("gtk-close"))); + + connect(btn, &QAbstractButton::clicked, this, &BaseToastNotification::closeRequested); +} + +bool BaseToastNotification::eventFilter(QObject* watched, QEvent* event) { + if (event->type() == QEvent::Type::KeyPress) { + return true; + } + else { + return QDialog::eventFilter(watched, event); + } +} + +void BaseToastNotification::closeEvent(QCloseEvent* event) { + event->ignore(); } diff --git a/src/librssguard/gui/notifications/basetoastnotification.h b/src/librssguard/gui/notifications/basetoastnotification.h index 73710f0be..6d2248e8a 100644 --- a/src/librssguard/gui/notifications/basetoastnotification.h +++ b/src/librssguard/gui/notifications/basetoastnotification.h @@ -14,8 +14,17 @@ class BaseToastNotification : public QDialog { explicit BaseToastNotification(QWidget* parent = nullptr); virtual ~BaseToastNotification(); + // If true, then notification is always moved as close to top as possible. + virtual bool alwaysOnTop() const = 0; + protected: + virtual bool eventFilter(QObject* watched, QEvent* event); + virtual void closeEvent(QCloseEvent* event); + void setupCloseButton(QAbstractButton* btn); + + signals: + void closeRequested(); }; #endif // BASETOASTNOTIFICATION_H diff --git a/src/librssguard/gui/notifications/toastnotification.cpp b/src/librssguard/gui/notifications/toastnotification.cpp index e3159ade5..7669135aa 100644 --- a/src/librssguard/gui/notifications/toastnotification.cpp +++ b/src/librssguard/gui/notifications/toastnotification.cpp @@ -19,6 +19,10 @@ ToastNotification::ToastNotification(Notification::Event event, loadNotification(event, msg, action); } +bool ToastNotification::alwaysOnTop() const { + return false; +} + void ToastNotification::loadNotification(Notification::Event event, const GuiMessage& msg, const GuiAction& action) { m_ui.m_lblTitle->setText(msg.m_title); m_ui.m_lblBody->setText(msg.m_message); diff --git a/src/librssguard/gui/notifications/toastnotification.h b/src/librssguard/gui/notifications/toastnotification.h index 555d29c77..3e2e1d2c5 100644 --- a/src/librssguard/gui/notifications/toastnotification.h +++ b/src/librssguard/gui/notifications/toastnotification.h @@ -18,6 +18,8 @@ class ToastNotification : public BaseToastNotification { const GuiAction& action, QWidget* parent = nullptr); + virtual bool alwaysOnTop() const; + private: void loadNotification(Notification::Event event, const GuiMessage& msg, const GuiAction& action); diff --git a/src/librssguard/gui/notifications/toastnotificationsmanager.cpp b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp index 9bd5a167d..e983af8bd 100644 --- a/src/librssguard/gui/notifications/toastnotificationsmanager.cpp +++ b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp @@ -2,8 +2,14 @@ #include "gui/notifications/toastnotificationsmanager.h" +#include "3rd-party/boolinq/boolinq.h" +#include "gui/notifications/basetoastnotification.h" #include "gui/notifications/toastnotification.h" +#include +#include +#include + ToastNotificationsManager::ToastNotificationsManager(QObject* parent) : QObject(parent), m_position(NotificationPosition::BottomRight), m_screen(-1) {} @@ -43,17 +49,115 @@ void ToastNotificationsManager::clear() { void ToastNotificationsManager::showNotification(Notification::Event event, const GuiMessage& msg, const GuiAction& action) { - // Remove top existing notifications as long as their combined height with height of this - // new notification extends. - ToastNotification* notif = new ToastNotification(event, msg, action, qApp->mainFormWidget()); - auto aa = notif->height(); + auto* screen = moveToProperScreen(notif); + // Insert new notification into free space. notif->show(); - auto bb = notif->height(); - auto cc = notif->height(); + auto notif_new_pos = cornerForNewNotification(screen->availableGeometry()); + + moveNotificationToCorner(notif, notif_new_pos); + + notif->move(notif_new_pos); + + // Make sure notification is finally resized. + notif->adjustSize(); + qApp->processEvents(); + + // Remove out-of-bounds old notifications and shift existing + // ones to make space for new notifications. + removeOutOfBoundsNotifications(notif->height()); + makeSpaceForNotification(notif->height()); + + m_activeNotifications.prepend(notif); } void ToastNotificationsManager::showNotification(const QList& new_messages) {} + +QScreen* ToastNotificationsManager::activeScreen() const { + if (m_screen >= 0) { + auto all_screens = QGuiApplication::screens(); + + if (m_screen < all_screens.size()) { + return all_screens.at(m_screen); + } + } + + return QGuiApplication::primaryScreen(); +} + +QPoint ToastNotificationsManager::cornerForNewNotification(QRect rect) { + switch (m_position) { + case ToastNotificationsManager::TopLeft: + return rect.topLeft() + QPoint(NOTIFICATIONS_MARGIN, NOTIFICATIONS_MARGIN); + + case ToastNotificationsManager::TopRight: + return rect.topRight() - QPoint(NOTIFICATIONS_WIDTH + NOTIFICATIONS_MARGIN, -NOTIFICATIONS_MARGIN); + + case ToastNotificationsManager::BottomLeft: + return rect.bottomLeft() - QPoint(-NOTIFICATIONS_MARGIN, NOTIFICATIONS_MARGIN); + + case ToastNotificationsManager::BottomRight: + return rect.bottomRight() - QPoint(NOTIFICATIONS_MARGIN, NOTIFICATIONS_MARGIN); + } +} + +void ToastNotificationsManager::moveNotificationToCorner(BaseToastNotification* notif, const QPoint& corner) { + switch (m_position) { + case ToastNotificationsManager::TopLeft: + notif->move(corner); + break; + + case ToastNotificationsManager::TopRight: + notif->move(corner); + break; + + case ToastNotificationsManager::BottomLeft: + notif->move(corner); + break; + + case ToastNotificationsManager::BottomRight: + notif->move(corner); + break; + } +} + +void ToastNotificationsManager::makeSpaceForNotification(int height_to_make_space) { + for (BaseToastNotification* notif : m_activeNotifications) { + notif->move(notif->pos().x(), notif->pos().y() + height_to_make_space + NOTIFICATIONS_MARGIN); + } +} + +void ToastNotificationsManager::removeOutOfBoundsNotifications(int height_to_reserve) { + auto* screen = activeScreen(); + + int available_height = screen->availableSize().height(); + + while (boolinq::from(m_activeNotifications).sum([](BaseToastNotification* notif) { + return notif->height() + NOTIFICATIONS_MARGIN; + }) + height_to_reserve > + available_height) { + if (!m_activeNotifications.isEmpty()) { + m_activeNotifications.takeLast()->deleteLater(); + } + else { + break; + } + } +} + +QScreen* ToastNotificationsManager::moveToProperScreen(BaseToastNotification* notif) { + if (m_screen >= 0) { + auto all_screens = QGuiApplication::screens(); + + if (m_screen < all_screens.size()) { + notif->windowHandle()->setScreen(all_screens.at(m_screen)); + + return all_screens.at(m_screen); + } + } + + return QGuiApplication::primaryScreen(); +} diff --git a/src/librssguard/gui/notifications/toastnotificationsmanager.h b/src/librssguard/gui/notifications/toastnotificationsmanager.h index 04af63f36..cac9f3501 100644 --- a/src/librssguard/gui/notifications/toastnotificationsmanager.h +++ b/src/librssguard/gui/notifications/toastnotificationsmanager.h @@ -9,6 +9,7 @@ class BaseToastNotification; class ToastNotification; +class QScreen; class ToastNotificationsManager : public QObject { Q_OBJECT @@ -39,9 +40,20 @@ class ToastNotificationsManager : public QObject { void showNotification(Notification::Event event, const GuiMessage& msg, const GuiAction& action); void showNotification(const QList& new_messages); + private: + QScreen* activeScreen() const; + QPoint cornerForNewNotification(QRect rect); + void moveNotificationToCorner(BaseToastNotification* notif, const QPoint& corner); + void makeSpaceForNotification(int height_to_make_space); + void removeOutOfBoundsNotifications(int height_to_reserve); + QScreen* moveToProperScreen(BaseToastNotification* notif); + private: NotificationPosition m_position; int m_screen; + + // List of all displayed notifications, newest notifications are in the beginning of the list + // and oldest at the end. QList m_activeNotifications; }; diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index b115eb2d0..060858132 100644 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -177,9 +177,11 @@ Application::Application(const QString& id, int& argc, char** argv, const QStrin #if defined(USE_WEBENGINE) m_webFactory->urlIinterceptor()->load(); - m_webFactory->engineProfile()->setCachePath(cacheFolder() + QDir::separator() + QSL("web") + QDir::separator() + QSL("cache")); + m_webFactory->engineProfile()->setCachePath(cacheFolder() + QDir::separator() + QSL("web") + QDir::separator() + + QSL("cache")); m_webFactory->engineProfile()->setHttpCacheType(QWebEngineProfile::HttpCacheType::DiskHttpCache); - m_webFactory->engineProfile()->setPersistentStoragePath(userDataFolder() + QDir::separator() + QSL("web") + QDir::separator() + QSL("storage")); + m_webFactory->engineProfile()->setPersistentStoragePath(userDataFolder() + QDir::separator() + QSL("web") + + QDir::separator() + QSL("storage")); m_webFactory->loadCustomCss(userDataFolder() + QDir::separator() + QSL("web") + QDir::separator() + QSL("user-styles.css")); @@ -833,7 +835,10 @@ void Application::showMessagesNumber(int unread_messages, bool any_feed_has_new_ m_trayIcon->setNumber(unread_messages, any_feed_has_new_unread_messages); } - // Set task bar overlay with number of unread articles. + // Use Qt function to set "badge" number directly in some cases. +#if defined(Q_OS_MACOS) && QT_VERSION >= 0x060500 // Qt >= 6.5.0 + qApp->setBadgeNumber(unread_messages); +#else #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // Use D-Bus "LauncherEntry" service on Linux. bool task_bar_count_enabled = settings()->value(GROUP(GUI), SETTING(GUI::UnreadNumbersOnTaskBar)).toBool(); @@ -882,6 +887,7 @@ void Application::showMessagesNumber(int unread_messages, bool any_feed_has_new_ else { qCriticalNN << LOGSEC_GUI << "Main form not set for setting numbers."; } +#endif #endif if (m_mainForm != nullptr) {