working on persistent regexps + multiple of other fixes and small enhancements

This commit is contained in:
Martin Rotter 2023-07-18 06:16:01 +02:00
parent 2e2aba943c
commit 59b80f784b
27 changed files with 741 additions and 33 deletions

View File

@ -63,8 +63,6 @@ set(SOURCES
gui/dialogs/formabout.h gui/dialogs/formabout.h
gui/dialogs/formaddaccount.cpp gui/dialogs/formaddaccount.cpp
gui/dialogs/formaddaccount.h gui/dialogs/formaddaccount.h
gui/dialogs/formaddeditlabel.cpp
gui/dialogs/formaddeditlabel.h
gui/dialogs/formbackupdatabasesettings.cpp gui/dialogs/formbackupdatabasesettings.cpp
gui/dialogs/formbackupdatabasesettings.h gui/dialogs/formbackupdatabasesettings.h
gui/dialogs/formdatabasecleanup.cpp gui/dialogs/formdatabasecleanup.cpp
@ -262,6 +260,10 @@ set(SOURCES
services/abstract/gui/formcategorydetails.h services/abstract/gui/formcategorydetails.h
services/abstract/gui/formfeeddetails.cpp services/abstract/gui/formfeeddetails.cpp
services/abstract/gui/formfeeddetails.h 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.h
services/abstract/gui/custommessagepreviewer.cpp services/abstract/gui/custommessagepreviewer.cpp
services/abstract/importantnode.cpp services/abstract/importantnode.cpp
@ -270,6 +272,10 @@ set(SOURCES
services/abstract/label.h services/abstract/label.h
services/abstract/labelsnode.cpp services/abstract/labelsnode.cpp
services/abstract/labelsnode.h 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.cpp
services/abstract/recyclebin.h services/abstract/recyclebin.h
services/abstract/rootitem.cpp services/abstract/rootitem.cpp
@ -427,7 +433,6 @@ set(SOURCES
set(UI_FILES set(UI_FILES
gui/dialogs/formabout.ui gui/dialogs/formabout.ui
gui/dialogs/formaddaccount.ui gui/dialogs/formaddaccount.ui
gui/dialogs/formaddeditlabel.ui
gui/dialogs/formbackupdatabasesettings.ui gui/dialogs/formbackupdatabasesettings.ui
gui/dialogs/formdatabasecleanup.ui gui/dialogs/formdatabasecleanup.ui
gui/dialogs/formmain.ui gui/dialogs/formmain.ui
@ -460,6 +465,8 @@ set(UI_FILES
services/abstract/gui/formaccountdetails.ui services/abstract/gui/formaccountdetails.ui
services/abstract/gui/formcategorydetails.ui services/abstract/gui/formcategorydetails.ui
services/abstract/gui/formfeeddetails.ui services/abstract/gui/formfeeddetails.ui
services/abstract/gui/formaddeditlabel.ui
services/abstract/gui/formaddeditprobe.ui
services/feedly/gui/feedlyaccountdetails.ui services/feedly/gui/feedlyaccountdetails.ui
services/gmail/gui/formaddeditemail.ui services/gmail/gui/formaddeditemail.ui
services/gmail/gui/gmailaccountdetails.ui services/gmail/gui/gmailaccountdetails.ui

View File

@ -7,6 +7,7 @@
#include "database/databasequeries.h" #include "database/databasequeries.h"
#include "definitions/definitions.h" #include "definitions/definitions.h"
#include "gui/dialogs/formmain.h" #include "gui/dialogs/formmain.h"
#include "gui/messagebox.h"
#include "miscellaneous/feedreader.h" #include "miscellaneous/feedreader.h"
#include "miscellaneous/iconfactory.h" #include "miscellaneous/iconfactory.h"
#include "miscellaneous/textfactory.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) { bool FeedsModel::markItemCleared(RootItem* item, bool clean_read_only) {
if (item != nullptr) { 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); return item->cleanMessages(clean_read_only);
} }

View File

@ -36,6 +36,7 @@ FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent)
m_priorities = {RootItem::Kind::Category, m_priorities = {RootItem::Kind::Category,
RootItem::Kind::Feed, RootItem::Kind::Feed,
RootItem::Kind::Labels, RootItem::Kind::Labels,
RootItem::Kind::Probes,
RootItem::Kind::Important, RootItem::Kind::Important,
RootItem::Kind::Unread, RootItem::Kind::Unread,
RootItem::Kind::Bin}; RootItem::Kind::Bin};

View File

@ -115,14 +115,14 @@ QSqlDatabase SqliteDriver::connection(const QString& connection_name, DesiredSto
database = QSqlDatabase::addDatabase(QSL(APP_DB_SQLITE_DRIVER), connection_name); database = QSqlDatabase::addDatabase(QSL(APP_DB_SQLITE_DRIVER), connection_name);
if (want_in_memory) { 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:")); database.setDatabaseName(QSL("file::memory:"));
} }
else { else {
const QDir db_path(m_databaseFilePath); const QDir db_path(m_databaseFilePath);
QFile db_file(db_path.absoluteFilePath(QSL(APP_DB_SQLITE_FILE))); 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()); 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); database = QSqlDatabase::addDatabase(QSL(APP_DB_SQLITE_DRIVER), connection_name);
if (in_memory) { 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 { else {
database.setConnectOptions(QSL("QSQLITE_ENABLE_SHARED_CACHE")); database.setConnectOptions(QSL("QSQLITE_ENABLE_SHARED_CACHE;QSQLITE_ENABLE_REGEXP"));
} }
database.setDatabaseName(db_file_name); database.setDatabaseName(db_file_name);

View File

@ -44,6 +44,7 @@
#define ID_IMPORTANT -3 #define ID_IMPORTANT -3
#define ID_LABELS -4 #define ID_LABELS -4
#define ID_UNREAD -5 #define ID_UNREAD -5
#define ID_PROBES -6
#define MSG_SCORE_MAX 100.0 #define MSG_SCORE_MAX 100.0
#define MSG_SCORE_MIN 0.0 #define MSG_SCORE_MIN 0.0

View File

@ -30,7 +30,8 @@
FeedsView::FeedsView(QWidget* parent) FeedsView::FeedsView(QWidget* parent)
: BaseTreeView(parent), m_contextMenuService(nullptr), m_contextMenuBin(nullptr), m_contextMenuCategories(nullptr), : BaseTreeView(parent), m_contextMenuService(nullptr), m_contextMenuBin(nullptr), m_contextMenuCategories(nullptr),
m_contextMenuFeeds(nullptr), m_contextMenuImportant(nullptr), m_contextMenuEmptySpace(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")); setObjectName(QSL("FeedsView"));
// Allocate models. // Allocate models.
@ -852,20 +853,42 @@ QMenu* FeedsView::initializeContextMenuLabel(RootItem* clicked_item) {
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList(); QList<QAction*> 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()) { if (!specific_actions.isEmpty()) {
m_contextMenuLabel->addSeparator(); m_contextMenuLabel->addSeparator();
m_contextMenuLabel->addActions(specific_actions); 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; 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<QAction*> 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() { void FeedsView::setupAppearance() {
// Setup column resize strategies. // Setup column resize strategies.
header()->setSectionResizeMode(FDS_MODEL_TITLE_INDEX, QHeaderView::ResizeMode::Stretch); 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) { else if (clicked_item->kind() == RootItem::Kind::Label) {
initializeContextMenuLabel(clicked_item)->exec(event->globalPos()); initializeContextMenuLabel(clicked_item)->exec(event->globalPos());
} }
else if (clicked_item->kind() == RootItem::Kind::Probe) {
initializeContextMenuProbe(clicked_item)->exec(event->globalPos());
}
else { else {
initializeContextMenuOtherItem(clicked_item)->exec(event->globalPos()); initializeContextMenuOtherItem(clicked_item)->exec(event->globalPos());
} }

View File

@ -129,6 +129,7 @@ class RSSGUARD_DLLSPEC FeedsView : public BaseTreeView {
QMenu* initializeContextMenuEmptySpace(); QMenu* initializeContextMenuEmptySpace();
QMenu* initializeContextMenuOtherItem(RootItem* clicked_item); QMenu* initializeContextMenuOtherItem(RootItem* clicked_item);
QMenu* initializeContextMenuLabel(RootItem* clicked_item); QMenu* initializeContextMenuLabel(RootItem* clicked_item);
QMenu* initializeContextMenuProbe(RootItem* clicked_item);
void setupAppearance(); void setupAppearance();
void saveExpandStates(RootItem* item); void saveExpandStates(RootItem* item);
@ -141,13 +142,14 @@ class RSSGUARD_DLLSPEC FeedsView : public BaseTreeView {
QMenu* m_contextMenuEmptySpace; QMenu* m_contextMenuEmptySpace;
QMenu* m_contextMenuOtherItems; QMenu* m_contextMenuOtherItems;
QMenu* m_contextMenuLabel; QMenu* m_contextMenuLabel;
QMenu* m_contextMenuProbe;
FeedsModel* m_sourceModel; FeedsModel* m_sourceModel;
FeedsProxyModel* m_proxyModel; FeedsProxyModel* m_proxyModel;
bool m_dontSaveExpandState; bool m_dontSaveExpandState;
// QTreeView interface // QTreeView interface
protected: 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 { inline FeedsProxyModel* FeedsView::model() const {

View File

@ -35,6 +35,14 @@ SettingsFeedsMessages::SettingsFeedsMessages(Settings* settings, QWidget* parent
"performance of article list with big number of articles."), "performance of article list with big number of articles."),
true); true);
QMetaEnum enumer = QMetaEnum::fromType<MessagesModel::MessageUnreadIcon>();
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_cbShowEnclosuresDirectly, &QCheckBox::toggled, this, &SettingsFeedsMessages::dirtifySettings);
connect(m_ui->m_spinHeightImageAttachments, connect(m_ui->m_spinHeightImageAttachments,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
@ -190,14 +198,6 @@ SettingsFeedsMessages::SettingsFeedsMessages(Settings* settings, QWidget* parent
} }
m_ui->m_spinRelativeArticleTime->setValue(-1); m_ui->m_spinRelativeArticleTime->setValue(-1);
QMetaEnum enumer = QMetaEnum::fromType<MessagesModel::MessageUnreadIcon>();
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() { SettingsFeedsMessages::~SettingsFeedsMessages() {

View File

@ -291,7 +291,12 @@ PreparedHtml SkinFactory::generateHtmlOfArticles(const QList<Message>& messages,
QUrl url(NetworkFactory::sanitizeUrl(feed->source())); QUrl url(NetworkFactory::sanitizeUrl(feed->source()));
if (url.isValid()) { 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();
}
} }
} }

View File

@ -1,6 +1,6 @@
// For license of this file, see <project-root-folder>/LICENSE.md. // For license of this file, see <project-root-folder>/LICENSE.md.
#include "gui/dialogs/formaddeditlabel.h" #include "services/abstract/gui/formaddeditlabel.h"
#include "gui/guiutilities.h" #include "gui/guiutilities.h"
#include "miscellaneous/application.h" #include "miscellaneous/application.h"

View File

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

View File

@ -0,0 +1,31 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef FORMADDEDITPROBE_H
#define FORMADDEDITPROBE_H
#include <QDialog>
#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

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FormAddEditProbe</class>
<widget class="QDialog" name="FormAddEditProbe">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>270</width>
<height>180</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="ColorToolButton" name="m_btnColor">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LineEditWithStatus" name="m_txtName" native="true"/>
</item>
<item row="1" column="1">
<widget class="LineEditWithStatus" name="m_txtFilter" native="true"/>
</item>
<item row="2" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="m_buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LineEditWithStatus</class>
<extends>QWidget</extends>
<header>lineeditwithstatus.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ColorToolButton</class>
<extends>QToolButton</extends>
<header>colortoolbutton.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>m_btnColor</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>m_buttonBox</sender>
<signal>accepted()</signal>
<receiver>FormAddEditProbe</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>214</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>141</y>
</hint>
</hints>
</connection>
<connection>
<sender>m_buttonBox</sender>
<signal>rejected()</signal>
<receiver>FormAddEditProbe</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>214</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>223</x>
<y>141</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -4,9 +4,9 @@
#include "database/databasefactory.h" #include "database/databasefactory.h"
#include "database/databasequeries.h" #include "database/databasequeries.h"
#include "gui/dialogs/formaddeditlabel.h"
#include "miscellaneous/application.h" #include "miscellaneous/application.h"
#include "services/abstract/cacheforserviceroot.h" #include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/gui/formaddeditlabel.h"
#include "services/abstract/labelsnode.h" #include "services/abstract/labelsnode.h"
#include "services/abstract/serviceroot.h" #include "services/abstract/serviceroot.h"

View File

@ -5,9 +5,9 @@
#include "database/databasefactory.h" #include "database/databasefactory.h"
#include "database/databasequeries.h" #include "database/databasequeries.h"
#include "exceptions/applicationexception.h" #include "exceptions/applicationexception.h"
#include "gui/dialogs/formaddeditlabel.h"
#include "miscellaneous/application.h" #include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h" #include "miscellaneous/iconfactory.h"
#include "services/abstract/gui/formaddeditlabel.h"
#include "services/abstract/serviceroot.h" #include "services/abstract/serviceroot.h"
#include "3rd-party/boolinq/boolinq.h" #include "3rd-party/boolinq/boolinq.h"

View File

@ -3,6 +3,7 @@
#include "services/abstract/recyclebin.h" #include "services/abstract/recyclebin.h"
#include "database/databasequeries.h" #include "database/databasequeries.h"
#include "gui/messagebox.h"
#include "miscellaneous/application.h" #include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h" #include "miscellaneous/iconfactory.h"
#include "miscellaneous/textfactory.h" #include "miscellaneous/textfactory.h"
@ -92,7 +93,6 @@ bool RecycleBin::cleanMessages(bool clear_only_read) {
parent_root->itemChanged(QList<RootItem*>() << this); parent_root->itemChanged(QList<RootItem*>() << this);
parent_root->requestReloadMessageList(true); parent_root->requestReloadMessageList(true);
return true; return true;
;
} }
else { else {
return false; return false;
@ -100,6 +100,17 @@ bool RecycleBin::cleanMessages(bool clear_only_read) {
} }
bool RecycleBin::empty() { 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); return cleanMessages(false);
} }

View File

@ -9,6 +9,7 @@
#include "services/abstract/feed.h" #include "services/abstract/feed.h"
#include "services/abstract/label.h" #include "services/abstract/label.h"
#include "services/abstract/recyclebin.h" #include "services/abstract/recyclebin.h"
#include "services/abstract/search.h"
#include "services/abstract/serviceroot.h" #include "services/abstract/serviceroot.h"
#include <QVariant> #include <QVariant>
@ -570,6 +571,10 @@ Label* RootItem::toLabel() const {
return qobject_cast<Label*>(const_cast<RootItem*>(this)); return qobject_cast<Label*>(const_cast<RootItem*>(this));
} }
Search* RootItem::toProbe() const {
return qobject_cast<Search*>(const_cast<RootItem*>(this));
}
ServiceRoot* RootItem::toServiceRoot() const { ServiceRoot* RootItem::toServiceRoot() const {
return qobject_cast<ServiceRoot*>(const_cast<RootItem*>(this)); return qobject_cast<ServiceRoot*>(const_cast<RootItem*>(this));
} }

View File

@ -12,6 +12,7 @@
class Category; class Category;
class Feed; class Feed;
class Label; class Label;
class Search;
class ServiceRoot; class ServiceRoot;
class QAction; class QAction;
@ -43,7 +44,9 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
Labels = 32, Labels = 32,
Important = 64, Important = 64,
Label = 128, Label = 128,
Unread = 256 Unread = 256,
Probes = 512,
Probe = 1024
}; };
// Constructors and destructors. // Constructors and destructors.
@ -196,6 +199,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
Category* toCategory() const; Category* toCategory() const;
Feed* toFeed() const; Feed* toFeed() const;
Label* toLabel() const; Label* toLabel() const;
Search* toProbe() const;
ServiceRoot* toServiceRoot() const; ServiceRoot* toServiceRoot() const;
bool keepOnTop() const; bool keepOnTop() const;

View File

@ -0,0 +1,178 @@
// For license of this file, see <project-root-folder>/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 <QPainter>
#include <QPainterPath>
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<Message> 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<CacheForServiceRoot*>(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;
}

View File

@ -0,0 +1,50 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef SEARCH_H
#define SEARCH_H
#include "services/abstract/rootitem.h"
#include <QColor>
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<Message> 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

View File

@ -0,0 +1,139 @@
// For license of this file, see <project-root-folder>/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<Search*>& probes) {
for (auto* prb : probes) {
appendChild(prb);
}
}
QList<Message> 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<Search*>(boolinq::from(chi).firstOrDefault([custom_id](RootItem* it) {
return it->customId() == custom_id;
}));
}
QList<Search*> SearchsNode::probes() const {
auto list = boolinq::from(childItems())
.select([](RootItem* it) {
return static_cast<Search*>(it);
})
.toStdList();
return FROM_STD_LIST(QList<Search*>, list);
}
QList<QAction*> 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<QAction*>{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();
}
}
}

