diff --git a/resources/skins/dark/metadata.xml b/resources/skins/dark/metadata.xml index db3f2ab4e..db8622652 100755 --- a/resources/skins/dark/metadata.xml +++ b/resources/skins/dark/metadata.xml @@ -1,5 +1,5 @@ - + Martin Rotter rotter.martinos@gmail.com @@ -7,5 +7,6 @@ #7ae2ff #f08f84 + #77dd77 \ No newline at end of file diff --git a/resources/skins/vergilius/metadata.xml b/resources/skins/vergilius/metadata.xml index caefe5f2e..9c47a6def 100755 --- a/resources/skins/vergilius/metadata.xml +++ b/resources/skins/vergilius/metadata.xml @@ -1,5 +1,5 @@ - + Martin Rotter rotter.martinos@gmail.com @@ -7,5 +7,6 @@ #4861f0 #d93636 + #77dd77 \ No newline at end of file diff --git a/src/librssguard/core/messagesforfiltersmodel.cpp b/src/librssguard/core/messagesforfiltersmodel.cpp new file mode 100755 index 000000000..7ebb1762a --- /dev/null +++ b/src/librssguard/core/messagesforfiltersmodel.cpp @@ -0,0 +1,132 @@ +// For license of this file, see /LICENSE.md. + +#include "core/messagesforfiltersmodel.h" + +#include "core/messagefilter.h" +#include "definitions/definitions.h" +#include "exceptions/filteringexception.h" +#include "miscellaneous/application.h" +#include "miscellaneous/skinfactory.h" + +MessagesForFiltersModel::MessagesForFiltersModel(QObject* parent) : QAbstractTableModel(parent) { + m_headerData << tr("Read") << tr("Important") << tr("Title") + << tr("URL") << tr("Author") << tr("Created on"); +} + +void MessagesForFiltersModel::setMessages(const QList& messages) { + m_filteringDecisions.clear(); + m_messages = messages; + + emit layoutAboutToBeChanged(); + emit layoutChanged(); +} + +int MessagesForFiltersModel::rowCount(const QModelIndex& parent) const { + Q_UNUSED(parent) + return m_messages.size(); +} + +int MessagesForFiltersModel::columnCount(const QModelIndex& parent) const { + Q_UNUSED(parent) + return m_headerData.size(); +} + +QVariant MessagesForFiltersModel::data(const QModelIndex& index, int role) const { + auto msg = messageForRow(index.row()); + + switch (role) { + case Qt::ItemDataRole::BackgroundRole: { + if (m_filteringDecisions.contains(index.row())) { + switch (m_filteringDecisions.value(index.row())) { + case MessageObject::FilteringAction::Accept: + return qApp->skins()->currentSkin().m_colorPalette[Skin::PaletteColors::Allright]; + + case MessageObject::FilteringAction::Ignore: + return qApp->skins()->currentSkin().m_colorPalette[Skin::PaletteColors::Error]; + } + } + + break; + } + + case Qt::ItemDataRole::DisplayRole: { + switch (index.column()) { + case MFM_MODEL_ISREAD: + return msg.m_isRead; + + case MFM_MODEL_ISIMPORTANT: + return msg.m_isImportant; + + case MFM_MODEL_TITLE: + return msg.m_title; + + case MFM_MODEL_URL: + return msg.m_url; + + case MFM_MODEL_AUTHOR: + return msg.m_author; + + case MFM_MODEL_CREATED: + return msg.m_created; + } + + break; + } + } + + return QVariant(); +} + +QVariant MessagesForFiltersModel::headerData(int section, Qt::Orientation orientation, int role) const { + Q_UNUSED(orientation) + + switch (role) { + case Qt::ItemDataRole::DisplayRole: + if (section >=0 && section < m_headerData.size()) { + return m_headerData.at(section); + } + + break; + } + + return QVariant(); +} + +Qt::ItemFlags MessagesForFiltersModel::flags(const QModelIndex& index) const { + Q_UNUSED(index) + return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable; +} + +int MessagesForFiltersModel::messagesCount() const { + return m_messages.size(); +} + +void MessagesForFiltersModel::testFilter(MessageFilter* filter, QJSEngine* engine, MessageObject* msg_proxy) { + m_filteringDecisions.clear(); + + for (int i = 0; i < m_messages.size(); i++) { + Message* msg = messageForRow(i); + + msg_proxy->setMessage(msg); + + try { + MessageObject::FilteringAction decision = filter->filterMessage(engine); + + m_filteringDecisions.insert(i, decision); + } + catch (const FilteringException& ex) { + throw ex; + } + } + + emit layoutAboutToBeChanged(); + emit layoutChanged(); +} + +Message* MessagesForFiltersModel::messageForRow(int row) { + return &m_messages[row]; +} + +Message MessagesForFiltersModel::messageForRow(int row) const { + return m_messages[row]; +} diff --git a/src/librssguard/core/messagesforfiltersmodel.h b/src/librssguard/core/messagesforfiltersmodel.h new file mode 100755 index 000000000..9945bad55 --- /dev/null +++ b/src/librssguard/core/messagesforfiltersmodel.h @@ -0,0 +1,44 @@ +// For license of this file, see /LICENSE.md. + +#ifndef MESSAGESFORFILTERSMODEL_H +#define MESSAGESFORFILTERSMODEL_H + +#include + +#include "core/messageobject.h" + +#include + +class MessageFilter; + +class MessagesForFiltersModel : public QAbstractTableModel { + Q_OBJECT + + public: + explicit MessagesForFiltersModel(QObject* parent = nullptr); + + virtual int rowCount(const QModelIndex& parent) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual QVariant data(const QModelIndex& index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + + public: + int messagesCount() const; + void testFilter(MessageFilter* filter, QJSEngine* engine, MessageObject* msg_proxy); + + Message messageForRow(int row) const; + Message* messageForRow(int row); + + public slots: + void setMessages(const QList& messages); + + private: + QList m_headerData{}; + QList m_messages{}; + + // Key is integer position of the message within the list of messages. + QMap m_filteringDecisions{}; +}; + +#endif // MESSAGESFORFILTERSMODEL_H diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index ecd9efb47..ce386eb55 100755 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -218,6 +218,14 @@ #define FDS_MODEL_TITLE_INDEX 0 #define FDS_MODEL_COUNTS_INDEX 1 +// Indexes of columns for message filter manager models. +#define MFM_MODEL_ISREAD 0 +#define MFM_MODEL_ISIMPORTANT 1 +#define MFM_MODEL_TITLE 2 +#define MFM_MODEL_URL 3 +#define MFM_MODEL_AUTHOR 4 +#define MFM_MODEL_CREATED 5 + #if defined(Q_OS_LINUX) #define OS_ID "Linux" #elif defined(Q_OS_OSX) diff --git a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp index 18cbf1bb4..85c869e2e 100644 --- a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp +++ b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp @@ -1,12 +1,13 @@ +// For license of this file, see /LICENSE.md. + #include #include #include -// For license of this file, see /LICENSE.md. - #include "gui/dialogs/formmessagefiltersmanager.h" #include "core/messagefilter.h" +#include "core/messagesforfiltersmodel.h" #include "exceptions/filteringexception.h" #include "gui/guiutilities.h" #include "gui/messagebox.h" @@ -19,9 +20,15 @@ FormMessageFiltersManager::FormMessageFiltersManager(FeedReader* reader, const QList& accounts, QWidget* parent) : QDialog(parent), m_feedsModel(new AccountCheckSortedModel(this)), m_rootItem(new RootItem()), - m_accounts(accounts), m_reader(reader), m_loadingFilter(false) { + m_accounts(accounts), m_reader(reader), m_loadingFilter(false), m_msgModel(new MessagesForFiltersModel(this)) { m_ui.setupUi(this); + std::sort(m_accounts.begin(), m_accounts.end(), [](const ServiceRoot* lhs, const ServiceRoot* rhs) { + return lhs->title().compare(rhs->title(), Qt::CaseSensitivity::CaseInsensitive) < 0; + }); + + m_ui.m_treeExistingMessages->setModel(m_msgModel); + GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("view-list-details"))); m_ui.m_treeFeeds->setIndentation(FEEDS_VIEW_INDENTATION); @@ -32,9 +39,17 @@ FormMessageFiltersManager::FormMessageFiltersManager(FeedReader* reader, const Q m_ui.m_btnRemoveSelected->setIcon(qApp->icons()->fromTheme(QSL("list-remove"))); m_ui.m_btnBeautify->setIcon(qApp->icons()->fromTheme(QSL("format-justify-fill"))); m_ui.m_btnTest->setIcon(qApp->icons()->fromTheme(QSL("media-playback-start"))); + m_ui.m_btnRunOnMessages->setIcon(qApp->icons()->fromTheme(QSL("media-playback-start"))); m_ui.m_btnDetailedHelp->setIcon(qApp->icons()->fromTheme(QSL("help-contents"))); m_ui.m_txtScript->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont)); + m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_ISREAD, QHeaderView::ResizeMode::ResizeToContents); + m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_ISIMPORTANT, QHeaderView::ResizeMode::ResizeToContents); + m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_AUTHOR, QHeaderView::ResizeMode::ResizeToContents); + m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_CREATED, QHeaderView::ResizeMode::ResizeToContents); + m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_TITLE, QHeaderView::ResizeMode::Interactive); + m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_URL, QHeaderView::ResizeMode::Interactive); + connect(m_ui.m_btnDetailedHelp, &QPushButton::clicked, this, []() { qApp->web()->openUrlInExternalBrowser(MSG_FILTERING_HELP); }); @@ -55,6 +70,8 @@ FormMessageFiltersManager::FormMessageFiltersManager(FeedReader* reader, const Q connect(m_ui.m_btnUncheckAll, &QPushButton::clicked, m_feedsModel->sourceModel(), &AccountCheckModel::uncheckAllItems); connect(m_feedsModel->sourceModel(), &AccountCheckModel::checkStateChanged, this, &FormMessageFiltersManager::onFeedChecked); + connect(m_ui.m_treeFeeds->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &FormMessageFiltersManager::displayMessagesOfFeed); initializeTestingMessage(); loadFilters(); @@ -104,7 +121,7 @@ void FormMessageFiltersManager::addNewFilter() { try { auto* fltr = m_reader->addMessageFilter( tr("New message filter"), - QSL("function filterMessage() { return 1; }")); + QSL("function filterMessage() { return MessageObject.Accept; }")); auto* it = new QListWidgetItem(fltr->name(), m_ui.m_listFilters); it->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(fltr)); @@ -145,6 +162,8 @@ void FormMessageFiltersManager::loadFilter() { } void FormMessageFiltersManager::testFilter() { + m_ui.m_txtErrors->clear(); + // Perform per-message filtering. QJSEngine filter_engine; QSqlDatabase database = qApp->database()->connection(metaObject()->className()); @@ -155,10 +174,24 @@ void FormMessageFiltersManager::testFilter() { : NO_PARENT_CATEGORY, {}); auto* fltr = selectedFilter(); - Message msg = testingMessage(); MessageFilter::initializeFilteringEngine(filter_engine, &msg_obj); + // Test real messages. + try { + m_msgModel->testFilter(fltr, &filter_engine, &msg_obj); + } + catch (const FilteringException& ex) { + m_ui.m_txtErrors->setTextColor(Qt::GlobalColor::red); + m_ui.m_txtErrors->insertPlainText(tr("EXISTING messages filtering error: '%1'.\n").arg(ex.message())); + + // See output. + m_ui.m_twMessages->setCurrentIndex(2); + } + + // Test sample message. + Message msg = testingMessage(); + msg_obj.setMessage(&msg); try { @@ -182,19 +215,37 @@ void FormMessageFiltersManager::testFilter() { QString::number(msg.m_created.toMSecsSinceEpoch()), msg.m_contents); - m_ui.m_txtErrors->setPlainText(answer); + m_ui.m_txtErrors->insertPlainText(answer); } catch (const FilteringException& ex) { m_ui.m_txtErrors->setTextColor(Qt::GlobalColor::red); - m_ui.m_txtErrors->setPlainText(tr("JavaScript-based filter contains errors: '%1'.").arg(ex.message())); - } + m_ui.m_txtErrors->insertPlainText(tr("SAMPLE message filtering error: '%1'.\n").arg(ex.message())); - // See output. - m_ui.m_tcMessage->setCurrentIndex(1); + // See output. + m_ui.m_twMessages->setCurrentIndex(2); + } +} + +void FormMessageFiltersManager::displayMessagesOfFeed() { + auto* item = m_feedsModel->sourceModel()->itemForIndex(m_feedsModel->mapToSource(m_ui.m_treeFeeds->currentIndex())); + + if (item != nullptr) { + m_msgModel->setMessages(item->undeletedMessages()); + } + else { + m_msgModel->setMessages({}); + } } void FormMessageFiltersManager::loadAccount(ServiceRoot* account) { m_feedsModel->setRootItem(account, false, true); + + if (account != nullptr) { + m_msgModel->setMessages(account->undeletedMessages()); + } + else { + m_msgModel->setMessages({}); + } } void FormMessageFiltersManager::loadFilterFeedAssignments(MessageFilter* filter, ServiceRoot* account) { @@ -233,6 +284,7 @@ void FormMessageFiltersManager::onFeedChecked(RootItem* item, Qt::CheckState sta return; } + // Update feed/filter assignemnts. switch (state) { case Qt::CheckState::Checked: m_reader->assignMessageFilterToFeed(feed, selectedFilter()); @@ -272,7 +324,7 @@ void FormMessageFiltersManager::showFilter(MessageFilter* filter) { } // See message. - m_ui.m_tcMessage->setCurrentIndex(0); + m_ui.m_twMessages->setCurrentIndex(0); m_loadingFilter = false; } @@ -298,7 +350,7 @@ void FormMessageFiltersManager::beautifyScript() { proc_clang_format.setProgram(QSL("clang-format")); #endif - if (!proc_clang_format.open()) { + if (!proc_clang_format.open() || proc_clang_format.error() == QProcess::ProcessError::FailedToStart) { MessageBox::show(this, QMessageBox::Icon::Critical, tr("Cannot find 'clang-format'"), tr("Script was not beautified, because 'clang-format' tool was not found.")); diff --git a/src/librssguard/gui/dialogs/formmessagefiltersmanager.h b/src/librssguard/gui/dialogs/formmessagefiltersmanager.h index a2ca53646..e97e6bce3 100644 --- a/src/librssguard/gui/dialogs/formmessagefiltersmanager.h +++ b/src/librssguard/gui/dialogs/formmessagefiltersmanager.h @@ -12,6 +12,7 @@ class AccountCheckSortedModel; class MessageFilter; class FeedReader; +class MessagesForFiltersModel; class FormMessageFiltersManager : public QDialog { Q_OBJECT @@ -30,6 +31,7 @@ class FormMessageFiltersManager : public QDialog { void loadFilter(); void loadFilters(); void testFilter(); + void displayMessagesOfFeed(); // Load feeds/categories tree. void loadAccount(ServiceRoot* account); @@ -56,6 +58,7 @@ class FormMessageFiltersManager : public QDialog { QList m_accounts; FeedReader* m_reader; bool m_loadingFilter; + MessagesForFiltersModel* m_msgModel; }; #endif // FORMMESSAGEFILTERSMANAGER_H diff --git a/src/librssguard/gui/dialogs/formmessagefiltersmanager.ui b/src/librssguard/gui/dialogs/formmessagefiltersmanager.ui index 816e54d1f..40b0bfaa7 100644 --- a/src/librssguard/gui/dialogs/formmessagefiltersmanager.ui +++ b/src/librssguard/gui/dialogs/formmessagefiltersmanager.ui @@ -6,386 +6,407 @@ 0 0 - 845 - 644 + 1229 + 546 Message filters - - - - - - - Remove selected - - - - - - - Add new - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - - - - - - - - - Account - - - m_cmbAccounts - - - - - - - - - - - - - 0 - 150 - - - - true - - - false - - - - - - + + + + + Qt::Horizontal + + + 1 + + + false + + + + + + &Check all - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + false + + + + &Uncheck all - - + + + + + + + + + + + Remove selected + + + + + + + &New filter + + + + + - Qt::Vertical + Qt::Horizontal - 20 - 40 + 40 + 20 + + + + true + + + + + + + Message filter details + + + + + + Title + + + m_txtTitle + + + + + + + + 0 + 0 + + + + + 300 + 16777215 + + + + Title of message filter + + + + + + + JavaScript code + + + m_txtScript + + + + + + + + 0 + 1 + + + + Your JavaScript-based message filtering logic + + + + + + + + + &Test + + + + + + + Process checked feeds + + + + + + + &Beautify + + + + + + + Detailed &help + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + - - - - - - - Message filter details - - - - - - Title + + + + + 1 + 0 + + + + 1 + + + + Existing messages + + + + 0 - - m_txtTitle + + 0 - - - - - - - - - 0 - 0 - - - - - 300 - 16777215 - - - - Title of message filter - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - - JavaScript code + + 0 - - m_txtScript + + 0 - - - - - - - Your JavaScript-based message filtering logic - - - - - - + + 0 - - - Sample message - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - - Read - - - - - - - Important - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Title - - - m_txtSampleTitle - - - - - - - - - - URL - - - m_txtSampleUrl - - - - - - - - - - Author - - - m_txtSampleAuthor - - - - - - - - - - Created on - - - m_txtSampleCreatedOn - - - - - - - - - - Contents - - - m_txtSampleContents - - - - - - - - - - - Script output - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 0 - 1 - - - - - - + + true + + + false + - - - - - + + + + Sample message + + + + 0 + + + 0 + + + 0 + + + 0 + + + - &Test! + Title + + + m_txtSampleTitle - - + + + + + - &Beautify! + URL + + + m_txtSampleUrl - - + + + + + - Detailed &help + Author + + + m_txtSampleAuthor - - - - Qt::Horizontal + + + + + + + Created on - - - 40 - 20 - + + m_txtSampleCreatedOn - + + + + + + + + + Contents + + + m_txtSampleContents + + + + + + + + + + + + Read + + + + + + + Important + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - + + + + Script output + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 1 + + + + + + + - + Qt::Horizontal @@ -411,17 +432,11 @@ m_txtTitle m_txtScript m_btnTest + m_btnRunOnMessages m_btnBeautify m_btnDetailedHelp - m_tcMessage - m_cbSampleRead - m_cbSampleImportant - m_txtSampleTitle - m_txtSampleUrl - m_txtSampleAuthor - m_txtSampleCreatedOn - m_txtSampleContents - m_txtErrors + m_twMessages + m_treeExistingMessages diff --git a/src/librssguard/librssguard.pro b/src/librssguard/librssguard.pro index e0de68e54..b3752738e 100644 --- a/src/librssguard/librssguard.pro +++ b/src/librssguard/librssguard.pro @@ -38,6 +38,7 @@ HEADERS += core/feeddownloader.h \ core/message.h \ core/messagefilter.h \ core/messageobject.h \ + core/messagesforfiltersmodel.h \ core/messagesmodel.h \ core/messagesmodelcache.h \ core/messagesmodelsqllayer.h \ @@ -195,6 +196,7 @@ SOURCES += core/feeddownloader.cpp \ core/message.cpp \ core/messagefilter.cpp \ core/messageobject.cpp \ + core/messagesforfiltersmodel.cpp \ core/messagesmodel.cpp \ core/messagesmodelcache.cpp \ core/messagesmodelsqllayer.cpp \ diff --git a/src/librssguard/miscellaneous/skinfactory.h b/src/librssguard/miscellaneous/skinfactory.h index 0c174c412..9c6b7073a 100644 --- a/src/librssguard/miscellaneous/skinfactory.h +++ b/src/librssguard/miscellaneous/skinfactory.h @@ -13,7 +13,8 @@ struct RSSGUARD_DLLSPEC Skin { enum class PaletteColors { Highlight = 1, - Error = 2 + Error = 2, + Allright = 3 }; QString m_baseName;