From f9f4b60b83c23d1c5b52aa21e6706a8de56325b9 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Wed, 8 Jan 2025 17:03:32 +0300 Subject: [PATCH] Allow to refresh existing search PR #22122. Closes #17184. --- src/base/search/searchpluginmanager.cpp | 2 +- src/gui/search/searchjobwidget.cpp | 89 ++++++++++++++++--------- src/gui/search/searchjobwidget.h | 6 +- src/gui/search/searchwidget.cpp | 87 ++++++++++++++++-------- src/gui/search/searchwidget.h | 10 ++- 5 files changed, 128 insertions(+), 66 deletions(-) diff --git a/src/base/search/searchpluginmanager.cpp b/src/base/search/searchpluginmanager.cpp index 2e77b3403..09b7a67cc 100644 --- a/src/base/search/searchpluginmanager.cpp +++ b/src/base/search/searchpluginmanager.cpp @@ -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) diff --git a/src/gui/search/searchjobwidget.cpp b/src/gui/search/searchjobwidget.cpp index bd637262e..4e75ed988 100644 --- a/src/gui/search/searchjobwidget.cpp +++ b/src/gui/search/searchjobwidget.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2018-2024 Vladimir Golovnev + * Copyright (C) 2018-2025 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * 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(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt()); diff --git a/src/gui/search/searchjobwidget.h b/src/gui/search/searchjobwidget.h index 78d0ee5ab..ca3ab6ce2 100644 --- a/src/gui/search/searchjobwidget.h +++ b/src/gui/search/searchjobwidget.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2018-2024 Vladimir Golovnev + * Copyright (C) 2018-2025 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * 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; diff --git a/src/gui/search/searchwidget.cpp b/src/gui/search/searchwidget.cpp index 7f94630e2..9ede5cf8c 100644 --- a/src/gui/search/searchwidget.cpp +++ b/src/gui/search/searchwidget.cpp @@ -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; QList 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(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); @@ -366,12 +382,10 @@ void SearchWidget::tabStatusChanged(QWidget *tab) const int tabIndex = m_ui->tabWidget->indexOf(tab); m_ui->tabWidget->setTabToolTip(tabIndex, tab->statusTip()); m_ui->tabWidget->setTabIcon(tabIndex, UIThemeManager::instance()->getIcon( - statusIconName(static_cast(tab)->status()))); + statusIconName(static_cast(tab)->status()))); 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); +} diff --git a/src/gui/search/searchwidget.h b/src/gui/search/searchwidget.h index 749083bba..d5bc59e94 100644 --- a/src/gui/search/searchwidget.h +++ b/src/gui/search/searchwidget.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2024 Vladimir Golovnev + * Copyright (C) 2015-2025 Vladimir Golovnev * Copyright (C) 2020, Will Da Silva * Copyright (C) 2006 Christophe Dumez * @@ -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 m_currentSearchTab; // Selected tab