diff --git a/resources/icons.qrc b/resources/icons.qrc index 68cbb2aee..2d41be1a3 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -8,6 +8,8 @@ ./graphics/Breeze/categories/32/applications-system.svg ./graphics/Breeze/actions/32/arrow-down.svg ./graphics/Breeze/actions/32/arrow-down-double.svg + ./graphics/Breeze/actions/32/arrow-left.svg + ./graphics/Breeze/actions/32/arrow-right.svg ./graphics/Breeze/actions/32/arrow-up.svg ./graphics/Breeze/actions/32/arrow-up-double.svg ./graphics/Breeze/actions/32/call-start.svg @@ -99,6 +101,8 @@ ./graphics/Breeze Dark/categories/32/applications-system.svg ./graphics/Breeze Dark/actions/32/arrow-down.svg ./graphics/Breeze Dark/actions/32/arrow-down-double.svg + ./graphics/Breeze Dark/actions/32/arrow-left.svg + ./graphics/Breeze Dark/actions/32/arrow-right.svg ./graphics/Breeze Dark/actions/32/arrow-up.svg ./graphics/Breeze Dark/actions/32/arrow-up-double.svg ./graphics/Breeze Dark/actions/32/call-start.svg @@ -274,6 +278,8 @@ ./graphics/Numix/22/categories/applications-system.svg ./graphics/Numix/22/actions/arrow-down.svg ./graphics/Numix/22/actions/arrow-down-double.svg + ./graphics/Numix/22/actions/arrow-left.svg + ./graphics/Numix/22/actions/arrow-right.svg ./graphics/Numix/22/actions/arrow-up.svg ./graphics/Numix/22/actions/arrow-up-double.svg ./graphics/Numix/22/actions/browser-download.svg diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index c2dccee1b..f6afa971d 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -1,4 +1,6 @@ set(SOURCES + core/articlelistnotificationmodel.cpp + core/articlelistnotificationmodel.h core/feeddownloader.cpp core/feeddownloader.h core/feedsmodel.cpp diff --git a/src/librssguard/core/articlelistnotificationmodel.cpp b/src/librssguard/core/articlelistnotificationmodel.cpp new file mode 100644 index 000000000..4b1998dfd --- /dev/null +++ b/src/librssguard/core/articlelistnotificationmodel.cpp @@ -0,0 +1,23 @@ +// For license of this file, see /LICENSE.md. + +#include "core/articlelistnotificationmodel.h" + +ArticleListNotificationModel::ArticleListNotificationModel(QObject* parent) + : QAbstractListModel(parent), m_currentPage(-1) {} + +ArticleListNotificationModel::~ArticleListNotificationModel() {} + +void ArticleListNotificationModel::setArticles(const QList& msgs) { + m_articles = msgs; + m_currentPage = 0; +} + +void ArticleListNotificationModel::nextPage() {} + +void ArticleListNotificationModel::previousPage() {} + +int ArticleListNotificationModel::rowCount(const QModelIndex& parent) const {} + +int ArticleListNotificationModel::columnCount(const QModelIndex& parent) const {} + +QVariant ArticleListNotificationModel::data(const QModelIndex& index, int role) const {} diff --git a/src/librssguard/core/articlelistnotificationmodel.h b/src/librssguard/core/articlelistnotificationmodel.h new file mode 100644 index 000000000..96a898a97 --- /dev/null +++ b/src/librssguard/core/articlelistnotificationmodel.h @@ -0,0 +1,33 @@ +// For license of this file, see /LICENSE.md. + +#ifndef ARTICLELISTNOTIFICATIONMODEL_H +#define ARTICLELISTNOTIFICATIONMODEL_H + +#include + +#include "core/message.h" + +class ArticleListNotificationModel : public QAbstractListModel { + public: + explicit ArticleListNotificationModel(QObject* parent = nullptr); + virtual ~ArticleListNotificationModel(); + + void setArticles(const QList& msgs); + + void nextPage(); + void previousPage(); + + virtual int rowCount(const QModelIndex& parent) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual QVariant data(const QModelIndex& index, int role) const; + + signals: + void nextPagePossibleChanged(bool possible); + void previousPagePossibleChanged(bool possible); + + private: + QList m_articles; + int m_currentPage; +}; + +#endif // ARTICLELISTNOTIFICATIONMODEL_H diff --git a/src/librssguard/core/feeddownloader.cpp b/src/librssguard/core/feeddownloader.cpp index 8b51c3d42..dbe1027a0 100644 --- a/src/librssguard/core/feeddownloader.cpp +++ b/src/librssguard/core/feeddownloader.cpp @@ -413,15 +413,17 @@ void FeedDownloader::updateOneFeed(ServiceRoot* acc, << "microseconds."; if (feed->status() != Feed::Status::NewMessages) { - feed->setStatus(updated_messages.first > 0 || updated_messages.second > 0 ? Feed::Status::NewMessages - : Feed::Status::Normal); + feed->setStatus((!updated_messages.m_all.isEmpty() || !updated_messages.m_unread.isEmpty()) + ? Feed::Status::NewMessages + : Feed::Status::Normal); } - qDebugNN << LOGSEC_FEEDDOWNLOADER << updated_messages << " messages for feed " << feed->customId() - << " stored in DB."; + qDebugNN << LOGSEC_FEEDDOWNLOADER << updated_messages.m_unread.size() << " unread messages and" + << NONQUOTE_W_SPACE(updated_messages.m_all.size()) "total messages for feed" + << QUOTE_W_SPACE(feed->customId()) << "stored in DB."; - if (updated_messages.first > 0) { - m_results.appendUpdatedFeed({feed, updated_messages.first}); + if (!updated_messages.m_unread.isEmpty()) { + m_results.appendUpdatedFeed(feed, updated_messages.m_unread); } } catch (const FeedFetchException& feed_ex) { @@ -445,7 +447,6 @@ void FeedDownloader::finalizeUpdate() { qDebugNN << LOGSEC_FEEDDOWNLOADER << "Finished feed updates in thread" << QUOTE_W_SPACE_DOT(QThread::currentThreadId()); - m_results.sort(); m_feeds.clear(); // Update of feeds has finished. @@ -528,7 +529,8 @@ QString FeedDownloadResults::overview(int how_many_feeds) const { QStringList result; for (int i = 0, number_items_output = qMin(how_many_feeds, m_updatedFeeds.size()); i < number_items_output; i++) { - result.append(m_updatedFeeds.at(i).first->title() + QSL(": ") + QString::number(m_updatedFeeds.at(i).second)); + result.append(m_updatedFeeds.keys().at(i)->title() + QSL(": ") + + QString::number(m_updatedFeeds.value(m_updatedFeeds.keys().at(i)).size())); } QString res_str = result.join(QSL("\n")); @@ -540,22 +542,14 @@ QString FeedDownloadResults::overview(int how_many_feeds) const { return res_str; } -void FeedDownloadResults::appendUpdatedFeed(const QPair& feed) { - m_updatedFeeds.append(feed); -} - -void FeedDownloadResults::sort() { - std::sort(m_updatedFeeds.begin(), - m_updatedFeeds.end(), - [](const QPair& lhs, const QPair& rhs) { - return lhs.second > rhs.second; - }); +void FeedDownloadResults::appendUpdatedFeed(Feed* feed, const QList& updated_unread_msgs) { + m_updatedFeeds.insert(feed, updated_unread_msgs); } void FeedDownloadResults::clear() { m_updatedFeeds.clear(); } -QList> FeedDownloadResults::updatedFeeds() const { +QHash> FeedDownloadResults::updatedFeeds() const { return m_updatedFeeds; } diff --git a/src/librssguard/core/feeddownloader.h b/src/librssguard/core/feeddownloader.h index 09e29fd5e..8e24fd925 100644 --- a/src/librssguard/core/feeddownloader.h +++ b/src/librssguard/core/feeddownloader.h @@ -5,29 +5,28 @@ #include -#include -#include - #include "core/message.h" #include "exceptions/applicationexception.h" #include "services/abstract/cacheforserviceroot.h" #include "services/abstract/feed.h" +#include +#include +#include + class MessageFilter; // Represents results of batch feed updates. class FeedDownloadResults { public: - QList> updatedFeeds() const; + QHash> updatedFeeds() const; QString overview(int how_many_feeds) const; - - void appendUpdatedFeed(const QPair& feed); - void sort(); + void appendUpdatedFeed(Feed* feed, const QList& updated_unread_msgs); void clear(); private: // QString represents title if the feed, int represents count of newly downloaded messages. - QList> m_updatedFeeds; + QHash> m_updatedFeeds; }; struct FeedUpdateRequest { diff --git a/src/librssguard/core/message.cpp b/src/librssguard/core/message.cpp index 181cee753..20c735f1f 100644 --- a/src/librssguard/core/message.cpp +++ b/src/librssguard/core/message.cpp @@ -2,7 +2,9 @@ #include "core/message.h" +#include "miscellaneous/application.h" #include "miscellaneous/textfactory.h" +#include "network-web/webfactory.h" #include "services/abstract/feed.h" #include "services/abstract/label.h" @@ -75,6 +77,8 @@ Message::Message() { void Message::sanitize(const Feed* feed, bool fix_future_datetimes) { // Sanitize title. + m_title = qApp->web()->stripTags(qApp->web()->unescapeHtml(m_title)); + m_title = m_title // Remove non-breaking spaces. @@ -89,6 +93,9 @@ void Message::sanitize(const Feed* feed, bool fix_future_datetimes) { // Remove non-breaking zero-width spaces. .remove(QChar(65279)); + // Sanitize author. + m_author = qApp->web()->stripTags(qApp->web()->unescapeHtml(m_author)); + // Sanitize URL. m_url = m_url.trimmed(); diff --git a/src/librssguard/database/databasequeries.cpp b/src/librssguard/database/databasequeries.cpp index a06807faa..aa9bb6107 100644 --- a/src/librssguard/database/databasequeries.cpp +++ b/src/librssguard/database/databasequeries.cpp @@ -1354,7 +1354,7 @@ QHash DatabaseQueries::bagsOfMessages(const QSqlDatabase& return ids; } -QPair DatabaseQueries::updateMessages(const QSqlDatabase& db, +UpdatedArticles DatabaseQueries::updateMessages(const QSqlDatabase& db, QList& messages, Feed* feed, bool force_update, @@ -1362,10 +1362,10 @@ QPair DatabaseQueries::updateMessages(const QSqlDatabase& db, bool* ok) { if (messages.isEmpty()) { *ok = true; - return {0, 0}; + return {}; } - QPair updated_messages = {0, 0}; + UpdatedArticles updated_messages; int account_id = feed->getParentServiceRoot()->accountId(); auto feed_custom_id = feed->customId(); @@ -1615,10 +1615,10 @@ QPair DatabaseQueries::updateMessages(const QSqlDatabase& db, << QUOTE_W_SPACE(message.m_url) << "in DB."; if (!message.m_isRead) { - updated_messages.first++; + updated_messages.m_unread.append(message); } - updated_messages.second++; + updated_messages.m_all.append(message); message.m_insertedUpdated = true; } else if (query_update.lastError().isValid()) { @@ -1655,10 +1655,10 @@ QPair DatabaseQueries::updateMessages(const QSqlDatabase& db, } if (!msg->m_isRead) { - updated_messages.first++; + updated_messages.m_unread.append(*msg); } - updated_messages.second++; + updated_messages.m_all.append(*msg); msg->m_insertedUpdated = true; vals.append(QSL("\n(':feed', ':title', :is_read, :is_important, :is_deleted, " @@ -1742,10 +1742,10 @@ QPair DatabaseQueries::updateMessages(const QSqlDatabase& db, // but its assigned labels were changed. Therefore we must count article // as updated. if (!message.m_isRead) { - updated_messages.first++; + updated_messages.m_unread.append(message); } - updated_messages.second++; + updated_messages.m_all.append(message); } } else { diff --git a/src/librssguard/database/databasequeries.h b/src/librssguard/database/databasequeries.h index f674d51dc..4a27d405b 100644 --- a/src/librssguard/database/databasequeries.h +++ b/src/librssguard/database/databasequeries.h @@ -21,11 +21,6 @@ #include #include -struct ArticleCounts { - int m_total = -1; - int m_unread = -1; -}; - class DatabaseQueries { public: static QMap messageTableAttributes(bool only_msg_table, bool is_sqlite); @@ -162,7 +157,7 @@ class DatabaseQueries { static void createOverwriteAccount(const QSqlDatabase& db, ServiceRoot* account); // Returns counts of updated messages . - static QPair updateMessages(const QSqlDatabase& db, + static UpdatedArticles updateMessages(const QSqlDatabase& db, QList& messages, Feed* feed, bool force_update, diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index d4e347dbd..61b67d787 100644 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -100,9 +100,10 @@ #define MAX_THREADPOOL_THREADS 32 #define WEB_BROWSER_SCROLL_STEP 50.0 -#define NOTIFICATIONS_MARGIN 16 -#define NOTIFICATIONS_WIDTH 256 -#define NOTIFICATIONS_TIMEOUT 15s +#define NOTIFICATIONS_MARGIN 16 +#define NOTIFICATIONS_WIDTH 256 +#define NOTIFICATIONS_TIMEOUT 15s +#define NOTIFICATIONS_PAGE_SIZE 10 #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" diff --git a/src/librssguard/definitions/typedefs.h b/src/librssguard/definitions/typedefs.h index 19e7bb02e..73e13d1ba 100644 --- a/src/librssguard/definitions/typedefs.h +++ b/src/librssguard/definitions/typedefs.h @@ -14,4 +14,14 @@ typedef QList> Assignment; typedef QPair AssignmentItem; typedef QPair ImportanceChange; +struct ArticleCounts { + int m_total = -1; + int m_unread = -1; +}; + +struct UpdatedArticles { + QList m_unread; + QList m_all; +}; + #endif // TYPEDEFS_H diff --git a/src/librssguard/gui/notifications/articlelistnotification.cpp b/src/librssguard/gui/notifications/articlelistnotification.cpp index 5eb62d926..ecb38c997 100644 --- a/src/librssguard/gui/notifications/articlelistnotification.cpp +++ b/src/librssguard/gui/notifications/articlelistnotification.cpp @@ -2,9 +2,21 @@ #include "gui/notifications/articlelistnotification.h" +#include "miscellaneous/iconfactory.h" + ArticleListNotification::ArticleListNotification(QWidget* parent) : BaseToastNotification(parent) { m_ui.setupUi(this); setupCloseButton(m_ui.m_btnClose); - setupTimedClosing(); + + m_ui.m_btnNextPage->setIcon(qApp->icons()->fromTheme(QSL("arrow-right"), QSL("stock_right"))); + m_ui.m_btnPreviousPage->setIcon(qApp->icons()->fromTheme(QSL("arrow-left"), QSL("stock_left"))); + m_ui.m_btnOpenArticleList->setIcon(qApp->icons()->fromTheme(QSL("view-list-details"))); + m_ui.m_btnOpenWebBrowser->setIcon(qApp->icons()->fromTheme(QSL("document-open"))); +} + +void ArticleListNotification::loadResults(const QHash>& new_messages) { + setupTimedClosing(); + + m_ui.m_treeArticles->setModel() } diff --git a/src/librssguard/gui/notifications/articlelistnotification.h b/src/librssguard/gui/notifications/articlelistnotification.h index 8aaea8095..f8888165f 100644 --- a/src/librssguard/gui/notifications/articlelistnotification.h +++ b/src/librssguard/gui/notifications/articlelistnotification.h @@ -5,14 +5,20 @@ #include "gui/notifications/basetoastnotification.h" +#include "core/message.h" + #include "ui_articlelistnotification.h" +class Feed; + class ArticleListNotification : public BaseToastNotification { Q_OBJECT public: explicit ArticleListNotification(QWidget* parent = nullptr); + void loadResults(const QHash>& new_messages); + private: Ui::ArticleListNotification m_ui; }; diff --git a/src/librssguard/gui/notifications/articlelistnotification.ui b/src/librssguard/gui/notifications/articlelistnotification.ui new file mode 100644 index 000000000..fda69a518 --- /dev/null +++ b/src/librssguard/gui/notifications/articlelistnotification.ui @@ -0,0 +1,130 @@ + + + ArticleListNotification + + + + 0 + 0 + 338 + 271 + + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + true + + + false + + + false + + + true + + + + + + + + + + + + Qt::AlignCenter + + + true + + + + + + + ... + + + + + + + + + + + Go to previous page + + + + + + + Go to next page + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Open article in article list + + + + + + + Open article in web browser + + + + + + + + + + PlainToolButton + QToolButton +
plaintoolbutton.h
+
+
+ + m_btnClose + m_treeArticles + m_btnPreviousPage + m_btnNextPage + m_btnOpenArticleList + m_btnOpenWebBrowser + + + +
diff --git a/src/librssguard/gui/notifications/basetoastnotification.cpp b/src/librssguard/gui/notifications/basetoastnotification.cpp index c396c6e56..a2a59addc 100644 --- a/src/librssguard/gui/notifications/basetoastnotification.cpp +++ b/src/librssguard/gui/notifications/basetoastnotification.cpp @@ -6,12 +6,13 @@ #include #include +#include #include using namespace std::chrono_literals; -BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent) { +BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent), m_timerId(-1) { setAttribute(Qt::WidgetAttribute::WA_ShowWithoutActivating); setFixedWidth(NOTIFICATIONS_WIDTH); setFocusPolicy(Qt::FocusPolicy::NoFocus); @@ -39,8 +40,15 @@ void BaseToastNotification::setupCloseButton(QAbstractButton* btn) { connect(btn, &QAbstractButton::clicked, this, &BaseToastNotification::close); } +void BaseToastNotification::stopTimedClosing() { + killTimer(m_timerId); + m_timerId = -1; +} + void BaseToastNotification::setupTimedClosing() { - QTimer::singleShot(NOTIFICATIONS_TIMEOUT, this, &BaseToastNotification::close); + if (m_timerId < 0) { + m_timerId = startTimer(NOTIFICATIONS_TIMEOUT); + } } bool BaseToastNotification::eventFilter(QObject* watched, QEvent* event) { @@ -48,12 +56,28 @@ bool BaseToastNotification::eventFilter(QObject* watched, QEvent* event) { return true; } else { + if (event->type() == QEvent::Type::Enter) { + stopTimedClosing(); + } + + if (event->type() == QEvent::Type::Leave) { + setupTimedClosing(); + } + return QDialog::eventFilter(watched, event); } } void BaseToastNotification::closeEvent(QCloseEvent* event) { + stopTimedClosing(); emit closeRequested(this); } void BaseToastNotification::reject() {} + +void BaseToastNotification::timerEvent(QTimerEvent* event) { + if (event->timerId() == m_timerId) { + stopTimedClosing(); + emit closeRequested(this); + } +} diff --git a/src/librssguard/gui/notifications/basetoastnotification.h b/src/librssguard/gui/notifications/basetoastnotification.h index 654e937ea..7749b073d 100644 --- a/src/librssguard/gui/notifications/basetoastnotification.h +++ b/src/librssguard/gui/notifications/basetoastnotification.h @@ -19,10 +19,12 @@ class BaseToastNotification : public QDialog { protected: virtual bool eventFilter(QObject* watched, QEvent* event); + virtual void timerEvent(QTimerEvent* event); virtual void closeEvent(QCloseEvent* event); - void setupCloseButton(QAbstractButton* btn); void setupTimedClosing(); + void setupCloseButton(QAbstractButton* btn); + void stopTimedClosing(); signals: void closeRequested(BaseToastNotification* notif); diff --git a/src/librssguard/gui/notifications/toastnotification.cpp b/src/librssguard/gui/notifications/toastnotification.cpp index 8918bb749..c057be392 100644 --- a/src/librssguard/gui/notifications/toastnotification.cpp +++ b/src/librssguard/gui/notifications/toastnotification.cpp @@ -43,7 +43,10 @@ void ToastNotification::loadNotification(Notification::Event event, const GuiMes if (action.m_action) { m_ui.m_btnAction->setText(action.m_title.isEmpty() ? tr("Do it!") : action.m_title); - connect(m_ui.m_btnAction, &QPushButton::clicked, this, action.m_action); + connect(m_ui.m_btnAction, &QPushButton::clicked, this, [this, action]() { + action.m_action(); + emit closeRequested(this); + }); } else { m_ui.m_mainLayout->removeItem(m_ui.m_actionLayout); diff --git a/src/librssguard/gui/notifications/toastnotification.ui b/src/librssguard/gui/notifications/toastnotification.ui index 029cec080..09e3b815b 100644 --- a/src/librssguard/gui/notifications/toastnotification.ui +++ b/src/librssguard/gui/notifications/toastnotification.ui @@ -10,9 +10,6 @@ 143 - - Dialog - 6 diff --git a/src/librssguard/gui/notifications/toastnotificationsmanager.cpp b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp index 73547ad85..d783c40ef 100644 --- a/src/librssguard/gui/notifications/toastnotificationsmanager.cpp +++ b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp @@ -12,6 +12,23 @@ #include #include +QString ToastNotificationsManager::textForPosition(ToastNotificationsManager::NotificationPosition pos) { + switch (pos) { + case TopLeft: + return QObject::tr("top-left"); + + case TopRight: + return QObject::tr("top-right"); + + case BottomLeft: + return QObject::tr("bottom-left"); + + case BottomRight: + default: + return QObject::tr("bottom-right"); + } +} + ToastNotificationsManager::ToastNotificationsManager(NotificationPosition position, int screen, QObject* parent) : QObject(parent), m_position(position), m_screen(screen), m_articleListNotification(nullptr) {} @@ -50,9 +67,26 @@ void ToastNotificationsManager::clear() { void ToastNotificationsManager::showNotification(Notification::Event event, const GuiMessage& msg, const GuiAction& action) { - ToastNotification* notif = new ToastNotification(event, msg, action, qApp->mainFormWidget()); + BaseToastNotification* notif; - hookNotification(notif); + if (!msg.m_feedFetchResults.updatedFeeds().isEmpty()) { + if (m_articleListNotification == nullptr) { + m_articleListNotification = new ArticleListNotification(); + hookNotification(m_articleListNotification); + } + else if (m_activeNotifications.contains(m_articleListNotification)) { + // Article notification is somewhere in list, clear first to move it to first positon. + closeNotification(m_articleListNotification, false); + } + + m_articleListNotification->loadResults(msg.m_feedFetchResults.updatedFeeds()); + + notif = m_articleListNotification; + } + else { + notif = new ToastNotification(event, msg, action, qApp->mainFormWidget()); + hookNotification(notif); + } auto* screen = moveToProperScreen(notif); @@ -76,39 +110,6 @@ void ToastNotificationsManager::showNotification(Notification::Event event, m_activeNotifications.prepend(notif); } -void ToastNotificationsManager::showNotification(const QList& new_messages) { - if (m_articleListNotification == nullptr) { - m_articleListNotification = new ArticleListNotification(); - hookNotification(m_articleListNotification); - } - - if (!m_activeNotifications.isEmpty() && m_activeNotifications.first() != m_articleListNotification) { - // Article notification is somewhere in list, clear first to move it to first positon. - closeNotification(m_articleListNotification, false); - } - - auto* screen = moveToProperScreen(m_articleListNotification); - - // Insert new notification into free space. - m_articleListNotification->show(); - - auto notif_new_pos = cornerForNewNotification(screen->availableGeometry()); - - // Make sure notification is finally resized. - m_articleListNotification->adjustSize(); - qApp->processEvents(); - - // Move notification, at this point we already need to know its precise size. - moveNotificationToCorner(m_articleListNotification, notif_new_pos); - - // Remove out-of-bounds old notifications and shift existing - // ones to make space for new notifications. - removeOutOfBoundsNotifications(m_articleListNotification->height()); - makeSpaceForNotification(m_articleListNotification->height()); - - m_activeNotifications.prepend(m_articleListNotification); -} - void ToastNotificationsManager::closeNotification(BaseToastNotification* notif, bool delete_from_memory) { auto notif_idx = m_activeNotifications.indexOf(notif); @@ -160,7 +161,7 @@ QPoint ToastNotificationsManager::cornerForNewNotification(QRect screen_rect) { void ToastNotificationsManager::hookNotification(BaseToastNotification* notif) { connect(notif, &BaseToastNotification::closeRequested, this, [this](BaseToastNotification* notif) { - closeNotification(notif, false); + closeNotification(notif, notif != m_articleListNotification); }); } diff --git a/src/librssguard/gui/notifications/toastnotificationsmanager.h b/src/librssguard/gui/notifications/toastnotificationsmanager.h index 1a28ca677..10f2b54ed 100644 --- a/src/librssguard/gui/notifications/toastnotificationsmanager.h +++ b/src/librssguard/gui/notifications/toastnotificationsmanager.h @@ -23,6 +23,10 @@ class ToastNotificationsManager : public QObject { BottomRight = 3 }; + Q_ENUM(NotificationPosition) + + static QString textForPosition(ToastNotificationsManager::NotificationPosition pos); + explicit ToastNotificationsManager(ToastNotificationsManager::NotificationPosition position, int screen, QObject* parent = nullptr); @@ -41,7 +45,6 @@ class ToastNotificationsManager : public QObject { public slots: void clear(); void showNotification(Notification::Event event, const GuiMessage& msg, const GuiAction& action); - void showNotification(const QList& new_messages); private slots: void closeNotification(BaseToastNotification* notif, bool delete_from_memory); diff --git a/src/librssguard/gui/reusable/helpspoiler.cpp b/src/librssguard/gui/reusable/helpspoiler.cpp index 71a79a010..6fbaab86a 100644 --- a/src/librssguard/gui/reusable/helpspoiler.cpp +++ b/src/librssguard/gui/reusable/helpspoiler.cpp @@ -26,6 +26,7 @@ HelpSpoiler::HelpSpoiler(QWidget* parent) m_btnToggle->setCheckable(true); m_btnToggle->setChecked(false); + m_content->setStyleSheet(QSL("QScrollArea { border: 1px solid %1; }").arg(palette().windowText().color().name())); m_content->setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Fixed); m_content->setMaximumHeight(0); m_content->setMinimumHeight(0); diff --git a/src/librssguard/gui/settings/settingsnotifications.cpp b/src/librssguard/gui/settings/settingsnotifications.cpp index 672062450..5ea235ffa 100644 --- a/src/librssguard/gui/settings/settingsnotifications.cpp +++ b/src/librssguard/gui/settings/settingsnotifications.cpp @@ -8,6 +8,8 @@ #include "miscellaneous/settings.h" #include +#include +#include SettingsNotifications::SettingsNotifications(Settings* settings, QWidget* parent) : SettingsPanel(settings, parent) { m_ui.setupUi(this); @@ -25,6 +27,14 @@ SettingsNotifications::SettingsNotifications(Settings* settings, QWidget* parent connect(m_ui.m_rbNativeNotifications, &QRadioButton::toggled, this, &SettingsNotifications::dirtifySettings); connect(m_ui.m_rbNativeNotifications, &QRadioButton::toggled, this, &SettingsNotifications::requireRestart); + connect(m_ui.m_sbScreen, &QSpinBox::valueChanged, this, &SettingsNotifications::dirtifySettings); + connect(m_ui.m_sbScreen, &QSpinBox::valueChanged, this, &SettingsNotifications::requireRestart); + + m_ui.m_sbScreen->setMinimum(-1); + m_ui.m_sbScreen->setMaximum(QGuiApplication::screens().size() - 1); + + connect(m_ui.m_sbScreen, &QSpinBox::valueChanged, this, &SettingsNotifications::showScreenInfo); + connect(m_ui.m_cbCustomNotificationsPosition, &QComboBox::currentIndexChanged, this, @@ -33,6 +43,15 @@ SettingsNotifications::SettingsNotifications(Settings* settings, QWidget* parent &QComboBox::currentIndexChanged, this, &SettingsNotifications::requireRestart); + + QMetaEnum enm = QMetaEnum::fromType(); + + for (int i = 0; i < enm.keyCount(); i++) { + m_ui.m_cbCustomNotificationsPosition + ->addItem(ToastNotificationsManager:: + textForPosition(ToastNotificationsManager::NotificationPosition(enm.value(i))), + enm.value(i)); + } } void SettingsNotifications::loadSettings() { @@ -43,6 +62,16 @@ void SettingsNotifications::loadSettings() { ->setChecked(settings()->value(GROUP(GUI), SETTING(GUI::EnableNotifications)).toBool()); m_ui.m_editor->loadNotifications(qApp->notifications()->allNotifications()); + m_ui.m_rbNativeNotifications + ->setChecked(!settings()->value(GROUP(GUI), SETTING(GUI::UseToastNotifications)).toBool()); + m_ui.m_sbScreen->setValue(settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsScreen)).toInt()); + + m_ui.m_cbCustomNotificationsPosition + ->setCurrentIndex(m_ui.m_cbCustomNotificationsPosition + ->findData(settings() + ->value(GROUP(GUI), SETTING(GUI::ToastNotificationsPosition)) + .value())); + onEndLoadSettings(); } @@ -53,5 +82,29 @@ void SettingsNotifications::saveSettings() { settings()->setValue(GROUP(GUI), GUI::EnableNotifications, m_ui.m_checkEnableNotifications->isChecked()); qApp->notifications()->save(m_ui.m_editor->allNotifications(), settings()); + settings()->setValue(GROUP(GUI), GUI::UseToastNotifications, m_ui.m_rbCustomNotifications->isChecked()); + settings()->setValue(GROUP(GUI), GUI::ToastNotificationsScreen, m_ui.m_sbScreen->value()); + + settings()->setValue(GROUP(GUI), + GUI::ToastNotificationsPosition, + m_ui.m_cbCustomNotificationsPosition->currentData() + .value()); + onEndSaveSettings(); } + +void SettingsNotifications::showScreenInfo(int index) { + QScreen* scr; + + if (index < 0 || index >= QGuiApplication::screens().size()) { + scr = QGuiApplication::primaryScreen(); + } + else { + scr = QGuiApplication::screens().at(index); + } + + m_ui.m_lblScreenInfo->setText(QSL("%1 (%2x%3)") + .arg(scr->name(), + QString::number(scr->virtualSize().width()), + QString::number(scr->virtualSize().height()))); +} diff --git a/src/librssguard/gui/settings/settingsnotifications.h b/src/librssguard/gui/settings/settingsnotifications.h index eac55fb4f..8c306377f 100644 --- a/src/librssguard/gui/settings/settingsnotifications.h +++ b/src/librssguard/gui/settings/settingsnotifications.h @@ -10,7 +10,7 @@ class Settings; class SettingsNotifications : public SettingsPanel { - Q_OBJECT + Q_OBJECT public: explicit SettingsNotifications(Settings* settings, QWidget* parent = nullptr); @@ -19,6 +19,9 @@ class SettingsNotifications : public SettingsPanel { virtual void loadSettings(); virtual void saveSettings(); + private slots: + void showScreenInfo(int index); + private: Ui::SettingsNotifications m_ui; }; diff --git a/src/librssguard/gui/settings/settingsnotifications.ui b/src/librssguard/gui/settings/settingsnotifications.ui index 917ffadf7..6f3aa6a4c 100644 --- a/src/librssguard/gui/settings/settingsnotifications.ui +++ b/src/librssguard/gui/settings/settingsnotifications.ui @@ -45,9 +45,6 @@ Native notifications (tray icon must be enabled) - - true - @@ -55,6 +52,9 @@ Custom notifications + + true + @@ -76,6 +76,27 @@ + + + + 99 + + + + + + + Screen + + + + + + + + + + diff --git a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp index a368e1be0..d4f761cf5 100644 --- a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp +++ b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp @@ -134,7 +134,7 @@ PreparedHtml TextBrowserViewer::prepareHtmlForMessage(const QList& mess html.m_html += is_plain ? Qt::convertFromPlainText(message.m_contents, Qt::WhiteSpaceMode::WhiteSpaceNormal) : message.m_contents; - static QRegularExpression img_tag_rgx("\\]*src\\s*=\\s*[\"\']([^\"\']*)[\"\'][^\\>]*\\>", + static QRegularExpression img_tag_rgx(QSL("\\]*src\\s*=\\s*[\"\']([^\"\']*)[\"\'][^\\>]*\\>"), QRegularExpression::PatternOption::CaseInsensitiveOption | QRegularExpression::PatternOption::InvertedGreedinessOption); diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index 0e669ca8c..f4ae62f14 100644 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -111,11 +111,13 @@ Application::Application(const QString& id, int& argc, char** argv, const QStrin m_downloadManager = nullptr; m_notifications = new NotificationFactory(this); m_toastNotifications = - new ToastNotificationsManager(settings() - ->value(GROUP(GUI), SETTING(GUI::ToastNotificationsPosition)) - .value(), - settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsScreen)).toInt(), - this); + settings()->value(GROUP(GUI), SETTING(GUI::UseToastNotifications)).toBool() + ? new ToastNotificationsManager(settings() + ->value(GROUP(GUI), SETTING(GUI::ToastNotificationsPosition)) + .value(), + settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsScreen)).toInt(), + this) + : nullptr; m_shouldRestart = false; #if defined(Q_OS_WIN) @@ -695,22 +697,26 @@ void Application::showGuiMessageCore(Notification::Event event, GuiMessageDestination dest, const GuiAction& action, QWidget* parent) { - m_toastNotifications->showNotification(event, msg, action); - return; - if (m_notifications->areNotificationsEnabled()) { auto notification = m_notifications->notificationForEvent(event); notification.playSound(this); - if (SystemTrayIcon::isSystemTrayDesired() && SystemTrayIcon::isSystemTrayAreaAvailable() && - notification.balloonEnabled() && dest.m_tray) { - trayIcon()->showMessage(msg.m_title.simplified().isEmpty() ? Notification::nameForEvent(notification.event()) - : msg.m_title, - msg.m_message, - msg.m_type, - TRAY_ICON_BUBBLE_TIMEOUT, - std::move(action.m_action)); + if (notification.balloonEnabled() && dest.m_tray) { + if (m_toastNotifications != nullptr) { + // Toasts are enabled. + m_toastNotifications->showNotification(event, msg, action); + } + else if (SystemTrayIcon::isSystemTrayDesired() && SystemTrayIcon::isSystemTrayAreaAvailable()) { + // Use tray icon balloons (which are implemented as native notifications on most systems. + trayIcon()->showMessage(msg.m_title.simplified().isEmpty() ? Notification::nameForEvent(notification.event()) + : msg.m_title, + msg.m_message, + msg.m_type, + TRAY_ICON_BUBBLE_TIMEOUT, + std::move(action.m_action)); + } + return; } } @@ -999,15 +1005,24 @@ void Application::onFeedUpdatesProgress(const Feed* feed, int current, int total } void Application::onFeedUpdatesFinished(const FeedDownloadResults& results) { - auto fds = results.updatedFeeds(); - bool some_unquiet_feed = boolinq::from(fds).any([](const QPair& fd) { - return !fd.first->isQuiet(); + auto fds = results.updatedFeeds().keys(); + bool some_unquiet_feed = boolinq::from(fds).any([](Feed* fd) { + return !fd->isQuiet(); }); if (some_unquiet_feed) { - // Now, inform about results via GUI message/notification. - qApp->showGuiMessage(Notification::Event::NewUnreadArticlesFetched, - {tr("Unread articles fetched"), results.overview(10), QSystemTrayIcon::MessageIcon::NoIcon}); + GuiMessage msg = {tr("Unread articles fetched"), QString(), QSystemTrayIcon::MessageIcon::NoIcon}; + + if (m_toastNotifications != nullptr) { + // Show custom and richer overview of updated feeds and articles. + msg.m_feedFetchResults = results; + } + else { + // Show simpler overview of updated feeds. + msg.m_message = results.overview(10); + } + + qApp->showGuiMessage(Notification::Event::NewUnreadArticlesFetched, msg); } #if defined(Q_OS_WIN) diff --git a/src/librssguard/miscellaneous/application.h b/src/librssguard/miscellaneous/application.h index 41e333d30..c1a6e92be 100644 --- a/src/librssguard/miscellaneous/application.h +++ b/src/librssguard/miscellaneous/application.h @@ -60,6 +60,7 @@ struct GuiMessage { QString m_title; QString m_message; QSystemTrayIcon::MessageIcon m_type; + FeedDownloadResults m_feedFetchResults; }; Q_DECLARE_METATYPE(GuiMessage) diff --git a/src/librssguard/miscellaneous/feedreader.cpp b/src/librssguard/miscellaneous/feedreader.cpp index c9ccbef19..4e1fd627e 100644 --- a/src/librssguard/miscellaneous/feedreader.cpp +++ b/src/librssguard/miscellaneous/feedreader.cpp @@ -255,19 +255,6 @@ void FeedReader::removeMessageFilterToFeedAssignment(Feed* feed, MessageFilter* } void FeedReader::updateAllFeeds() { - 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", []() { - qDebugNN << "aa"; - })); - updateFeeds(m_feedsModel->rootItem()->getSubTreeFeeds()); } diff --git a/src/librssguard/services/abstract/serviceroot.cpp b/src/librssguard/services/abstract/serviceroot.cpp index d316fba4a..09e9da7e9 100644 --- a/src/librssguard/services/abstract/serviceroot.cpp +++ b/src/librssguard/services/abstract/serviceroot.cpp @@ -1110,8 +1110,8 @@ ServiceRoot::LabelOperation operator&(ServiceRoot::LabelOperation lhs, ServiceRo return static_cast(static_cast(lhs) & static_cast(rhs)); } -QPair ServiceRoot::updateMessages(QList& messages, Feed* feed, bool force_update, QMutex* db_mutex) { - QPair updated_messages = {0, 0}; +UpdatedArticles ServiceRoot::updateMessages(QList& messages, Feed* feed, bool force_update, QMutex* db_mutex) { + UpdatedArticles updated_messages; if (messages.isEmpty()) { qDebugNN << "No messages to be updated/added in DB for feed" << QUOTE_W_SPACE_DOT(feed->customId()); @@ -1125,7 +1125,7 @@ QPair ServiceRoot::updateMessages(QList& messages, Feed* feed updated_messages = DatabaseQueries::updateMessages(database, messages, feed, force_update, db_mutex, &ok); - if (updated_messages.first > 0 || updated_messages.second > 0) { + if (!updated_messages.m_unread.isEmpty() || !updated_messages.m_all.isEmpty()) { QMutexLocker lck(db_mutex); // Something was added or updated in the DB, update numbers. diff --git a/src/librssguard/services/abstract/serviceroot.h b/src/librssguard/services/abstract/serviceroot.h index 054d37c12..6b3a1e7d5 100644 --- a/src/librssguard/services/abstract/serviceroot.h +++ b/src/librssguard/services/abstract/serviceroot.h @@ -6,7 +6,6 @@ #include "services/abstract/rootitem.h" #include "core/message.h" -#include "core/messagefilter.h" #include "definitions/typedefs.h" #include @@ -206,7 +205,7 @@ class ServiceRoot : public RootItem { void completelyRemoveAllData(); // Returns counts of updated messages . - QPair updateMessages(QList& messages, Feed* feed, bool force_update, QMutex* db_mutex); + UpdatedArticles updateMessages(QList& messages, Feed* feed, bool force_update, QMutex* db_mutex); QIcon feedIconForMessage(const QString& feed_custom_id) const; diff --git a/src/librssguard/services/feedly/feedlynetwork.cpp b/src/librssguard/services/feedly/feedlynetwork.cpp index 25a1c0dfa..901da883c 100644 --- a/src/librssguard/services/feedly/feedlynetwork.cpp +++ b/src/librssguard/services/feedly/feedlynetwork.cpp @@ -418,7 +418,7 @@ QList FeedlyNetwork::decodeStreamContents(const QByteArray& stream_cont Message message; message.m_feedId = entry_obj[QSL("origin")].toObject()[QSL("streamId")].toString(); - message.m_title = qApp->web()->stripTags(entry_obj[QSL("title")].toString()); + message.m_title = entry_obj[QSL("title")].toString(); message.m_author = entry_obj[QSL("author")].toString(); message.m_contents = entry_obj[QSL("content")].toObject()[QSL("content")].toString(); message.m_rawContents = QJsonDocument(entry_obj).toJson(QJsonDocument::JsonFormat::Compact); diff --git a/src/librssguard/services/greader/greadernetwork.cpp b/src/librssguard/services/greader/greadernetwork.cpp index 27f3bece6..8c79194de 100644 --- a/src/librssguard/services/greader/greadernetwork.cpp +++ b/src/librssguard/services/greader/greadernetwork.cpp @@ -957,8 +957,8 @@ QList GreaderNetwork::decodeStreamContents(ServiceRoot* root, auto message_obj = obj.toObject(); Message message; - message.m_title = qApp->web()->unescapeHtml(message_obj[QSL("title")].toString()); - message.m_author = qApp->web()->unescapeHtml(message_obj[QSL("author")].toString()); + message.m_title = message_obj[QSL("title")].toString(); + message.m_author = message_obj[QSL("author")].toString(); message.m_created = QDateTime::fromSecsSinceEpoch(message_obj[QSL("published")].toInt(), Qt::TimeSpec::UTC); message.m_createdFromFeed = true; message.m_customId = message_obj[QSL("id")].toString(); diff --git a/src/librssguard/services/standard/parsers/feedparser.cpp b/src/librssguard/services/standard/parsers/feedparser.cpp index b13f7784c..cdfd2f3a0 100644 --- a/src/librssguard/services/standard/parsers/feedparser.cpp +++ b/src/librssguard/services/standard/parsers/feedparser.cpp @@ -100,9 +100,9 @@ QList FeedParser::messages() { Message new_message; // Fill available data. - new_message.m_title = qApp->web()->stripTags(qApp->web()->unescapeHtml(xmlMessageTitle(message_item))); + new_message.m_title = xmlMessageTitle(message_item); new_message.m_contents = xmlMessageDescription(message_item); - new_message.m_author = qApp->web()->stripTags(qApp->web()->unescapeHtml(xmlMessageAuthor(message_item))); + new_message.m_author = xmlMessageAuthor(message_item); new_message.m_url = xmlMessageUrl(message_item); new_message.m_created = xmlMessageDateCreated(message_item); new_message.m_customId = xmlMessageId(message_item); @@ -128,9 +128,9 @@ QList FeedParser::messages() { Message new_message; // Fill available data. - new_message.m_title = qApp->web()->stripTags(qApp->web()->unescapeHtml(jsonMessageTitle(message_item))); + new_message.m_title = jsonMessageTitle(message_item); new_message.m_contents = jsonMessageDescription(message_item); - new_message.m_author = qApp->web()->stripTags(qApp->web()->unescapeHtml(jsonMessageAuthor(message_item))); + new_message.m_author = jsonMessageAuthor(message_item); new_message.m_url = jsonMessageUrl(message_item); new_message.m_created = jsonMessageDateCreated(message_item); new_message.m_customId = jsonMessageId(message_item);