better article list filtering

This commit is contained in:
Martin Rotter 2023-02-10 14:04:42 +01:00
parent 0d9521a33c
commit ac8629c751
11 changed files with 235 additions and 36 deletions

View file

@ -131,6 +131,8 @@ set(SOURCES
gui/reusable/searchtextwidget.h
gui/reusable/squeezelabel.cpp
gui/reusable/squeezelabel.h
gui/reusable/searchlineedit.cpp
gui/reusable/searchlineedit.h
gui/reusable/styleditemdelegatewithoutfocus.cpp
gui/reusable/styleditemdelegatewithoutfocus.h
gui/reusable/timespinbox.cpp

View file

@ -21,7 +21,7 @@ MessagesProxyModel::MessagesProxyModel(MessagesModel* source_model, QObject* par
setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
setFilterKeyColumn(-1);
setFilterRole(LOWER_TITLE_ROLE);
setFilterRole(Qt::ItemDataRole::EditRole);
setDynamicSortFilter(false);
setSourceModel(m_sourceModel);

View file

@ -16,8 +16,8 @@
#include "gui/messagebox.h"
#include "gui/messagepreviewer.h"
#include "gui/messagesview.h"
#include "gui/reusable/baselineedit.h"
#include "gui/reusable/plaintoolbutton.h"
#include "gui/reusable/searchlineedit.h"
#include "gui/systemtrayicon.h"
#include "gui/tabbar.h"
#include "gui/toolbars/feedstoolbar.h"

View file

@ -230,10 +230,7 @@ void FeedMessageViewer::displayMessage(const Message& message, RootItem* root) {
void FeedMessageViewer::createConnections() {
// Filtering & searching.
connect(m_toolBarMessages,
&MessagesToolBar::messageSearchPatternChanged,
m_messagesView,
&MessagesView::searchMessages);
connect(m_toolBarMessages, &MessagesToolBar::searchCriteriaChanged, m_messagesView, &MessagesView::searchMessages);
connect(m_toolBarFeeds, &FeedsToolBar::feedsFilterPatternChanged, m_feedsView, &FeedsView::filterItems);
connect(m_toolBarMessages,
&MessagesToolBar::messageHighlighterChanged,

View file

@ -11,6 +11,7 @@
#include "gui/reusable/labelsmenu.h"
#include "gui/reusable/styleditemdelegatewithoutfocus.h"
#include "gui/reusable/treeviewcolumnsmenu.h"
#include "gui/toolbars/messagestoolbar.h"
#include "miscellaneous/externaltool.h"
#include "miscellaneous/feedreader.h"
#include "miscellaneous/settings.h"
@ -756,10 +757,33 @@ void MessagesView::selectNextUnreadItem() {
}
}
void MessagesView::searchMessages(const QString& pattern) {
qDebugNN << LOGSEC_GUI << "Running search of messages with pattern" << QUOTE_W_SPACE_DOT(pattern);
void MessagesView::searchMessages(SearchLineEdit::SearchMode mode,
Qt::CaseSensitivity sensitivity,
int custom_criteria,
const QString& phrase) {
qDebugNN << LOGSEC_GUI << "Running search of messages with pattern" << QUOTE_W_SPACE_DOT(phrase);
m_proxyModel->setFilterRegularExpression(pattern.toLower());
switch (mode) {
case SearchLineEdit::SearchMode::Wildcard:
m_proxyModel->setFilterWildcard(phrase);
break;
case SearchLineEdit::SearchMode::RegularExpression:
m_proxyModel->setFilterRegularExpression(phrase);
break;
case SearchLineEdit::SearchMode::FixedString:
default:
m_proxyModel->setFilterFixedString(phrase);
break;
}
m_proxyModel->setFilterCaseSensitivity(sensitivity);
MessagesToolBar::SearchFields where_search = MessagesToolBar::SearchFields(custom_criteria);
m_proxyModel->setFilterKeyColumn(where_search == MessagesToolBar::SearchFields::SearchTitleOnly ? MSG_DB_TITLE_INDEX
: -1);
if (selectionModel()->selectedRows().isEmpty()) {
emit currentMessageRemoved();

View file

@ -7,6 +7,7 @@
#include "core/messagesmodel.h"
#include "core/messagesproxymodel.h"
#include "gui/reusable/searchlineedit.h"
#include "services/abstract/rootitem.h"
#include <QHeaderView>
@ -61,7 +62,10 @@ class MessagesView : public BaseTreeView {
void selectNextUnreadItem();
// Searchs the visible message according to given pattern.
void searchMessages(const QString& pattern);
void searchMessages(SearchLineEdit::SearchMode mode,
Qt::CaseSensitivity sensitivity,
int custom_criteria,
const QString& phrase);
void highlightMessages(MessagesModel::MessageHighlighter highlighter);
void changeFilter(MessagesProxyModel::MessageListFilter filter);

View file

@ -3,8 +3,9 @@
#include "gui/reusable/plaintoolbutton.h"
#include <QAction>
#include <QPainter>
#include <QPaintEvent>
#include <QPainter>
#include <QPainterPath>
#include <QStyle>
#include <QStyleOption>
#include <QToolButton>
@ -29,6 +30,22 @@ void PlainToolButton::paintEvent(QPaintEvent* e) {
}
icon().paint(&p, rect);
if (menu() != nullptr) {
// Draw "dropdown" triangle.
QPainterPath path;
const int triangle_width = int(rect.width() * 0.4);
const int triangle_height = int(triangle_width * 0.5);
const auto triangle_origin = rect.bottomRight() - QPoint(triangle_width, triangle_height);
path.moveTo(triangle_origin);
path.lineTo(QPoint(rect.right(), triangle_origin.y()));
path.lineTo(triangle_origin + QPoint(triangle_width / 2, triangle_height));
path.lineTo(triangle_origin);
p.fillPath(path, QBrush(Qt::GlobalColor::black));
}
}
int PlainToolButton::padding() const {

View file

@ -0,0 +1,112 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "gui/reusable/searchlineedit.h"
#include "3rd-party/boolinq/boolinq.h"
#include "gui/reusable/plaintoolbutton.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include <QActionGroup>
#include <QTimer>
#include <QWidgetAction>
SearchLineEdit::SearchLineEdit(const QList<CustomSearchChoice>& choices, QWidget* parent) : BaseLineEdit(parent) {
QWidgetAction* act = new QWidgetAction(this);
PlainToolButton* btn = new PlainToolButton(this);
m_tmrSearchPattern = new QTimer(this);
m_tmrSearchPattern->setSingleShot(true);
m_tmrSearchPattern->setInterval(300);
m_menu = new QMenu(btn);
m_actionGroupChoices = new QActionGroup(this);
m_actionGroupChoices->setExclusive(true);
m_actionGroupModes = new QActionGroup(this);
m_actionGroupModes->setExclusive(true);
m_actCaseSensitivity = m_menu->addAction(tr("Case-sensitive"));
m_actCaseSensitivity->setCheckable(true);
m_menu->addSeparator();
// Setup tool button.
btn->setIcon(qApp->icons()->fromTheme(QSL("system-search")));
btn->setPopupMode(QToolButton::ToolButtonPopupMode::InstantPopup);
btn->setMenu(m_menu);
act->setDefaultWidget(btn);
addAction(act, QLineEdit::ActionPosition::LeadingPosition);
// Load predefined modes.
for (SearchMode mode : {SearchMode::FixedString, SearchMode::Wildcard, SearchMode::RegularExpression}) {
QAction* ac = m_actionGroupModes->addAction(m_menu->addAction(titleForMode(mode)));
ac->setCheckable(true);
ac->setData(int(mode));
}
m_actionGroupModes->actions().first()->setChecked(true);
if (!choices.isEmpty()) {
m_menu->addSeparator();
// Load custom coices.
for (const CustomSearchChoice& choice : choices) {
QAction* ac = m_actionGroupChoices->addAction(m_menu->addAction(choice.m_title));
ac->setCheckable(true);
ac->setData(choice.m_data);
}
m_actionGroupChoices->actions().first()->setChecked(true);
}
// NOTE: When any change is made, (re)start the timer which fires
// the signal with delay to avoid throttling.
connect(this, &SearchLineEdit::textChanged, m_tmrSearchPattern, QOverload<>::of(&QTimer::start));
connect(m_menu, &QMenu::triggered, m_tmrSearchPattern, QOverload<>::of(&QTimer::start));
connect(m_tmrSearchPattern, &QTimer::timeout, this, &SearchLineEdit::startSearch);
}
void SearchLineEdit::startSearch() {
SearchMode mode = SearchMode(boolinq::from(m_actionGroupModes->actions())
.first([](const QAction* act) {
return act->isChecked();
})
->data()
.toInt());
int custom_criteria = boolinq::from(m_actionGroupChoices->actions())
.first([](const QAction* act) {
return act->isChecked();
})
->data()
.toInt();
bool case_sensitive = m_actCaseSensitivity->isChecked();
emit searchCriteriaChanged(mode,
case_sensitive ? Qt::CaseSensitivity::CaseSensitive : Qt::CaseSensitivity::CaseInsensitive,
custom_criteria,
text());
}
QString SearchLineEdit::titleForMode(SearchMode mode) {
switch (mode) {
case SearchLineEdit::SearchMode::FixedString:
return tr("Fixed text");
case SearchLineEdit::SearchMode::Wildcard:
return tr("Wildcard");
case SearchLineEdit::SearchMode::RegularExpression:
return tr("Regular expression");
default:
return {};
}
}

View file

@ -0,0 +1,46 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef SEARCHLINEEDIT_H
#define SEARCHLINEEDIT_H
#include "gui/reusable/baselineedit.h"
class QActionGroup;
class SearchLineEdit : public BaseLineEdit {
Q_OBJECT
public:
struct CustomSearchChoice {
public:
CustomSearchChoice(const QString& title, int data) : m_title(title), m_data(data) {}
QString m_title;
int m_data;
};
enum class SearchMode { FixedString = 1, Wildcard = 2, RegularExpression = 4 };
explicit SearchLineEdit(const QList<CustomSearchChoice>& choices, QWidget* parent = nullptr);
private slots:
void startSearch();
signals:
void searchCriteriaChanged(SearchMode mode,
Qt::CaseSensitivity sensitivity,
int custom_criteria,
const QString& phrase);
private:
QString titleForMode(SearchMode mode);
private:
QTimer* m_tmrSearchPattern;
QMenu* m_menu;
QAction* m_actCaseSensitivity;
QActionGroup* m_actionGroupModes;
QActionGroup* m_actionGroupChoices;
};
#endif // SEARCHLINEEDIT_H

View file

@ -4,7 +4,6 @@
#include "3rd-party/boolinq/boolinq.h"
#include "definitions/definitions.h"
#include "gui/reusable/baselineedit.h"
#include "miscellaneous/iconfactory.h"
#include "miscellaneous/settings.h"
@ -151,10 +150,10 @@ void MessagesToolBar::handleMessageFilterChange(QAction* action) {
}
void MessagesToolBar::initializeSearchBox() {
m_tmrSearchPattern = new QTimer(this);
m_tmrSearchPattern->setSingleShot(true);
m_txtSearchMessages = new BaseLineEdit(this);
m_txtSearchMessages =
new SearchLineEdit({SearchLineEdit::CustomSearchChoice(tr("Everywhere"), int(SearchFields::SearchAll)),
SearchLineEdit::CustomSearchChoice(tr("Titles only"), int(SearchFields::SearchTitleOnly))},
this);
m_txtSearchMessages->setSizePolicy(QSizePolicy::Policy::Expanding,
m_txtSearchMessages->sizePolicy().verticalPolicy());
m_txtSearchMessages->setPlaceholderText(tr("Search articles (regex only)"));
@ -166,10 +165,7 @@ void MessagesToolBar::initializeSearchBox() {
m_actionSearchMessages->setProperty("type", SEARCH_BOX_ACTION_NAME);
m_actionSearchMessages->setProperty("name", tr("Article search box"));
connect(m_txtSearchMessages, &BaseLineEdit::textChanged, this, &MessagesToolBar::onSearchPatternChanged);
connect(m_tmrSearchPattern, &QTimer::timeout, this, [this]() {
emit messageSearchPatternChanged(m_searchPattern);
});
connect(m_txtSearchMessages, &SearchLineEdit::searchCriteriaChanged, this, &MessagesToolBar::searchCriteriaChanged);
}
void MessagesToolBar::addActionToMenu(QMenu* menu,
@ -305,7 +301,7 @@ void MessagesToolBar::saveToolButtonSelection(const QString& button_name, const
qApp->settings()->setValue(GROUP(GUI), GUI::MessagesToolbarDefaultButtons, action_names.join(QSL(",")));
}
BaseLineEdit* MessagesToolBar::searchBox() const {
SearchLineEdit* MessagesToolBar::searchBox() const {
return m_txtSearchMessages;
}
@ -347,8 +343,3 @@ QStringList MessagesToolBar::savedActions() const {
QString::SplitBehavior::SkipEmptyParts);
#endif
}
void MessagesToolBar::onSearchPatternChanged(const QString& search_pattern) {
m_searchPattern = search_pattern;
m_tmrSearchPattern->start(search_pattern.isEmpty() ? 0ms : 300ms);
}

View file

@ -7,8 +7,8 @@
#include "core/messagesmodel.h"
#include "core/messagesproxymodel.h"
#include "gui/reusable/searchlineedit.h"
class BaseLineEdit;
class QWidgetAction;
class QToolButton;
class QMenu;
@ -18,6 +18,8 @@ class MessagesToolBar : public BaseToolBar {
Q_OBJECT
public:
enum class SearchFields { SearchTitleOnly = 1, SearchAll = 2 };
explicit MessagesToolBar(const QString& title, QWidget* parent = nullptr);
virtual QList<QAction*> availableActions() const;
@ -28,24 +30,30 @@ class MessagesToolBar : public BaseToolBar {
virtual QStringList defaultActions() const;
virtual QStringList savedActions() const;
BaseLineEdit *searchBox() const;
SearchLineEdit* searchBox() const;
signals:
void messageSearchPatternChanged(const QString& pattern);
void searchCriteriaChanged(SearchLineEdit::SearchMode mode,
Qt::CaseSensitivity sensitivity,
int custom_criteria,
const QString& phrase);
void messageHighlighterChanged(MessagesModel::MessageHighlighter highlighter);
void messageFilterChanged(MessagesProxyModel::MessageListFilter filter);
private slots:
void onSearchPatternChanged(const QString& search_pattern);
void handleMessageHighlighterChange(QAction* action);
void handleMessageFilterChange(QAction* action);
private:
void initializeSearchBox();
void addActionToMenu(QMenu* menu, const QIcon& icon, const QString& title, const QVariant& value, const QString& name);
void addActionToMenu(QMenu* menu,
const QIcon& icon,
const QString& title,
const QVariant& value,
const QString& name);
void initializeHighlighter();
void activateAction(const QString& action_name, QWidgetAction* widget_action);
void saveToolButtonSelection(const QString& button_name, const QList<QAction *> &actions) const;
void saveToolButtonSelection(const QString& button_name, const QList<QAction*>& actions) const;
private:
QWidgetAction* m_actionMessageHighlighter;
@ -55,9 +63,7 @@ class MessagesToolBar : public BaseToolBar {
QMenu* m_menuMessageHighlighter;
QMenu* m_menuMessageFilter;
QWidgetAction* m_actionSearchMessages;
BaseLineEdit* m_txtSearchMessages;
QTimer* m_tmrSearchPattern;
QString m_searchPattern;
SearchLineEdit* m_txtSearchMessages;
};
#endif // NEWSTOOLBAR_H