┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlessio Bonfiglio <[email protected]>2026-03-11 20:52:55 +0100
committerMéven Car <[email protected]>2026-03-12 10:51:45 +0000
commit3084a4e11e54b71065a408c7d288489bc72ea8a2 (patch)
tree77b44ab7462b05af02822a2ab264d32796bfa7e6 /src
parent287ff66e678f4f5a4edea28fc2699d73cefc3233 (diff)
filterbar: Add support to match case and glob patterns for the filter bar
Currently, Dolphin's filter bar defaults to plain text, but it actually has a hidden regex functionality too: it tries to auto-detect and switch to a regular expression if characters like '*', '?', or '[' are present in the search string. This approach has a couple of issues. First, the regex/wildcard functionality is completely hidden from the user. Second, the auto-detection is flawed because those are perfectly valid characters in Linux filenames. If a user tries to filter for a file literally named [draft].txt, the auto-switching kicks in and causes unexpected behavior. This MR fixes this by making the filtering modes explicit through a ComboBox at the side of the filter bar, with the options 'Plain Text', 'Glob' and 'Regular Expression'. It also adds a button to toggle the case sensitive matching. A visual feedback for when the user is inputting an invalid expression has also been implemented by turning the bar background red and making appear an error symbol.
Diffstat (limited to 'src')
-rw-r--r--src/dolphinviewcontainer.cpp18
-rw-r--r--src/dolphinviewcontainer.h10
-rw-r--r--src/filterbar/filterbar.cpp100
-rw-r--r--src/filterbar/filterbar.h22
-rw-r--r--src/kitemviews/kfileitemmodel.cpp28
-rw-r--r--src/kitemviews/kfileitemmodel.h6
-rw-r--r--src/kitemviews/private/kfileitemmodelfilter.cpp62
-rw-r--r--src/kitemviews/private/kfileitemmodelfilter.h36
-rw-r--r--src/views/dolphinview.cpp20
-rw-r--r--src/views/dolphinview.h13
10 files changed, 293 insertions, 22 deletions
diff --git a/src/dolphinviewcontainer.cpp b/src/dolphinviewcontainer.cpp
index 5c054eab8..4d7472ed3 100644
--- a/src/dolphinviewcontainer.cpp
+++ b/src/dolphinviewcontainer.cpp
@@ -110,6 +110,8 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent)
m_filterBar->setVisible(GeneralSettings::filterBar(), WithoutAnimation);
connect(m_filterBar, &FilterBar::filterChanged, this, &DolphinViewContainer::setNameFilter);
+ connect(m_filterBar, &FilterBar::filterModeChanged, this, &DolphinViewContainer::setFilterMode);
+ connect(m_filterBar, &FilterBar::caseSensitiveChanged, this, &DolphinViewContainer::setFilterCaseSensitive);
connect(m_filterBar, &FilterBar::closeRequest, this, &DolphinViewContainer::closeFilterBar);
connect(m_filterBar, &FilterBar::focusViewRequest, this, &DolphinViewContainer::requestFocus);
@@ -208,6 +210,10 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent)
connect(placesModel, &KFilePlacesModel::rowsRemoved, this, &DolphinViewContainer::slotPlacesModelChanged);
QApplication::instance()->installEventFilter(this);
+
+ // Update the view with the current state of the filter bar (from the state config)
+ m_view->setFilterMode(m_filterBar->filterMode());
+ m_view->setFilterCaseSensitive(m_filterBar->isCaseSensitive());
}
DolphinViewContainer::~DolphinViewContainer() = default;
@@ -867,6 +873,18 @@ void DolphinViewContainer::setNameFilter(const QString &nameFilter)
delayedStatusBarUpdate();
}
+void DolphinViewContainer::setFilterMode(const KFileItemModelFilter::FilterMode mode)
+{
+ m_view->setFilterMode(mode);
+ delayedStatusBarUpdate();
+}
+
+void DolphinViewContainer::setFilterCaseSensitive(const bool caseSensitive)
+{
+ m_view->setFilterCaseSensitive(caseSensitive);
+ delayedStatusBarUpdate();
+}
+
void DolphinViewContainer::activate()
{
setActive(true);
diff --git a/src/dolphinviewcontainer.h b/src/dolphinviewcontainer.h
index 3829a4f78..f7ec01885 100644
--- a/src/dolphinviewcontainer.h
+++ b/src/dolphinviewcontainer.h
@@ -372,6 +372,16 @@ private Q_SLOTS:
void setNameFilter(const QString &nameFilter);
/**
+ * Set the filtering mode of the filter.
+ */
+ void setFilterMode(const KFileItemModelFilter::FilterMode mode);
+
+ /**
+ * Enable or disable the case sensitive filtering.
+ */
+ void setFilterCaseSensitive(const bool caseSensitive);
+
+ /**
* Marks the view container as active
* (see DolphinViewContainer::setActive()).
*/
diff --git a/src/filterbar/filterbar.cpp b/src/filterbar/filterbar.cpp
index 6cb8d5e2a..08bff24fc 100644
--- a/src/filterbar/filterbar.cpp
+++ b/src/filterbar/filterbar.cpp
@@ -2,18 +2,25 @@
* SPDX-FileCopyrightText: 2006-2010 Peter Penz <[email protected]>
* SPDX-FileCopyrightText: 2006 Gregor Kališnik <[email protected]>
* SPDX-FileCopyrightText: 2012 Stuart Citrin <[email protected]>
+ * SPDX-FileCopyrightText: 2026 Alessio Bonfiglio <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "filterbar.h"
+#include <KConfigGroup>
#include <KLocalizedString>
+#include <KSharedConfig>
+#include <KColorScheme>
+#include <QAction>
#include <QApplication>
+#include <QComboBox>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLineEdit>
+#include <QPalette>
#include <QToolButton>
FilterBar::FilterBar(QWidget *parent)
@@ -35,8 +42,35 @@ FilterBar::FilterBar(QWidget *parent)
m_filterInput->setClearButtonEnabled(true);
m_filterInput->setPlaceholderText(i18n("Filter…"));
connect(m_filterInput, &QLineEdit::textChanged, this, &FilterBar::filterChanged);
+ connect(m_filterInput, &QLineEdit::textChanged, this, &FilterBar::updateInvalidPatternView);
setFocusProxy(m_filterInput);
+ m_invalidPatternAction = new QAction(m_filterInput);
+ m_invalidPatternAction->setCheckable(false);
+ m_invalidPatternAction->setIcon(QIcon::fromTheme(QStringLiteral("error-symbolic")));
+ m_invalidPatternAction->setToolTip(i18n("Invalid expression"));
+ m_filterInput->addAction(m_invalidPatternAction, QLineEdit::TrailingPosition);
+ m_invalidPatternAction->setVisible(false);
+
+ // Create case sensitive button
+ m_caseSensitiveButton = new QToolButton(contentsContainer);
+ m_caseSensitiveButton->setAutoRaise(true);
+ m_caseSensitiveButton->setCheckable(true);
+ m_caseSensitiveButton->setIcon(QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold"))));
+ m_caseSensitiveButton->setToolTip(i18nc("@info:tooltip", "Match case"));
+ connect(m_caseSensitiveButton, &QToolButton::toggled, this, &FilterBar::caseSensitiveChanged);
+ connect(m_caseSensitiveButton, &QToolButton::toggled, this, &FilterBar::updateInvalidPatternView);
+
+ // Create filter mode combobox
+ m_filterModeComboBox = new QComboBox(contentsContainer);
+ m_filterModeComboBox->addItem(i18nc("@item:inlistbox", "Plain Text"), KFileItemModelFilter::FilterMode::PlainText);
+ m_filterModeComboBox->addItem(i18nc("@item:inlistbox", "Glob Pattern"), KFileItemModelFilter::FilterMode::Glob);
+ m_filterModeComboBox->addItem(i18nc("@item:inlistbox", "Regular Expression"), KFileItemModelFilter::FilterMode::Regex);
+ connect(m_filterModeComboBox, &QComboBox::currentIndexChanged, this, [this](int index) {
+ Q_EMIT filterModeChanged(m_filterModeComboBox->itemData(index).value<KFileItemModelFilter::FilterMode>());
+ });
+ connect(m_filterModeComboBox, &QComboBox::currentIndexChanged, this, &FilterBar::updateInvalidPatternView);
+
// Create close button
QToolButton *closeButton = new QToolButton(contentsContainer);
closeButton->setAutoRaise(true);
@@ -49,13 +83,25 @@ FilterBar::FilterBar(QWidget *parent)
hLayout->setContentsMargins(0, 0, 0, 0);
hLayout->addWidget(m_lockButton);
hLayout->addWidget(m_filterInput);
+ hLayout->addWidget(m_caseSensitiveButton);
+ hLayout->addWidget(m_filterModeComboBox);
hLayout->addWidget(closeButton);
- setTabOrder(m_lockButton, closeButton);
- setTabOrder(closeButton, m_filterInput);
+ setTabOrder({m_lockButton, m_caseSensitiveButton, m_filterModeComboBox, closeButton, m_filterInput});
+
+ KConfigGroup filterBarConfig(KSharedConfig::openStateConfig(), QStringLiteral("FilterBar"));
+ bool caseSensitiveEnabled = filterBarConfig.readEntry("caseSensitive", false);
+ int filterModeComboBoxIndex = filterBarConfig.readEntry("filterMode", m_filterModeComboBox->findData(KFileItemModelFilter::FilterMode::Glob));
+ m_caseSensitiveButton->setChecked(caseSensitiveEnabled);
+ m_filterModeComboBox->setCurrentIndex(filterModeComboBoxIndex);
}
-FilterBar::~FilterBar() = default;
+FilterBar::~FilterBar()
+{
+ KConfigGroup filterBarConfig(KSharedConfig::openStateConfig(), QStringLiteral("FilterBar"));
+ filterBarConfig.writeEntry("caseSensitive", this->m_caseSensitiveButton->isChecked());
+ filterBarConfig.writeEntry("filterMode", this->m_filterModeComboBox->currentIndex());
+}
void FilterBar::closeFilterBar()
{
@@ -71,6 +117,16 @@ void FilterBar::selectAll()
m_filterInput->selectAll();
}
+KFileItemModelFilter::FilterMode FilterBar::filterMode() const
+{
+ return m_filterModeComboBox->itemData(m_filterModeComboBox->currentIndex()).value<KFileItemModelFilter::FilterMode>();
+}
+
+bool FilterBar::isCaseSensitive() const
+{
+ return m_caseSensitiveButton->isChecked();
+}
+
void FilterBar::clear()
{
m_filterInput->clear();
@@ -93,6 +149,39 @@ void FilterBar::slotToggleLockButton(bool checked)
}
}
+void FilterBar::updateInvalidPatternView()
+{
+ bool valid = true;
+
+ KFileItemModelFilter::FilterMode current_filter_mode = filterMode();
+ if (current_filter_mode != KFileItemModelFilter::FilterMode::PlainText) {
+ QRegularExpression regExp = QRegularExpression();
+ QString pattern = m_filterInput->text();
+
+ QRegularExpression::PatternOptions options =
+ m_caseSensitiveButton->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption;
+ if (current_filter_mode == KFileItemModelFilter::FilterMode::Regex) {
+ regExp.setPattern(pattern);
+ regExp.setPatternOptions(options);
+ } else if (current_filter_mode == KFileItemModelFilter::FilterMode::Glob) {
+ regExp.setPattern(QRegularExpression::wildcardToRegularExpression(pattern, QRegularExpression::UnanchoredWildcardConversion));
+ regExp.setPatternOptions(options);
+ }
+
+ valid = regExp.isValid();
+ }
+
+ if (valid) {
+ m_filterInput->setPalette(QPalette());
+ m_invalidPatternAction->setVisible(false);
+ } else {
+ auto pal = m_filterInput->palette();
+ KColorScheme::adjustBackground(pal, KColorScheme::NegativeBackground);
+ m_filterInput->setPalette(pal);
+ m_invalidPatternAction->setVisible(true);
+ }
+}
+
void FilterBar::showEvent(QShowEvent *event)
{
if (!event->spontaneous()) {
@@ -137,7 +226,10 @@ void FilterBar::keyPressEvent(QKeyEvent *event)
int FilterBar::preferredHeight() const
{
- return std::max(m_filterInput->sizeHint().height(), m_lockButton->sizeHint().height());
+ return std::max({m_filterInput->sizeHint().height(),
+ m_lockButton->sizeHint().height(),
+ m_caseSensitiveButton->sizeHint().height(),
+ m_filterModeComboBox->sizeHint().height()});
}
#include "moc_filterbar.cpp"
diff --git a/src/filterbar/filterbar.h b/src/filterbar/filterbar.h
index 1424f4cb8..924c6dba3 100644
--- a/src/filterbar/filterbar.h
+++ b/src/filterbar/filterbar.h
@@ -10,9 +10,12 @@
#define FILTERBAR_H
#include "animatedheightwidget.h"
+#include "kitemviews/private/kfileitemmodelfilter.h"
class QLineEdit;
class QToolButton;
+class QComboBox;
+class QAction;
/**
* @brief Provides an input field for filtering the currently shown items.
@@ -35,6 +38,9 @@ public:
*/
void selectAll();
+ KFileItemModelFilter::FilterMode filterMode() const;
+ bool isCaseSensitive() const;
+
public Q_SLOTS:
/** Clears the input field. */
void clear();
@@ -51,6 +57,16 @@ Q_SIGNALS:
void filterChanged(const QString &nameFilter);
/**
+ * Emitted when the case sensitive mode has been changed
+ */
+ void caseSensitiveChanged(bool caseSensitive);
+
+ /**
+ * Emitted when the filter mode has been changed
+ */
+ void filterModeChanged(KFileItemModelFilter::FilterMode mode);
+
+ /**
* Emitted as soon as the filterbar should get closed.
*/
void closeRequest();
@@ -70,6 +86,12 @@ protected:
private:
QLineEdit *m_filterInput;
QToolButton *m_lockButton;
+ QToolButton *m_caseSensitiveButton;
+ QComboBox *m_filterModeComboBox;
+ QAction *m_invalidPatternAction;
+
+ /** Enable or disable the alterative view for when a pattern is invalid */
+ void updateInvalidPatternView();
};
#endif
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp
index c8bac0b9d..1f291d40c 100644
--- a/src/kitemviews/kfileitemmodel.cpp
+++ b/src/kitemviews/kfileitemmodel.cpp
@@ -817,6 +817,34 @@ QStringList KFileItemModel::mimeTypeFilters() const
return m_filter.mimeTypes();
}
+void KFileItemModel::setFilterMode(KFileItemModelFilter::FilterMode mode)
+{
+ if (m_filter.filterMode() != mode) {
+ dispatchPendingItemsToInsert();
+ m_filter.setFilterMode(mode);
+ applyFilters();
+ }
+}
+
+KFileItemModelFilter::FilterMode KFileItemModel::filterMode() const
+{
+ return m_filter.filterMode();
+}
+
+void KFileItemModel::setFilterCaseSensitive(bool caseSensitive)
+{
+ if (m_filter.isCaseSensitive() != caseSensitive) {
+ dispatchPendingItemsToInsert();
+ m_filter.setCaseSensitive(caseSensitive);
+ applyFilters();
+ }
+}
+
+bool KFileItemModel::isFilterCaseSensitive() const
+{
+ return m_filter.isCaseSensitive();
+}
+
void KFileItemModel::setExcludeMimeTypeFilter(const QStringList &filters)
{
if (m_filter.excludeMimeTypes() != filters) {
diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h
index 3749b0c1b..2a6e710c3 100644
--- a/src/kitemviews/kfileitemmodel.h
+++ b/src/kitemviews/kfileitemmodel.h
@@ -184,6 +184,12 @@ public:
void setMimeTypeFilters(const QStringList &filters);
QStringList mimeTypeFilters() const;
+ void setFilterMode(KFileItemModelFilter::FilterMode mode);
+ KFileItemModelFilter::FilterMode filterMode() const;
+
+ void setFilterCaseSensitive(bool caseSensitive);
+ bool isFilterCaseSensitive() const;
+
void setExcludeMimeTypeFilter(const QStringList &filters);
QStringList excludeMimeTypeFilter() const;
diff --git a/src/kitemviews/private/kfileitemmodelfilter.cpp b/src/kitemviews/private/kfileitemmodelfilter.cpp
index 45c62e7ca..48d2f6276 100644
--- a/src/kitemviews/private/kfileitemmodelfilter.cpp
+++ b/src/kitemviews/private/kfileitemmodelfilter.cpp
@@ -13,7 +13,8 @@
#include <KFileItem>
KFileItemModelFilter::KFileItemModelFilter()
- : m_useRegExp(false)
+ : m_filterMode(Glob)
+ , m_caseSensitive(false)
, m_regExp(nullptr)
, m_lowerCasePattern()
, m_pattern()
@@ -31,16 +32,29 @@ void KFileItemModelFilter::setPattern(const QString &filter)
m_pattern = filter;
m_lowerCasePattern = filter.toLower();
- if (filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['))) {
- if (!m_regExp) {
- m_regExp = new QRegularExpression();
- m_regExp->setPatternOptions(QRegularExpression::CaseInsensitiveOption);
- }
- m_regExp->setPattern(QRegularExpression::wildcardToRegularExpression(filter));
- m_useRegExp = m_regExp->isValid();
- } else {
- m_useRegExp = false;
- }
+ updateFilter();
+}
+
+void KFileItemModelFilter::setFilterMode(FilterMode mode)
+{
+ m_filterMode = mode;
+ updateFilter();
+}
+
+KFileItemModelFilter::FilterMode KFileItemModelFilter::filterMode() const
+{
+ return m_filterMode;
+}
+
+void KFileItemModelFilter::setCaseSensitive(bool caseSensitive)
+{
+ m_caseSensitive = caseSensitive;
+ updateFilter();
+}
+
+bool KFileItemModelFilter::isCaseSensitive() const
+{
+ return m_caseSensitive;
}
QString KFileItemModelFilter::pattern() const
@@ -48,6 +62,26 @@ QString KFileItemModelFilter::pattern() const
return m_pattern;
}
+void KFileItemModelFilter::updateFilter()
+{
+ if (m_filterMode == PlainText) {
+ return;
+ }
+
+ if (!m_regExp) {
+ m_regExp = new QRegularExpression();
+ }
+
+ QRegularExpression::PatternOptions options = m_caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption;
+ if (m_filterMode == Regex) {
+ m_regExp->setPattern(m_pattern);
+ m_regExp->setPatternOptions(options);
+ } else if (m_filterMode == Glob) {
+ m_regExp->setPattern(QRegularExpression::wildcardToRegularExpression(m_pattern, QRegularExpression::UnanchoredWildcardConversion));
+ m_regExp->setPatternOptions(options);
+ }
+}
+
void KFileItemModelFilter::setMimeTypes(const QStringList &types)
{
m_mimeTypes = types;
@@ -98,8 +132,10 @@ bool KFileItemModelFilter::matches(const KFileItem &item) const
bool KFileItemModelFilter::matchesPattern(const KFileItem &item) const
{
- if (m_useRegExp) {
- return m_regExp->match(item.text()).hasMatch();
+ if (m_filterMode == Glob || m_filterMode == Regex) {
+ return m_regExp->isValid() && m_regExp->match(item.text()).hasMatch();
+ } else if (m_caseSensitive) {
+ return item.text().contains(m_pattern);
} else {
return item.text().toLower().contains(m_lowerCasePattern);
}
diff --git a/src/kitemviews/private/kfileitemmodelfilter.h b/src/kitemviews/private/kfileitemmodelfilter.h
index ce6cbeebb..9d93d42cc 100644
--- a/src/kitemviews/private/kfileitemmodelfilter.h
+++ b/src/kitemviews/private/kfileitemmodelfilter.h
@@ -28,16 +28,36 @@ public:
KFileItemModelFilter();
virtual ~KFileItemModelFilter();
+ /** Filtering modes of KFileItemModelFilter */
+ enum FilterMode {
+ /** Substring matching. */
+ PlainText = 0,
+ /** Matching with glob, default. */
+ Glob,
+ /** Matching with regex. */
+ Regex
+ };
+
/**
* Sets the pattern that is used for a comparison with the item
- * in KFileItemModelFilter::matches(). Per default the pattern
- * defines a sub-string. As soon as the pattern contains at least
- * a '*', '?' or '[' the pattern represents a regular expression.
+ * in KFileItemModelFilter::matches().
*/
void setPattern(const QString &pattern);
QString pattern() const;
/**
+ * Sets the filtering mode used in KFileItemModelFilter::matches().
+ */
+ void setFilterMode(FilterMode mode);
+ FilterMode filterMode() const;
+
+ /**
+ * Enable or disable the case sensitive filtering.
+ */
+ void setCaseSensitive(bool caseSensitive);
+ bool isCaseSensitive() const;
+
+ /**
* Set the list of mimetypes that are used for comparison with the
* item in KFileItemModelFilter::matchesMimeType.
*/
@@ -73,8 +93,14 @@ private:
*/
bool matchesType(const KFileItem &item) const;
- bool m_useRegExp; // If true, m_regExp is used for filtering,
- // otherwise m_lowerCaseFilter is used.
+ /**
+ * Instantiate and configure m_regExp according to m_filterMode and m_caseSensitive.
+ */
+ void updateFilter();
+
+ FilterMode m_filterMode; // The current filtering mode.
+ bool m_caseSensitive; // If true the matching will be case sensitive.
+
QRegularExpression *m_regExp;
QString m_lowerCasePattern; // Lowercase version of m_filter for
// faster comparison in matches().
diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp
index b48e1ae74..2213e2c82 100644
--- a/src/views/dolphinview.cpp
+++ b/src/views/dolphinview.cpp
@@ -639,6 +639,26 @@ QStringList DolphinView::mimeTypeFilters() const
return m_model->mimeTypeFilters();
}
+void DolphinView::setFilterMode(KFileItemModelFilter::FilterMode mode)
+{
+ m_model->setFilterMode(mode);
+}
+
+KFileItemModelFilter::FilterMode DolphinView::filterMode() const
+{
+ return m_model->filterMode();
+}
+
+void DolphinView::setFilterCaseSensitive(bool caseSensitive)
+{
+ m_model->setFilterCaseSensitive(caseSensitive);
+}
+
+bool DolphinView::isFilterCaseSensitive() const
+{
+ return m_model->isFilterCaseSensitive();
+}
+
void DolphinView::requestStatusBarText()
{
if (m_statJobForStatusBarText) {
diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h
index 67233b668..6aa5b595d 100644
--- a/src/views/dolphinview.h
+++ b/src/views/dolphinview.h
@@ -10,6 +10,7 @@
#include "dolphin_export.h"
#include "dolphintabwidget.h"
+#include "kitemviews/private/kfileitemmodelfilter.h"
#include "tooltips/tooltipmanager.h"
#include "config-dolphin.h"
@@ -277,6 +278,18 @@ public:
QStringList mimeTypeFilters() const;
/**
+ * Sets the filtering mode of the currently used nameFilter.
+ */
+ void setFilterMode(KFileItemModelFilter::FilterMode mode);
+ KFileItemModelFilter::FilterMode filterMode() const;
+
+ /**
+ * Enables or disable the caseSensitive matching of the currently used nameFilter.
+ */
+ void setFilterCaseSensitive(bool caseSensitive);
+ bool isFilterCaseSensitive() const;
+
+ /**
* Tells the view to generate an updated status bar text. The result
* is returned through the statusBarTextChanged(QString statusBarText) signal.
* It will carry a textual representation of the state of the current