mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-07-15 01:33:07 -07:00
parent
3ef4d0d798
commit
3978137534
9 changed files with 681 additions and 38 deletions
|
@ -655,6 +655,32 @@ void Preferences::setSearchEnabled(const bool enabled)
|
|||
setValue(u"Preferences/Search/SearchEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::storeOpenedSearchTabs() const
|
||||
{
|
||||
return value(u"Search/StoreOpenedSearchTabs"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setStoreOpenedSearchTabs(const bool enabled)
|
||||
{
|
||||
if (enabled == storeOpenedSearchTabs())
|
||||
return;
|
||||
|
||||
setValue(u"Search/StoreOpenedSearchTabs"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::storeOpenedSearchTabResults() const
|
||||
{
|
||||
return value(u"Search/StoreOpenedSearchTabResults"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setStoreOpenedSearchTabResults(const bool enabled)
|
||||
{
|
||||
if (enabled == storeOpenedSearchTabResults())
|
||||
return;
|
||||
|
||||
setValue(u"Search/StoreOpenedSearchTabResults"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUIEnabled() const
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
|
|
|
@ -172,6 +172,12 @@ public:
|
|||
bool isSearchEnabled() const;
|
||||
void setSearchEnabled(bool enabled);
|
||||
|
||||
// Search UI
|
||||
bool storeOpenedSearchTabs() const;
|
||||
void setStoreOpenedSearchTabs(bool enabled);
|
||||
bool storeOpenedSearchTabResults() const;
|
||||
void setStoreOpenedSearchTabResults(bool enabled);
|
||||
|
||||
// HTTP Server
|
||||
bool isWebUIEnabled() const;
|
||||
void setWebUIEnabled(bool enabled);
|
||||
|
|
|
@ -164,6 +164,7 @@ OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
|
|||
m_ui->tabSelection->item(TAB_DOWNLOADS)->setIcon(UIThemeManager::instance()->getIcon(u"download"_s, u"folder-download"_s));
|
||||
m_ui->tabSelection->item(TAB_SPEED)->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_s, u"chronometer"_s));
|
||||
m_ui->tabSelection->item(TAB_RSS)->setIcon(UIThemeManager::instance()->getIcon(u"application-rss"_s, u"application-rss+xml"_s));
|
||||
m_ui->tabSelection->item(TAB_SEARCH)->setIcon(UIThemeManager::instance()->getIcon(u"edit-find"_s));
|
||||
#ifdef DISABLE_WEBUI
|
||||
m_ui->tabSelection->item(TAB_WEBUI)->setHidden(true);
|
||||
#else
|
||||
|
@ -190,6 +191,7 @@ OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
|
|||
loadSpeedTabOptions();
|
||||
loadBittorrentTabOptions();
|
||||
loadRSSTabOptions();
|
||||
loadSearchTabOptions();
|
||||
#ifndef DISABLE_WEBUI
|
||||
loadWebUITabOptions();
|
||||
#endif
|
||||
|
@ -1273,6 +1275,25 @@ void OptionsDialog::saveRSSTabOptions() const
|
|||
autoDownloader->setDownloadRepacks(m_ui->checkSmartFilterDownloadRepacks->isChecked());
|
||||
}
|
||||
|
||||
void OptionsDialog::loadSearchTabOptions()
|
||||
{
|
||||
const auto *pref = Preferences::instance();
|
||||
|
||||
m_ui->groupStoreOpenedTabs->setChecked(pref->storeOpenedSearchTabs());
|
||||
m_ui->checkStoreTabsSearchResults->setChecked(pref->storeOpenedSearchTabResults());
|
||||
|
||||
connect(m_ui->groupStoreOpenedTabs, &QGroupBox::toggled, this, &OptionsDialog::enableApplyButton);
|
||||
connect(m_ui->checkStoreTabsSearchResults, &QCheckBox::toggled, this, &OptionsDialog::enableApplyButton);
|
||||
}
|
||||
|
||||
void OptionsDialog::saveSearchTabOptions() const
|
||||
{
|
||||
auto *pref = Preferences::instance();
|
||||
|
||||
pref->setStoreOpenedSearchTabs(m_ui->groupStoreOpenedTabs->isChecked());
|
||||
pref->setStoreOpenedSearchTabResults(m_ui->checkStoreTabsSearchResults->isChecked());
|
||||
}
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
void OptionsDialog::loadWebUITabOptions()
|
||||
{
|
||||
|
@ -1465,6 +1486,7 @@ void OptionsDialog::saveOptions() const
|
|||
saveSpeedTabOptions();
|
||||
saveBittorrentTabOptions();
|
||||
saveRSSTabOptions();
|
||||
saveSearchTabOptions();
|
||||
#ifndef DISABLE_WEBUI
|
||||
saveWebUITabOptions();
|
||||
#endif
|
||||
|
|
|
@ -73,6 +73,7 @@ class OptionsDialog final : public GUIApplicationComponent<QDialog>
|
|||
TAB_CONNECTION,
|
||||
TAB_SPEED,
|
||||
TAB_BITTORRENT,
|
||||
TAB_SEARCH,
|
||||
TAB_RSS,
|
||||
TAB_WEBUI,
|
||||
TAB_ADVANCED
|
||||
|
@ -136,6 +137,9 @@ private:
|
|||
void loadRSSTabOptions();
|
||||
void saveRSSTabOptions() const;
|
||||
|
||||
void loadSearchTabOptions();
|
||||
void saveSearchTabOptions() const;
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
void loadWebUITabOptions();
|
||||
void saveWebUITabOptions() const;
|
||||
|
|
|
@ -72,6 +72,11 @@
|
|||
<string>BitTorrent</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RSS</string>
|
||||
|
@ -3210,6 +3215,85 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabSearchPage">
|
||||
<layout class="QVBoxLayout" name="searchPageLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="searchPageScrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="searchPageScrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>521</width>
|
||||
<height>541</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="searchPageScrollAreaWidgetContentsLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupSearchUI">
|
||||
<property name="title">
|
||||
<string>Search UI</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="groupSearchUILayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupStoreOpenedTabs">
|
||||
<property name="title">
|
||||
<string>Store opened tabs</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="groupStoreOpenedTabsLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkStoreTabsSearchResults">
|
||||
<property name="text">
|
||||
<string>Also store search results</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="searchPageScrollAreaSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>422</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabRSSPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_25">
|
||||
<property name="leftMargin">
|
||||
|
|
|
@ -82,10 +82,11 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
|
||||
SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidget *parent)
|
||||
: GUIApplicationComponent(app, parent)
|
||||
, m_ui {new Ui::SearchJobWidget}
|
||||
, m_nameFilteringMode {u"Search/FilteringMode"_s}
|
||||
, m_id {id}
|
||||
, m_ui {new Ui::SearchJobWidget}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
|
@ -151,9 +152,6 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
|
|||
connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings);
|
||||
|
||||
fillFilterComboBoxes();
|
||||
setStatusTip(statusText(m_status));
|
||||
|
||||
assignSearchHandler(searchHandler);
|
||||
|
||||
m_lineEditSearchResultsFilter = new LineEdit(this);
|
||||
m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results..."));
|
||||
|
@ -186,19 +184,42 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
|
|||
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &SearchJobWidget::onUIThemeChanged);
|
||||
}
|
||||
|
||||
SearchJobWidget::SearchJobWidget(const QString &id, const QString &searchPattern
|
||||
, const QList<SearchResult> &searchResults, IGUIApplication *app, QWidget *parent)
|
||||
: SearchJobWidget(id, app, parent)
|
||||
{
|
||||
m_searchPattern = searchPattern;
|
||||
m_proxyModel->setNameFilter(m_searchPattern);
|
||||
updateFilter();
|
||||
|
||||
appendSearchResults(searchResults);
|
||||
}
|
||||
|
||||
SearchJobWidget::SearchJobWidget(const QString &id, SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
|
||||
: SearchJobWidget(id, app, parent)
|
||||
{
|
||||
assignSearchHandler(searchHandler);
|
||||
}
|
||||
|
||||
SearchJobWidget::~SearchJobWidget()
|
||||
{
|
||||
saveSettings();
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
QString SearchJobWidget::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
QString SearchJobWidget::searchPattern() const
|
||||
{
|
||||
Q_ASSERT(m_searchHandler);
|
||||
if (!m_searchHandler) [[unlikely]]
|
||||
return {};
|
||||
return m_searchPattern;
|
||||
}
|
||||
|
||||
return m_searchHandler->pattern();
|
||||
QList<SearchResult> SearchJobWidget::searchResults() const
|
||||
{
|
||||
return m_searchResults;
|
||||
}
|
||||
|
||||
void SearchJobWidget::onItemDoubleClicked(const QModelIndex &index)
|
||||
|
@ -264,6 +285,7 @@ void SearchJobWidget::assignSearchHandler(SearchHandler *searchHandler)
|
|||
if (!searchHandler) [[unlikely]]
|
||||
return;
|
||||
|
||||
m_searchResults.clear();
|
||||
m_searchListModel->removeRows(0, m_searchListModel->rowCount());
|
||||
delete m_searchHandler;
|
||||
|
||||
|
@ -273,7 +295,9 @@ void SearchJobWidget::assignSearchHandler(SearchHandler *searchHandler)
|
|||
connect(m_searchHandler, &SearchHandler::searchFinished, this, &SearchJobWidget::searchFinished);
|
||||
connect(m_searchHandler, &SearchHandler::searchFailed, this, &SearchJobWidget::searchFailed);
|
||||
|
||||
m_proxyModel->setNameFilter(m_searchHandler->pattern());
|
||||
m_searchPattern = m_searchHandler->pattern();
|
||||
|
||||
m_proxyModel->setNameFilter(m_searchPattern);
|
||||
updateFilter();
|
||||
|
||||
setStatus(Status::Ongoing);
|
||||
|
@ -281,8 +305,7 @@ void SearchJobWidget::assignSearchHandler(SearchHandler *searchHandler)
|
|||
|
||||
void SearchJobWidget::cancelSearch()
|
||||
{
|
||||
Q_ASSERT(m_searchHandler);
|
||||
if (!m_searchHandler) [[unlikely]]
|
||||
if (!m_searchHandler)
|
||||
return;
|
||||
|
||||
m_searchHandler->cancelSearch();
|
||||
|
@ -363,7 +386,7 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
|
|||
}
|
||||
else
|
||||
{
|
||||
SearchDownloadHandler *downloadHandler = m_searchHandler->manager()->downloadTorrent(engineName, torrentUrl);
|
||||
SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(engineName, torrentUrl);
|
||||
connect(downloadHandler, &SearchDownloadHandler::downloadFinished
|
||||
, this, [this, option](const QString &source) { addTorrentToSession(source, option); });
|
||||
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
||||
|
@ -605,6 +628,7 @@ void SearchJobWidget::appendSearchResults(const QList<SearchResult> &results)
|
|||
setModelData(SearchSortModel::PUB_DATE, QLocale().toString(result.pubDate.toLocalTime(), QLocale::ShortFormat), result.pubDate);
|
||||
}
|
||||
|
||||
m_searchResults.append(results);
|
||||
updateResultsCount();
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
|
||||
enum class Status
|
||||
{
|
||||
Ready,
|
||||
Ongoing,
|
||||
Finished,
|
||||
Error,
|
||||
|
@ -76,10 +77,13 @@ public:
|
|||
NoResults
|
||||
};
|
||||
|
||||
SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent = nullptr);
|
||||
SearchJobWidget(const QString &id, const QString &searchPattern, const QList<SearchResult> &searchResults, IGUIApplication *app, QWidget *parent = nullptr);
|
||||
SearchJobWidget(const QString &id, SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent = nullptr);
|
||||
~SearchJobWidget() override;
|
||||
|
||||
QString id() const;
|
||||
QString searchPattern() const;
|
||||
QList<SearchResult> searchResults() const;
|
||||
Status status() const;
|
||||
int visibleResultsCount() const;
|
||||
LineEdit *lineEditSearchResultsFilter() const;
|
||||
|
@ -98,6 +102,8 @@ private slots:
|
|||
void displayColumnHeaderMenu();
|
||||
|
||||
private:
|
||||
SearchJobWidget(const QString &id, IGUIApplication *app, QWidget *parent);
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
void updateFilter();
|
||||
|
@ -127,15 +133,18 @@ private:
|
|||
void copyTorrentNames() const;
|
||||
void copyField(int column) const;
|
||||
|
||||
SettingValue<NameFilteringMode> m_nameFilteringMode;
|
||||
|
||||
QString m_id;
|
||||
QString m_searchPattern;
|
||||
QList<SearchResult> m_searchResults;
|
||||
Ui::SearchJobWidget *m_ui = nullptr;
|
||||
SearchHandler *m_searchHandler = nullptr;
|
||||
QStandardItemModel *m_searchListModel = nullptr;
|
||||
SearchSortModel *m_proxyModel = nullptr;
|
||||
LineEdit *m_lineEditSearchResultsFilter = nullptr;
|
||||
Status m_status = Status::Ongoing;
|
||||
Status m_status = Status::Ready;
|
||||
bool m_noSearchResults = true;
|
||||
|
||||
SettingValue<NameFilteringMode> m_nameFilteringMode;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(SearchJobWidget::NameFilteringMode)
|
||||
|
|
|
@ -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>
|
||||
*
|
||||
|
@ -34,12 +34,13 @@
|
|||
|
||||
#include <utility>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#include <QDebug>
|
||||
#include <QEvent>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonValue>
|
||||
#include <QList>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
|
@ -47,11 +48,18 @@
|
|||
#include <QObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QShortcut>
|
||||
#include <QThread>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/search/searchhandler.h"
|
||||
#include "base/search/searchpluginmanager.h"
|
||||
#include "base/utils/datetime.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/foreignapps.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "gui/desktopintegration.h"
|
||||
#include "gui/interfaces/iguiapplication.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
@ -59,11 +67,37 @@
|
|||
#include "searchjobwidget.h"
|
||||
#include "ui_searchwidget.h"
|
||||
|
||||
#define SEARCHHISTORY_MAXSIZE 50
|
||||
#define URL_COLUMN 5
|
||||
const QString DATA_FOLDER_NAME = u"SearchUI"_s;
|
||||
const QString SESSION_FILE_NAME = u"Session.json"_s;
|
||||
|
||||
const QString KEY_SESSION_TABS = u"Tabs"_s;
|
||||
const QString KEY_SESSION_CURRENTTAB = u"CurrentTab"_s;
|
||||
const QString KEY_TAB_ID = u"ID"_s;
|
||||
const QString KEY_TAB_SEARCHPATTERN = u"SearchPattern"_s;
|
||||
const QString KEY_RESULT_FILENAME = u"FileName"_s;
|
||||
const QString KEY_RESULT_FILEURL = u"FileURL"_s;
|
||||
const QString KEY_RESULT_FILESIZE = u"FileSize"_s;
|
||||
const QString KEY_RESULT_SEEDERSCOUNT = u"SeedersCount"_s;
|
||||
const QString KEY_RESULT_LEECHERSCOUNT = u"LeechersCount"_s;
|
||||
const QString KEY_RESULT_ENGINENAME = u"EngineName"_s;
|
||||
const QString KEY_RESULT_SITEURL = u"SiteURL"_s;
|
||||
const QString KEY_RESULT_DESCRLINK = u"DescrLink"_s;
|
||||
const QString KEY_RESULT_PUBDATE = u"PubDate"_s;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct TabData
|
||||
{
|
||||
QString tabID;
|
||||
QString searchPattern;
|
||||
};
|
||||
|
||||
struct SessionData
|
||||
{
|
||||
QList<TabData> tabs;
|
||||
QString currentTabID;
|
||||
};
|
||||
|
||||
QString statusIconName(const SearchJobWidget::Status st)
|
||||
{
|
||||
switch (st)
|
||||
|
@ -81,11 +115,187 @@ namespace
|
|||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Path makeDataFilePath(const QString &fileName)
|
||||
{
|
||||
return specialFolderLocation(SpecialFolder::Data) / Path(DATA_FOLDER_NAME) / Path(fileName);
|
||||
}
|
||||
|
||||
QString makeTabName(SearchJobWidget *searchJobWdget)
|
||||
{
|
||||
Q_ASSERT(searchJobWdget);
|
||||
if (!searchJobWdget) [[unlikely]]
|
||||
return {};
|
||||
|
||||
QString tabName = searchJobWdget->searchPattern();
|
||||
tabName.replace(QRegularExpression(u"&{1}"_s), u"&&"_s);
|
||||
return tabName;
|
||||
}
|
||||
|
||||
nonstd::expected<SessionData, QString> loadSession(const Path &filePath)
|
||||
{
|
||||
const int fileMaxSize = 10 * 1024 * 1024;
|
||||
const auto readResult = Utils::IO::readFile(filePath, fileMaxSize);
|
||||
if (!readResult)
|
||||
{
|
||||
if (readResult.error().status == Utils::IO::ReadError::NotExist)
|
||||
return {};
|
||||
|
||||
return nonstd::make_unexpected(readResult.error().message);
|
||||
}
|
||||
|
||||
const QString formatErrorMsg = SearchWidget::tr("Invalid data format.");
|
||||
QJsonParseError jsonError;
|
||||
const QJsonDocument sessionDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError)
|
||||
return nonstd::make_unexpected(jsonError.errorString());
|
||||
|
||||
if (!sessionDoc.isObject())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
const QJsonObject sessionObj = sessionDoc.object();
|
||||
const QJsonValue tabsVal = sessionObj[KEY_SESSION_TABS];
|
||||
if (!tabsVal.isArray())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
QList<TabData> tabs;
|
||||
QSet<QString> tabIDs;
|
||||
for (const QJsonValue &tabVal : asConst(tabsVal.toArray()))
|
||||
{
|
||||
if (!tabVal.isObject())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
const QJsonObject tabObj = tabVal.toObject();
|
||||
|
||||
const QJsonValue tabIDVal = tabObj[KEY_TAB_ID];
|
||||
if (!tabIDVal.isString())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
const QJsonValue patternVal = tabObj[KEY_TAB_SEARCHPATTERN];
|
||||
if (!patternVal.isString())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
const QString tabID = tabIDVal.toString();
|
||||
tabIDs.insert(tabID);
|
||||
tabs.emplaceBack(TabData {tabID, patternVal.toString()});
|
||||
if (tabs.size() != tabIDs.size()) // duplicate ID
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
}
|
||||
|
||||
const QJsonValue currentTabVal = sessionObj[KEY_SESSION_CURRENTTAB];
|
||||
if (!currentTabVal.isString())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
return SessionData {.tabs = tabs, .currentTabID = currentTabVal.toString()};
|
||||
}
|
||||
|
||||
nonstd::expected<QList<SearchResult>, QString> loadSearchResults(const Path &filePath)
|
||||
{
|
||||
const int fileMaxSize = 10 * 1024 * 1024;
|
||||
const auto readResult = Utils::IO::readFile(filePath, fileMaxSize);
|
||||
if (!readResult)
|
||||
{
|
||||
if (readResult.error().status != Utils::IO::ReadError::NotExist)
|
||||
{
|
||||
return nonstd::make_unexpected(readResult.error().message);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const QString formatErrorMsg = SearchWidget::tr("Invalid data format.");
|
||||
QJsonParseError jsonError;
|
||||
const QJsonDocument searchResultsDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError)
|
||||
return nonstd::make_unexpected(jsonError.errorString());
|
||||
|
||||
if (!searchResultsDoc.isArray())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
const QJsonArray resultsList = searchResultsDoc.array();
|
||||
QList<SearchResult> searchResults;
|
||||
for (const QJsonValue &resultVal : resultsList)
|
||||
{
|
||||
if (!resultVal.isObject())
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
const QJsonObject resultObj = resultVal.toObject();
|
||||
SearchResult &searchResult = searchResults.emplaceBack();
|
||||
|
||||
if (const QJsonValue fileNameVal = resultObj[KEY_RESULT_FILENAME]; fileNameVal.isString())
|
||||
searchResult.fileName = fileNameVal.toString();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue fileURLVal = resultObj[KEY_RESULT_FILEURL]; fileURLVal.isString())
|
||||
searchResult.fileUrl= fileURLVal.toString();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue fileSizeVal = resultObj[KEY_RESULT_FILESIZE]; fileSizeVal.isDouble())
|
||||
searchResult.fileSize= fileSizeVal.toInteger();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue seedersCountVal = resultObj[KEY_RESULT_SEEDERSCOUNT]; seedersCountVal.isDouble())
|
||||
searchResult.nbSeeders = seedersCountVal.toInteger();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue leechersCountVal = resultObj[KEY_RESULT_LEECHERSCOUNT]; leechersCountVal.isDouble())
|
||||
searchResult.nbLeechers = leechersCountVal.toInteger();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue engineNameVal = resultObj[KEY_RESULT_ENGINENAME]; engineNameVal.isString())
|
||||
searchResult.engineName= engineNameVal.toString();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue siteURLVal = resultObj[KEY_RESULT_SITEURL]; siteURLVal.isString())
|
||||
searchResult.siteUrl= siteURLVal.toString();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue descrLinkVal = resultObj[KEY_RESULT_DESCRLINK]; descrLinkVal.isString())
|
||||
searchResult.descrLink= descrLinkVal.toString();
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
|
||||
if (const QJsonValue pubDateVal = resultObj[KEY_RESULT_PUBDATE]; pubDateVal.isDouble())
|
||||
searchResult.pubDate = QDateTime::fromSecsSinceEpoch(pubDateVal.toInteger());
|
||||
else
|
||||
return nonstd::make_unexpected(formatErrorMsg);
|
||||
}
|
||||
|
||||
return searchResults;
|
||||
}
|
||||
}
|
||||
|
||||
class SearchWidget::DataStorage final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DataStorage)
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
void loadSession(bool withSearchResults);
|
||||
void storeSession(const SessionData &sessionData);
|
||||
void removeSession();
|
||||
void storeTab(const QString &tabID, const QList<SearchResult> &searchResults);
|
||||
void removeTab(const QString &tabID);
|
||||
|
||||
signals:
|
||||
void sessionLoaded(const SessionData &sessionData);
|
||||
void tabLoaded(const QString &tabID, const QString &searchPattern, const QList<SearchResult> &searchResults);
|
||||
};
|
||||
|
||||
SearchWidget::SearchWidget(IGUIApplication *app, QWidget *parent)
|
||||
: GUIApplicationComponent(app, parent)
|
||||
, m_ui {new Ui::SearchWidget()}
|
||||
, m_ioThread {new QThread}
|
||||
, m_dataStorage {new DataStorage(this)}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
|
@ -120,6 +330,8 @@ SearchWidget::SearchWidget(IGUIApplication *app, QWidget *parent)
|
|||
#endif
|
||||
connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab);
|
||||
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &SearchWidget::currentTabChanged);
|
||||
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &SearchWidget::saveSession);
|
||||
connect(m_ui->tabWidget->tabBar(), &QTabBar::tabMoved, this, &SearchWidget::saveSession);
|
||||
|
||||
connect(m_ui->tabWidget, &QTabWidget::tabBarDoubleClicked, this, [this](const int tabIndex)
|
||||
{
|
||||
|
@ -166,6 +378,17 @@ SearchWidget::SearchWidget(IGUIApplication *app, QWidget *parent)
|
|||
connect(focusSearchHotkey, &QShortcut::activated, this, &SearchWidget::toggleFocusBetweenLineEdits);
|
||||
const auto *focusSearchHotkeyAlternative = new QShortcut((Qt::CTRL | Qt::Key_E), this);
|
||||
connect(focusSearchHotkeyAlternative, &QShortcut::activated, this, &SearchWidget::toggleFocusBetweenLineEdits);
|
||||
|
||||
m_storeOpenedTabs = Preferences::instance()->storeOpenedSearchTabs();
|
||||
m_storeOpenedTabsResults = Preferences::instance()->storeOpenedSearchTabResults();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &SearchWidget::onPreferencesChanged);
|
||||
|
||||
m_dataStorage->moveToThread(m_ioThread.get());
|
||||
connect(m_ioThread.get(), &QThread::finished, m_dataStorage, &QObject::deleteLater);
|
||||
m_ioThread->setObjectName("SearchWidget m_ioThread");
|
||||
m_ioThread->start();
|
||||
|
||||
restoreSession();
|
||||
}
|
||||
|
||||
bool SearchWidget::eventFilter(QObject *object, QEvent *event)
|
||||
|
@ -199,6 +422,55 @@ bool SearchWidget::eventFilter(QObject *object, QEvent *event)
|
|||
return QWidget::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void SearchWidget::onPreferencesChanged()
|
||||
{
|
||||
const auto *pref = Preferences::instance();
|
||||
|
||||
const bool storeOpenedTabs = pref->storeOpenedSearchTabs();
|
||||
const bool isStoreOpenedTabsChanged = storeOpenedTabs != m_storeOpenedTabs;
|
||||
if (isStoreOpenedTabsChanged)
|
||||
{
|
||||
m_storeOpenedTabs = storeOpenedTabs;
|
||||
if (m_storeOpenedTabs)
|
||||
{
|
||||
saveSession();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this] { m_dataStorage->removeSession(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const bool storeOpenedTabsResults = pref->storeOpenedSearchTabResults();
|
||||
const bool isStoreOpenedTabsResultsChanged = storeOpenedTabsResults != m_storeOpenedTabsResults;
|
||||
if (isStoreOpenedTabsResultsChanged)
|
||||
m_storeOpenedTabsResults = storeOpenedTabsResults;
|
||||
|
||||
if (isStoreOpenedTabsResultsChanged || isStoreOpenedTabsChanged)
|
||||
{
|
||||
if (m_storeOpenedTabsResults)
|
||||
{
|
||||
for (int tabIndex = (m_ui->tabWidget->count() - 1); tabIndex >= 0; --tabIndex)
|
||||
{
|
||||
const auto *tab = static_cast<SearchJobWidget *>(m_ui->tabWidget->widget(tabIndex));
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this, tabID = tab->id(), searchResults = tab->searchResults()]
|
||||
{
|
||||
m_dataStorage->storeTab(tabID, searchResults);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int tabIndex = (m_ui->tabWidget->count() - 1); tabIndex >= 0; --tabIndex)
|
||||
{
|
||||
const auto *tab = static_cast<SearchJobWidget *>(m_ui->tabWidget->widget(tabIndex));
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this, tabID = tab->id()] { m_dataStorage->removeTab(tabID); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SearchWidget::fillCatCombobox()
|
||||
{
|
||||
m_ui->comboCategory->clear();
|
||||
|
@ -259,6 +531,65 @@ QStringList SearchWidget::selectedPlugins() const
|
|||
return {itemText};
|
||||
}
|
||||
|
||||
QString SearchWidget::generateTabID() const
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
const QString tabID = QString::number(qHash(QDateTime::currentDateTimeUtc()));
|
||||
if (!m_tabWidgets.contains(tabID))
|
||||
return tabID;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int SearchWidget::addTab(const QString &tabID, SearchJobWidget *searchJobWdget)
|
||||
{
|
||||
Q_ASSERT(!m_tabWidgets.contains(tabID));
|
||||
|
||||
connect(searchJobWdget, &SearchJobWidget::statusChanged, this, [this, searchJobWdget]() { tabStatusChanged(searchJobWdget); });
|
||||
m_tabWidgets.insert(tabID, searchJobWdget);
|
||||
return m_ui->tabWidget->addTab(searchJobWdget, makeTabName(searchJobWdget));
|
||||
}
|
||||
|
||||
void SearchWidget::saveSession() const
|
||||
{
|
||||
if (!m_storeOpenedTabs)
|
||||
return;
|
||||
|
||||
const int currentIndex = m_ui->tabWidget->currentIndex();
|
||||
SessionData sessionData;
|
||||
for (int tabIndex = 0; tabIndex < m_ui->tabWidget->count(); ++tabIndex)
|
||||
{
|
||||
auto *searchJobWidget = static_cast<SearchJobWidget *>(m_ui->tabWidget->widget(tabIndex));
|
||||
sessionData.tabs.emplaceBack(TabData {searchJobWidget->id(), searchJobWidget->searchPattern()});
|
||||
if (currentIndex == tabIndex)
|
||||
sessionData.currentTabID = searchJobWidget->id();
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this, sessionData] { m_dataStorage->storeSession(sessionData); });
|
||||
}
|
||||
|
||||
void SearchWidget::restoreSession()
|
||||
{
|
||||
if (!m_storeOpenedTabs)
|
||||
return;
|
||||
|
||||
connect(m_dataStorage, &DataStorage::tabLoaded, this
|
||||
, [this](const QString &tabID, const QString &searchPattern, const QList<SearchResult> &searchResults)
|
||||
{
|
||||
auto *restoredTab = new SearchJobWidget(tabID, searchPattern, searchResults, app(), this);
|
||||
addTab(tabID, restoredTab);
|
||||
});
|
||||
|
||||
connect(m_dataStorage, &DataStorage::sessionLoaded, this, [this](const SessionData &sessionData)
|
||||
{
|
||||
m_ui->tabWidget->setCurrentWidget(m_tabWidgets.value(sessionData.currentTabID));
|
||||
});
|
||||
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this] { m_dataStorage->loadSession(m_storeOpenedTabsResults); });
|
||||
}
|
||||
|
||||
void SearchWidget::selectActivePage()
|
||||
{
|
||||
if (SearchPluginManager::instance()->allPlugins().isEmpty())
|
||||
|
@ -412,16 +743,14 @@ void SearchWidget::searchButtonClicked()
|
|||
auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), selectedPlugins());
|
||||
|
||||
// Tab Addition
|
||||
auto *newTab = new SearchJobWidget(searchHandler, app(), this);
|
||||
|
||||
QString tabName = pattern;
|
||||
tabName.replace(QRegularExpression(u"&{1}"_s), u"&&"_s);
|
||||
m_ui->tabWidget->addTab(newTab, tabName);
|
||||
const QString newTabID = generateTabID();
|
||||
auto *newTab = new SearchJobWidget(newTabID, searchHandler, app(), this);
|
||||
const int tabIndex = addTab(newTabID, newTab);
|
||||
m_ui->tabWidget->setTabToolTip(tabIndex, newTab->statusTip());
|
||||
m_ui->tabWidget->setTabIcon(tabIndex, UIThemeManager::instance()->getIcon(statusIconName(newTab->status())));
|
||||
m_ui->tabWidget->setCurrentWidget(newTab);
|
||||
|
||||
connect(newTab, &SearchJobWidget::statusChanged, this, [this, newTab]() { tabStatusChanged(newTab); });
|
||||
|
||||
tabStatusChanged(newTab);
|
||||
adjustSearchButton();
|
||||
saveSession();
|
||||
}
|
||||
|
||||
void SearchWidget::stopButtonClicked()
|
||||
|
@ -442,19 +771,38 @@ void SearchWidget::tabStatusChanged(SearchJobWidget *tab)
|
|||
adjustSearchButton();
|
||||
|
||||
emit searchFinished(tab->status() == SearchJobWidget::Status::Error);
|
||||
|
||||
if (m_storeOpenedTabsResults)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this, tabID = tab->id(), searchResults = tab->searchResults()]
|
||||
{
|
||||
m_dataStorage->storeTab(tabID, searchResults);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SearchWidget::closeTab(const int index)
|
||||
{
|
||||
const QWidget *tab = m_ui->tabWidget->widget(index);
|
||||
delete tab;
|
||||
const auto *tab = static_cast<SearchJobWidget *>(m_ui->tabWidget->widget(index));
|
||||
const QString tabID = tab->id();
|
||||
delete m_tabWidgets.take(tabID);
|
||||
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this, tabID] { m_dataStorage->removeTab(tabID); });
|
||||
saveSession();
|
||||
}
|
||||
|
||||
void SearchWidget::closeAllTabs()
|
||||
{
|
||||
for (int i = (m_ui->tabWidget->count() - 1); i >= 0; --i)
|
||||
closeTab(i);
|
||||
for (int tabIndex = (m_ui->tabWidget->count() - 1); tabIndex >= 0; --tabIndex)
|
||||
{
|
||||
const auto *tab = static_cast<SearchJobWidget *>(m_ui->tabWidget->widget(tabIndex));
|
||||
const QString tabID = tab->id();
|
||||
delete m_tabWidgets.take(tabID);
|
||||
QMetaObject::invokeMethod(m_dataStorage, [this, tabID] { m_dataStorage->removeTab(tabID); });
|
||||
}
|
||||
|
||||
saveSession();
|
||||
}
|
||||
|
||||
void SearchWidget::refreshTab(SearchJobWidget *searchJobWidget)
|
||||
|
@ -468,5 +816,106 @@ void SearchWidget::refreshTab(SearchJobWidget *searchJobWidget)
|
|||
// Re-launch search
|
||||
auto *searchHandler = SearchPluginManager::instance()->startSearch(searchJobWidget->searchPattern(), selectedCategory(), selectedPlugins());
|
||||
searchJobWidget->assignSearchHandler(searchHandler);
|
||||
tabStatusChanged(searchJobWidget);
|
||||
}
|
||||
|
||||
void SearchWidget::DataStorage::loadSession(const bool withSearchResults)
|
||||
{
|
||||
const Path sessionFilePath = makeDataFilePath(SESSION_FILE_NAME);
|
||||
const auto loadResult = ::loadSession(sessionFilePath);
|
||||
if (!loadResult)
|
||||
{
|
||||
LogMsg(tr("Failed to load Search UI saved state data. File: \"%1\". Error: \"%2\"")
|
||||
.arg(sessionFilePath.toString(), loadResult.error()), Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
const SessionData &sessionData = loadResult.value();
|
||||
|
||||
for (const auto &[tabID, searchPattern] : sessionData.tabs)
|
||||
{
|
||||
QList<SearchResult> searchResults;
|
||||
|
||||
if (withSearchResults)
|
||||
{
|
||||
const Path tabStateFilePath = makeDataFilePath(tabID + u".json");
|
||||
if (const auto loadTabStateResult = loadSearchResults(tabStateFilePath))
|
||||
{
|
||||
searchResults = loadTabStateResult.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Failed to load saved search results. Tab: \"%1\". File: \"%2\". Error: \"%3\"")
|
||||
.arg(searchPattern, tabStateFilePath.toString(), loadTabStateResult.error()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
emit tabLoaded(tabID, searchPattern, searchResults);
|
||||
}
|
||||
|
||||
emit sessionLoaded(sessionData);
|
||||
}
|
||||
|
||||
void SearchWidget::DataStorage::storeSession(const SessionData &sessionData)
|
||||
{
|
||||
QJsonArray tabsList;
|
||||
for (const auto &[tabID, searchPattern] : sessionData.tabs)
|
||||
{
|
||||
const QJsonObject tabObj {
|
||||
{u"ID"_s, tabID},
|
||||
{u"SearchPattern"_s, searchPattern}
|
||||
};
|
||||
tabsList.append(tabObj);
|
||||
}
|
||||
|
||||
const QJsonObject sessionObj {
|
||||
{u"Tabs"_s, tabsList},
|
||||
{u"CurrentTab"_s, sessionData.currentTabID}
|
||||
};
|
||||
|
||||
const Path sessionFilePath = makeDataFilePath(SESSION_FILE_NAME);
|
||||
const auto saveResult = Utils::IO::saveToFile(sessionFilePath, QJsonDocument(sessionObj).toJson());
|
||||
if (!saveResult)
|
||||
{
|
||||
LogMsg(tr("Failed to save Search UI state. File: \"%1\". Error: \"%2\"")
|
||||
.arg(sessionFilePath.toString(), saveResult.error()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
void SearchWidget::DataStorage::removeSession()
|
||||
{
|
||||
Utils::Fs::removeFile(makeDataFilePath(SESSION_FILE_NAME));
|
||||
}
|
||||
|
||||
void SearchWidget::DataStorage::storeTab(const QString &tabID, const QList<SearchResult> &searchResults)
|
||||
{
|
||||
QJsonArray searchResultsArray;
|
||||
for (const SearchResult &searchResult : searchResults)
|
||||
{
|
||||
searchResultsArray.append(QJsonObject {
|
||||
{KEY_RESULT_FILENAME, searchResult.fileName},
|
||||
{KEY_RESULT_FILEURL, searchResult.fileUrl},
|
||||
{KEY_RESULT_FILESIZE, searchResult.fileSize},
|
||||
{KEY_RESULT_SEEDERSCOUNT, searchResult.nbSeeders},
|
||||
{KEY_RESULT_LEECHERSCOUNT, searchResult.nbLeechers},
|
||||
{KEY_RESULT_ENGINENAME, searchResult.engineName},
|
||||
{KEY_RESULT_SITEURL, searchResult.siteUrl},
|
||||
{KEY_RESULT_DESCRLINK, searchResult.descrLink},
|
||||
{KEY_RESULT_PUBDATE, Utils::DateTime::toSecsSinceEpoch(searchResult.pubDate)}
|
||||
});
|
||||
}
|
||||
|
||||
const Path filePath = makeDataFilePath(tabID + u".json");
|
||||
const auto saveResult = Utils::IO::saveToFile(filePath, QJsonDocument(searchResultsArray).toJson());
|
||||
if (!saveResult)
|
||||
{
|
||||
LogMsg(tr("Failed to save search results. Tab: \"%1\". File: \"%2\". Error: \"%3\"")
|
||||
.arg(tabID, filePath.toString(), saveResult.error()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
void SearchWidget::DataStorage::removeTab(const QString &tabID)
|
||||
{
|
||||
Utils::Fs::removeFile(makeDataFilePath(tabID + u".json"));
|
||||
}
|
||||
|
||||
#include "searchwidget.moc"
|
||||
|
|
|
@ -31,8 +31,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QWidget>
|
||||
|
||||
#include "base/utils/thread.h"
|
||||
#include "gui/guiapplicationcomponent.h"
|
||||
|
||||
class QEvent;
|
||||
|
@ -62,6 +64,8 @@ signals:
|
|||
private:
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
void onPreferencesChanged();
|
||||
|
||||
void pluginsButtonClicked();
|
||||
void searchButtonClicked();
|
||||
void stopButtonClicked();
|
||||
|
@ -86,7 +90,22 @@ private:
|
|||
QString selectedCategory() const;
|
||||
QStringList selectedPlugins() const;
|
||||
|
||||
QString generateTabID() const;
|
||||
int addTab(const QString &tabID, SearchJobWidget *searchJobWdget);
|
||||
|
||||
void saveSession() const;
|
||||
void restoreSession();
|
||||
|
||||
Ui::SearchWidget *m_ui = nullptr;
|
||||
QPointer<SearchJobWidget> m_currentSearchTab; // Selected tab
|
||||
bool m_isNewQueryString = false;
|
||||
QHash<QString, SearchJobWidget *> m_tabWidgets;
|
||||
|
||||
bool m_storeOpenedTabs = false;
|
||||
bool m_storeOpenedTabsResults = false;
|
||||
|
||||
Utils::Thread::UniquePtr m_ioThread;
|
||||
|
||||
class DataStorage;
|
||||
DataStorage *m_dataStorage = nullptr;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue