work on toasts

This commit is contained in:
Martin Rotter 2023-09-12 09:02:00 +02:00
parent 606f69929a
commit 9745f29547
19 changed files with 223 additions and 31 deletions

View file

@ -12,6 +12,7 @@
<file>./graphics/Breeze/actions/32/arrow-up-double.svg</file>
<file>./graphics/Breeze/actions/32/call-start.svg</file>
<file>./graphics/Breeze/actions/32/dialog-cancel.svg</file>
<file>./graphics/Breeze/actions/22/dialog-close.svg</file>
<file>./graphics/Breeze/status/64/dialog-error.svg</file>
<file>./graphics/Breeze/status/64/dialog-information.svg</file>
<file>./graphics/Breeze/actions/32/dialog-ok.svg</file>
@ -53,6 +54,7 @@
<file>./graphics/Breeze/actions/32/go-previous.svg</file>
<file>./graphics/Breeze/actions/32/go-up.svg</file>
<file>./graphics/Breeze/actions/22/gtk-cancel.svg</file>
<file>./graphics/Breeze/actions/22/gtk-close.svg</file>
<file>./graphics/Breeze/actions/22/gtk-edit.svg</file>
<file>./graphics/Breeze/actions/32/help-about.svg</file>
<file>./graphics/Breeze/actions/22/help-contents.svg</file>
@ -101,6 +103,7 @@
<file>./graphics/Breeze Dark/actions/32/arrow-up-double.svg</file>
<file>./graphics/Breeze Dark/actions/32/call-start.svg</file>
<file>./graphics/Breeze Dark/actions/32/dialog-cancel.svg</file>
<file>./graphics/Breeze Dark/actions/22/dialog-close.svg</file>
<file>./graphics/Breeze Dark/status/64/dialog-error.svg</file>
<file>./graphics/Breeze Dark/status/64/dialog-information.svg</file>
<file>./graphics/Breeze Dark/actions/32/dialog-ok.svg</file>
@ -142,6 +145,7 @@
<file>./graphics/Breeze Dark/actions/32/go-previous.svg</file>
<file>./graphics/Breeze Dark/actions/32/go-up.svg</file>
<file>./graphics/Breeze Dark/actions/22/gtk-cancel.svg</file>
<file>./graphics/Breeze Dark/actions/22/gtk-close.svg</file>
<file>./graphics/Breeze Dark/actions/22/gtk-edit.svg</file>
<file>./graphics/Breeze Dark/actions/32/help-about.svg</file>
<file>./graphics/Breeze Dark/actions/22/help-contents.svg</file>
@ -226,6 +230,7 @@
<file>./graphics/Faenza/actions/64/go-previous.png</file>
<file>./graphics/Faenza/actions/64/go-up.png</file>
<file>./graphics/Faenza/actions/64/gtk-cancel.png</file>
<file>./graphics/Faenza/actions/64/gtk-close.png</file>
<file>./graphics/Faenza/actions/64/gtk-edit.png</file>
<file>./graphics/Faenza/actions/64/help-about.png</file>
<file>./graphics/Faenza/actions/64/help-contents.png</file>
@ -274,6 +279,7 @@
<file>./graphics/Numix/22/actions/browser-download.svg</file>
<file>./graphics/Numix/22/actions/call-start.svg</file>
<file>./graphics/Numix/22/actions/dialog-cancel.svg</file>
<file>./graphics/Numix/22/actions/dialog-close.svg</file>
<file>./graphics/Numix/22/status/dialog-error.svg</file>
<file>./graphics/Numix/22/status/dialog-information.svg</file>
<file>./graphics/Numix/22/actions/dialog-no.svg</file>
@ -315,6 +321,7 @@
<file>./graphics/Numix/22/actions/go-previous.svg</file>
<file>./graphics/Numix/22/actions/go-up.svg</file>
<file>./graphics/Numix/22/actions/gtk-cancel.svg</file>
<file>./graphics/Numix/22/actions/gtk-close.svg</file>
<file>./graphics/Numix/22/actions/gtk-edit.svg</file>
<file>./graphics/Numix/22/categories/help-about.svg</file>
<file>./graphics/Numix/22/actions/help-contents.svg</file>

View file

@ -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

View file

@ -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;

View file

@ -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>() << 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<RecycleBin*>(m_selectedItem) != nullptr) {
if (m_selectedItem->kind() == RootItem::Kind::Bin) {
setData(index(message.row(), MSG_DB_PDELETED_INDEX), 1);
}
else {

View file

@ -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<Message> messagesAt(const QList<int>& row_indices) const;
Message messageAt(int row_index) const;
int messageId(int row_index) const;

View file

@ -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<const MessagesModel*>(index.model())->record(index.row());
}
m_msgCache[index.row()].setValue(index.column(), value);

View file

@ -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<int, QSqlRecord> m_msgCache;

View file

@ -2716,15 +2716,14 @@ QList<MessageFilter*> DatabaseQueries::getMessageFilters(const QSqlDatabase& db,
QList<MessageFilter*> 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<QString, int> 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) {

View file

@ -47,6 +47,8 @@
#include <QDesktopWidget>
#endif
#include <QWindow>
FormMain::FormMain(QWidget* parent, Qt::WindowFlags f)
: QMainWindow(parent, f), m_ui(new Ui::FormMain), m_trayMenu(nullptr), m_statusBar(nullptr) {
qDebugNN << LOGSEC_GUI

View file

@ -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)) {

View file

@ -0,0 +1,24 @@
// For license of this file, see <project-root-folder>/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")));
}

View file

@ -0,0 +1,21 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef BASETOASTNOTIFICATION_H
#define BASETOASTNOTIFICATION_H
#include <QDialog>
class QAbstractButton;
class BaseToastNotification : public QDialog {
Q_OBJECT
public:
explicit BaseToastNotification(QWidget* parent = nullptr);
virtual ~BaseToastNotification();
protected:
void setupCloseButton(QAbstractButton* btn);
};
#endif // BASETOASTNOTIFICATION_H

View file

@ -0,0 +1,59 @@
// For license of this file, see <project-root-folder>/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<BaseToastNotification*> 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<Message>& new_messages) {}

View file

@ -0,0 +1,48 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef TOASTNOTIFICATIONSMANAGER_H
#define TOASTNOTIFICATIONSMANAGER_H
#include <QObject>
#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<BaseToastNotification*> 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<Message>& new_messages);
private:
NotificationPosition m_position;
int m_screen;
QList<BaseToastNotification*> m_activeNotifications;
};
#endif // TOASTNOTIFICATIONSMANAGER_H

View file

@ -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);

View file

@ -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;

View file

@ -26,7 +26,6 @@ class NotificationFactory : public QObject {
private:
QList<Notification> m_notifications = {};
};
#endif // NOTIFICATIONFACTORY_H

View file

@ -60,6 +60,14 @@ QList<QAction*> SearchsNode::contextMenuFeedsList() {
return QList<QAction*>{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();

View file

@ -19,6 +19,9 @@ class SearchsNode : public RootItem {
virtual QList<Message> undeletedMessages() const;
virtual QList<QAction*> contextMenuFeedsList();
virtual int countOfUnreadMessages() const;
virtual int countOfAllMessages() const;
Search* probeById(const QString& custom_id);
public slots: