diff --git a/resources/rssguard.qrc b/resources/rssguard.qrc index 258511cab..b0ba56fda 100755 --- a/resources/rssguard.qrc +++ b/resources/rssguard.qrc @@ -6,6 +6,11 @@ text/COPYING_GNU_GPL text/COPYING_GNU_GPL_HTML + sounds/boing.wav + sounds/doorbell.wav + sounds/rooster.wav + sounds/sheep.wav + scripts/adblock/adblock-server.js scripts/public_suffix_list.dat diff --git a/resources/sounds/boing.wav b/resources/sounds/boing.wav new file mode 100755 index 000000000..426fe37be Binary files /dev/null and b/resources/sounds/boing.wav differ diff --git a/resources/sounds/doorbell.wav b/resources/sounds/doorbell.wav new file mode 100755 index 000000000..ec05baa00 Binary files /dev/null and b/resources/sounds/doorbell.wav differ diff --git a/resources/sounds/rooster.wav b/resources/sounds/rooster.wav new file mode 100755 index 000000000..f1c18546d Binary files /dev/null and b/resources/sounds/rooster.wav differ diff --git a/resources/sounds/sheep.wav b/resources/sounds/sheep.wav new file mode 100755 index 000000000..e26c3f5df Binary files /dev/null and b/resources/sounds/sheep.wav differ diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index 88ff96b8c..ba5f99034 100755 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -45,6 +45,7 @@ #define MSG_SCORE_MAX 100.0 #define MSG_SCORE_MIN 0.0 +#define SOUNDS_BUILTIN_DIRECTORY ":/sounds" #define ARGUMENTS_LIST_SEPARATOR "\n" #define IS_IN_ARRAY(offset, array) ((offset >= 0) && (offset < array.count())) #define DEFAULT_SQL_MESSAGES_FILTER "0 > 1" diff --git a/src/librssguard/gui/newspaperpreviewer.cpp b/src/librssguard/gui/newspaperpreviewer.cpp index 384a007e3..b20490d1c 100644 --- a/src/librssguard/gui/newspaperpreviewer.cpp +++ b/src/librssguard/gui/newspaperpreviewer.cpp @@ -42,13 +42,13 @@ void NewspaperPreviewer::showMoreMessages() { prev->loadMessage(msg, m_root.data()); } - m_ui->m_btnShowMoreMessages->setText(tr("Show more messages (%n remaining)", "", m_messages.size())); + m_ui->m_btnShowMoreMessages->setText(tr("Show more articles (%n remaining)", "", m_messages.size())); m_ui->m_btnShowMoreMessages->setEnabled(!m_messages.isEmpty()); m_ui->scrollArea->verticalScrollBar()->setValue(current_scroll); } else { - qApp->showGuiMessage(tr("Cannot show more messages"), - tr("Cannot show more messages because parent feed was removed."), + qApp->showGuiMessage(tr("Cannot show more articles"), + tr("Cannot show more articles because parent feed was removed."), QSystemTrayIcon::MessageIcon::Warning, qApp->mainForm(), true); diff --git a/src/librssguard/gui/notifications/notificationseditor.cpp b/src/librssguard/gui/notifications/notificationseditor.cpp new file mode 100755 index 000000000..5bb9a45de --- /dev/null +++ b/src/librssguard/gui/notifications/notificationseditor.cpp @@ -0,0 +1,41 @@ +// For license of this file, see /LICENSE.md. + +#include "gui/notifications/notificationseditor.h" + +#include "3rd-party/boolinq/boolinq.h" +#include "gui/notifications/singlenotificationeditor.h" + +#include + +NotificationsEditor::NotificationsEditor(QWidget* parent) : QScrollArea(parent), m_layout(new QVBoxLayout(this)) { + m_ui.setupUi(this); + setLayout(m_layout); +} + +void NotificationsEditor::loadNotifications(const QList& notifications) { + auto all_events = Notification::allEvents(); + auto notif = boolinq::from(notifications); + + for (auto ev : all_events) { + if (notif.any([ev](auto n) { + return n.event() == ev; + })) { + auto* notif_editor = new SingleNotificationEditor(notif.first([ev](auto n) { + return n.event() == ev; + }), this); + + notif_editor->setNotificationEnabled(true); + + m_layout->addWidget(notif_editor); + } + else { + auto* notif_editor = new SingleNotificationEditor(Notification(ev, {}), this); + + notif_editor->setNotificationEnabled(false); + m_layout->addWidget(notif_editor); + + } + } + + m_layout->addSpacerItem(new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); +} diff --git a/src/librssguard/gui/notifications/notificationseditor.h b/src/librssguard/gui/notifications/notificationseditor.h new file mode 100755 index 000000000..df7d66561 --- /dev/null +++ b/src/librssguard/gui/notifications/notificationseditor.h @@ -0,0 +1,27 @@ +// For license of this file, see /LICENSE.md. + +#ifndef NOTIFICATIONSEDITOR_H +#define NOTIFICATIONSEDITOR_H + +#include + +#include "ui_notificationseditor.h" + +#include "miscellaneous/notification.h" + +class QVBoxLayout; + +class NotificationsEditor : public QScrollArea { + Q_OBJECT + + public: + explicit NotificationsEditor(QWidget* parent = nullptr); + + void loadNotifications(const QList& notifications); + + private: + Ui::NotificationsEditor m_ui; + QVBoxLayout* m_layout; +}; + +#endif // NOTIFICATIONSEDITOR_H diff --git a/src/librssguard/gui/notifications/notificationseditor.ui b/src/librssguard/gui/notifications/notificationseditor.ui new file mode 100755 index 000000000..ebf93bf17 --- /dev/null +++ b/src/librssguard/gui/notifications/notificationseditor.ui @@ -0,0 +1,19 @@ + + + NotificationsEditor + + + + 0 + 0 + 400 + 300 + + + + QFrame::NoFrame + + + + + diff --git a/src/librssguard/gui/notifications/singlenotificationeditor.cpp b/src/librssguard/gui/notifications/singlenotificationeditor.cpp new file mode 100755 index 000000000..934fdd236 --- /dev/null +++ b/src/librssguard/gui/notifications/singlenotificationeditor.cpp @@ -0,0 +1,41 @@ +// For license of this file, see /LICENSE.md. + +#include "gui/notifications/singlenotificationeditor.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" + +SingleNotificationEditor::SingleNotificationEditor(const Notification& notification, QWidget* parent) + : QWidget(parent), m_notificationEvent(Notification::Event::UnknownEvent) { + m_ui.setupUi(this); + + m_ui.m_btnBrowseSound->setIcon(qApp->icons()->fromTheme(QSL("document-open"))); + m_ui.m_btnPlaySound->setIcon(qApp->icons()->fromTheme(QSL("media-playback-start"))); + + connect(m_ui.m_btnPlaySound, &QPushButton::clicked, this, &SingleNotificationEditor::playSound); + + loadNotification(notification); +} + +Notification SingleNotificationEditor::notification() const { + return Notification(m_notificationEvent, m_ui.m_txtSound->text()); +} + +bool SingleNotificationEditor::notificationEnabled() const { + return m_ui.m_gbNotification->isChecked(); +} + +void SingleNotificationEditor::setNotificationEnabled(bool enabled) { + m_ui.m_gbNotification->setChecked(enabled); +} + +void SingleNotificationEditor::playSound() { + Notification({}, m_ui.m_txtSound->text()).playSound(qApp); +} + +void SingleNotificationEditor::loadNotification(const Notification& notification) { + m_ui.m_txtSound->setText(notification.soundPath()); + m_ui.m_gbNotification->setTitle(Notification::nameForEvent(notification.event())); + + m_notificationEvent = notification.event(); +} diff --git a/src/librssguard/gui/notifications/singlenotificationeditor.h b/src/librssguard/gui/notifications/singlenotificationeditor.h new file mode 100755 index 000000000..bf9d1020f --- /dev/null +++ b/src/librssguard/gui/notifications/singlenotificationeditor.h @@ -0,0 +1,34 @@ +// For license of this file, see /LICENSE.md. + +#ifndef SINGLENOTIFICATIONEDITOR_H +#define SINGLENOTIFICATIONEDITOR_H + +#include + +#include "ui_singlenotificationeditor.h" + +#include "miscellaneous/notification.h" + +class SingleNotificationEditor : public QWidget { + Q_OBJECT + + public: + explicit SingleNotificationEditor(const Notification& notification, QWidget* parent = nullptr); + + Notification notification() const; + + bool notificationEnabled() const; + void setNotificationEnabled(bool enabled); + + private slots: + void playSound(); + + private: + void loadNotification(const Notification& notification); + + private: + Ui::SingleNotificationEditor m_ui; + Notification::Event m_notificationEvent; +}; + +#endif // SINGLENOTIFICATIONEDITOR_H diff --git a/src/librssguard/gui/notifications/singlenotificationeditor.ui b/src/librssguard/gui/notifications/singlenotificationeditor.ui new file mode 100755 index 000000000..e04d22d0e --- /dev/null +++ b/src/librssguard/gui/notifications/singlenotificationeditor.ui @@ -0,0 +1,86 @@ + + + SingleNotificationEditor + + + + 0 + 0 + 400 + 125 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + + + + Sound + + + + + + + + + Full path to your WAV sound file + + + + + + + &Browse + + + + + + + &Play + + + + + + + + + + + + + diff --git a/src/librssguard/gui/settings/settingsnotifications.cpp b/src/librssguard/gui/settings/settingsnotifications.cpp index 16245f7f2..3416f8cb2 100755 --- a/src/librssguard/gui/settings/settingsnotifications.cpp +++ b/src/librssguard/gui/settings/settingsnotifications.cpp @@ -2,19 +2,38 @@ #include "gui/settings/settingsnotifications.h" +#include "3rd-party/boolinq/boolinq.h" +#include "gui/guiutilities.h" +#include "gui/notifications/notificationseditor.h" +#include "miscellaneous/application.h" +#include "miscellaneous/notificationfactory.h" #include "miscellaneous/settings.h" +#include + SettingsNotifications::SettingsNotifications(Settings* settings, QWidget* parent) : SettingsPanel(settings, parent) { m_ui.setupUi(this); + GuiUtilities::setLabelAsNotice(*m_ui.m_lblAvailableSounds, false); + connect(m_ui.m_checkEnableNotifications, &QCheckBox::toggled, this, &SettingsNotifications::dirtifySettings); } void SettingsNotifications::loadSettings() { onBeginLoadSettings(); + auto builtin_sounds = QDir(SOUNDS_BUILTIN_DIRECTORY).entryInfoList(QDir::Filter::Files, + QDir::SortFlag::Name); + auto iter = boolinq::from(builtin_sounds).select([](const QFileInfo& i) { + return QSL(" %1").arg(i.absoluteFilePath()); + }).toStdList(); + auto descs = FROM_STD_LIST(QStringList, iter).join(QSL("\n")); + + m_ui.m_lblAvailableSounds->setText(QSL("Built-in sounds:\n%1").arg(descs)); + // Load fancy notification settings. m_ui.m_checkEnableNotifications->setChecked(settings()->value(GROUP(Notifications), SETTING(Notifications::EnableNotifications)).toBool()); + m_ui.m_editor->loadNotifications(qApp->notifications()->allNotifications()); onEndLoadSettings(); } diff --git a/src/librssguard/gui/settings/settingsnotifications.ui b/src/librssguard/gui/settings/settingsnotifications.ui index ebde7f9b8..5a193102e 100755 --- a/src/librssguard/gui/settings/settingsnotifications.ui +++ b/src/librssguard/gui/settings/settingsnotifications.ui @@ -26,28 +26,59 @@ 0 - - - - Qt::Vertical - - - - 20 - 40 - - - - - Enable popup balloon tooltips + Enable notifications + + + + false + + + + 0 + 1 + + + + + + + + + + NotificationsEditor + QWidget +
notificationseditor.h
+ 1 +
+
+ + m_checkEnableNotifications + - + + + m_checkEnableNotifications + toggled(bool) + m_editor + setEnabled(bool) + + + 199 + 8 + + + 0 + 31 + + + + diff --git a/src/librssguard/librssguard.pro b/src/librssguard/librssguard.pro index c255b4dc9..7d0ac14a2 100644 --- a/src/librssguard/librssguard.pro +++ b/src/librssguard/librssguard.pro @@ -60,6 +60,8 @@ HEADERS += core/feeddownloader.h \ exceptions/ioexception.h \ exceptions/networkexception.h \ exceptions/scriptexception.h \ + gui/notifications/notificationseditor.h \ + gui/notifications/singlenotificationeditor.h \ gui/reusable/baselineedit.h \ gui/settings/settingsnotifications.h \ gui/toolbars/basetoolbar.h \ @@ -242,6 +244,8 @@ SOURCES += core/feeddownloader.cpp \ exceptions/ioexception.cpp \ exceptions/networkexception.cpp \ exceptions/scriptexception.cpp \ + gui/notifications/notificationseditor.cpp \ + gui/notifications/singlenotificationeditor.cpp \ gui/reusable/baselineedit.cpp \ gui/settings/settingsnotifications.cpp \ gui/toolbars/basetoolbar.cpp \ @@ -402,6 +406,8 @@ FORMS += gui/dialogs/formabout.ui \ gui/dialogs/formrestoredatabasesettings.ui \ gui/dialogs/formsettings.ui \ gui/dialogs/formupdate.ui \ + gui/notifications/notificationseditor.ui \ + gui/notifications/singlenotificationeditor.ui \ gui/reusable/networkproxydetails.ui \ gui/newspaperpreviewer.ui \ gui/reusable/searchtextwidget.ui \ @@ -494,6 +500,7 @@ INCLUDEPATH += $$PWD/. \ $$PWD/gui/dialogs \ $$PWD/gui/reusable \ $$PWD/gui/toolbars \ + $$PWD/gui/notifications \ $$PWD/dynamic-shortcuts TRANSLATIONS += $$files($$PWD/../../localization/rssguard_*.ts, false) \ diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index 76a7f7fe1..724cd4055 100755 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -87,7 +87,15 @@ Application::Application(const QString& id, int& argc, char** argv) }); #endif - m_notifications->load(settings()); + if (isFirstRun()) { + m_notifications->save({ + Notification(Notification::Event::NewArticlesFetched, + QSL("%1/rooster.wav").arg(SOUNDS_BUILTIN_DIRECTORY)) + }, settings()); + } + else { + m_notifications->load(settings()); + } qDebugNN << LOGSEC_CORE << "OpenSSL version:" @@ -231,6 +239,11 @@ void Application::eliminateFirstRuns() { settings()->setValue(GROUP(General), QString(General::FirstRun) + QL1C('_') + APP_VERSION, false); } +NotificationFactory* Application::notifications() const +{ + return m_notifications; +} + void Application::setFeedReader(FeedReader* feed_reader) { m_feedReader = feed_reader; connect(m_feedReader, &FeedReader::feedUpdatesFinished, this, &Application::onFeedUpdatesFinished); diff --git a/src/librssguard/miscellaneous/application.h b/src/librssguard/miscellaneous/application.h index 958e4cf7d..4832d78f8 100755 --- a/src/librssguard/miscellaneous/application.h +++ b/src/librssguard/miscellaneous/application.h @@ -76,6 +76,7 @@ class RSSGUARD_DLLSPEC Application : public SingleApplication { FormMain* mainForm(); QWidget* mainFormWidget(); SystemTrayIcon* trayIcon(); + NotificationFactory* notifications() const; QIcon desktopAwareIcon() const; diff --git a/src/librssguard/miscellaneous/notification.cpp b/src/librssguard/miscellaneous/notification.cpp index 89bf43b71..b9a80877b 100755 --- a/src/librssguard/miscellaneous/notification.cpp +++ b/src/librssguard/miscellaneous/notification.cpp @@ -30,3 +30,27 @@ void Notification::setSoundPath(const QString& sound_path) { void Notification::playSound(Application* app) const { QSound::play(QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath))); } + +QList Notification::allEvents() { + return { + Event::NewArticlesFetched, + Event::ArticlesFetchingStarted, + Event::LoginDataRefreshed + }; +} + +QString Notification::nameForEvent(Notification::Event event) { + switch (event) { + case Notification::Event::NewArticlesFetched: + return QObject::tr("New articles fetched"); + + case Notification::Event::ArticlesFetchingStarted: + return QObject::tr("Fetching articles right now"); + + case Notification::Event::LoginDataRefreshed: + return QObject::tr("Login data refreshed"); + + default: + return QObject::tr("Unknown event"); + } +} diff --git a/src/librssguard/miscellaneous/notification.h b/src/librssguard/miscellaneous/notification.h index 6d2436c39..51c46972d 100755 --- a/src/librssguard/miscellaneous/notification.h +++ b/src/librssguard/miscellaneous/notification.h @@ -10,16 +10,20 @@ class Application; class Notification { public: enum class Event { + UnknownEvent = 0, + // New (unread) messages were downloaded for some feed. - NewMessagesDownloaded = 1, + NewArticlesFetched = 1, // RSS Guard started downloading messages for some feed. - MesssagesDownloadStarted = 2, + ArticlesFetchingStarted = 2, // Login tokens were successfuly refreshed. // NOTE: This is primarily used in accounts which use // OAuth or similar mechanism. - LoginDataRefreshed = 4 + LoginDataRefreshed = 4, + + // TODO: app update is available }; explicit Notification(); @@ -36,6 +40,9 @@ class Notification { void playSound(Application* app) const; + static QList allEvents(); + static QString nameForEvent(Event event); + private: Event m_event; QString m_soundPath; diff --git a/src/librssguard/miscellaneous/notificationfactory.cpp b/src/librssguard/miscellaneous/notificationfactory.cpp index 2de3356f6..54c1171b8 100755 --- a/src/librssguard/miscellaneous/notificationfactory.cpp +++ b/src/librssguard/miscellaneous/notificationfactory.cpp @@ -7,8 +7,14 @@ #include "exceptions/applicationexception.h" #include "miscellaneous/settings.h" +#include + NotificationFactory::NotificationFactory(QObject* parent) : QObject(parent) {} +QList NotificationFactory::allNotifications() const { + return m_notifications; +} + Notification NotificationFactory::notificationForEvent(Notification::Event event) const { auto good_n = boolinq::from(m_notifications).where([event](const Notification& n) { return n.event() == event; @@ -23,15 +29,23 @@ Notification NotificationFactory::notificationForEvent(Notification::Event event } void NotificationFactory::load(Settings* settings) { - //settings->allKeys(Notifications::ID) + auto notif_keys = settings->allKeys(GROUP(Notifications)).filter(QRegularExpression(QSL("^\\d+$"))); - m_notifications = { - Notification() - }; + m_notifications.clear(); + + for (const auto& key : notif_keys) { + auto event = Notification::Event(key.toInt()); + auto sound = settings->value(GROUP(Notifications), key).toString(); + + m_notifications.append(Notification(event, sound)); + } } void NotificationFactory::save(const QList& new_notifications, Settings* settings) { + settings->remove(GROUP(Notifications)); m_notifications = new_notifications; - // + for (const auto& n : qAsConst(m_notifications)) { + settings->setValue(GROUP(Notifications), QString::number(int(n.event())), n.soundPath()); + } } diff --git a/src/librssguard/miscellaneous/notificationfactory.h b/src/librssguard/miscellaneous/notificationfactory.h index 96fc76547..719ed61fd 100755 --- a/src/librssguard/miscellaneous/notificationfactory.h +++ b/src/librssguard/miscellaneous/notificationfactory.h @@ -15,6 +15,7 @@ class NotificationFactory : public QObject { public: explicit NotificationFactory(QObject* parent = nullptr); + QList allNotifications() const; Notification notificationForEvent(Notification::Event event) const; public slots: diff --git a/src/librssguard/miscellaneous/settings.h b/src/librssguard/miscellaneous/settings.h index 30e644adf..02e6e4e7b 100644 --- a/src/librssguard/miscellaneous/settings.h +++ b/src/librssguard/miscellaneous/settings.h @@ -432,7 +432,7 @@ class Settings : public QSettings { void setValue(const QString& key, const QVariant& value); bool contains(const QString& section, const QString& key) const; - void remove(const QString& section, const QString& key); + void remove(const QString& section, const QString& key = {}); // Returns the path which contains the settings. QString pathName() const; @@ -487,7 +487,14 @@ inline bool Settings::contains(const QString& section, const QString& key) const } inline void Settings::remove(const QString& section, const QString& key) { - QSettings::remove(QString(QSL("%1/%2")).arg(section, key)); + if (key.isEmpty()) { + beginGroup(section); + QSettings::remove({}); + endGroup(); + } + else { + QSettings::remove(QString(QSL("%1/%2")).arg(section, key)); + } } #endif // SETTINGS_H