qBittorrent
searchjobwidget.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
4  * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give permission to
21  * link this program with the OpenSSL project's "OpenSSL" library (or with
22  * modified versions of it that use the same license as the "OpenSSL" library),
23  * and distribute the linked executables. You must obey the GNU General Public
24  * License in all respects for all of the code used other than "OpenSSL". If you
25  * modify file(s), you may extend this exception to your version of the file(s),
26  * but you are not obligated to do so. If you do not wish to do so, delete this
27  * exception statement from your version.
28  */
29 
30 #include "searchjobwidget.h"
31 
32 #include <QApplication>
33 #include <QClipboard>
34 #include <QDesktopServices>
35 #include <QHeaderView>
36 #include <QKeyEvent>
37 #include <QMenu>
38 #include <QPalette>
39 #include <QStandardItemModel>
40 #include <QTableView>
41 #include <QUrl>
42 
44 #include "base/preferences.h"
48 #include "base/settingvalue.h"
49 #include "base/utils/misc.h"
51 #include "gui/lineedit.h"
52 #include "gui/uithememanager.h"
53 #include "gui/utils.h"
54 #include "searchsortmodel.h"
55 #include "ui_searchjobwidget.h"
56 
57 SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, QWidget *parent)
58  : QWidget(parent)
59  , m_ui(new Ui::SearchJobWidget)
60  , m_searchHandler(searchHandler)
61 {
62  m_ui->setupUi(this);
63 
64  // This hack fixes reordering of first column with Qt5.
65  // https://github.com/qtproject/qtbase/commit/e0fc088c0c8bc61dbcaf5928b24986cd61a22777
66  QTableView unused;
67  unused.setVerticalHeader(m_ui->resultsBrowser->header());
68  m_ui->resultsBrowser->header()->setParent(m_ui->resultsBrowser);
69  unused.setVerticalHeader(new QHeaderView(Qt::Horizontal));
70 
71  loadSettings();
72 
73  header()->setStretchLastSection(false);
74  header()->setTextElideMode(Qt::ElideRight);
75 
76  // Set Search results list model
77  m_searchListModel = new QStandardItemModel(0, SearchSortModel::NB_SEARCH_COLUMNS, this);
78  m_searchListModel->setHeaderData(SearchSortModel::NAME, Qt::Horizontal, tr("Name", "i.e: file name"));
79  m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, tr("Size", "i.e: file size"));
80  m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources"));
81  m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources"));
82  m_searchListModel->setHeaderData(SearchSortModel::ENGINE_URL, Qt::Horizontal, tr("Search engine"));
83  // Set columns text alignment
84  m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
85  m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
86  m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
87 
88  m_proxyModel = new SearchSortModel(this);
89  m_proxyModel->setDynamicSortFilter(true);
90  m_proxyModel->setSourceModel(m_searchListModel);
91  m_proxyModel->setNameFilter(searchHandler->pattern());
92  m_ui->resultsBrowser->setModel(m_proxyModel);
93 
94  m_ui->resultsBrowser->hideColumn(SearchSortModel::DL_LINK); // Hide url column
95  m_ui->resultsBrowser->hideColumn(SearchSortModel::DESC_LINK);
96 
97  m_ui->resultsBrowser->setSelectionMode(QAbstractItemView::ExtendedSelection);
98  m_ui->resultsBrowser->setRootIsDecorated(false);
99  m_ui->resultsBrowser->setAllColumnsShowFocus(true);
100  m_ui->resultsBrowser->setSortingEnabled(true);
101  m_ui->resultsBrowser->setEditTriggers(QAbstractItemView::NoEditTriggers);
102 
103  // Ensure that at least one column is visible at all times
104  bool atLeastOne = false;
105  for (int i = 0; i < SearchSortModel::DL_LINK; ++i)
106  {
107  if (!m_ui->resultsBrowser->isColumnHidden(i))
108  {
109  atLeastOne = true;
110  break;
111  }
112  }
113  if (!atLeastOne)
114  m_ui->resultsBrowser->setColumnHidden(SearchSortModel::NAME, false);
115  // To also mitigate the above issue, we have to resize each column when
116  // its size is 0, because explicitly 'showing' the column isn't enough
117  // in the above scenario.
118  for (int i = 0; i < SearchSortModel::DL_LINK; ++i)
119  if ((m_ui->resultsBrowser->columnWidth(i) <= 0) && !m_ui->resultsBrowser->isColumnHidden(i))
120  m_ui->resultsBrowser->resizeColumnToContents(i);
121 
122  header()->setContextMenuPolicy(Qt::CustomContextMenu);
123  connect(header(), &QWidget::customContextMenuRequested, this, &SearchJobWidget::displayToggleColumnsMenu);
124  connect(header(), &QHeaderView::sectionResized, this, &SearchJobWidget::saveSettings);
125  connect(header(), &QHeaderView::sectionMoved, this, &SearchJobWidget::saveSettings);
126  connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings);
127 
129 
130  updateFilter();
131 
133  m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results..."));
134  m_lineEditSearchResultsFilter->setContextMenuPolicy(Qt::CustomContextMenu);
135  connect(m_lineEditSearchResultsFilter, &QWidget::customContextMenuRequested, this, &SearchJobWidget::showFilterContextMenu);
136  m_ui->horizontalLayout->insertWidget(0, m_lineEditSearchResultsFilter);
137 
138  connect(m_lineEditSearchResultsFilter, &LineEdit::textChanged, this, &SearchJobWidget::filterSearchResults);
139  connect(m_ui->filterMode, qOverload<int>(&QComboBox::currentIndexChanged)
141  connect(m_ui->minSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
142  connect(m_ui->minSeeds, qOverload<int>(&QSpinBox::valueChanged)
144  connect(m_ui->maxSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
145  connect(m_ui->maxSeeds, qOverload<int>(&QSpinBox::valueChanged)
147  connect(m_ui->minSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
148  connect(m_ui->minSize, qOverload<double>(&QDoubleSpinBox::valueChanged)
150  connect(m_ui->maxSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
151  connect(m_ui->maxSize, qOverload<double>(&QDoubleSpinBox::valueChanged)
153  connect(m_ui->minSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
155  connect(m_ui->maxSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
157 
158  connect(m_ui->resultsBrowser, &QAbstractItemView::doubleClicked, this, &SearchJobWidget::onItemDoubleClicked);
159 
161  connect(searchHandler, &SearchHandler::searchFinished, this, &SearchJobWidget::searchFinished);
162  connect(searchHandler, &SearchHandler::searchFailed, this, &SearchJobWidget::searchFailed);
163  connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
164 
165  setStatusTip(statusText(m_status));
166 }
167 
169 {
170  saveSettings();
171  delete m_ui;
172 }
173 
174 void SearchJobWidget::onItemDoubleClicked(const QModelIndex &index)
175 {
176  downloadTorrent(index);
177 }
178 
179 QHeaderView *SearchJobWidget::header() const
180 {
181  return m_ui->resultsBrowser->header();
182 }
183 
184 // Set the color of a row in data model
185 void SearchJobWidget::setRowColor(int row, const QColor &color)
186 {
187  m_proxyModel->setDynamicSortFilter(false);
188  for (int i = 0; i < m_proxyModel->columnCount(); ++i)
189  m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
190 
191  m_proxyModel->setDynamicSortFilter(true);
192 }
193 
195 {
196  return m_status;
197 }
198 
200 {
201  return m_proxyModel->rowCount();
202 }
203 
205 {
207 }
208 
210 {
212 }
213 
215 {
216  const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
217  for (const QModelIndex &rowIndex : rows)
218  downloadTorrent(rowIndex, option);
219 }
220 
222 {
223  const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
224  for (const QModelIndex &rowIndex : rows)
225  {
226  const QString descrLink = m_proxyModel->data(
227  m_proxyModel->index(rowIndex.row(), SearchSortModel::DESC_LINK)).toString();
228  if (!descrLink.isEmpty())
229  QDesktopServices::openUrl(QUrl::fromEncoded(descrLink.toUtf8()));
230  }
231 }
232 
234 {
236 }
237 
239 {
241 }
242 
244 {
246 }
247 
248 void SearchJobWidget::copyField(const int column) const
249 {
250  const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
251  QStringList list;
252 
253  for (const QModelIndex &rowIndex : rows)
254  {
255  const QString field = m_proxyModel->data(
256  m_proxyModel->index(rowIndex.row(), column)).toString();
257  if (!field.isEmpty())
258  list << field;
259  }
260 
261  if (!list.empty())
262  QApplication::clipboard()->setText(list.join('\n'));
263 }
264 
266 {
267  if (m_status == value) return;
268 
269  m_status = value;
270  setStatusTip(statusText(value));
271  emit statusChanged();
272 }
273 
274 void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorrentOption option)
275 {
276  const QString torrentUrl = m_proxyModel->data(
277  m_proxyModel->index(rowIndex.row(), SearchSortModel::DL_LINK)).toString();
278  const QString siteUrl = m_proxyModel->data(
279  m_proxyModel->index(rowIndex.row(), SearchSortModel::ENGINE_URL)).toString();
280 
281  if (torrentUrl.startsWith("magnet:", Qt::CaseInsensitive))
282  {
283  addTorrentToSession(torrentUrl, option);
284  }
285  else
286  {
287  SearchDownloadHandler *downloadHandler = m_searchHandler->manager()->downloadTorrent(siteUrl, torrentUrl);
288  connect(downloadHandler, &SearchDownloadHandler::downloadFinished
289  , this, [this, option](const QString &source) { addTorrentToSession(source, option); });
290  connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
291  }
292  setRowColor(rowIndex.row(), QApplication::palette().color(QPalette::LinkVisited));
293 }
294 
296 {
297  if (source.isEmpty()) return;
298 
301  else
303 }
304 
306 {
307  const int totalResults = m_searchListModel->rowCount();
308  const int filteredResults = m_proxyModel->rowCount();
309  m_ui->resultsLbl->setText(tr("Results (showing <i>%1</i> out of <i>%2</i>):", "i.e: Search results")
310  .arg(filteredResults).arg(totalResults));
311 
312  m_noSearchResults = (totalResults == 0);
313  emit resultsCountUpdated();
314 }
315 
317 {
318  using Utils::Misc::SizeUnit;
319 
321  // we update size and seeds filter parameters in the model even if they are disabled
322  m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value());
324  sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex())),
325  sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
326 
328 
329  m_proxyModel->invalidate();
331 }
332 
334 {
335  using Utils::Misc::SizeUnit;
337 
338  QStringList unitStrings;
339  unitStrings.append(unitString(SizeUnit::Byte));
340  unitStrings.append(unitString(SizeUnit::KibiByte));
341  unitStrings.append(unitString(SizeUnit::MebiByte));
342  unitStrings.append(unitString(SizeUnit::GibiByte));
343  unitStrings.append(unitString(SizeUnit::TebiByte));
344  unitStrings.append(unitString(SizeUnit::PebiByte));
345  unitStrings.append(unitString(SizeUnit::ExbiByte));
346 
347  m_ui->minSizeUnit->clear();
348  m_ui->maxSizeUnit->clear();
349  m_ui->minSizeUnit->addItems(unitStrings);
350  m_ui->maxSizeUnit->addItems(unitStrings);
351 
352  m_ui->minSize->setValue(0);
353  m_ui->minSizeUnit->setCurrentIndex(static_cast<int>(SizeUnit::MebiByte));
354 
355  m_ui->maxSize->setValue(-1);
356  m_ui->maxSizeUnit->setCurrentIndex(static_cast<int>(SizeUnit::GibiByte));
357 
358  m_ui->filterMode->clear();
359 
360  m_ui->filterMode->addItem(tr("Torrent names only"), static_cast<int>(NameFilteringMode::OnlyNames));
361  m_ui->filterMode->addItem(tr("Everywhere"), static_cast<int>(NameFilteringMode::Everywhere));
362 
363  QVariant selectedMode = static_cast<int>(nameFilteringModeSetting().get(NameFilteringMode::OnlyNames));
364  int index = m_ui->filterMode->findData(selectedMode);
365  m_ui->filterMode->setCurrentIndex((index == -1) ? 0 : index);
366 }
367 
368 void SearchJobWidget::filterSearchResults(const QString &name)
369 {
372  m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
374 }
375 
377 {
378  const Preferences *pref = Preferences::instance();
379 
380  QMenu *menu = m_lineEditSearchResultsFilter->createStandardContextMenu();
381  menu->setAttribute(Qt::WA_DeleteOnClose);
382  menu->addSeparator();
383 
384  QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
385  useRegexAct->setCheckable(true);
386  useRegexAct->setChecked(pref->getRegexAsFilteringPatternForSearchJob());
387  connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForSearchJob);
388  connect(useRegexAct, &QAction::toggled, this, [this]() { filterSearchResults(m_lineEditSearchResultsFilter->text()); });
389 
390  menu->popup(QCursor::pos());
391 }
392 
393 void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
394 {
395  auto *menu = new QMenu(this);
396  menu->setAttribute(Qt::WA_DeleteOnClose);
397 
398  menu->addAction(UIThemeManager::instance()->getIcon("download"), tr("Open download window")
399  , this, [this]() { downloadTorrents(AddTorrentOption::ShowDialog); });
400  menu->addAction(UIThemeManager::instance()->getIcon("download"), tr("Download")
401  , this, [this]() { downloadTorrents(AddTorrentOption::SkipDialog); });
402  menu->addSeparator();
403  menu->addAction(UIThemeManager::instance()->getIcon("application-x-mswinurl"), tr("Open description page")
405 
406  QMenu *copySubMenu = menu->addMenu(
407  UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
408 
409  copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Name")
411  copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Download link")
413  copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Description page URL")
415 
416  menu->popup(event->globalPos());
417 }
418 
420 {
421  switch (st)
422  {
423  case Status::Ongoing:
424  return tr("Searching...");
425  case Status::Finished:
426  return tr("Search has finished");
427  case Status::Aborted:
428  return tr("Search aborted");
429  case Status::Error:
430  return tr("An error occurred during search...");
431  case Status::NoResults:
432  return tr("Search returned no results");
433  default:
434  return {};
435  }
436 }
437 
439 {
440  return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());
441 }
442 
444 {
445  header()->restoreState(Preferences::instance()->getSearchTabHeaderState());
446 }
447 
449 {
451 }
452 
454 {
455  auto menu = new QMenu(this);
456  menu->setAttribute(Qt::WA_DeleteOnClose);
457  menu->setTitle(tr("Column visibility"));
458 
459  for (int i = 0; i < SearchSortModel::DL_LINK; ++i)
460  {
461  QAction *myAct = menu->addAction(m_searchListModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());
462  myAct->setCheckable(true);
463  myAct->setChecked(!m_ui->resultsBrowser->isColumnHidden(i));
464  myAct->setData(i);
465  }
466 
467  connect(menu, &QMenu::triggered, this, [this](const QAction *action)
468  {
469  int visibleCols = 0;
470  for (int i = 0; i < SearchSortModel::DL_LINK; ++i)
471  {
472  if (!m_ui->resultsBrowser->isColumnHidden(i))
473  ++visibleCols;
474 
475  if (visibleCols > 1)
476  break;
477  }
478 
479  const int col = action->data().toInt();
480 
481  if ((!m_ui->resultsBrowser->isColumnHidden(col)) && (visibleCols == 1))
482  return;
483 
484  m_ui->resultsBrowser->setColumnHidden(col, !m_ui->resultsBrowser->isColumnHidden(col));
485 
486  if ((!m_ui->resultsBrowser->isColumnHidden(col)) && (m_ui->resultsBrowser->columnWidth(col) <= 5))
487  m_ui->resultsBrowser->resizeColumnToContents(col);
488 
489  saveSettings();
490  });
491 
492  menu->popup(QCursor::pos());
493 }
494 
496 {
497  if (cancelled)
499  else if (m_noSearchResults)
501  else
503 }
504 
506 {
508 }
509 
510 void SearchJobWidget::appendSearchResults(const QVector<SearchResult> &results)
511 {
512  for (const SearchResult &result : results)
513  {
514  // Add item to search result list
515  int row = m_searchListModel->rowCount();
516  m_searchListModel->insertRow(row);
517 
518  const auto setModelData = [this, row] (const int column, const QString &displayData
519  , const QVariant &underlyingData, const Qt::Alignment textAlignmentData = {})
520  {
521  const QMap<int, QVariant> data =
522  {
523  {Qt::DisplayRole, displayData},
524  {SearchSortModel::UnderlyingDataRole, underlyingData},
525  {Qt::TextAlignmentRole, QVariant {textAlignmentData}}
526  };
527  m_searchListModel->setItemData(m_searchListModel->index(row, column), data);
528  };
529 
530  setModelData(SearchSortModel::NAME, result.fileName, result.fileName);
531  setModelData(SearchSortModel::DL_LINK, result.fileUrl, result.fileUrl);
532  setModelData(SearchSortModel::ENGINE_URL, result.siteUrl, result.siteUrl);
533  setModelData(SearchSortModel::DESC_LINK, result.descrLink, result.descrLink);
534  setModelData(SearchSortModel::SIZE, Utils::Misc::friendlyUnit(result.fileSize), result.fileSize, (Qt::AlignRight | Qt::AlignVCenter));
535  setModelData(SearchSortModel::SEEDS, QString::number(result.nbSeeders), result.nbSeeders, (Qt::AlignRight | Qt::AlignVCenter));
536  setModelData(SearchSortModel::LEECHES, QString::number(result.nbLeechers), result.nbLeechers, (Qt::AlignRight | Qt::AlignVCenter));
537  }
538 
540 }
541 
543 {
544  static SettingValue<NameFilteringMode> setting {"Search/FilteringMode"};
545  return setting;
546 }
547 
548 void SearchJobWidget::keyPressEvent(QKeyEvent *event)
549 {
550  switch (event->key())
551  {
552  case Qt::Key_Enter:
553  case Qt::Key_Return:
555  break;
556  default:
557  QWidget::keyPressEvent(event);
558  }
559 }
QBITTORRENT_HAS_EXECINFO_H if(NOT QBITTORRENT_HAS_EXECINFO_H) message(FATAL_ERROR "execinfo.h header file not found.\n" "Please either disable the STACKTRACE feature or use a libc that has this header file
static void show(const QString &source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
static Session * instance()
Definition: session.cpp:997
bool addTorrent(const QString &source, const AddTorrentParams &params=AddTorrentParams())
Definition: session.cpp:2007
static Preferences * instance()
void setRegexAsFilteringPatternForSearchJob(bool checked)
void setSearchTabHeaderState(const QByteArray &state)
bool getRegexAsFilteringPatternForSearchJob() const
void downloadFinished(const QString &path)
SearchPluginManager * manager() const
QString pattern() const
void searchFinished(bool cancelled=false)
void searchFailed()
void newSearchResults(const QVector< SearchResult > &results)
SearchSortModel * m_proxyModel
NameFilteringMode filteringMode() const
void openTorrentPages() const
LineEdit * m_lineEditSearchResultsFilter
void setRowColor(int row, const QColor &color)
static QString statusText(Status st)
void copyTorrentURLs() const
int visibleResultsCount() const
QHeaderView * header() const
void displayToggleColumnsMenu(const QPoint &)
~SearchJobWidget() override
Status status() const
void statusChanged()
void copyTorrentNames() const
void appendSearchResults(const QVector< SearchResult > &results)
void downloadTorrent(const QModelIndex &rowIndex, AddTorrentOption option=AddTorrentOption::Default)
void downloadTorrents(AddTorrentOption option=AddTorrentOption::Default)
void keyPressEvent(QKeyEvent *event) override
void showFilterContextMenu(const QPoint &)
void saveSettings() const
void setStatus(Status value)
void filterSearchResults(const QString &name)
void searchFinished(bool cancelled)
SearchHandler * m_searchHandler
void addTorrentToSession(const QString &source, AddTorrentOption option=AddTorrentOption::Default)
static SettingValue< NameFilteringMode > & nameFilteringModeSetting()
QStandardItemModel * m_searchListModel
Ui::SearchJobWidget * m_ui
void resultsCountUpdated()
void onItemDoubleClicked(const QModelIndex &index)
LineEdit * lineEditSearchResultsFilter() const
void contextMenuEvent(QContextMenuEvent *event) override
void copyTorrentDownloadLinks() const
SearchJobWidget(SearchHandler *searchHandler, QWidget *parent=nullptr)
void copyField(int column) const
SearchDownloadHandler * downloadTorrent(const QString &siteUrl, const QString &url)
void setNameFilter(const QString &searchTerm={})
void enableNameFilter(bool enabled)
void setSeedsFilter(int minSeeds, int maxSeeds)
Sets parameters for filtering by seeds number.
void setSizeFilter(qint64 minSize, qint64 maxSize)
Sets parameters for filtering by size.
static UIThemeManager * instance()
QIcon getIcon(const QString &iconId, const QString &fallback={}) const
SizeUnit
Definition: misc.h:49
QString unitString(SizeUnit unit, bool isSpeed=false)
Definition: misc.cpp:252
qint64 sizeInBytes(qreal size, SizeUnit unit)
Definition: misc.cpp:288
QString friendlyUnit(qint64 bytes, bool isSpeed=false)
Definition: misc.cpp:261
QString wildcardToRegexPattern(const QString &pattern)
Definition: string.cpp:57
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
action
Definition: tstool.py:143