From 3084a4e11e54b71065a408c7d288489bc72ea8a2 Mon Sep 17 00:00:00 2001 From: Alessio Bonfiglio Date: Wed, 11 Mar 2026 20:52:55 +0100 Subject: 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. --- src/filterbar/filterbar.cpp | 100 ++++++++++++++++++++++++++++++++++++++++++-- src/filterbar/filterbar.h | 22 ++++++++++ 2 files changed, 118 insertions(+), 4 deletions(-) (limited to 'src/filterbar') 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 * SPDX-FileCopyrightText: 2006 Gregor Kališnik * SPDX-FileCopyrightText: 2012 Stuart Citrin + * SPDX-FileCopyrightText: 2026 Alessio Bonfiglio * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "filterbar.h" +#include #include +#include +#include +#include #include +#include #include #include #include +#include #include 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()); + }); + 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(); +} + +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(); @@ -50,6 +56,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. */ @@ -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 -- cgit v1.3