Improve search results filtering implementation

PR #23430.
Closes #23396.
This commit is contained in:
Vladimir Golovnev
2025-11-02 14:47:39 +03:00
committed by GitHub
parent 1be4e646e1
commit 85f1c774f6
4 changed files with 126 additions and 51 deletions

View File

@@ -85,6 +85,8 @@ namespace
} }
} }
using Utils::Misc::SizeUnit;
SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidget *parent) SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidget *parent)
: GUIApplicationComponent(app, parent) : GUIApplicationComponent(app, parent)
, m_nameFilteringMode {u"Search/FilteringMode"_s} , m_nameFilteringMode {u"Search/FilteringMode"_s}
@@ -99,6 +101,8 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
header()->setStretchLastSection(false); header()->setStretchLastSection(false);
header()->setTextElideMode(Qt::ElideRight); header()->setTextElideMode(Qt::ElideRight);
fillFilterComboBoxes();
// Set Search results list model // Set Search results list model
m_searchListModel = new QStandardItemModel(0, SearchSortModel::NB_SEARCH_COLUMNS, this); m_searchListModel = new QStandardItemModel(0, SearchSortModel::NB_SEARCH_COLUMNS, this);
m_searchListModel->setHeaderData(SearchSortModel::NAME, Qt::Horizontal, tr("Name", "i.e: file name")); m_searchListModel->setHeaderData(SearchSortModel::NAME, Qt::Horizontal, tr("Name", "i.e: file name"));
@@ -116,6 +120,13 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
m_proxyModel = new SearchSortModel(this); m_proxyModel = new SearchSortModel(this);
m_proxyModel->setDynamicSortFilter(true); m_proxyModel->setDynamicSortFilter(true);
m_proxyModel->setSourceModel(m_searchListModel); m_proxyModel->setSourceModel(m_searchListModel);
m_proxyModel->enableNameFilter(m_nameFilteringMode.get(NameFilteringMode::OnlyNames) == NameFilteringMode::OnlyNames);
m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value());
m_proxyModel->setSizeFilter(sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex()))
, sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
updateResultsCount();
m_ui->resultsBrowser->setModel(m_proxyModel); m_ui->resultsBrowser->setModel(m_proxyModel);
m_ui->resultsBrowser->hideColumn(SearchSortModel::DL_LINK); // Hide url column m_ui->resultsBrowser->hideColumn(SearchSortModel::DL_LINK); // Hide url column
@@ -154,8 +165,6 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
connect(header(), &QHeaderView::sectionMoved, this, &SearchJobWidget::saveSettings); connect(header(), &QHeaderView::sectionMoved, this, &SearchJobWidget::saveSettings);
connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings); connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings);
fillFilterComboBoxes();
m_lineEditSearchResultsFilter = new LineEdit(this); m_lineEditSearchResultsFilter = new LineEdit(this);
m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results...")); m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results..."));
m_lineEditSearchResultsFilter->setContextMenuPolicy(Qt::CustomContextMenu); m_lineEditSearchResultsFilter->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -163,24 +172,17 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
connect(m_lineEditSearchResultsFilter, &LineEdit::textChanged, this, &SearchJobWidget::filterSearchResults); connect(m_lineEditSearchResultsFilter, &LineEdit::textChanged, this, &SearchJobWidget::filterSearchResults);
m_ui->horizontalLayout->insertWidget(0, m_lineEditSearchResultsFilter); m_ui->horizontalLayout->insertWidget(0, m_lineEditSearchResultsFilter);
connect(m_ui->filterMode, qOverload<int>(&QComboBox::currentIndexChanged) connect(m_ui->filterMode, qOverload<int>(&QComboBox::currentIndexChanged), this, &SearchJobWidget::updateNameFilter);
, this, &SearchJobWidget::updateFilter); connect(m_ui->minSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSeedsFilter);
connect(m_ui->minSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter); connect(m_ui->minSeeds, qOverload<int>(&QSpinBox::valueChanged), this, &SearchJobWidget::updateSeedsFilter);
connect(m_ui->minSeeds, qOverload<int>(&QSpinBox::valueChanged) connect(m_ui->maxSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSeedsFilter);
, this, &SearchJobWidget::updateFilter); connect(m_ui->maxSeeds, qOverload<int>(&QSpinBox::valueChanged), this, &SearchJobWidget::updateSeedsFilter);
connect(m_ui->maxSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter); connect(m_ui->minSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSizeFilter);
connect(m_ui->maxSeeds, qOverload<int>(&QSpinBox::valueChanged) connect(m_ui->minSize, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &SearchJobWidget::updateSizeFilter);
, this, &SearchJobWidget::updateFilter); connect(m_ui->maxSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSizeFilter);
connect(m_ui->minSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter); connect(m_ui->maxSize, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &SearchJobWidget::updateSizeFilter);
connect(m_ui->minSize, qOverload<double>(&QDoubleSpinBox::valueChanged) connect(m_ui->minSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged), this, &SearchJobWidget::updateSizeFilter);
, this, &SearchJobWidget::updateFilter); connect(m_ui->maxSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged), this, &SearchJobWidget::updateSizeFilter);
connect(m_ui->maxSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
connect(m_ui->maxSize, qOverload<double>(&QDoubleSpinBox::valueChanged)
, this, &SearchJobWidget::updateFilter);
connect(m_ui->minSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
, this, &SearchJobWidget::updateFilter);
connect(m_ui->maxSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
, this, &SearchJobWidget::updateFilter);
connect(m_ui->resultsBrowser, &QAbstractItemView::doubleClicked, this, &SearchJobWidget::onItemDoubleClicked); connect(m_ui->resultsBrowser, &QAbstractItemView::doubleClicked, this, &SearchJobWidget::onItemDoubleClicked);
@@ -193,7 +195,6 @@ SearchJobWidget::SearchJobWidget(const QString &id, const QString &searchPattern
{ {
m_searchPattern = searchPattern; m_searchPattern = searchPattern;
m_proxyModel->setNameFilter(m_searchPattern); m_proxyModel->setNameFilter(m_searchPattern);
updateFilter();
appendSearchResults(searchResults); appendSearchResults(searchResults);
} }
@@ -301,8 +302,8 @@ void SearchJobWidget::assignSearchHandler(SearchHandler *searchHandler)
m_searchPattern = m_searchHandler->pattern(); m_searchPattern = m_searchHandler->pattern();
m_proxyModel->setNameFilter(m_searchPattern); m_proxyModel->setNameFilter(m_searchPattern);
updateFilter();
updateResultsCount();
setStatus(Status::Ongoing); setStatus(Status::Ongoing);
} }
@@ -454,26 +455,34 @@ void SearchJobWidget::updateResultsCount()
emit resultsCountUpdated(); emit resultsCountUpdated();
} }
void SearchJobWidget::updateFilter() void SearchJobWidget::updateNameFilter()
{ {
using Utils::Misc::SizeUnit; const auto filteringMode = static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());
m_proxyModel->enableNameFilter(filteringMode == NameFilteringMode::OnlyNames);
m_nameFilteringMode = filteringMode;
m_proxyModel->enableNameFilter(filteringMode() == NameFilteringMode::OnlyNames); updateResultsCount();
}
void SearchJobWidget::updateSeedsFilter()
{
// we update size and seeds filter parameters in the model even if they are disabled // we update size and seeds filter parameters in the model even if they are disabled
m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value()); m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value());
m_proxyModel->setSizeFilter(
sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex())),
sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
m_nameFilteringMode = filteringMode(); updateResultsCount();
}
void SearchJobWidget::updateSizeFilter()
{
// we update size and seeds filter parameters in the model even if they are disabled
m_proxyModel->setSizeFilter(sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex()))
, sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
m_proxyModel->invalidate();
updateResultsCount(); updateResultsCount();
} }
void SearchJobWidget::fillFilterComboBoxes() void SearchJobWidget::fillFilterComboBoxes()
{ {
using Utils::Misc::SizeUnit;
using Utils::Misc::unitString; using Utils::Misc::unitString;
QStringList unitStrings; QStringList unitStrings;
@@ -500,16 +509,15 @@ void SearchJobWidget::fillFilterComboBoxes()
m_ui->filterMode->addItem(tr("Torrent names only"), static_cast<int>(NameFilteringMode::OnlyNames)); m_ui->filterMode->addItem(tr("Torrent names only"), static_cast<int>(NameFilteringMode::OnlyNames));
m_ui->filterMode->addItem(tr("Everywhere"), static_cast<int>(NameFilteringMode::Everywhere)); m_ui->filterMode->addItem(tr("Everywhere"), static_cast<int>(NameFilteringMode::Everywhere));
const auto selectedFilteringMode = static_cast<int>(m_nameFilteringMode.get(NameFilteringMode::OnlyNames));
const QVariant selectedMode = static_cast<int>(m_nameFilteringMode.get(NameFilteringMode::OnlyNames)); const int index = m_ui->filterMode->findData(selectedFilteringMode);
const int index = m_ui->filterMode->findData(selectedMode);
m_ui->filterMode->setCurrentIndex((index == -1) ? 0 : index); m_ui->filterMode->setCurrentIndex((index == -1) ? 0 : index);
} }
void SearchJobWidget::filterSearchResults(const QString &name) void SearchJobWidget::filterSearchResults(const QString &name)
{ {
const QString pattern = (Preferences::instance()->getRegexAsFilteringPatternForSearchJob() const QString pattern = (Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
? name : Utils::String::wildcardToRegexPattern(name)); ? name : Utils::String::wildcardToRegexPattern(name));
m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
updateResultsCount(); updateResultsCount();
} }
@@ -557,11 +565,6 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
menu->popup(event->globalPos()); menu->popup(event->globalPos());
} }
SearchJobWidget::NameFilteringMode SearchJobWidget::filteringMode() const
{
return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());
}
void SearchJobWidget::loadSettings() void SearchJobWidget::loadSettings()
{ {
header()->restoreState(Preferences::instance()->getSearchTabHeaderState()); header()->restoreState(Preferences::instance()->getSearchTabHeaderState());

View File

@@ -106,7 +106,9 @@ private:
void loadSettings(); void loadSettings();
void saveSettings() const; void saveSettings() const;
void updateFilter(); void updateNameFilter();
void updateSeedsFilter();
void updateSizeFilter();
void filterSearchResults(const QString &name); void filterSearchResults(const QString &name);
void showFilterContextMenu(); void showFilterContextMenu();
void contextMenuEvent(QContextMenuEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override;
@@ -119,7 +121,6 @@ private:
void downloadTorrent(const QModelIndex &rowIndex, AddTorrentOption option = AddTorrentOption::Default); void downloadTorrent(const QModelIndex &rowIndex, AddTorrentOption option = AddTorrentOption::Default);
void addTorrentToSession(const QString &source, AddTorrentOption option = AddTorrentOption::Default); void addTorrentToSession(const QString &source, AddTorrentOption option = AddTorrentOption::Default);
void fillFilterComboBoxes(); void fillFilterComboBoxes();
NameFilteringMode filteringMode() const;
QHeaderView *header() const; QHeaderView *header() const;
int visibleColumnsCount() const; int visibleColumnsCount() const;
void setRowColor(int row, const QColor &color); void setRowColor(int row, const QColor &color);

View File

@@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2013 sledgehammer999 <hammered999@gmail.com> * Copyright (C) 2013 sledgehammer999 <hammered999@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -39,34 +40,103 @@ SearchSortModel::SearchSortModel(QObject *parent)
void SearchSortModel::enableNameFilter(const bool enabled) void SearchSortModel::enableNameFilter(const bool enabled)
{ {
if (m_isNameFilterEnabled == enabled)
return;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
m_isNameFilterEnabled = enabled; m_isNameFilterEnabled = enabled;
endFilterChange(Direction::Rows);
#else
m_isNameFilterEnabled = enabled;
invalidateRowsFilter();
#endif
} }
void SearchSortModel::setNameFilter(const QString &searchTerm) void SearchSortModel::setNameFilter(const QString &searchTerm)
{ {
if (m_searchTerm == searchTerm)
return;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
m_searchTerm = searchTerm; m_searchTerm = searchTerm;
if ((searchTerm.length() > 2) && searchTerm.startsWith(u'"') && searchTerm.endsWith(u'"')) if ((searchTerm.length() > 2) && searchTerm.startsWith(u'"') && searchTerm.endsWith(u'"'))
m_searchTermWords = QStringList(m_searchTerm.sliced(1, (m_searchTerm.length() - 2))); m_searchTermWords = QStringList(m_searchTerm.sliced(1, (m_searchTerm.length() - 2)));
else else
m_searchTermWords = searchTerm.split(u' ', Qt::SkipEmptyParts); m_searchTermWords = searchTerm.split(u' ', Qt::SkipEmptyParts);
endFilterChange(Direction::Rows);
#else
m_searchTerm = searchTerm;
if ((searchTerm.length() > 2) && searchTerm.startsWith(u'"') && searchTerm.endsWith(u'"'))
m_searchTermWords = QStringList(m_searchTerm.sliced(1, (m_searchTerm.length() - 2)));
else
m_searchTermWords = searchTerm.split(u' ', Qt::SkipEmptyParts);
invalidateRowsFilter();
#endif
} }
void SearchSortModel::setSizeFilter(const qint64 minSize, const qint64 maxSize) void SearchSortModel::setSizeFilter(qint64 minSize, qint64 maxSize)
{ {
m_minSize = std::max(static_cast<qint64>(0), minSize); minSize = std::max<qint64>(0, minSize);
m_maxSize = std::max(static_cast<qint64>(-1), maxSize); maxSize = std::max<qint64>(-1, maxSize);
if ((m_minSize == minSize) && (m_maxSize == maxSize))
return;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
m_minSize = minSize;
m_maxSize = maxSize;
endFilterChange(Direction::Rows);
#else
m_minSize = minSize;
m_maxSize = maxSize;
invalidateRowsFilter();
#endif
} }
void SearchSortModel::setSeedsFilter(const int minSeeds, const int maxSeeds) void SearchSortModel::setSeedsFilter(int minSeeds, int maxSeeds)
{ {
m_minSeeds = std::max(0, minSeeds); minSeeds = std::max(0, minSeeds);
m_maxSeeds = std::max(-1, maxSeeds); maxSeeds = std::max(-1, maxSeeds);
if ((m_minSeeds == minSeeds) && (m_maxSeeds == maxSeeds))
return;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
m_minSeeds = minSeeds;
m_maxSeeds = maxSeeds;
endFilterChange(Direction::Rows);
#else
m_minSeeds = minSeeds;
m_maxSeeds = maxSeeds;
invalidateRowsFilter();
#endif
} }
void SearchSortModel::setLeechesFilter(const int minLeeches, const int maxLeeches) void SearchSortModel::setLeechesFilter(int minLeeches, int maxLeeches)
{ {
m_minLeeches = std::max(0, minLeeches); minLeeches = std::max(0, minLeeches);
m_maxLeeches = std::max(-1, maxLeeches); maxLeeches = std::max(-1, maxLeeches);
if ((m_minLeeches == minLeeches) && (m_maxLeeches == maxLeeches))
return;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
m_minLeeches = minLeeches;
m_maxLeeches = maxLeeches;
endFilterChange(Direction::Rows);
#else
m_minLeeches = minLeeches;
m_maxLeeches = maxLeeches;
invalidateRowsFilter();
#endif
} }
bool SearchSortModel::isNameFilterEnabled() const bool SearchSortModel::isNameFilterEnabled() const

View File

@@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2013 sledgehammer999 <hammered999@gmail.com> * Copyright (C) 2013 sledgehammer999 <hammered999@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or