View File

@ -0,0 +1,34 @@
// For license of this file, see <project-root-folder>/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<Search*> probes() const;
void loadProbes(const QList<Search*>& probes);
virtual QList<Message> undeletedMessages() const;
virtual QList<QAction*> 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

View File

@ -17,17 +17,19 @@
#include "services/abstract/importantnode.h" #include "services/abstract/importantnode.h"
#include "services/abstract/labelsnode.h" #include "services/abstract/labelsnode.h"
#include "services/abstract/recyclebin.h" #include "services/abstract/recyclebin.h"
#include "services/abstract/search.h"
#include "services/abstract/searchsnode.h"
#include "services/abstract/unreadnode.h" #include "services/abstract/unreadnode.h"
ServiceRoot::ServiceRoot(RootItem* parent) ServiceRoot::ServiceRoot(RootItem* parent)
: RootItem(parent), m_recycleBin(new RecycleBin(this)), m_importantNode(new ImportantNode(this)), : 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_labelsNode(new LabelsNode(this)), m_probesNode(new SearchsNode(this)), m_unreadNode(new UnreadNode(this)),
m_networkProxy(QNetworkProxy()) { m_accountId(NO_PARENT_CATEGORY), m_networkProxy(QNetworkProxy()) {
setKind(RootItem::Kind::ServiceRoot); setKind(RootItem::Kind::ServiceRoot);
appendCommonNodes(); appendCommonNodes();
} }
ServiceRoot::~ServiceRoot() = default; ServiceRoot::~ServiceRoot() {}
bool ServiceRoot::deleteViaGui() { bool ServiceRoot::deleteViaGui() {
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
@ -242,6 +244,10 @@ void ServiceRoot::appendCommonNodes() {
if (labelsNode() != nullptr && !childItems().contains(labelsNode())) { if (labelsNode() != nullptr && !childItems().contains(labelsNode())) {
appendChild(labelsNode()); appendChild(labelsNode());
} }
if (probesNode() != nullptr && !childItems().contains(probesNode())) {
appendChild(probesNode());
}
} }
bool ServiceRoot::cleanFeeds(const QList<Feed*>& items, bool clean_read_only) { bool ServiceRoot::cleanFeeds(const QList<Feed*>& items, bool clean_read_only) {
@ -455,6 +461,10 @@ LabelsNode* ServiceRoot::labelsNode() const {
return m_labelsNode; return m_labelsNode;
} }
SearchsNode* ServiceRoot::probesNode() const {
return m_probesNode;
}
UnreadNode* ServiceRoot::unreadNode() const { UnreadNode* ServiceRoot::unreadNode() const {
return m_unreadNode; return m_unreadNode;
} }
@ -715,6 +725,11 @@ bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) {
"Messages.account_id = %1") "Messages.account_id = %1")
.arg(QString::number(accountId()))); .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) { else if (item->kind() == RootItem::Kind::Label) {
// Show messages with particular label. // Show messages with particular label.
model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND " model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND "

View File

@ -19,6 +19,7 @@ class FeedsModel;
class RecycleBin; class RecycleBin;
class ImportantNode; class ImportantNode;
class UnreadNode; class UnreadNode;
class SearchsNode;
class LabelsNode; class LabelsNode;
class Label; class Label;
class MessagesModel; class MessagesModel;
@ -52,6 +53,7 @@ class ServiceRoot : public RootItem {
RecycleBin* recycleBin() const; RecycleBin* recycleBin() const;
ImportantNode* importantNode() const; ImportantNode* importantNode() const;
LabelsNode* labelsNode() const; LabelsNode* labelsNode() const;
SearchsNode* probesNode() const;
UnreadNode* unreadNode() const; UnreadNode* unreadNode() const;
virtual void updateCounts(bool including_total_count); virtual void updateCounts(bool including_total_count);
@ -297,6 +299,7 @@ class ServiceRoot : public RootItem {
RecycleBin* m_recycleBin; RecycleBin* m_recycleBin;
ImportantNode* m_importantNode; ImportantNode* m_importantNode;
LabelsNode* m_labelsNode; LabelsNode* m_labelsNode;
SearchsNode* m_probesNode;
UnreadNode* m_unreadNode; UnreadNode* m_unreadNode;
int m_accountId; int m_accountId;
QList<QAction*> m_serviceMenu; QList<QAction*> m_serviceMenu;

View File

@ -44,7 +44,7 @@ void TtRssServiceRoot::start(bool freshly_activated) {
DatabaseQueries::loadRootFromDatabase<Category, TtRssFeed>(this); DatabaseQueries::loadRootFromDatabase<Category, TtRssFeed>(this);
loadCacheFromFile(); loadCacheFromFile();
auto lbls = m_labelsNode->labels(); auto lbls = labelsNode()->labels();
boolinq::from(lbls).for_each([](Label* lbl) { boolinq::from(lbls).for_each([](Label* lbl) {
if (lbl->customNumericId() == TTRSS_PUBLISHED_LABEL_ID) { if (lbl->customNumericId() == TTRSS_PUBLISHED_LABEL_ID) {