Allow to refresh existing search

PR #22122.
Closes #17184.
This commit is contained in:
Vladimir Golovnev 2025-01-08 17:03:32 +03:00 committed by GitHub
commit f9f4b60b83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 128 additions and 66 deletions

View file

@ -359,7 +359,7 @@ SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QS
// No search pattern entered
Q_ASSERT(!pattern.isEmpty());
return new SearchHandler {pattern, category, usedPlugins, this};
return new SearchHandler(pattern, category, usedPlugins, this);
}
QString SearchPluginManager::categoryFullName(const QString &categoryName)

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -61,12 +61,30 @@ namespace
{
return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText);
}
QString statusText(SearchJobWidget::Status st)
{
switch (st)
{
case SearchJobWidget::Status::Ongoing:
return SearchJobWidget::tr("Searching...");
case SearchJobWidget::Status::Finished:
return SearchJobWidget::tr("Search has finished");
case SearchJobWidget::Status::Aborted:
return SearchJobWidget::tr("Search aborted");
case SearchJobWidget::Status::Error:
return SearchJobWidget::tr("An error occurred during search...");
case SearchJobWidget::Status::NoResults:
return SearchJobWidget::tr("Search returned no results");
default:
return {};
}
}
}
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
: GUIApplicationComponent(app, parent)
, m_ui {new Ui::SearchJobWidget}
, m_searchHandler {searchHandler}
, m_nameFilteringMode {u"Search/FilteringMode"_s}
{
m_ui->setupUi(this);
@ -94,7 +112,6 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
m_proxyModel = new SearchSortModel(this);
m_proxyModel->setDynamicSortFilter(true);
m_proxyModel->setSourceModel(m_searchListModel);
m_proxyModel->setNameFilter(searchHandler->pattern());
m_ui->resultsBrowser->setModel(m_proxyModel);
m_ui->resultsBrowser->hideColumn(SearchSortModel::DL_LINK); // Hide url column
@ -134,8 +151,9 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings);
fillFilterComboBoxes();
setStatusTip(statusText(m_status));
updateFilter();
assignSearchHandler(searchHandler);
m_lineEditSearchResultsFilter = new LineEdit(this);
m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results..."));
@ -165,13 +183,6 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
connect(m_ui->resultsBrowser, &QAbstractItemView::doubleClicked, this, &SearchJobWidget::onItemDoubleClicked);
connect(searchHandler, &SearchHandler::newSearchResults, this, &SearchJobWidget::appendSearchResults);
connect(searchHandler, &SearchHandler::searchFinished, this, &SearchJobWidget::searchFinished);
connect(searchHandler, &SearchHandler::searchFailed, this, &SearchJobWidget::searchFailed);
connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
setStatusTip(statusText(m_status));
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &SearchJobWidget::onUIThemeChanged);
}
@ -181,6 +192,15 @@ SearchJobWidget::~SearchJobWidget()
delete m_ui;
}
QString SearchJobWidget::searchPattern() const
{
Q_ASSERT(m_searchHandler);
if (!m_searchHandler) [[unlikely]]
return {};
return m_searchHandler->pattern();
}
void SearchJobWidget::onItemDoubleClicked(const QModelIndex &index)
{
downloadTorrent(index);
@ -238,8 +258,33 @@ LineEdit *SearchJobWidget::lineEditSearchResultsFilter() const
return m_lineEditSearchResultsFilter;
}
void SearchJobWidget::assignSearchHandler(SearchHandler *searchHandler)
{
Q_ASSERT(searchHandler);
if (!searchHandler) [[unlikely]]
return;
m_searchListModel->removeRows(0, m_searchListModel->rowCount());
delete m_searchHandler;
m_searchHandler = searchHandler;
m_searchHandler->setParent(this);
connect(m_searchHandler, &SearchHandler::newSearchResults, this, &SearchJobWidget::appendSearchResults);
connect(m_searchHandler, &SearchHandler::searchFinished, this, &SearchJobWidget::searchFinished);
connect(m_searchHandler, &SearchHandler::searchFailed, this, &SearchJobWidget::searchFailed);
m_proxyModel->setNameFilter(m_searchHandler->pattern());
updateFilter();
setStatus(Status::Ongoing);
}
void SearchJobWidget::cancelSearch()
{
Q_ASSERT(m_searchHandler);
if (!m_searchHandler) [[unlikely]]
return;
m_searchHandler->cancelSearch();
}
@ -296,7 +341,8 @@ void SearchJobWidget::copyField(const int column) const
void SearchJobWidget::setStatus(Status value)
{
if (m_status == value) return;
if (m_status == value)
return;
m_status = value;
setStatusTip(statusText(value));
@ -444,25 +490,6 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
menu->popup(event->globalPos());
}
QString SearchJobWidget::statusText(SearchJobWidget::Status st)
{
switch (st)
{
case Status::Ongoing:
return tr("Searching...");
case Status::Finished:
return tr("Search has finished");
case Status::Aborted:
return tr("Search aborted");
case Status::Error:
return tr("An error occurred during search...");
case Status::NoResults:
return tr("Search returned no results");
default:
return {};
}
}
SearchJobWidget::NameFilteringMode SearchJobWidget::filteringMode() const
{
return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -79,10 +79,12 @@ public:
SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent = nullptr);
~SearchJobWidget() override;
QString searchPattern() const;
Status status() const;
int visibleResultsCount() const;
LineEdit *lineEditSearchResultsFilter() const;
void assignSearchHandler(SearchHandler *searchHandler);
void cancelSearch();
signals:
@ -125,8 +127,6 @@ private:
void copyTorrentNames() const;
void copyField(int column) const;
static QString statusText(Status st);
Ui::SearchJobWidget *m_ui = nullptr;
SearchHandler *m_searchHandler = nullptr;
QStandardItemModel *m_searchListModel = nullptr;

