From c2cf898ccdcd624a0720689ce09009e1b63f34de Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Wed, 19 Jun 2024 15:25:48 +0300 Subject: [PATCH] Allow to use regular expression to filter torrent content PR #20944. Closes #19934. --- src/gui/CMakeLists.txt | 3 + src/gui/addnewtorrentdialog.cpp | 30 ++++++++- src/gui/addnewtorrentdialog.h | 6 +- src/gui/filterpatternformat.h | 48 +++++++++++++++ src/gui/filterpatternformatmenu.cpp | 82 +++++++++++++++++++++++++ src/gui/filterpatternformatmenu.h | 45 ++++++++++++++ src/gui/properties/propertieswidget.cpp | 30 ++++++++- src/gui/properties/propertieswidget.h | 9 ++- src/gui/torrentcontentwidget.cpp | 18 ++++-- src/gui/torrentcontentwidget.h | 5 +- 10 files changed, 263 insertions(+), 13 deletions(-) create mode 100644 src/gui/filterpatternformat.h create mode 100644 src/gui/filterpatternformatmenu.cpp create mode 100644 src/gui/filterpatternformatmenu.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 1a426ed6e..9db94e77e 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -52,6 +52,8 @@ add_library(qbt_gui STATIC desktopintegration.h downloadfromurldialog.h executionlogwidget.h + filterpatternformat.h + filterpatternformatmenu.h flowlayout.h fspathedit.h fspathedit_p.h @@ -151,6 +153,7 @@ add_library(qbt_gui STATIC desktopintegration.cpp downloadfromurldialog.cpp executionlogwidget.cpp + filterpatternformatmenu.cpp flowlayout.cpp fspathedit.cpp fspathedit_p.cpp diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 98c778e2d..07eb4cc16 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022-2023 Vladimir Golovnev + * Copyright (C) 2022-2024 Vladimir Golovnev * Copyright (C) 2012 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -64,6 +64,7 @@ #include "base/utils/fs.h" #include "base/utils/misc.h" #include "base/utils/string.h" +#include "filterpatternformatmenu.h" #include "lineedit.h" #include "torrenttagsdialog.h" @@ -295,6 +296,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to , m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)} , m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)} , m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)} + , m_storeFilterPatternFormat {u"GUI/" SETTINGS_KEY(u"FilterPatternFormat"_s)} { m_ui->setupUi(this); @@ -321,6 +323,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to // Torrent content filtering m_filterLine->setPlaceholderText(tr("Filter files...")); m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_filterLine->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_filterLine, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::showContentFilterContextMenu); m_ui->contentFilterLayout->insertWidget(3, m_filterLine); const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this); connect(focusSearchHotkey, &QShortcut::activated, this, [this]() @@ -365,7 +369,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to }); dlg->open(); }); - connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern); + connect(m_filterLine, &LineEdit::textChanged, this, &AddNewTorrentDialog::setContentFilterPattern); connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll); connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone); connect(Preferences::instance(), &Preferences::changed, [] @@ -696,6 +700,28 @@ void AddNewTorrentDialog::saveTorrentFile() } } +void AddNewTorrentDialog::showContentFilterContextMenu() +{ + QMenu *menu = m_filterLine->createStandardContextMenu(); + + auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu); + connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format) + { + m_storeFilterPatternFormat = format; + setContentFilterPattern(); + }); + + menu->addSeparator(); + menu->addMenu(formatMenu); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(QCursor::pos()); +} + +void AddNewTorrentDialog::setContentFilterPattern() +{ + m_ui->contentTreeView->setFilterPattern(m_filterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards)); +} + void AddNewTorrentDialog::populateSavePaths() { Q_ASSERT(m_currentContext); diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index 1dd1eb1b1..f81bd305b 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022-2023 Vladimir Golovnev + * Copyright (C) 2022-2024 Vladimir Golovnev * Copyright (C) 2012 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -35,6 +35,7 @@ #include "base/path.h" #include "base/settingvalue.h" +#include "filterpatternformat.h" class LineEdit; @@ -92,6 +93,8 @@ private: void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = {}); void setupTreeview(); void saveTorrentFile(); + void showContentFilterContextMenu(); + void setContentFilterPattern(); Ui::AddNewTorrentDialog *m_ui = nullptr; std::unique_ptr m_contentAdaptor; @@ -107,4 +110,5 @@ private: SettingValue m_storeRememberLastSavePath; SettingValue m_storeTreeHeaderState; SettingValue m_storeSplitterState; + SettingValue m_storeFilterPatternFormat; }; diff --git a/src/gui/filterpatternformat.h b/src/gui/filterpatternformat.h new file mode 100644 index 000000000..71b3c5c51 --- /dev/null +++ b/src/gui/filterpatternformat.h @@ -0,0 +1,48 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised +// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files. +// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779 +inline namespace FilterPatternFormatNS +{ + Q_NAMESPACE + + enum class FilterPatternFormat + { + PlainText, + Wildcards, + Regex + }; + + Q_ENUM_NS(FilterPatternFormat) +} diff --git a/src/gui/filterpatternformatmenu.cpp b/src/gui/filterpatternformatmenu.cpp new file mode 100644 index 000000000..d13ab685a --- /dev/null +++ b/src/gui/filterpatternformatmenu.cpp @@ -0,0 +1,82 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "filterpatternformatmenu.h" + +#include + +FilterPatternFormatMenu::FilterPatternFormatMenu(const FilterPatternFormat format, QWidget *parent) + : QMenu(parent) +{ + setTitle(tr("Pattern Format")); + + auto *patternFormatGroup = new QActionGroup(this); + patternFormatGroup->setExclusive(true); + + QAction *plainTextAction = addAction(tr("Plain text")); + plainTextAction->setCheckable(true); + patternFormatGroup->addAction(plainTextAction); + + QAction *wildcardsAction = addAction(tr("Wildcards")); + wildcardsAction->setCheckable(true); + patternFormatGroup->addAction(wildcardsAction); + + QAction *regexAction = addAction(tr("Regular expression")); + regexAction->setCheckable(true); + patternFormatGroup->addAction(regexAction); + + switch (format) + { + case FilterPatternFormat::Wildcards: + default: + wildcardsAction->setChecked(true); + break; + case FilterPatternFormat::PlainText: + plainTextAction->setChecked(true); + break; + case FilterPatternFormat::Regex: + regexAction->setChecked(true); + break; + } + + connect(plainTextAction, &QAction::toggled, this, [this](const bool checked) + { + if (checked) + emit patternFormatChanged(FilterPatternFormat::PlainText); + }); + connect(wildcardsAction, &QAction::toggled, this, [this](const bool checked) + { + if (checked) + emit patternFormatChanged(FilterPatternFormat::Wildcards); + }); + connect(regexAction, &QAction::toggled, this, [this](const bool checked) + { + if (checked) + emit patternFormatChanged(FilterPatternFormat::Regex); + }); +} diff --git a/src/gui/filterpatternformatmenu.h b/src/gui/filterpatternformatmenu.h new file mode 100644 index 000000000..e201fd89d --- /dev/null +++ b/src/gui/filterpatternformatmenu.h @@ -0,0 +1,45 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +#include "filterpatternformat.h" + +class FilterPatternFormatMenu final : public QMenu +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(FilterPatternFormatMenu) + +public: + explicit FilterPatternFormatMenu(FilterPatternFormat format, QWidget *parent = nullptr); + +signals: + void patternFormatChanged(FilterPatternFormat format); +}; diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 980978cb9..b35623b96 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2024 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -52,6 +52,7 @@ #include "base/utils/misc.h" #include "base/utils/string.h" #include "gui/autoexpandabledialog.h" +#include "gui/filterpatternformatmenu.h" #include "gui/lineedit.h" #include "gui/trackerlist/trackerlistwidget.h" #include "gui/uithememanager.h" @@ -66,6 +67,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget(parent) , m_ui {new Ui::PropertiesWidget} + , m_storeFilterPatternFormat {u"GUI/PropertiesWidget/FilterPatternFormat"_s} { m_ui->setupUi(this); #ifndef Q_OS_MACOS @@ -78,7 +80,9 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) m_contentFilterLine = new LineEdit(this); m_contentFilterLine->setPlaceholderText(tr("Filter files...")); m_contentFilterLine->setFixedWidth(300); - connect(m_contentFilterLine, &LineEdit::textChanged, m_ui->filesList, &TorrentContentWidget::setFilterPattern); + m_contentFilterLine->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_contentFilterLine, &QWidget::customContextMenuRequested, this, &PropertiesWidget::showContentFilterContextMenu); + connect(m_contentFilterLine, &LineEdit::textChanged, this, &PropertiesWidget::setContentFilterPattern); m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine); m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open); @@ -274,6 +278,28 @@ void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent) m_ui->labelSavePathVal->setText(m_torrent->savePath().toString()); } +void PropertiesWidget::showContentFilterContextMenu() +{ + QMenu *menu = m_contentFilterLine->createStandardContextMenu(); + + auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu); + connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format) + { + m_storeFilterPatternFormat = format; + setContentFilterPattern(); + }); + + menu->addSeparator(); + menu->addMenu(formatMenu); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(QCursor::pos()); +} + +void PropertiesWidget::setContentFilterPattern() +{ + m_ui->filesList->setFilterPattern(m_contentFilterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards)); +} + void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent *const torrent) { if (torrent == m_torrent) diff --git a/src/gui/properties/propertieswidget.h b/src/gui/properties/propertieswidget.h index 31a1963b0..21b1b5fc5 100644 --- a/src/gui/properties/propertieswidget.h +++ b/src/gui/properties/propertieswidget.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2024 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -32,7 +32,8 @@ #include #include -#include "base/pathfwd.h" +#include "base/settingvalue.h" +#include "gui/filterpatternformat.h" class QPushButton; class QTreeView; @@ -102,6 +103,8 @@ private slots: private: QPushButton *getButtonFromIndex(int index); + void showContentFilterContextMenu(); + void setContentFilterPattern(); Ui::PropertiesWidget *m_ui = nullptr; BitTorrent::Torrent *m_torrent = nullptr; @@ -115,4 +118,6 @@ private: PropTabBar *m_tabBar = nullptr; LineEdit *m_contentFilterLine = nullptr; int m_handleWidth = -1; + + SettingValue m_storeFilterPatternFormat; }; diff --git a/src/gui/torrentcontentwidget.cpp b/src/gui/torrentcontentwidget.cpp index 1b3817408..6e6e9d051 100644 --- a/src/gui/torrentcontentwidget.cpp +++ b/src/gui/torrentcontentwidget.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2024 Vladimir Golovnev * Copyright (C) 2014 Ivan Sorokin * * This program is free software; you can redistribute it and/or @@ -173,10 +173,20 @@ Path TorrentContentWidget::getItemPath(const QModelIndex &index) const return path; } -void TorrentContentWidget::setFilterPattern(const QString &patternText) +void TorrentContentWidget::setFilterPattern(const QString &patternText, const FilterPatternFormat format) { - const QString pattern = Utils::String::wildcardToRegexPattern(patternText); - m_filterModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + if (format == FilterPatternFormat::PlainText) + { + m_filterModel->setFilterFixedString(patternText); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + } + else + { + const QString pattern = ((format == FilterPatternFormat::Regex) + ? patternText : Utils::String::wildcardToRegexPattern(patternText)); + m_filterModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + } + if (patternText.isEmpty()) { collapseAll(); diff --git a/src/gui/torrentcontentwidget.h b/src/gui/torrentcontentwidget.h index 4baccb883..1e9f0835c 100644 --- a/src/gui/torrentcontentwidget.h +++ b/src/gui/torrentcontentwidget.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2024 Vladimir Golovnev * Copyright (C) 2014 Ivan Sorokin * * This program is free software; you can redistribute it and/or @@ -33,6 +33,7 @@ #include "base/bittorrent/downloadpriority.h" #include "base/pathfwd.h" +#include "filterpatternformat.h" class QShortcut; @@ -92,7 +93,7 @@ public: int getFileIndex(const QModelIndex &index) const; Path getItemPath(const QModelIndex &index) const; - void setFilterPattern(const QString &patternText); + void setFilterPattern(const QString &patternText, FilterPatternFormat format = FilterPatternFormat::Wildcards); void checkAll(); void checkNone();