diff --git a/resources/icons.qrc b/resources/icons.qrc index 94acc3416..68cbb2aee 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -12,6 +12,7 @@ ./graphics/Breeze/actions/32/arrow-up-double.svg ./graphics/Breeze/actions/32/call-start.svg ./graphics/Breeze/actions/32/dialog-cancel.svg + ./graphics/Breeze/actions/22/dialog-close.svg ./graphics/Breeze/status/64/dialog-error.svg ./graphics/Breeze/status/64/dialog-information.svg ./graphics/Breeze/actions/32/dialog-ok.svg @@ -53,6 +54,7 @@ ./graphics/Breeze/actions/32/go-previous.svg ./graphics/Breeze/actions/32/go-up.svg ./graphics/Breeze/actions/22/gtk-cancel.svg + ./graphics/Breeze/actions/22/gtk-close.svg ./graphics/Breeze/actions/22/gtk-edit.svg ./graphics/Breeze/actions/32/help-about.svg ./graphics/Breeze/actions/22/help-contents.svg @@ -101,6 +103,7 @@ ./graphics/Breeze Dark/actions/32/arrow-up-double.svg ./graphics/Breeze Dark/actions/32/call-start.svg ./graphics/Breeze Dark/actions/32/dialog-cancel.svg + ./graphics/Breeze Dark/actions/22/dialog-close.svg ./graphics/Breeze Dark/status/64/dialog-error.svg ./graphics/Breeze Dark/status/64/dialog-information.svg ./graphics/Breeze Dark/actions/32/dialog-ok.svg @@ -142,6 +145,7 @@ ./graphics/Breeze Dark/actions/32/go-previous.svg ./graphics/Breeze Dark/actions/32/go-up.svg ./graphics/Breeze Dark/actions/22/gtk-cancel.svg + ./graphics/Breeze Dark/actions/22/gtk-close.svg ./graphics/Breeze Dark/actions/22/gtk-edit.svg ./graphics/Breeze Dark/actions/32/help-about.svg ./graphics/Breeze Dark/actions/22/help-contents.svg @@ -226,6 +230,7 @@ ./graphics/Faenza/actions/64/go-previous.png ./graphics/Faenza/actions/64/go-up.png ./graphics/Faenza/actions/64/gtk-cancel.png + ./graphics/Faenza/actions/64/gtk-close.png ./graphics/Faenza/actions/64/gtk-edit.png ./graphics/Faenza/actions/64/help-about.png ./graphics/Faenza/actions/64/help-contents.png @@ -274,6 +279,7 @@ ./graphics/Numix/22/actions/browser-download.svg ./graphics/Numix/22/actions/call-start.svg ./graphics/Numix/22/actions/dialog-cancel.svg + ./graphics/Numix/22/actions/dialog-close.svg ./graphics/Numix/22/status/dialog-error.svg ./graphics/Numix/22/status/dialog-information.svg ./graphics/Numix/22/actions/dialog-no.svg @@ -315,6 +321,7 @@ ./graphics/Numix/22/actions/go-previous.svg ./graphics/Numix/22/actions/go-up.svg ./graphics/Numix/22/actions/gtk-cancel.svg + ./graphics/Numix/22/actions/gtk-close.svg ./graphics/Numix/22/actions/gtk-edit.svg ./graphics/Numix/22/categories/help-about.svg ./graphics/Numix/22/actions/help-contents.svg diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index 01fb0573d..33fe22989 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -93,6 +93,8 @@ set(SOURCES gui/messagepreviewer.h gui/messagesview.cpp gui/messagesview.h + gui/notifications/basetoastnotification.cpp + gui/notifications/basetoastnotification.h gui/notifications/notificationseditor.cpp gui/notifications/notificationseditor.h gui/notifications/singlenotificationeditor.cpp @@ -173,6 +175,10 @@ set(SOURCES gui/tabcontent.h gui/tabwidget.cpp gui/tabwidget.h + gui/notifications/toastnotification.cpp + gui/notifications/toastnotification.h + gui/notifications/toastnotificationsmanager.cpp + gui/notifications/toastnotificationsmanager.h gui/webviewers/webviewer.h gui/richtexteditor/mrichtextedit.cpp gui/richtexteditor/mrichtextedit.h @@ -447,6 +453,7 @@ set(UI_FILES gui/notifications/singlenotificationeditor.ui gui/reusable/networkproxydetails.ui gui/itemdetails.ui + gui/notifications/toastnotification.ui gui/richtexteditor/mrichtextedit.ui gui/newspaperpreviewer.ui gui/reusable/searchtextwidget.ui diff --git a/src/librssguard/core/messageobject.cpp b/src/librssguard/core/messageobject.cpp index 0e96fb1b0..c29979e2e 100644 --- a/src/librssguard/core/messageobject.cpp +++ b/src/librssguard/core/messageobject.cpp @@ -99,7 +99,7 @@ bool MessageObject::isDuplicateWithAttribute(MessageObject::DuplicateCheck attri qDebugNN << LOGSEC_DB << "Executed SQL for message duplicates check:" << QUOTE_W_SPACE_DOT(DatabaseFactory::lastExecutedQuery(q)); - if (q.record().value(0).toInt() > 0) { + if (q.value(0).toInt() > 0) { // Whoops, we have the "same" message in database. qDebugNN << LOGSEC_CORE << "Message" << QUOTE_W_SPACE(title()) << "was identified as duplicate by filter script."; return true; diff --git a/src/librssguard/core/messagesmodel.cpp b/src/librssguard/core/messagesmodel.cpp index 86faca92d..2fcb8301a 100644 --- a/src/librssguard/core/messagesmodel.cpp +++ b/src/librssguard/core/messagesmodel.cpp @@ -160,7 +160,7 @@ void MessagesModel::repopulate() { bool MessagesModel::setData(const QModelIndex& index, const QVariant& value, int role) { Q_UNUSED(role) - m_cache->setData(index, value, record(index.row())); + m_cache->setData(index, value); return true; } @@ -609,9 +609,7 @@ bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) { Message message = messageAt(row_index); - if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, - QList() << message, - read)) { + if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, {message}, read)) { // Cannot change read status of the item. Abort. return false; } @@ -760,7 +758,7 @@ bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList& messages) { msgs.append(msg); message_ids.append(QString::number(msg.m_id)); - if (qobject_cast(m_selectedItem) != nullptr) { + if (m_selectedItem->kind() == RootItem::Kind::Bin) { setData(index(message.row(), MSG_DB_PDELETED_INDEX), 1); } else { diff --git a/src/librssguard/core/messagesmodel.h b/src/librssguard/core/messagesmodel.h index f29588063..c1e72de02 100644 --- a/src/librssguard/core/messagesmodel.h +++ b/src/librssguard/core/messagesmodel.h @@ -22,9 +22,17 @@ class MessagesModel : public QSqlQueryModel, public MessagesModelSqlLayer { public: // Enum which describes basic highlighting schemes // for messages. - enum class MessageHighlighter { NoHighlighting = 1, HighlightUnread = 2, HighlightImportant = 4 }; + enum class MessageHighlighter { + NoHighlighting = 1, + HighlightUnread = 2, + HighlightImportant = 4 + }; - enum class MessageUnreadIcon { Dot = 1, Envelope = 2, FeedIcon = 3 }; + enum class MessageUnreadIcon { + Dot = 1, + Envelope = 2, + FeedIcon = 3 + }; Q_ENUM(MessageUnreadIcon) @@ -43,8 +51,6 @@ class MessagesModel : public QSqlQueryModel, public MessagesModelSqlLayer { QVariant headerData(int section, Qt::Orientation orientation, int role) const; Qt::ItemFlags flags(const QModelIndex& index) const; - // Returns message at given index. - QList messagesAt(const QList& row_indices) const; Message messageAt(int row_index) const; int messageId(int row_index) const; diff --git a/src/librssguard/core/messagesmodelcache.cpp b/src/librssguard/core/messagesmodelcache.cpp index 4f33d7657..2e6e3624e 100644 --- a/src/librssguard/core/messagesmodelcache.cpp +++ b/src/librssguard/core/messagesmodelcache.cpp @@ -2,11 +2,13 @@ #include "core/messagesmodelcache.h" +#include "core/messagesmodel.h" + MessagesModelCache::MessagesModelCache(QObject* parent) : QObject(parent) {} -void MessagesModelCache::setData(const QModelIndex& index, const QVariant& value, const QSqlRecord& record) { +void MessagesModelCache::setData(const QModelIndex& index, const QVariant& value) { if (!m_msgCache.contains(index.row())) { - m_msgCache[index.row()] = record; + m_msgCache[index.row()] = static_cast(index.model())->record(index.row()); } m_msgCache[index.row()].setValue(index.column(), value); diff --git a/src/librssguard/core/messagesmodelcache.h b/src/librssguard/core/messagesmodelcache.h index 8b7c59f10..d955ff457 100644 --- a/src/librssguard/core/messagesmodelcache.h +++ b/src/librssguard/core/messagesmodelcache.h @@ -22,7 +22,7 @@ class MessagesModelCache : public QObject { QVariant data(const QModelIndex& idx); void clear(); - void setData(const QModelIndex& index, const QVariant& value, const QSqlRecord& record); + void setData(const QModelIndex& index, const QVariant& value); private: QHash m_msgCache; diff --git a/src/librssguard/database/databasequeries.cpp b/src/librssguard/database/databasequeries.cpp index 9dfd0112d..505e62f30 100644 --- a/src/librssguard/database/databasequeries.cpp +++ b/src/librssguard/database/databasequeries.cpp @@ -2716,15 +2716,14 @@ QList DatabaseQueries::getMessageFilters(const QSqlDatabase& db, QList filters; q.setForwardOnly(true); - q.prepare(QSL("SELECT * FROM MessageFilters;")); + q.prepare(QSL("SELECT id, name, script FROM MessageFilters;")); if (q.exec()) { while (q.next()) { - auto rec = q.record(); - auto* filter = new MessageFilter(rec.value(0).toInt()); + auto* filter = new MessageFilter(q.value(0).toInt()); - filter->setName(rec.value(1).toString()); - filter->setScript(rec.value(2).toString()); + filter->setName(q.value(1).toString()); + filter->setScript(q.value(2).toString()); filters.append(filter); } @@ -2753,9 +2752,7 @@ QMultiMap DatabaseQueries::messageFiltersInFeeds(const QSqlDatabas if (q.exec()) { while (q.next()) { - auto rec = q.record(); - - filters_in_feeds.insert(rec.value(1).toString(), rec.value(0).toInt()); + filters_in_feeds.insert(q.value(1).toString(), q.value(0).toInt()); } if (ok != nullptr) { diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 4a80e473b..7a4d49d0d 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -47,6 +47,8 @@ #include #endif +#include + FormMain::FormMain(QWidget* parent, Qt::WindowFlags f) : QMainWindow(parent, f), m_ui(new Ui::FormMain), m_trayMenu(nullptr), m_statusBar(nullptr) { qDebugNN << LOGSEC_GUI diff --git a/src/librssguard/gui/messagesview.cpp b/src/librssguard/gui/messagesview.cpp index 4190e497d..f33b90659 100644 --- a/src/librssguard/gui/messagesview.cpp +++ b/src/librssguard/gui/messagesview.cpp @@ -255,7 +255,8 @@ void MessagesView::reloadSelections() { const bool is_current_selected = selectionModel()->selectedRows().contains(m_proxyModel->index(current_index.row(), 0, current_index.parent())); const QModelIndex mapped_current_index = m_proxyModel->mapToSource(current_index); - const Message selected_message = m_sourceModel->messageAt(mapped_current_index.row()); + const int selected_message_id = + m_sourceModel->data(mapped_current_index.row(), MSG_DB_ID_INDEX, Qt::ItemDataRole::EditRole).toInt(); const int col = header()->sortIndicatorSection(); const Qt::SortOrder ord = header()->sortIndicatorOrder(); bool do_not_mark_read_on_select = false; @@ -264,19 +265,21 @@ void MessagesView::reloadSelections() { sort(col, ord, true, false, false, true); // Now, we must find the same previously focused message. - if (selected_message.m_id > 0) { + if (selected_message_id > 0) { if (m_proxyModel->rowCount() == 0 || !is_current_selected) { current_index = QModelIndex(); } else { for (int i = 0; i < m_proxyModel->rowCount(); i++) { QModelIndex msg_idx = m_proxyModel->index(i, MSG_DB_TITLE_INDEX); - Message msg = m_sourceModel->messageAt(m_proxyModel->mapToSource(msg_idx).row()); + QModelIndex msg_source_idx = m_proxyModel->mapToSource(msg_idx); + int msg_id = m_sourceModel->data(msg_source_idx.row(), MSG_DB_ID_INDEX, Qt::ItemDataRole::EditRole).toInt(); - if (msg.m_id == selected_message.m_id) { + if (msg_id == selected_message_id) { current_index = msg_idx; - if (!msg.m_isRead /* && selected_message.m_isRead */) { + if (!m_sourceModel->data(msg_source_idx.row(), MSG_DB_READ_INDEX, Qt::ItemDataRole::EditRole) + .toBool() /* && selected_message.m_isRead */) { do_not_mark_read_on_select = true; } @@ -296,7 +299,7 @@ void MessagesView::reloadSelections() { m_processingRightMouseButton = do_not_mark_read_on_select; setCurrentIndex(current_index); - reselectIndexes(QModelIndexList() << current_index); + reselectIndexes({current_index}); m_processingRightMouseButton = false; } @@ -867,8 +870,10 @@ void MessagesView::openSelectedMessagesWithExternalTool() { auto rws = selectionModel()->selectedRows(); for (const QModelIndex& index : qAsConst(rws)) { - const QString link = m_sourceModel->messageAt(m_proxyModel->mapToSource(index).row()) - .m_url.replace(QRegularExpression(QSL("[\\t\\n]")), QString()); + const QString link = + m_sourceModel->data(m_proxyModel->mapToSource(index).row(), MSG_DB_URL_INDEX, Qt::ItemDataRole::EditRole) + .toString() + .replace(QRegularExpression(QSL("[\\t\\n]")), QString()); if (!link.isEmpty()) { if (!tool.run(link)) { diff --git a/src/librssguard/gui/notifications/basetoastnotification.cpp b/src/librssguard/gui/notifications/basetoastnotification.cpp new file mode 100644 index 000000000..e37fd5c83 --- /dev/null +++ b/src/librssguard/gui/notifications/basetoastnotification.cpp @@ -0,0 +1,24 @@ +// For license of this file, see /LICENSE.md. + +#include "gui/notifications/basetoastnotification.h" + +#include "miscellaneous/iconfactory.h" + +BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent) { + setAttribute(Qt::WidgetAttribute::WA_ShowWithoutActivating); + setAttribute(Qt::WidgetAttribute::WA_TranslucentBackground); + + setWindowFlags( +#ifdef Q_OS_MAC + Qt::WindowType::SubWindow | +#else + Qt::WindowType::Tool | +#endif + Qt::WindowType::FramelessWindowHint | Qt::WindowType::WindowStaysOnTopHint | Qt::WindowType::WindowSystemMenuHint); +} + +BaseToastNotification::~BaseToastNotification() {} + +void BaseToastNotification::setupCloseButton(QAbstractButton* btn) { + btn->setIcon(qApp->icons()->fromTheme(QSL("dialog-close"), QSL("gtk-close"))); +} diff --git a/src/librssguard/gui/notifications/basetoastnotification.h b/src/librssguard/gui/notifications/basetoastnotification.h new file mode 100644 index 000000000..73710f0be --- /dev/null +++ b/src/librssguard/gui/notifications/basetoastnotification.h @@ -0,0 +1,21 @@ +// For license of this file, see /LICENSE.md. + +#ifndef BASETOASTNOTIFICATION_H +#define BASETOASTNOTIFICATION_H + +#include + +class QAbstractButton; + +class BaseToastNotification : public QDialog { + Q_OBJECT + + public: + explicit BaseToastNotification(QWidget* parent = nullptr); + virtual ~BaseToastNotification(); + + protected: + void setupCloseButton(QAbstractButton* btn); +}; + +#endif // BASETOASTNOTIFICATION_H diff --git a/src/librssguard/gui/notifications/toastnotificationsmanager.cpp b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp new file mode 100644 index 000000000..9bd5a167d --- /dev/null +++ b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp @@ -0,0 +1,59 @@ +// For license of this file, see /LICENSE.md. + +#include "gui/notifications/toastnotificationsmanager.h" + +#include "gui/notifications/toastnotification.h" + +ToastNotificationsManager::ToastNotificationsManager(QObject* parent) + : QObject(parent), m_position(NotificationPosition::BottomRight), m_screen(-1) {} + +ToastNotificationsManager::~ToastNotificationsManager() { + clear(); +} + +QList ToastNotificationsManager::activeNotifications() const { + return m_activeNotifications; +} + +int ToastNotificationsManager::screen() const { + return m_screen; +} + +void ToastNotificationsManager::setScreen(int screen) { + m_screen = screen; +} + +ToastNotificationsManager::NotificationPosition ToastNotificationsManager::position() const { + return m_position; +} + +void ToastNotificationsManager::setPosition(NotificationPosition position) { + m_position = position; +} + +void ToastNotificationsManager::clear() { + for (BaseToastNotification* nt : m_activeNotifications) { + nt->close(); + nt->deleteLater(); + } + + m_activeNotifications.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(); + + notif->show(); + + auto bb = notif->height(); + auto cc = notif->height(); +} + +void ToastNotificationsManager::showNotification(const QList& new_messages) {} diff --git a/src/librssguard/gui/notifications/toastnotificationsmanager.h b/src/librssguard/gui/notifications/toastnotificationsmanager.h new file mode 100644 index 000000000..04af63f36 --- /dev/null +++ b/src/librssguard/gui/notifications/toastnotificationsmanager.h @@ -0,0 +1,48 @@ +// For license of this file, see /LICENSE.md. + +#ifndef TOASTNOTIFICATIONSMANAGER_H +#define TOASTNOTIFICATIONSMANAGER_H + +#include + +#include "miscellaneous/application.h" + +class BaseToastNotification; +class ToastNotification; + +class ToastNotificationsManager : public QObject { + Q_OBJECT + + public: + enum NotificationPosition { + TopLeft = 0, + TopRight = 1, + BottomLeft = 2, + BottomRight = 3 + }; + + explicit ToastNotificationsManager(QObject* parent = nullptr); + virtual ~ToastNotificationsManager(); + + QList activeNotifications() const; + + // Screen ID, setting this to -1 means using default/primary + // monitor. + int screen() const; + void setScreen(int screen); + + NotificationPosition position() const; + void setPosition(NotificationPosition position); + + public slots: + void clear(); + void showNotification(Notification::Event event, const GuiMessage& msg, const GuiAction& action); + void showNotification(const QList& new_messages); + + private: + NotificationPosition m_position; + int m_screen; + QList m_activeNotifications; +}; + +#endif // TOASTNOTIFICATIONSMANAGER_H diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index a79fa4863..52e2dfeb9 100644 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -17,6 +17,7 @@ #include "gui/dialogs/formlog.h" #include "gui/dialogs/formmain.h" #include "gui/messagebox.h" +#include "gui/notifications/toastnotificationsmanager.h" #include "gui/toolbars/statusbar.h" #include "gui/webviewers/qtextbrowser/textbrowserviewer.h" #include "miscellaneous/feedreader.h" @@ -108,6 +109,7 @@ Application::Application(const QString& id, int& argc, char** argv, const QStrin m_database = new DatabaseFactory(this); m_downloadManager = nullptr; m_notifications = new NotificationFactory(this); + m_toastNotifications = new ToastNotificationsManager(this); m_shouldRestart = false; #if defined(Q_OS_WIN) @@ -679,6 +681,8 @@ void Application::showGuiMessageCore(Notification::Event event, GuiMessageDestination dest, const GuiAction& action, QWidget* parent) { + m_toastNotifications->showNotification(event, msg, action); + if (SystemTrayIcon::areNotificationsEnabled()) { auto notification = m_notifications->notificationForEvent(event); diff --git a/src/librssguard/miscellaneous/application.h b/src/librssguard/miscellaneous/application.h index b74716d00..e56d7d493 100644 --- a/src/librssguard/miscellaneous/application.h +++ b/src/librssguard/miscellaneous/application.h @@ -44,6 +44,7 @@ class QWebEngineDownloadItem; class WebFactory; class NotificationFactory; +class ToastNotificationsManager; class WebViewer; #if defined(Q_OS_WIN) @@ -277,6 +278,7 @@ class RSSGUARD_DLLSPEC Application : public SingleApplication { DatabaseFactory* m_database; DownloadManager* m_downloadManager; NotificationFactory* m_notifications; + ToastNotificationsManager* m_toastNotifications; NodeJs* m_nodejs; QThreadPool* m_workHorsePool; bool m_shouldRestart; diff --git a/src/librssguard/miscellaneous/notificationfactory.h b/src/librssguard/miscellaneous/notificationfactory.h index 719ed61fd..3ace5b49a 100644 --- a/src/librssguard/miscellaneous/notificationfactory.h +++ b/src/librssguard/miscellaneous/notificationfactory.h @@ -10,7 +10,7 @@ class Settings; class NotificationFactory : public QObject { - Q_OBJECT + Q_OBJECT public: explicit NotificationFactory(QObject* parent = nullptr); @@ -26,7 +26,6 @@ class NotificationFactory : public QObject { private: QList m_notifications = {}; - }; #endif // NOTIFICATIONFACTORY_H diff --git a/src/librssguard/services/abstract/searchsnode.cpp b/src/librssguard/services/abstract/searchsnode.cpp index 5b40d0b43..3733d2837 100644 --- a/src/librssguard/services/abstract/searchsnode.cpp +++ b/src/librssguard/services/abstract/searchsnode.cpp @@ -60,6 +60,14 @@ QList SearchsNode::contextMenuFeedsList() { return QList{m_actProbeNew}; } +int SearchsNode::countOfUnreadMessages() const { + return -1; +} + +int SearchsNode::countOfAllMessages() const { + return -1; +} + void SearchsNode::createProbe() { FormAddEditProbe frm(qApp->mainFormWidget()); Search* new_prb = frm.execForAdd(); diff --git a/src/librssguard/services/abstract/searchsnode.h b/src/librssguard/services/abstract/searchsnode.h index 699821d33..0506741a7 100644 --- a/src/librssguard/services/abstract/searchsnode.h +++ b/src/librssguard/services/abstract/searchsnode.h @@ -19,6 +19,9 @@ class SearchsNode : public RootItem { virtual QList undeletedMessages() const; virtual QList contextMenuFeedsList(); + virtual int countOfUnreadMessages() const; + virtual int countOfAllMessages() const; + Search* probeById(const QString& custom_id); public slots: