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 // No search pattern entered
Q_ASSERT(!pattern.isEmpty()); Q_ASSERT(!pattern.isEmpty());
return new SearchHandler {pattern, category, usedPlugins, this}; return new SearchHandler(pattern, category, usedPlugins, this);
} }
QString SearchPluginManager::categoryFullName(const QString &categoryName) QString SearchPluginManager::categoryFullName(const QString &categoryName)

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -61,12 +61,30 @@ namespace
{ {
return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText); 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) SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
: GUIApplicationComponent(app, parent) : GUIApplicationComponent(app, parent)
, m_ui {new Ui::SearchJobWidget} , m_ui {new Ui::SearchJobWidget}
, m_searchHandler {searchHandler}
, m_nameFilteringMode {u"Search/FilteringMode"_s} , m_nameFilteringMode {u"Search/FilteringMode"_s}
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
@ -94,7 +112,6 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
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->setNameFilter(searchHandler->pattern());
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
@ -134,8 +151,9 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings); connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings);
fillFilterComboBoxes(); fillFilterComboBoxes();
setStatusTip(statusText(m_status));
updateFilter(); assignSearchHandler(searchHandler);
m_lineEditSearchResultsFilter = new LineEdit(this); m_lineEditSearchResultsFilter = new LineEdit(this);
m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results...")); 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(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); connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &SearchJobWidget::onUIThemeChanged);
} }
@ -181,6 +192,15 @@ SearchJobWidget::~SearchJobWidget()
delete m_ui; 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) void SearchJobWidget::onItemDoubleClicked(const QModelIndex &index)
{ {
downloadTorrent(index); downloadTorrent(index);
@ -238,8 +258,33 @@ LineEdit *SearchJobWidget::lineEditSearchResultsFilter() const
return m_lineEditSearchResultsFilter; 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() void SearchJobWidget::cancelSearch()
{ {
Q_ASSERT(m_searchHandler);
if (!m_searchHandler) [[unlikely]]
return;
m_searchHandler->cancelSearch(); m_searchHandler->cancelSearch();
} }
@ -296,7 +341,8 @@ void SearchJobWidget::copyField(const int column) const
void SearchJobWidget::setStatus(Status value) void SearchJobWidget::setStatus(Status value)
{ {
if (m_status == value) return; if (m_status == value)
return;
m_status = value; m_status = value;
setStatusTip(statusText(value)); setStatusTip(statusText(value));
@ -444,25 +490,6 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
menu->popup(event->globalPos()); 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 SearchJobWidget::NameFilteringMode SearchJobWidget::filteringMode() const
{ {
return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt()); 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. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * 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(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent = nullptr);
~SearchJobWidget() override; ~SearchJobWidget() override;
QString searchPattern() const;
Status status() const; Status status() const;
int visibleResultsCount() const; int visibleResultsCount() const;
LineEdit *lineEditSearchResultsFilter() const; LineEdit *lineEditSearchResultsFilter() const;
void assignSearchHandler(SearchHandler *searchHandler);
void cancelSearch(); void cancelSearch();
signals: signals:
@ -125,8 +127,6 @@ private:
void copyTorrentNames() const; void copyTorrentNames() const;
void copyField(int column) const; void copyField(int column) const;
static QString statusText(Status st);
Ui::SearchJobWidget *m_ui = nullptr; Ui::SearchJobWidget *m_ui = nullptr;
SearchHandler *m_searchHandler = nullptr; SearchHandler *m_searchHandler = nullptr;
QStandardItemModel *m_searchListModel = nullptr; QStandardItemModel *m_searchListModel = nullptr;

View file

@ -162,15 +162,13 @@ bool SearchWidget::eventFilter(QObject *object, QEvent *event)
closeTab(tabIndex); closeTab(tabIndex);
return true; return true;
} }
if (mouseEvent->button() == Qt::RightButton) if (mouseEvent->button() == Qt::RightButton)
{ {
QMenu *menu = new QMenu(this); showTabMenu(tabIndex);
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());
return true; return true;
} }
return false; return false;
} }
@ -184,7 +182,8 @@ void SearchWidget::fillCatCombobox()
using QStrPair = std::pair<QString, QString>; using QStrPair = std::pair<QString, QString>;
QList<QStrPair> tmpList; 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); 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); }); 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(); 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() void SearchWidget::selectActivePage()
@ -265,7 +272,8 @@ void SearchWidget::tabChanged(const int index)
void SearchWidget::selectMultipleBox([[maybe_unused]] 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(); 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() void SearchWidget::on_pluginsButton_clicked()
{ {
auto *dlg = new PluginSelectDialog(SearchPluginManager::instance(), this); auto *dlg = new PluginSelectDialog(SearchPluginManager::instance(), this);
@ -305,12 +331,6 @@ void SearchWidget::giveFocusToSearchInput()
// Function called when we click on search button // Function called when we click on search button
void SearchWidget::on_searchButton_clicked() 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) if (m_activeSearchTab)
{ {
m_activeSearchTab->cancelSearch(); m_activeSearchTab->cancelSearch();
@ -331,20 +351,16 @@ void SearchWidget::on_searchButton_clicked()
return; return;
} }
const QString plugin = selectedPlugin(); if (!Utils::ForeignApps::pythonInfo().isValid())
{
QStringList plugins; app()->desktopIntegration()->showNotification(tr("Search Engine"), tr("Please install Python to use the Search Engine."));
if (plugin == u"all") return;
plugins = SearchPluginManager::instance()->allPlugins(); }
else if ((plugin == u"enabled") || (plugin == u"multi"))
plugins = SearchPluginManager::instance()->enabledPlugins();
else
plugins << plugin;
qDebug("Search with category: %s", qUtf8Printable(selectedCategory())); qDebug("Search with category: %s", qUtf8Printable(selectedCategory()));
// Launch search // Launch search
auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), plugins); auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), selectedPlugins());
// Tab Addition // Tab Addition
auto *newTab = new SearchJobWidget(searchHandler, app(), this); auto *newTab = new SearchJobWidget(searchHandler, app(), this);
@ -366,12 +382,10 @@ void SearchWidget::tabStatusChanged(QWidget *tab)
const int tabIndex = m_ui->tabWidget->indexOf(tab); const int tabIndex = m_ui->tabWidget->indexOf(tab);
m_ui->tabWidget->setTabToolTip(tabIndex, tab->statusTip()); m_ui->tabWidget->setTabToolTip(tabIndex, tab->statusTip());
m_ui->tabWidget->setTabIcon(tabIndex, UIThemeManager::instance()->getIcon( m_ui->tabWidget->setTabIcon(tabIndex, UIThemeManager::instance()->getIcon(
statusIconName(static_cast<SearchJobWidget *>(tab)->status()))); statusIconName(static_cast<SearchJobWidget *>(tab)->status())));
if ((tab == m_activeSearchTab) && (m_activeSearchTab->status() != SearchJobWidget::Status::Ongoing)) 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); emit activeSearchFinished(m_activeSearchTab->status() == SearchJobWidget::Status::Error);
m_activeSearchTab = nullptr; m_activeSearchTab = nullptr;
@ -393,3 +407,20 @@ void SearchWidget::closeAllTabs()
for (int i = (m_ui->tabWidget->count() - 1); i >= 0; --i) for (int i = (m_ui->tabWidget->count() - 1); i >= 0; --i)
closeTab(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. * 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) 2020, Will Da Silva <will@willdasilva.xyz>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
@ -66,9 +66,13 @@ private slots:
private: private:
bool eventFilter(QObject *object, QEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override;
void tabChanged(int index); void tabChanged(int index);
void tabStatusChanged(QWidget *tab);
void closeTab(int index); void closeTab(int index);
void closeAllTabs(); void closeAllTabs();
void tabStatusChanged(QWidget *tab); void refreshTab(SearchJobWidget *searchJobWidget);
void showTabMenu(int index);
void selectMultipleBox(int index); void selectMultipleBox(int index);
void toggleFocusBetweenLineEdits(); void toggleFocusBetweenLineEdits();
@ -78,7 +82,7 @@ private:
void searchTextEdited(const QString &); void searchTextEdited(const QString &);
QString selectedCategory() const; QString selectedCategory() const;
QString selectedPlugin() const; QStringList selectedPlugins() const;
Ui::SearchWidget *m_ui = nullptr; Ui::SearchWidget *m_ui = nullptr;
QPointer<SearchJobWidget> m_currentSearchTab; // Selected tab QPointer<SearchJobWidget> m_currentSearchTab; // Selected tab