View file

@ -162,15 +162,13 @@ bool SearchWidget::eventFilter(QObject *object, QEvent *event)
closeTab(tabIndex);
return true;
}
if (mouseEvent->button() == Qt::RightButton)
{
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(tr("Close tab"), this, [this, tabIndex]() { closeTab(tabIndex); });
menu->addAction(tr("Close all tabs"), this, &SearchWidget::closeAllTabs);
menu->popup(QCursor::pos());
showTabMenu(tabIndex);
return true;
}
return false;
}
@ -184,7 +182,8 @@ void SearchWidget::fillCatCombobox()
using QStrPair = std::pair<QString, QString>;
QList<QStrPair> tmpList;
for (const QString &cat : asConst(SearchPluginManager::instance()->getPluginCategories(selectedPlugin())))
const auto selectedPlugin = m_ui->selectPlugin->itemData(m_ui->selectPlugin->currentIndex()).toString();
for (const QString &cat : asConst(SearchPluginManager::instance()->getPluginCategories(selectedPlugin)))
tmpList << std::make_pair(SearchPluginManager::categoryFullName(cat), cat);
std::sort(tmpList.begin(), tmpList.end(), [](const QStrPair &l, const QStrPair &r) { return (QString::localeAwareCompare(l.first, r.first) < 0); });
@ -223,9 +222,17 @@ QString SearchWidget::selectedCategory() const
return m_ui->comboCategory->itemData(m_ui->comboCategory->currentIndex()).toString();
}
QString SearchWidget::selectedPlugin() const
QStringList SearchWidget::selectedPlugins() const
{
return m_ui->selectPlugin->itemData(m_ui->selectPlugin->currentIndex()).toString();
const auto itemText = m_ui->selectPlugin->itemData(m_ui->selectPlugin->currentIndex()).toString();
if (itemText == u"all")
return SearchPluginManager::instance()->allPlugins();
if ((itemText == u"enabled") || (itemText == u"multi"))
return SearchPluginManager::instance()->enabledPlugins();
return {itemText};
}
void SearchWidget::selectActivePage()
@ -265,7 +272,8 @@ void SearchWidget::tabChanged(const int index)
void SearchWidget::selectMultipleBox([[maybe_unused]] const int index)
{
if (selectedPlugin() == u"multi")
const auto itemText = m_ui->selectPlugin->itemData(m_ui->selectPlugin->currentIndex()).toString();
if (itemText == u"multi")
on_pluginsButton_clicked();
}
@ -283,6 +291,24 @@ void SearchWidget::toggleFocusBetweenLineEdits()
}
}
void SearchWidget::showTabMenu(const int index)
{
QMenu *menu = new QMenu(this);
if (auto *searchJobWidget = static_cast<SearchJobWidget *>(m_ui->tabWidget->widget(index));
searchJobWidget->status() != SearchJobWidget::Status::Ongoing)
{
menu->addAction(tr("Refresh"), this, [this, searchJobWidget] { refreshTab(searchJobWidget); });
menu->addSeparator();
}
menu->addAction(tr("Close tab"), this, [this, index] { closeTab(index); });
menu->addAction(tr("Close all tabs"), this, &SearchWidget::closeAllTabs);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(QCursor::pos());
}
void SearchWidget::on_pluginsButton_clicked()
{
auto *dlg = new PluginSelectDialog(SearchPluginManager::instance(), this);
@ -305,12 +331,6 @@ void SearchWidget::giveFocusToSearchInput()
// Function called when we click on search button
void SearchWidget::on_searchButton_clicked()
{
if (!Utils::ForeignApps::pythonInfo().isValid())
{
app()->desktopIntegration()->showNotification(tr("Search Engine"), tr("Please install Python to use the Search Engine."));
return;
}
if (m_activeSearchTab)
{
m_activeSearchTab->cancelSearch();
@ -331,20 +351,16 @@ void SearchWidget::on_searchButton_clicked()
return;
}
const QString plugin = selectedPlugin();
QStringList plugins;
if (plugin == u"all")
plugins = SearchPluginManager::instance()->allPlugins();
else if ((plugin == u"enabled") || (plugin == u"multi"))
plugins = SearchPluginManager::instance()->enabledPlugins();
else
plugins << plugin;
if (!Utils::ForeignApps::pythonInfo().isValid())
{
app()->desktopIntegration()->showNotification(tr("Search Engine"), tr("Please install Python to use the Search Engine."));
return;
}
qDebug("Search with category: %s", qUtf8Printable(selectedCategory()));
// Launch search
auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), plugins);
auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), selectedPlugins());
// Tab Addition
auto *newTab = new SearchJobWidget(searchHandler, app(), this);
@ -370,8 +386,6 @@ void SearchWidget::tabStatusChanged(QWidget *tab)
if ((tab == m_activeSearchTab) && (m_activeSearchTab->status() != SearchJobWidget::Status::Ongoing))
{
Q_ASSERT(m_activeSearchTab->status() != SearchJobWidget::Status::Ongoing);
emit activeSearchFinished(m_activeSearchTab->status() == SearchJobWidget::Status::Error);
m_activeSearchTab = nullptr;
@ -393,3 +407,20 @@ void SearchWidget::closeAllTabs()
for (int i = (m_ui->tabWidget->count() - 1); i >= 0; --i)
closeTab(i);
}
void SearchWidget::refreshTab(SearchJobWidget *searchJobWidget)
{
if (!Utils::ForeignApps::pythonInfo().isValid())
{
app()->desktopIntegration()->showNotification(tr("Search Engine"), tr("Please install Python to use the Search Engine."));
return;
}
// Re-launch search
auto *searchHandler = SearchPluginManager::instance()->startSearch(searchJobWidget->searchPattern(), selectedCategory(), selectedPlugins());
searchJobWidget->assignSearchHandler(searchHandler);
if (!m_isNewQueryString)
m_ui->searchButton->setText(tr("Stop"));
m_activeSearchTab = searchJobWidget;
tabStatusChanged(searchJobWidget);
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020, Will Da Silva <will@willdasilva.xyz>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
@ -66,9 +66,13 @@ private slots:
private:
bool eventFilter(QObject *object, QEvent *event) override;
void tabChanged(int index);
void tabStatusChanged(QWidget *tab);
void closeTab(int index);
void closeAllTabs();
void tabStatusChanged(QWidget *tab);
void refreshTab(SearchJobWidget *searchJobWidget);
void showTabMenu(int index);
void selectMultipleBox(int index);
void toggleFocusBetweenLineEdits();
@ -78,7 +82,7 @@ private:
void searchTextEdited(const QString &);
QString selectedCategory() const;
QString selectedPlugin() const;
QStringList selectedPlugins() const;
Ui::SearchWidget *m_ui = nullptr;
QPointer<SearchJobWidget> m_currentSearchTab; // Selected tab