diff --git a/src/common/CutterSearchable.cpp b/src/common/CutterSearchable.cpp index 97a235672..7c2740777 100644 --- a/src/common/CutterSearchable.cpp +++ b/src/common/CutterSearchable.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include void CutterSearchableHelper::setupConnections(QWidget *parent, SearchBarWidget *searchBar) { @@ -75,3 +77,39 @@ void CutterSearchableHelper::positionSearchBar(QWidget *parent, SearchBarWidget searchBar->move(x, y); } + +void CutterSearchableHelper::applyFilter(QSortFilterProxyModel *proxyModel, + const QString &filterText, int options) +{ + if (!proxyModel) { + return; + } + + if (filterText.isEmpty()) { + proxyModel->setFilterFixedString(QString()); + return; + } + + QRegularExpression::PatternOptions patternOptions; + if (!(options & CaseSensitive)) { + patternOptions |= QRegularExpression::CaseInsensitiveOption; + } + + if (options & RegExp) { + QRegularExpression regExp(filterText, patternOptions); + if (regExp.isValid()) { + proxyModel->setFilterRegularExpression(regExp); + } + } else if (options & WholeWords) { + QString pattern = QString("\\b%1\\b").arg(QRegularExpression::escape(filterText)); + QRegularExpression regExp(pattern, patternOptions); + if (regExp.isValid()) { + proxyModel->setFilterRegularExpression(regExp); + } + } else { + proxyModel->setFilterCaseSensitivity((options & CaseSensitive) + ? Qt::CaseSensitive + : Qt::CaseInsensitive); + proxyModel->setFilterWildcard(filterText); + } +} diff --git a/src/common/CutterSearchable.h b/src/common/CutterSearchable.h index 73082f7b3..c733eb138 100644 --- a/src/common/CutterSearchable.h +++ b/src/common/CutterSearchable.h @@ -2,6 +2,7 @@ #define CUTTERSEARCHABLE_H class QString; +class QSortFilterProxyModel; class SearchBarWidget; class QWidget; @@ -79,6 +80,15 @@ void setupConnections(QWidget *parent, SearchBarWidget *bar); */ void positionSearchBar(QWidget *parent, SearchBarWidget *searchBar, QWidget *searchArea, int hPadding, int vPadding); -}; + +/** + * @brief Applies filter options to a proxy model + * @param proxyModel The proxy model to filter + * @param filterText The text to filter by + * @param options Bitwise combination of SearchOption flags (CaseSensitive, WholeWords, RegExp) + */ +void applyFilter(QSortFilterProxyModel *proxyModel, const QString &filterText, int options); + +} // namespace CutterSearchableHelper #endif // CUTTERSEARCHABLE_H diff --git a/src/dialogs/TypesVariablesDialog.cpp b/src/dialogs/TypesVariablesDialog.cpp index 58fc40f46..ddf8517fb 100644 --- a/src/dialogs/TypesVariablesDialog.cpp +++ b/src/dialogs/TypesVariablesDialog.cpp @@ -1,5 +1,6 @@ #include "TypesVariablesDialog.h" #include "ui_TypesVariablesDialog.h" +#include "common/CutterSearchable.h" QString toString(VariableScope scope) { @@ -160,8 +161,10 @@ TypesVariablesDialog::TypesVariablesDialog(QWidget *parent, const QString &typeN auto updateCount = [this]() { tree->showItemsNumber(proxyModel->rowCount()); }; - connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, proxyModel, - &TypesVariablesProxyModel::setFilterFixedString); + connect(ui->quickFilterView, &ComboQuickFilterView::filterChanged, this, + [this](const QString &text, int options) { + CutterSearchableHelper::applyFilter(proxyModel, text, options); + }); connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, this, updateCount); connect(scopeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, diff --git a/src/widgets/ComboQuickFilterView.cpp b/src/widgets/ComboQuickFilterView.cpp index ec7574b56..def789c7f 100644 --- a/src/widgets/ComboQuickFilterView.cpp +++ b/src/widgets/ComboQuickFilterView.cpp @@ -1,16 +1,44 @@ #include "ComboQuickFilterView.h" #include "ui_ComboQuickFilterView.h" +#include "common/CutterSearchable.h" +#include ComboQuickFilterView::ComboQuickFilterView(QWidget *parent) : QWidget(parent), ui(new Ui::ComboQuickFilterView) { ui->setupUi(this); + QMenu *optionsMenu = new QMenu(this); + m_caseSensitiveAction = optionsMenu->addAction(tr("&Case Sensitive")); + m_caseSensitiveAction->setCheckable(true); + + m_wholeWordsAction = optionsMenu->addAction(tr("Exact Match")); + m_wholeWordsAction->setCheckable(true); + + m_regexAction = optionsMenu->addAction(tr("Regular Expression")); + m_regexAction->setCheckable(true); + + auto emitFilterChanged = [this]() { + emit filterChanged(ui->lineEdit->text(), filterOptions()); + emit filterTextChanged(ui->lineEdit->text()); + }; + connect(m_caseSensitiveAction, &QAction::triggered, this, emitFilterChanged); + connect(m_wholeWordsAction, &QAction::triggered, this, emitFilterChanged); + connect(m_regexAction, &QAction::triggered, this, emitFilterChanged); + + QAction *optionsAction = new QAction(this); + optionsAction->setIcon(QIcon(":/img/icons/cog_light.svg")); + optionsAction->setMenu(optionsMenu); + ui->lineEdit->addAction(optionsAction, QLineEdit::LeadingPosition); + debounceTimer = new QTimer(this); debounceTimer->setSingleShot(true); connect(debounceTimer, &QTimer::timeout, this, - [this]() { emit filterTextChanged(ui->lineEdit->text()); }); + [this]() { + emit filterChanged(ui->lineEdit->text(), filterOptions()); + emit filterTextChanged(ui->lineEdit->text()); + }); connect(ui->lineEdit, &QLineEdit::textChanged, this, [this]() { debounceTimer->start(150); }); } @@ -47,3 +75,18 @@ void ComboQuickFilterView::closeFilter() hide(); emit filterClosed(); } + +int ComboQuickFilterView::filterOptions() const +{ + int options = 0; + if (m_caseSensitiveAction->isChecked()) { + options |= CaseSensitive; + } + if (m_wholeWordsAction->isChecked()) { + options |= WholeWords; + } + if (m_regexAction->isChecked()) { + options |= RegExp; + } + return options; +} diff --git a/src/widgets/ComboQuickFilterView.h b/src/widgets/ComboQuickFilterView.h index 5dc217dcb..8f3af0cb6 100644 --- a/src/widgets/ComboQuickFilterView.h +++ b/src/widgets/ComboQuickFilterView.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace Ui { class ComboQuickFilterView; @@ -29,11 +30,18 @@ public slots: signals: void filterTextChanged(const QString &text); + void filterChanged(const QString &text, int options); void filterClosed(); private: Ui::ComboQuickFilterView *ui; QTimer *debounceTimer; + + QAction *m_caseSensitiveAction; + QAction *m_wholeWordsAction; + QAction *m_regexAction; + + int filterOptions() const; }; #endif // COMBOQUICKFILTERVIEW_H diff --git a/src/widgets/GlobalsWidget.cpp b/src/widgets/GlobalsWidget.cpp index 85547a05a..cb8da46f6 100644 --- a/src/widgets/GlobalsWidget.cpp +++ b/src/widgets/GlobalsWidget.cpp @@ -2,6 +2,7 @@ #include "ui_GlobalsWidget.h" #include "core/MainWindow.h" #include "common/Helpers.h" +#include "common/CutterSearchable.h" #include "dialogs/GlobalVariableDialog.h" #include "shortcuts/ShortcutManager.h" @@ -170,8 +171,10 @@ GlobalsWidget::GlobalsWidget(MainWindow *main) // Setup custom context menu ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, globalsProxyModel, - &QSortFilterProxyModel::setFilterWildcard); + connect(ui->quickFilterView, &ComboQuickFilterView::filterChanged, this, + [this](const QString &text, int options) { + CutterSearchableHelper::applyFilter(globalsProxyModel, text, options); + }); connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, this, [this] { tree->showItemsNumber(globalsProxyModel->rowCount()); }); diff --git a/src/widgets/ListDockWidget.cpp b/src/widgets/ListDockWidget.cpp index ffcd05b79..b178e6ca3 100644 --- a/src/widgets/ListDockWidget.cpp +++ b/src/widgets/ListDockWidget.cpp @@ -2,6 +2,7 @@ #include "ui_ListDockWidget.h" #include "core/MainWindow.h" #include "common/Helpers.h" +#include "common/CutterSearchable.h" #include "shortcuts/ShortcutManager.h" #include @@ -57,8 +58,10 @@ void ListDockWidget::setModels(AddressableFilterProxyModel *objectFilterProxyMod ui->treeView->setModel(objectFilterProxyModel); - connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, objectFilterProxyModel, - &QSortFilterProxyModel::setFilterWildcard); + connect(ui->quickFilterView, &QuickFilterView::filterChanged, this, + [=](const QString &text, int options) { + CutterSearchableHelper::applyFilter(objectFilterProxyModel, text, options); + }); connect(ui->quickFilterView, &QuickFilterView::filterClosed, ui->treeView, static_cast(&QWidget::setFocus)); diff --git a/src/widgets/ProcessesWidget.cpp b/src/widgets/ProcessesWidget.cpp index b729b49e6..c44c603fd 100644 --- a/src/widgets/ProcessesWidget.cpp +++ b/src/widgets/ProcessesWidget.cpp @@ -2,6 +2,7 @@ #include "ProcessesWidget.h" #include "ui_ProcessesWidget.h" #include "common/JsonModel.h" +#include "common/CutterSearchable.h" #include "QuickFilterView.h" #include @@ -49,8 +50,10 @@ ProcessesWidget::ProcessesWidget(MainWindow *main) refreshDeferrer = createRefreshDeferrer([this]() { updateContents(); }); - connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, modelFilter, - &ProcessesFilterModel::setFilterWildcard); + connect(ui->quickFilterView, &QuickFilterView::filterChanged, this, + [this](const QString &text, int options) { + CutterSearchableHelper::applyFilter(modelFilter, text, options); + }); connect(Core(), &CutterCore::refreshAll, this, &ProcessesWidget::updateContents); connect(Core(), &CutterCore::registersChanged, this, &ProcessesWidget::updateContents); connect(Core(), &CutterCore::debugTaskStateChanged, this, &ProcessesWidget::updateContents); diff --git a/src/widgets/QuickFilterView.cpp b/src/widgets/QuickFilterView.cpp index bf1303336..d8eaefe53 100644 --- a/src/widgets/QuickFilterView.cpp +++ b/src/widgets/QuickFilterView.cpp @@ -1,19 +1,46 @@ - #include "QuickFilterView.h" #include "ui_QuickFilterView.h" +#include "common/CutterSearchable.h" +#include QuickFilterView::QuickFilterView(QWidget *parent, bool defaultOn) : QWidget(parent), ui(new Ui::QuickFilterView()) { ui->setupUi(this); + QMenu *optionsMenu = new QMenu(this); + m_caseSensitiveAction = optionsMenu->addAction(tr("&Case Sensitive")); + m_caseSensitiveAction->setCheckable(true); + + m_wholeWordsAction = optionsMenu->addAction(tr("Exact Match")); + m_wholeWordsAction->setCheckable(true); + + m_regexAction = optionsMenu->addAction(tr("Regular Expression")); + m_regexAction->setCheckable(true); + + auto emitFilterChanged = [this]() { + emit filterChanged(ui->filterLineEdit->text(), filterOptions()); + emit filterTextChanged(ui->filterLineEdit->text()); + }; + connect(m_caseSensitiveAction, &QAction::triggered, this, emitFilterChanged); + connect(m_wholeWordsAction, &QAction::triggered, this, emitFilterChanged); + connect(m_regexAction, &QAction::triggered, this, emitFilterChanged); + + QAction *optionsAction = new QAction(this); + optionsAction->setIcon(QIcon(":/img/icons/cog_light.svg")); + optionsAction->setMenu(optionsMenu); + ui->filterLineEdit->addAction(optionsAction, QLineEdit::LeadingPosition); + debounceTimer = new QTimer(this); debounceTimer->setSingleShot(true); connect(ui->closeFilterButton, &QAbstractButton::clicked, this, &QuickFilterView::closeFilter); connect(debounceTimer, &QTimer::timeout, this, - [this]() { emit filterTextChanged(ui->filterLineEdit->text()); }); + [this]() { + emit filterChanged(ui->filterLineEdit->text(), filterOptions()); + emit filterTextChanged(ui->filterLineEdit->text()); + }); connect(ui->filterLineEdit, &QLineEdit::textChanged, this, [this]() { debounceTimer->start(150); }); @@ -46,3 +73,18 @@ void QuickFilterView::closeFilter() hide(); emit filterClosed(); } + +int QuickFilterView::filterOptions() const +{ + int options = 0; + if (m_caseSensitiveAction->isChecked()) { + options |= CaseSensitive; + } + if (m_wholeWordsAction->isChecked()) { + options |= WholeWords; + } + if (m_regexAction->isChecked()) { + options |= RegExp; + } + return options; +} diff --git a/src/widgets/QuickFilterView.h b/src/widgets/QuickFilterView.h index b6cbb15b5..0e3728734 100644 --- a/src/widgets/QuickFilterView.h +++ b/src/widgets/QuickFilterView.h @@ -1,4 +1,3 @@ - #ifndef QUICKFILTERVIEW_H #define QUICKFILTERVIEW_H @@ -8,6 +7,7 @@ #include #include +#include namespace Ui { class QuickFilterView; @@ -28,11 +28,18 @@ public slots: signals: void filterTextChanged(const QString &text); + void filterChanged(const QString &text, int options); void filterClosed(); private: std::unique_ptr ui; QTimer *debounceTimer; + + QAction *m_caseSensitiveAction; + QAction *m_wholeWordsAction; + QAction *m_regexAction; + + int filterOptions() const; }; #endif // QUICKFILTERVIEW_H diff --git a/src/widgets/RegisterRefsWidget.cpp b/src/widgets/RegisterRefsWidget.cpp index 4c90e58a4..f3210fd24 100644 --- a/src/widgets/RegisterRefsWidget.cpp +++ b/src/widgets/RegisterRefsWidget.cpp @@ -2,6 +2,7 @@ #include "ui_RegisterRefsWidget.h" #include "core/MainWindow.h" #include "common/Helpers.h" +#include "common/CutterSearchable.h" #include "shortcuts/ShortcutManager.h" #include @@ -150,8 +151,10 @@ RegisterRefsWidget::RegisterRefsWidget(MainWindow *main) &QuickFilterView::showFilter); search_shortcut->setContext(Qt::WidgetWithChildrenShortcut); - connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, registerRefProxyModel, - &QSortFilterProxyModel::setFilterWildcard); + connect(ui->quickFilterView, &QuickFilterView::filterChanged, this, + [this](const QString &text, int options) { + CutterSearchableHelper::applyFilter(registerRefProxyModel, text, options); + }); connect(ui->quickFilterView, &QuickFilterView::filterClosed, ui->registerRefTreeView, [this]() { ui->registerRefTreeView->setFocus(); }); setScrollMode(); diff --git a/src/widgets/StringsWidget.cpp b/src/widgets/StringsWidget.cpp index 325d47d12..6a307f05f 100644 --- a/src/widgets/StringsWidget.cpp +++ b/src/widgets/StringsWidget.cpp @@ -2,6 +2,7 @@ #include "ui_StringsWidget.h" #include "core/MainWindow.h" #include "common/Helpers.h" +#include "common/CutterSearchable.h" #include "shortcuts/ShortcutManager.h" #include @@ -182,8 +183,10 @@ StringsWidget::StringsWidget(MainWindow *main) auto menu = ui->stringsTreeView->getItemContextMenu(); menu->addAction(ui->actionCopy_String); - connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, proxyModel, - &QSortFilterProxyModel::setFilterWildcard); + connect(ui->quickFilterView, &ComboQuickFilterView::filterChanged, this, + [this](const QString &text, int options) { + CutterSearchableHelper::applyFilter(proxyModel, text, options); + }); connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, this, [this] { tree->showItemsNumber(proxyModel->rowCount()); }); diff --git a/src/widgets/ThreadsWidget.cpp b/src/widgets/ThreadsWidget.cpp index 7992b5c0c..5c255a229 100644 --- a/src/widgets/ThreadsWidget.cpp +++ b/src/widgets/ThreadsWidget.cpp @@ -2,6 +2,7 @@ #include "ThreadsWidget.h" #include "CutterCommon.h" #include "Helpers.h" +#include "CutterSearchable.h" #include "ui_ThreadsWidget.h" #include "QuickFilterView.h" #include @@ -144,8 +145,10 @@ ThreadsWidget::ThreadsWidget(MainWindow *main) menuText.setSeparator(true); qhelpers::prependQAction(&menuText, &addressableItemContextMenu); - connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, modelFilter, - &QSortFilterProxyModel::setFilterWildcard); + connect(ui->quickFilterView, &QuickFilterView::filterChanged, this, + [this](const QString &text, int options) { + CutterSearchableHelper::applyFilter(modelFilter, text, options); + }); connect(Core(), &CutterCore::refreshAll, this, &ThreadsWidget::updateContents); connect(Core(), &CutterCore::registersChanged, this, &ThreadsWidget::updateContents); connect(Core(), &CutterCore::debugTaskStateChanged, this, &ThreadsWidget::updateContents); diff --git a/src/widgets/TypesWidget.cpp b/src/widgets/TypesWidget.cpp index 880eca99d..fee33c16f 100644 --- a/src/widgets/TypesWidget.cpp +++ b/src/widgets/TypesWidget.cpp @@ -2,6 +2,7 @@ #include "ui_TypesWidget.h" #include "core/MainWindow.h" #include "common/Helpers.h" +#include "common/CutterSearchable.h" #include "dialogs/TypesInteractionDialog.h" #include "dialogs/TypesVariablesDialog.h" #include "shortcuts/ShortcutManager.h" @@ -171,8 +172,10 @@ TypesWidget::TypesWidget(MainWindow *main) ui->typesTreeView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, types_proxy_model, - &QSortFilterProxyModel::setFilterWildcard); + connect(ui->quickFilterView, &ComboQuickFilterView::filterChanged, this, + [this](const QString &text, int options) { + CutterSearchableHelper::applyFilter(types_proxy_model, text, options); + }); connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, this, [this] { tree->showItemsNumber(types_proxy_model->rowCount()); }); diff --git a/src/widgets/VTablesWidget.cpp b/src/widgets/VTablesWidget.cpp index b80ccd140..51e4bfaf1 100644 --- a/src/widgets/VTablesWidget.cpp +++ b/src/widgets/VTablesWidget.cpp @@ -3,6 +3,7 @@ #include "core/MainWindow.h" #include "common/Helpers.h" +#include "common/CutterSearchable.h" #include "shortcuts/ShortcutManager.h" #include "VTablesWidget.h" @@ -150,8 +151,10 @@ VTablesWidget::VTablesWidget(MainWindow *main) &QuickFilterView::showFilter); search_shortcut->setContext(Qt::WidgetWithChildrenShortcut); - connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, proxy, - &QSortFilterProxyModel::setFilterWildcard); + connect(ui->quickFilterView, &QuickFilterView::filterChanged, this, + [=](const QString &text, int options) { + CutterSearchableHelper::applyFilter(proxy, text, options); + }); connect(ui->quickFilterView, &QuickFilterView::filterClosed, ui->vTableTreeView, [this]() { ui->vTableTreeView->setFocus(); });