From 59b80f784bbb70ebdd1a27322bb12342bef7ccb8 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 18 Jul 2023 06:16:01 +0200 Subject: [PATCH] working on persistent regexps + multiple of other fixes and small enhancements --- src/librssguard/CMakeLists.txt | 13 +- src/librssguard/core/feedsmodel.cpp | 12 ++ src/librssguard/core/feedsproxymodel.cpp | 1 + src/librssguard/database/sqlitedriver.cpp | 8 +- src/librssguard/definitions/definitions.h | 1 + src/librssguard/gui/feedsview.cpp | 40 +++- src/librssguard/gui/feedsview.h | 4 +- .../gui/settings/settingsfeedsmessages.cpp | 16 +- src/librssguard/miscellaneous/skinfactory.cpp | 7 +- .../abstract/gui}/formaddeditlabel.cpp | 2 +- .../abstract/gui}/formaddeditlabel.h | 0 .../abstract/gui}/formaddeditlabel.ui | 0 .../abstract/gui/formaddeditprobe.cpp | 81 ++++++++ .../services/abstract/gui/formaddeditprobe.h | 31 +++ .../services/abstract/gui/formaddeditprobe.ui | 103 ++++++++++ src/librssguard/services/abstract/label.cpp | 2 +- .../services/abstract/labelsnode.cpp | 2 +- .../services/abstract/recyclebin.cpp | 13 +- .../services/abstract/rootitem.cpp | 5 + src/librssguard/services/abstract/rootitem.h | 6 +- src/librssguard/services/abstract/search.cpp | 178 ++++++++++++++++++ src/librssguard/services/abstract/search.h | 50 +++++ .../services/abstract/searchsnode.cpp | 139 ++++++++++++++ .../services/abstract/searchsnode.h | 34 ++++ .../services/abstract/serviceroot.cpp | 21 ++- .../services/abstract/serviceroot.h | 3 + .../services/tt-rss/ttrssserviceroot.cpp | 2 +- 27 files changed, 741 insertions(+), 33 deletions(-) rename src/librssguard/{gui/dialogs => services/abstract/gui}/formaddeditlabel.cpp (94%) rename src/librssguard/{gui/dialogs => services/abstract/gui}/formaddeditlabel.h (100%) rename src/librssguard/{gui/dialogs => services/abstract/gui}/formaddeditlabel.ui (100%) create mode 100755 src/librssguard/services/abstract/gui/formaddeditprobe.cpp create mode 100755 src/librssguard/services/abstract/gui/formaddeditprobe.h create mode 100755 src/librssguard/services/abstract/gui/formaddeditprobe.ui create mode 100755 src/librssguard/services/abstract/search.cpp create mode 100755 src/librssguard/services/abstract/search.h create mode 100755 src/librssguard/services/abstract/searchsnode.cpp create mode 100755 src/librssguard/services/abstract/searchsnode.h diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index 60e068db9..410735201 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -63,8 +63,6 @@ set(SOURCES gui/dialogs/formabout.h gui/dialogs/formaddaccount.cpp gui/dialogs/formaddaccount.h - gui/dialogs/formaddeditlabel.cpp - gui/dialogs/formaddeditlabel.h gui/dialogs/formbackupdatabasesettings.cpp gui/dialogs/formbackupdatabasesettings.h gui/dialogs/formdatabasecleanup.cpp @@ -262,6 +260,10 @@ set(SOURCES services/abstract/gui/formcategorydetails.h services/abstract/gui/formfeeddetails.cpp services/abstract/gui/formfeeddetails.h + services/abstract/gui/formaddeditlabel.cpp + services/abstract/gui/formaddeditlabel.h + services/abstract/gui/formaddeditprobe.cpp + services/abstract/gui/formaddeditprobe.h services/abstract/gui/custommessagepreviewer.h services/abstract/gui/custommessagepreviewer.cpp services/abstract/importantnode.cpp @@ -270,6 +272,10 @@ set(SOURCES services/abstract/label.h services/abstract/labelsnode.cpp services/abstract/labelsnode.h + services/abstract/search.cpp + services/abstract/search.h + services/abstract/searchsnode.cpp + services/abstract/searchsnode.h services/abstract/recyclebin.cpp services/abstract/recyclebin.h services/abstract/rootitem.cpp @@ -427,7 +433,6 @@ set(SOURCES set(UI_FILES gui/dialogs/formabout.ui gui/dialogs/formaddaccount.ui - gui/dialogs/formaddeditlabel.ui gui/dialogs/formbackupdatabasesettings.ui gui/dialogs/formdatabasecleanup.ui gui/dialogs/formmain.ui @@ -460,6 +465,8 @@ set(UI_FILES services/abstract/gui/formaccountdetails.ui services/abstract/gui/formcategorydetails.ui services/abstract/gui/formfeeddetails.ui + services/abstract/gui/formaddeditlabel.ui + services/abstract/gui/formaddeditprobe.ui services/feedly/gui/feedlyaccountdetails.ui services/gmail/gui/formaddeditemail.ui services/gmail/gui/gmailaccountdetails.ui diff --git a/src/librssguard/core/feedsmodel.cpp b/src/librssguard/core/feedsmodel.cpp index 662eee9ea..e5ac08b0b 100644 --- a/src/librssguard/core/feedsmodel.cpp +++ b/src/librssguard/core/feedsmodel.cpp @@ -7,6 +7,7 @@ #include "database/databasequeries.h" #include "definitions/definitions.h" #include "gui/dialogs/formmain.h" +#include "gui/messagebox.h" #include "miscellaneous/feedreader.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/textfactory.h" @@ -510,6 +511,17 @@ bool FeedsModel::markItemRead(RootItem* item, RootItem::ReadStatus read) { bool FeedsModel::markItemCleared(RootItem* item, bool clean_read_only) { if (item != nullptr) { + if (MsgBox::show(nullptr, + QMessageBox::Icon::Question, + tr("Are you sure?"), + tr("Do you really want to clean all articles from selected item?"), + {}, + {}, + QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, + QMessageBox::StandardButton::No) != QMessageBox::StandardButton::Yes) { + return false; + } + return item->cleanMessages(clean_read_only); } diff --git a/src/librssguard/core/feedsproxymodel.cpp b/src/librssguard/core/feedsproxymodel.cpp index 37c9578d7..69db2a21d 100644 --- a/src/librssguard/core/feedsproxymodel.cpp +++ b/src/librssguard/core/feedsproxymodel.cpp @@ -36,6 +36,7 @@ FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent) m_priorities = {RootItem::Kind::Category, RootItem::Kind::Feed, RootItem::Kind::Labels, + RootItem::Kind::Probes, RootItem::Kind::Important, RootItem::Kind::Unread, RootItem::Kind::Bin}; diff --git a/src/librssguard/database/sqlitedriver.cpp b/src/librssguard/database/sqlitedriver.cpp index 8e77f3e60..84d79b018 100644 --- a/src/librssguard/database/sqlitedriver.cpp +++ b/src/librssguard/database/sqlitedriver.cpp @@ -115,14 +115,14 @@ QSqlDatabase SqliteDriver::connection(const QString& connection_name, DesiredSto database = QSqlDatabase::addDatabase(QSL(APP_DB_SQLITE_DRIVER), connection_name); if (want_in_memory) { - database.setConnectOptions(QSL("QSQLITE_OPEN_URI;QSQLITE_ENABLE_SHARED_CACHE")); + database.setConnectOptions(QSL("QSQLITE_OPEN_URI;QSQLITE_ENABLE_SHARED_CACHE;QSQLITE_ENABLE_REGEXP")); database.setDatabaseName(QSL("file::memory:")); } else { const QDir db_path(m_databaseFilePath); QFile db_file(db_path.absoluteFilePath(QSL(APP_DB_SQLITE_FILE))); - database.setConnectOptions(QSL("QSQLITE_ENABLE_SHARED_CACHE")); + database.setConnectOptions(QSL("QSQLITE_ENABLE_SHARED_CACHE;QSQLITE_ENABLE_REGEXP")); database.setDatabaseName(db_file.fileName()); } } @@ -203,10 +203,10 @@ QSqlDatabase SqliteDriver::initializeDatabase(const QString& connection_name, bo database = QSqlDatabase::addDatabase(QSL(APP_DB_SQLITE_DRIVER), connection_name); if (in_memory) { - database.setConnectOptions(QSL("QSQLITE_OPEN_URI;QSQLITE_ENABLE_SHARED_CACHE")); + database.setConnectOptions(QSL("QSQLITE_OPEN_URI;QSQLITE_ENABLE_SHARED_CACHE;QSQLITE_ENABLE_REGEXP")); } else { - database.setConnectOptions(QSL("QSQLITE_ENABLE_SHARED_CACHE")); + database.setConnectOptions(QSL("QSQLITE_ENABLE_SHARED_CACHE;QSQLITE_ENABLE_REGEXP")); } database.setDatabaseName(db_file_name); diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index fcdfdf5c9..ec6af0b23 100644 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -44,6 +44,7 @@ #define ID_IMPORTANT -3 #define ID_LABELS -4 #define ID_UNREAD -5 +#define ID_PROBES -6 #define MSG_SCORE_MAX 100.0 #define MSG_SCORE_MIN 0.0 diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp index 74a11526f..dabe3a187 100644 --- a/src/librssguard/gui/feedsview.cpp +++ b/src/librssguard/gui/feedsview.cpp @@ -30,7 +30,8 @@ FeedsView::FeedsView(QWidget* parent) : BaseTreeView(parent), m_contextMenuService(nullptr), m_contextMenuBin(nullptr), m_contextMenuCategories(nullptr), m_contextMenuFeeds(nullptr), m_contextMenuImportant(nullptr), m_contextMenuEmptySpace(nullptr), - m_contextMenuOtherItems(nullptr), m_contextMenuLabel(nullptr), m_dontSaveExpandState(false) { + m_contextMenuOtherItems(nullptr), m_contextMenuLabel(nullptr), m_contextMenuProbe(nullptr), + m_dontSaveExpandState(false) { setObjectName(QSL("FeedsView")); // Allocate models. @@ -852,20 +853,42 @@ QMenu* FeedsView::initializeContextMenuLabel(RootItem* clicked_item) { QList specific_actions = clicked_item->contextMenuFeedsList(); + m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionEditSelectedItem); + m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead); + m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread); + m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionDeleteSelectedItem); + if (!specific_actions.isEmpty()) { m_contextMenuLabel->addSeparator(); m_contextMenuLabel->addActions(specific_actions); } - else { - m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionEditSelectedItem); - m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead); - m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread); - m_contextMenuLabel->addAction(qApp->mainForm()->m_ui->m_actionDeleteSelectedItem); - } return m_contextMenuLabel; } +QMenu* FeedsView::initializeContextMenuProbe(RootItem* clicked_item) { + if (m_contextMenuProbe == nullptr) { + m_contextMenuProbe = new QMenu(tr("Context menu for probe"), this); + } + else { + m_contextMenuProbe->clear(); + } + + QList specific_actions = clicked_item->contextMenuFeedsList(); + + m_contextMenuProbe->addAction(qApp->mainForm()->m_ui->m_actionEditSelectedItem); + m_contextMenuProbe->addAction(qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead); + m_contextMenuProbe->addAction(qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread); + m_contextMenuProbe->addAction(qApp->mainForm()->m_ui->m_actionDeleteSelectedItem); + + if (!specific_actions.isEmpty()) { + m_contextMenuProbe->addSeparator(); + m_contextMenuProbe->addActions(specific_actions); + } + + return m_contextMenuProbe; +} + void FeedsView::setupAppearance() { // Setup column resize strategies. header()->setSectionResizeMode(FDS_MODEL_TITLE_INDEX, QHeaderView::ResizeMode::Stretch); @@ -950,6 +973,9 @@ void FeedsView::contextMenuEvent(QContextMenuEvent* event) { else if (clicked_item->kind() == RootItem::Kind::Label) { initializeContextMenuLabel(clicked_item)->exec(event->globalPos()); } + else if (clicked_item->kind() == RootItem::Kind::Probe) { + initializeContextMenuProbe(clicked_item)->exec(event->globalPos()); + } else { initializeContextMenuOtherItem(clicked_item)->exec(event->globalPos()); } diff --git a/src/librssguard/gui/feedsview.h b/src/librssguard/gui/feedsview.h index 9ebced41e..8836531be 100644 --- a/src/librssguard/gui/feedsview.h +++ b/src/librssguard/gui/feedsview.h @@ -129,6 +129,7 @@ class RSSGUARD_DLLSPEC FeedsView : public BaseTreeView { QMenu* initializeContextMenuEmptySpace(); QMenu* initializeContextMenuOtherItem(RootItem* clicked_item); QMenu* initializeContextMenuLabel(RootItem* clicked_item); + QMenu* initializeContextMenuProbe(RootItem* clicked_item); void setupAppearance(); void saveExpandStates(RootItem* item); @@ -141,13 +142,14 @@ class RSSGUARD_DLLSPEC FeedsView : public BaseTreeView { QMenu* m_contextMenuEmptySpace; QMenu* m_contextMenuOtherItems; QMenu* m_contextMenuLabel; + QMenu* m_contextMenuProbe; FeedsModel* m_sourceModel; FeedsProxyModel* m_proxyModel; bool m_dontSaveExpandState; // QTreeView interface protected: - virtual void drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const; + virtual void drawRow(QPainter* painter, const QStyleOptionViewItem& options, const QModelIndex& index) const; }; inline FeedsProxyModel* FeedsView::model() const { diff --git a/src/librssguard/gui/settings/settingsfeedsmessages.cpp b/src/librssguard/gui/settings/settingsfeedsmessages.cpp index e748efd99..48c4cd5ec 100644 --- a/src/librssguard/gui/settings/settingsfeedsmessages.cpp +++ b/src/librssguard/gui/settings/settingsfeedsmessages.cpp @@ -35,6 +35,14 @@ SettingsFeedsMessages::SettingsFeedsMessages(Settings* settings, QWidget* parent "performance of article list with big number of articles."), true); + QMetaEnum enumer = QMetaEnum::fromType(); + + for (int i = 0; i < enumer.keyCount(); i++) { + auto en = MessagesModel::MessageUnreadIcon(enumer.value(i)); + + m_ui->m_cmbUnreadIconType->addItem(MessagesModel::descriptionOfUnreadIcon(en), int(en)); + } + connect(m_ui->m_cbShowEnclosuresDirectly, &QCheckBox::toggled, this, &SettingsFeedsMessages::dirtifySettings); connect(m_ui->m_spinHeightImageAttachments, static_cast(&QSpinBox::valueChanged), @@ -190,14 +198,6 @@ SettingsFeedsMessages::SettingsFeedsMessages(Settings* settings, QWidget* parent } m_ui->m_spinRelativeArticleTime->setValue(-1); - - QMetaEnum enumer = QMetaEnum::fromType(); - - for (int i = 0; i < enumer.keyCount(); i++) { - auto en = MessagesModel::MessageUnreadIcon(enumer.value(i)); - - m_ui->m_cmbUnreadIconType->addItem(MessagesModel::descriptionOfUnreadIcon(en), int(en)); - } } SettingsFeedsMessages::~SettingsFeedsMessages() { diff --git a/src/librssguard/miscellaneous/skinfactory.cpp b/src/librssguard/miscellaneous/skinfactory.cpp index 9b8757208..ee6c6c708 100644 --- a/src/librssguard/miscellaneous/skinfactory.cpp +++ b/src/librssguard/miscellaneous/skinfactory.cpp @@ -291,7 +291,12 @@ PreparedHtml SkinFactory::generateHtmlOfArticles(const QList& messages, QUrl url(NetworkFactory::sanitizeUrl(feed->source())); if (url.isValid()) { - base_url = url.scheme() + QSL("://") + url.host(); + if (url.isLocalFile()) { + base_url = url.scheme() + QSL("://") + url.toLocalFile(); + } + else { + base_url = url.scheme() + QSL("://") + url.host(); + } } } diff --git a/src/librssguard/gui/dialogs/formaddeditlabel.cpp b/src/librssguard/services/abstract/gui/formaddeditlabel.cpp similarity index 94% rename from src/librssguard/gui/dialogs/formaddeditlabel.cpp rename to src/librssguard/services/abstract/gui/formaddeditlabel.cpp index b26f7322c..f7fa578a7 100644 --- a/src/librssguard/gui/dialogs/formaddeditlabel.cpp +++ b/src/librssguard/services/abstract/gui/formaddeditlabel.cpp @@ -1,6 +1,6 @@ // For license of this file, see /LICENSE.md. -#include "gui/dialogs/formaddeditlabel.h" +#include "services/abstract/gui/formaddeditlabel.h" #include "gui/guiutilities.h" #include "miscellaneous/application.h" diff --git a/src/librssguard/gui/dialogs/formaddeditlabel.h b/src/librssguard/services/abstract/gui/formaddeditlabel.h similarity index 100% rename from src/librssguard/gui/dialogs/formaddeditlabel.h rename to src/librssguard/services/abstract/gui/formaddeditlabel.h diff --git a/src/librssguard/gui/dialogs/formaddeditlabel.ui b/src/librssguard/services/abstract/gui/formaddeditlabel.ui similarity index 100% rename from src/librssguard/gui/dialogs/formaddeditlabel.ui rename to src/librssguard/services/abstract/gui/formaddeditlabel.ui diff --git a/src/librssguard/services/abstract/gui/formaddeditprobe.cpp b/src/librssguard/services/abstract/gui/formaddeditprobe.cpp new file mode 100755 index 000000000..7aa5ddb6e --- /dev/null +++ b/src/librssguard/services/abstract/gui/formaddeditprobe.cpp @@ -0,0 +1,81 @@ +// For license of this file, see /LICENSE.md. + +#include "services/abstract/gui/formaddeditprobe.h" + +#include "gui/guiutilities.h" +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/abstract/search.h" + +FormAddEditProbe::FormAddEditProbe(QWidget* parent) : QDialog(parent), m_editableProbe(nullptr) { + m_ui.setupUi(this); + m_ui.m_txtName->lineEdit()->setPlaceholderText(tr("Name for your probe")); + m_ui.m_txtFilter->lineEdit()->setPlaceholderText(tr("Regular expression")); + + connect(m_ui.m_txtName->lineEdit(), &QLineEdit::textChanged, this, [this](const QString& text) { + if (text.isEmpty()) { + m_ui.m_txtName->setStatus(LineEditWithStatus::StatusType::Error, tr("Label's name cannot be empty.")); + } + else { + m_ui.m_txtName->setStatus(LineEditWithStatus::StatusType::Ok, tr("Perfect!")); + } + }); + connect(m_ui.m_txtFilter->lineEdit(), &QLineEdit::textChanged, this, [this](const QString& text) { + if (text.isEmpty()) { + m_ui.m_txtFilter->setStatus(LineEditWithStatus::StatusType::Error, tr("Probe name cannot be empty.")); + } + else if (!QRegularExpression(text).isValid()) { + m_ui.m_txtFilter->setStatus(LineEditWithStatus::StatusType::Error, tr("Regular expression is not well-formed.")); + } + else { + m_ui.m_txtFilter->setStatus(LineEditWithStatus::StatusType::Ok, tr("Perfect!")); + } + }); + + emit m_ui.m_txtName->lineEdit()->textChanged({}); + emit m_ui.m_txtFilter->lineEdit()->textChanged({}); +} + +Search* FormAddEditProbe::execForAdd() { + GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("tag-new")), tr("Create new probe")); + + m_ui.m_btnColor->setRandomColor(); + m_ui.m_txtName->lineEdit()->setText(tr("Hot stuff")); + m_ui.m_txtFilter->setFocus(); + + auto exit_code = exec(); + + if (exit_code == QDialog::DialogCode::Accepted) { + return new Search(m_ui.m_txtName->lineEdit()->text(), + m_ui.m_txtFilter->lineEdit()->text(), + m_ui.m_btnColor->color()); + } + else { + return nullptr; + } +} + +bool FormAddEditProbe::execForEdit(Search* prb) { + GuiUtilities::applyDialogProperties(*this, + qApp->icons()->fromTheme(QSL("tag-properties")), + tr("Edit probe '%1'").arg(prb->title())); + + m_editableProbe = prb; + + m_ui.m_btnColor->setColor(prb->color()); + m_ui.m_txtName->lineEdit()->setText(prb->title()); + m_ui.m_txtFilter->lineEdit()->setText(prb->filter()); + m_ui.m_txtFilter->setFocus(); + + auto exit_code = exec(); + + if (exit_code == QDialog::DialogCode::Accepted) { + m_editableProbe->setColor(m_ui.m_btnColor->color()); + m_editableProbe->setFilter(m_ui.m_txtFilter->lineEdit()->text()); + m_editableProbe->setTitle(m_ui.m_txtName->lineEdit()->text()); + return true; + } + else { + return false; + } +} diff --git a/src/librssguard/services/abstract/gui/formaddeditprobe.h b/src/librssguard/services/abstract/gui/formaddeditprobe.h new file mode 100755 index 000000000..83253704b --- /dev/null +++ b/src/librssguard/services/abstract/gui/formaddeditprobe.h @@ -0,0 +1,31 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FORMADDEDITPROBE_H +#define FORMADDEDITPROBE_H + +#include + +#include "ui_formaddeditprobe.h" + +namespace Ui { + class FormAddEditProbe; +} + +class Search; + +class FormAddEditProbe : public QDialog { + Q_OBJECT + + public: + explicit FormAddEditProbe(QWidget* parent = nullptr); + + public slots: + Search* execForAdd(); + bool execForEdit(Search* prb); + + private: + Ui::FormAddEditProbe m_ui; + Search* m_editableProbe; +}; + +#endif // FORMADDEDITPROBE_H diff --git a/src/librssguard/services/abstract/gui/formaddeditprobe.ui b/src/librssguard/services/abstract/gui/formaddeditprobe.ui new file mode 100755 index 000000000..ec9cfcd17 --- /dev/null +++ b/src/librssguard/services/abstract/gui/formaddeditprobe.ui @@ -0,0 +1,103 @@ + + + FormAddEditProbe + + + + 0 + 0 + 270 + 180 + + + + + + + ... + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + LineEditWithStatus + QWidget +
lineeditwithstatus.h
+ 1 +
+ + ColorToolButton + QToolButton +
colortoolbutton.h
+
+
+ + m_btnColor + + + + + m_buttonBox + accepted() + FormAddEditProbe + accept() + + + 214 + 132 + + + 157 + 141 + + + + + m_buttonBox + rejected() + FormAddEditProbe + reject() + + + 214 + 132 + + + 223 + 141 + + + + +
diff --git a/src/librssguard/services/abstract/label.cpp b/src/librssguard/services/abstract/label.cpp index 053899541..4afee9dc1 100644 --- a/src/librssguard/services/abstract/label.cpp +++ b/src/librssguard/services/abstract/label.cpp @@ -4,9 +4,9 @@ #include "database/databasefactory.h" #include "database/databasequeries.h" -#include "gui/dialogs/formaddeditlabel.h" #include "miscellaneous/application.h" #include "services/abstract/cacheforserviceroot.h" +#include "services/abstract/gui/formaddeditlabel.h" #include "services/abstract/labelsnode.h" #include "services/abstract/serviceroot.h" diff --git a/src/librssguard/services/abstract/labelsnode.cpp b/src/librssguard/services/abstract/labelsnode.cpp index aa1c4c138..bb2fafd47 100644 --- a/src/librssguard/services/abstract/labelsnode.cpp +++ b/src/librssguard/services/abstract/labelsnode.cpp @@ -5,9 +5,9 @@ #include "database/databasefactory.h" #include "database/databasequeries.h" #include "exceptions/applicationexception.h" -#include "gui/dialogs/formaddeditlabel.h" #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" +#include "services/abstract/gui/formaddeditlabel.h" #include "services/abstract/serviceroot.h" #include "3rd-party/boolinq/boolinq.h" diff --git a/src/librssguard/services/abstract/recyclebin.cpp b/src/librssguard/services/abstract/recyclebin.cpp index 793882f0b..c6419036f 100644 --- a/src/librssguard/services/abstract/recyclebin.cpp +++ b/src/librssguard/services/abstract/recyclebin.cpp @@ -3,6 +3,7 @@ #include "services/abstract/recyclebin.h" #include "database/databasequeries.h" +#include "gui/messagebox.h" #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/textfactory.h" @@ -92,7 +93,6 @@ bool RecycleBin::cleanMessages(bool clear_only_read) { parent_root->itemChanged(QList() << this); parent_root->requestReloadMessageList(true); return true; - ; } else { return false; @@ -100,6 +100,17 @@ bool RecycleBin::cleanMessages(bool clear_only_read) { } bool RecycleBin::empty() { + if (MsgBox::show(nullptr, + QMessageBox::Icon::Question, + tr("Are you sure?"), + tr("Do you really want to empty your recycle bin?"), + {}, + {}, + QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, + QMessageBox::StandardButton::No) != QMessageBox::StandardButton::Yes) { + return false; + } + return cleanMessages(false); } diff --git a/src/librssguard/services/abstract/rootitem.cpp b/src/librssguard/services/abstract/rootitem.cpp index cf483a2c2..ef06cc215 100644 --- a/src/librssguard/services/abstract/rootitem.cpp +++ b/src/librssguard/services/abstract/rootitem.cpp @@ -9,6 +9,7 @@ #include "services/abstract/feed.h" #include "services/abstract/label.h" #include "services/abstract/recyclebin.h" +#include "services/abstract/search.h" #include "services/abstract/serviceroot.h" #include @@ -570,6 +571,10 @@ Label* RootItem::toLabel() const { return qobject_cast(const_cast(this)); } +Search* RootItem::toProbe() const { + return qobject_cast(const_cast(this)); +} + ServiceRoot* RootItem::toServiceRoot() const { return qobject_cast(const_cast(this)); } diff --git a/src/librssguard/services/abstract/rootitem.h b/src/librssguard/services/abstract/rootitem.h index 2a5a39a1a..748519d13 100644 --- a/src/librssguard/services/abstract/rootitem.h +++ b/src/librssguard/services/abstract/rootitem.h @@ -12,6 +12,7 @@ class Category; class Feed; class Label; +class Search; class ServiceRoot; class QAction; @@ -43,7 +44,9 @@ class RSSGUARD_DLLSPEC RootItem : public QObject { Labels = 32, Important = 64, Label = 128, - Unread = 256 + Unread = 256, + Probes = 512, + Probe = 1024 }; // Constructors and destructors. @@ -196,6 +199,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject { Category* toCategory() const; Feed* toFeed() const; Label* toLabel() const; + Search* toProbe() const; ServiceRoot* toServiceRoot() const; bool keepOnTop() const; diff --git a/src/librssguard/services/abstract/search.cpp b/src/librssguard/services/abstract/search.cpp new file mode 100755 index 000000000..fbd33ce22 --- /dev/null +++ b/src/librssguard/services/abstract/search.cpp @@ -0,0 +1,178 @@ +// For license of this file, see /LICENSE.md. + +#include "services/abstract/search.h" + +#include "database/databasefactory.h" +#include "database/databasequeries.h" +#include "miscellaneous/application.h" +#include "services/abstract/cacheforserviceroot.h" +#include "services/abstract/gui/formaddeditprobe.h" +#include "services/abstract/labelsnode.h" +#include "services/abstract/serviceroot.h" + +#include +#include + +Search::Search(const QString& name, const QString& filter, const QColor& color, RootItem* parent_item) + : Search(parent_item) { + setColor(color); + setTitle(name); + setFilter(filter); +} + +Search::Search(RootItem* parent_item) : RootItem(parent_item) { + setKind(RootItem::Kind::Probe); +} + +QColor Search::color() const { + return m_color; +} + +void Search::setColor(const QColor& color) { + setIcon(generateIcon(color)); + m_color = color; +} + +int Search::countOfUnreadMessages() const { + return m_unreadCount; +} + +int Search::countOfAllMessages() const { + return m_totalCount; +} + +bool Search::canBeEdited() const { + return true; +} + +bool Search::editViaGui() { + FormAddEditProbe form(qApp->mainFormWidget()); + + if (form.execForEdit(this)) { + QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); + + return true; + // return DatabaseQueries::updateLabel(db, this); + } + else { + return false; + } + + return false; +} + +bool Search::canBeDeleted() const { + return true; +} + +bool Search::deleteViaGui() { + QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); + + /* + if (DatabaseQueries::deleteLabel(db, this)) { + getParentServiceRoot()->requestItemRemoval(this); + return true; + } + else { + return false; + } + */ + return false; +} + +void Search::updateCounts(bool including_total_count) { + QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className()); + int account_id = getParentServiceRoot()->accountId(); + + /* + auto ac = DatabaseQueries::getMessageCountsForLabel(database, this, account_id); + + if (including_total_count) { + setCountOfAllMessages(ac.m_total); + } + + setCountOfUnreadMessages(ac.m_unread); + */ +} + +QList Search::undeletedMessages() const { + return {}; + /* + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + + return DatabaseQueries::getUndeletedMessagesWithLabel(database, this); + */ +} + +QIcon Search::generateIcon(const QColor& color) { + QPixmap pxm(64, 64); + + pxm.fill(Qt::GlobalColor::transparent); + + QPainter paint(&pxm); + + paint.setBrush(color); + paint.setPen(Qt::GlobalColor::transparent); + paint.drawEllipse(pxm.rect().marginsRemoved(QMargins(2, 2, 2, 2))); + + return pxm; +} + +QString Search::filter() const { + return m_filter; +} + +void Search::setFilter(const QString& new_filter) { + m_filter = new_filter; +} + +void Search::setCountOfAllMessages(int totalCount) { + m_totalCount = totalCount; +} + +void Search::setCountOfUnreadMessages(int unreadCount) { + m_unreadCount = unreadCount; +} + +bool Search::cleanMessages(bool clear_only_read) { + ServiceRoot* service = getParentServiceRoot(); + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + + return false; + /* + if (DatabaseQueries::cleanLabelledMessages(database, clear_only_read, this)) { + service->updateCounts(true); + service->itemChanged(service->getSubTree()); + service->requestReloadMessageList(true); + return true; + } + else { + return false; + } + */ +} + +bool Search::markAsReadUnread(RootItem::ReadStatus status) { + ServiceRoot* service = getParentServiceRoot(); + auto* cache = dynamic_cast(service); + + /* + if (cache != nullptr) { + cache->addMessageStatesToCache(service->customIDSOfMessagesForItem(this, status), status); + } + + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + + if (DatabaseQueries::markLabelledMessagesReadUnread(database, this, status)) { + service->updateCounts(false); + service->itemChanged(service->getSubTree()); + service->requestReloadMessageList(status == RootItem::ReadStatus::Read); + return true; + } + else { + return false; + } + */ + + return false; +} diff --git a/src/librssguard/services/abstract/search.h b/src/librssguard/services/abstract/search.h new file mode 100755 index 000000000..d04288603 --- /dev/null +++ b/src/librssguard/services/abstract/search.h @@ -0,0 +1,50 @@ +// For license of this file, see /LICENSE.md. + +#ifndef SEARCH_H +#define SEARCH_H + +#include "services/abstract/rootitem.h" + +#include + +class RSSGUARD_DLLSPEC Search : public RootItem { + Q_OBJECT + + // Added for message filtering with labels. + Q_PROPERTY(QColor color READ color) + + public: + explicit Search(const QString& name, const QString& filter, const QColor& color, RootItem* parent_item = nullptr); + explicit Search(RootItem* parent_item = nullptr); + + QColor color() const; + void setColor(const QColor& color); + + QString filter() const; + void setFilter(const QString& new_filter); + + void setCountOfAllMessages(int totalCount); + void setCountOfUnreadMessages(int unreadCount); + + virtual bool cleanMessages(bool clear_only_read); + virtual bool markAsReadUnread(ReadStatus status); + virtual int countOfAllMessages() const; + virtual int countOfUnreadMessages() const; + virtual bool canBeEdited() const; + virtual bool editViaGui(); + virtual bool canBeDeleted() const; + virtual bool deleteViaGui(); + virtual void updateCounts(bool including_total_count); + virtual QList undeletedMessages() const; + + public: + static QIcon generateIcon(const QColor& color); + + private: + QString m_filter; + QColor m_color; + int m_totalCount{}; + int m_unreadCount{}; +}; + +#endif // SEARCH_H diff --git a/src/librssguard/services/abstract/searchsnode.cpp b/src/librssguard/services/abstract/searchsnode.cpp new file mode 100755 index 000000000..bfd85a92c --- /dev/null +++ b/src/librssguard/services/abstract/searchsnode.cpp @@ -0,0 +1,139 @@ +// For license of this file, see /LICENSE.md. + +#include "services/abstract/searchsnode.h" + +#include "database/databasefactory.h" +#include "database/databasequeries.h" +#include "exceptions/applicationexception.h" +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/abstract/gui/formaddeditprobe.h" +#include "services/abstract/serviceroot.h" + +#include "3rd-party/boolinq/boolinq.h" + +SearchsNode::SearchsNode(RootItem* parent_item) : RootItem(parent_item), m_actProbeNew(nullptr) { + setKind(RootItem::Kind::Probes); + setId(ID_PROBES); + setIcon(qApp->icons()->fromTheme(QSL("system-search"))); + setTitle(tr("Article probes")); + setDescription(tr("You can see all your permanent article probes here.")); +} + +void SearchsNode::loadProbes(const QList& probes) { + for (auto* prb : probes) { + appendChild(prb); + } +} + +QList SearchsNode::undeletedMessages() const { + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + + return {}; + // return DatabaseQueries::getUndeletedLabelledMessages(database, getParentServiceRoot()->accountId()); +} + +int SearchsNode::countOfUnreadMessages() const { + auto chi = childItems(); + + if (chi.isEmpty()) { + return 0; + } + + return boolinq::from(chi) + .max([](RootItem* it) { + return it->countOfUnreadMessages(); + }) + ->countOfUnreadMessages(); +} + +int SearchsNode::countOfAllMessages() const { + auto chi = childItems(); + + if (chi.isEmpty()) { + return 0; + } + + return boolinq::from(chi) + .max([](RootItem* it) { + return it->countOfAllMessages(); + }) + ->countOfAllMessages(); +} + +void SearchsNode::updateCounts(bool including_total_count) { + // TODO: This is still rather slow because this is automatically + // called when message is marked (un)read or starred. + // It would be enough if only labels which are assigned to article + // are recounted, not all. + + QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className()); + int account_id = getParentServiceRoot()->accountId(); + auto acc = DatabaseQueries::getMessageCountsForAllLabels(database, account_id); + /* + for (Label* lbl : probes()) { + if (!acc.contains(lbl->customId())) { + if (including_total_count) { + lbl->setCountOfAllMessages(0); + } + + lbl->setCountOfUnreadMessages(0); + } + else { + auto ac = acc.value(lbl->customId()); + + if (including_total_count) { + lbl->setCountOfAllMessages(ac.m_total); + } + + lbl->setCountOfUnreadMessages(ac.m_unread); + } + } + */ +} + +Search* SearchsNode::probeById(const QString& custom_id) { + auto chi = childItems(); + + return qobject_cast(boolinq::from(chi).firstOrDefault([custom_id](RootItem* it) { + return it->customId() == custom_id; + })); +} + +QList SearchsNode::probes() const { + auto list = boolinq::from(childItems()) + .select([](RootItem* it) { + return static_cast(it); + }) + .toStdList(); + + return FROM_STD_LIST(QList, list); +} + +QList SearchsNode::contextMenuFeedsList() { + if (m_actProbeNew == nullptr) { + m_actProbeNew = new QAction(qApp->icons()->fromTheme(QSL("system-search")), tr("New article probe"), this); + + connect(m_actProbeNew, &QAction::triggered, this, &SearchsNode::createProbe); + } + + return QList{m_actProbeNew}; +} + +void SearchsNode::createProbe() { + FormAddEditProbe frm(qApp->mainFormWidget()); + Search* new_prb = frm.execForAdd(); + + if (new_prb != nullptr) { + QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); + + try { + // DatabaseQueries::createLabel(db, new_prb, getParentServiceRoot()->accountId()); + + getParentServiceRoot()->requestItemReassignment(new_prb, this); + } + catch (const ApplicationException&) { + new_prb->deleteLater(); + } + } +} diff --git a/src/librssguard/services/abstract/searchsnode.h b/src/librssguard/services/abstract/searchsnode.h new file mode 100755 index 000000000..119072a6d --- /dev/null +++ b/src/librssguard/services/abstract/searchsnode.h @@ -0,0 +1,34 @@ +// For license of this file, see /LICENSE.md. + +#ifndef SEARCHSNODE_H +#define SEARCHSNODE_H + +#include "services/abstract/rootitem.h" + +#include "services/abstract/search.h" + +class SearchsNode : public RootItem { + Q_OBJECT + + public: + explicit SearchsNode(RootItem* parent_item = nullptr); + + QList probes() const; + void loadProbes(const QList& probes); + + virtual QList undeletedMessages() const; + virtual QList contextMenuFeedsList(); + virtual int countOfUnreadMessages() const; + virtual int countOfAllMessages() const; + virtual void updateCounts(bool including_total_count); + + Search* probeById(const QString& custom_id); + + public slots: + void createProbe(); + + private: + QAction* m_actProbeNew; +}; + +#endif // SEARCHSNODE_H diff --git a/src/librssguard/services/abstract/serviceroot.cpp b/src/librssguard/services/abstract/serviceroot.cpp index f8709ccb9..37e27df21 100644 --- a/src/librssguard/services/abstract/serviceroot.cpp +++ b/src/librssguard/services/abstract/serviceroot.cpp @@ -17,17 +17,19 @@ #include "services/abstract/importantnode.h" #include "services/abstract/labelsnode.h" #include "services/abstract/recyclebin.h" +#include "services/abstract/search.h" +#include "services/abstract/searchsnode.h" #include "services/abstract/unreadnode.h" ServiceRoot::ServiceRoot(RootItem* parent) : RootItem(parent), m_recycleBin(new RecycleBin(this)), m_importantNode(new ImportantNode(this)), - m_labelsNode(new LabelsNode(this)), m_unreadNode(new UnreadNode(this)), m_accountId(NO_PARENT_CATEGORY), - m_networkProxy(QNetworkProxy()) { + m_labelsNode(new LabelsNode(this)), m_probesNode(new SearchsNode(this)), m_unreadNode(new UnreadNode(this)), + m_accountId(NO_PARENT_CATEGORY), m_networkProxy(QNetworkProxy()) { setKind(RootItem::Kind::ServiceRoot); appendCommonNodes(); } -ServiceRoot::~ServiceRoot() = default; +ServiceRoot::~ServiceRoot() {} bool ServiceRoot::deleteViaGui() { QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); @@ -242,6 +244,10 @@ void ServiceRoot::appendCommonNodes() { if (labelsNode() != nullptr && !childItems().contains(labelsNode())) { appendChild(labelsNode()); } + + if (probesNode() != nullptr && !childItems().contains(probesNode())) { + appendChild(probesNode()); + } } bool ServiceRoot::cleanFeeds(const QList& items, bool clean_read_only) { @@ -455,6 +461,10 @@ LabelsNode* ServiceRoot::labelsNode() const { return m_labelsNode; } +SearchsNode* ServiceRoot::probesNode() const { + return m_probesNode; +} + UnreadNode* ServiceRoot::unreadNode() const { return m_unreadNode; } @@ -715,6 +725,11 @@ bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) { "Messages.account_id = %1") .arg(QString::number(accountId()))); } + else if (item->kind() == RootItem::Kind::Probe) { + model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1 AND " + "Messages.contents REGEXP '%2'") + .arg(QString::number(accountId()), item->toProbe()->filter())); + } else if (item->kind() == RootItem::Kind::Label) { // Show messages with particular label. model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND " diff --git a/src/librssguard/services/abstract/serviceroot.h b/src/librssguard/services/abstract/serviceroot.h index bd8686f53..384cdc91d 100644 --- a/src/librssguard/services/abstract/serviceroot.h +++ b/src/librssguard/services/abstract/serviceroot.h @@ -19,6 +19,7 @@ class FeedsModel; class RecycleBin; class ImportantNode; class UnreadNode; +class SearchsNode; class LabelsNode; class Label; class MessagesModel; @@ -52,6 +53,7 @@ class ServiceRoot : public RootItem { RecycleBin* recycleBin() const; ImportantNode* importantNode() const; LabelsNode* labelsNode() const; + SearchsNode* probesNode() const; UnreadNode* unreadNode() const; virtual void updateCounts(bool including_total_count); @@ -297,6 +299,7 @@ class ServiceRoot : public RootItem { RecycleBin* m_recycleBin; ImportantNode* m_importantNode; LabelsNode* m_labelsNode; + SearchsNode* m_probesNode; UnreadNode* m_unreadNode; int m_accountId; QList m_serviceMenu; diff --git a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp index e4dd3c8f5..44ae51f6c 100644 --- a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp +++ b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp @@ -44,7 +44,7 @@ void TtRssServiceRoot::start(bool freshly_activated) { DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); - auto lbls = m_labelsNode->labels(); + auto lbls = labelsNode()->labels(); boolinq::from(lbls).for_each([](Label* lbl) { if (lbl->customNumericId() == TTRSS_PUBLISHED_LABEL_ID) {