qBittorrent
transferlistfilterswidget.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL". If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
30 
31 #include <QCheckBox>
32 #include <QIcon>
33 #include <QListWidgetItem>
34 #include <QMenu>
35 #include <QPainter>
36 #include <QScrollArea>
37 #include <QStyleOptionButton>
38 #include <QUrl>
39 #include <QVBoxLayout>
40 
43 #include "base/global.h"
44 #include "base/logger.h"
46 #include "base/preferences.h"
47 #include "base/torrentfilter.h"
48 #include "base/utils/compare.h"
49 #include "base/utils/fs.h"
50 #include "categoryfilterwidget.h"
51 #include "tagfilterwidget.h"
52 #include "transferlistwidget.h"
53 #include "uithememanager.h"
54 #include "utils.h"
55 
56 namespace
57 {
59  {
64  };
65 
66  QString getScheme(const QString &tracker)
67  {
68  const QUrl url {tracker};
69  QString scheme = url.scheme();
70  if (scheme.isEmpty())
71  scheme = "http";
72  return scheme;
73  }
74 
75  QString getHost(const QString &tracker)
76  {
77  // We want the domain + tld. Subdomains should be disregarded
78  const QUrl url {tracker};
79  const QString host {url.host()};
80 
81  // host is in IP format
82  if (!QHostAddress(host).isNull())
83  return host;
84 
85  return host.section('.', -2, -1);
86  }
87 
88  class ArrowCheckBox final : public QCheckBox
89  {
90  public:
91  using QCheckBox::QCheckBox;
92 
93  private:
94  void paintEvent(QPaintEvent *) override
95  {
96  QPainter painter(this);
97 
98  QStyleOptionViewItem indicatorOption;
99  indicatorOption.initFrom(this);
100  indicatorOption.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &indicatorOption, this);
101  indicatorOption.state |= (QStyle::State_Children
102  | (isChecked() ? QStyle::State_Open : QStyle::State_None));
103  style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOption, &painter, this);
104 
105  QStyleOptionButton labelOption;
106  initStyleOption(&labelOption);
107  labelOption.rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &labelOption, this);
108  style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &painter, this);
109  }
110  };
111 
112  const QString NULL_HOST {""};
113 }
114 
116  : QListWidget(parent)
117  , transferList(transferList)
118 {
119  setFrameShape(QFrame::NoFrame);
120  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
121  setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
122  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
123  setUniformItemSizes(true);
124  setSpacing(0);
125 
126  setIconSize(Utils::Gui::smallIconSize());
127 
128 #if defined(Q_OS_MACOS)
129  setAttribute(Qt::WA_MacShowFocusRect, false);
130 #endif
131 
132  setContextMenuPolicy(Qt::CustomContextMenu);
133  connect(this, &BaseFilterWidget::customContextMenuRequested, this, &BaseFilterWidget::showMenu);
134  connect(this, &BaseFilterWidget::currentRowChanged, this, &BaseFilterWidget::applyFilter);
135 
140 }
141 
143 {
144  return
145  {
146  // Width should be exactly the width of the content
147  sizeHintForColumn(0),
148  // Height should be exactly the height of the content
149  static_cast<int>((sizeHintForRow(0) + 2 * spacing()) * (count() + 0.5)),
150  };
151 }
152 
154 {
155  QSize size = sizeHint();
156  size.setWidth(6);
157  return size;
158 }
159 
161 {
162  setVisible(checked);
163  if (checked)
164  applyFilter(currentRow());
165  else
167 }
168 
170  : BaseFilterWidget(parent, transferList)
171 {
178 
179  // Add status filters
180  auto *all = new QListWidgetItem(this);
181  all->setData(Qt::DisplayRole, tr("All (0)", "this is for the status filter"));
182  all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("filterall")));
183  auto *downloading = new QListWidgetItem(this);
184  downloading->setData(Qt::DisplayRole, tr("Downloading (0)"));
185  downloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("downloading")));
186  auto *seeding = new QListWidgetItem(this);
187  seeding->setData(Qt::DisplayRole, tr("Seeding (0)"));
188  seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("uploading")));
189  auto *completed = new QListWidgetItem(this);
190  completed->setData(Qt::DisplayRole, tr("Completed (0)"));
191  completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("completed")));
192  auto *resumed = new QListWidgetItem(this);
193  resumed->setData(Qt::DisplayRole, tr("Resumed (0)"));
194  resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("resumed")));
195  auto *paused = new QListWidgetItem(this);
196  paused->setData(Qt::DisplayRole, tr("Paused (0)"));
197  paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("paused")));
198  auto *active = new QListWidgetItem(this);
199  active->setData(Qt::DisplayRole, tr("Active (0)"));
200  active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("filteractive")));
201  auto *inactive = new QListWidgetItem(this);
202  inactive->setData(Qt::DisplayRole, tr("Inactive (0)"));
203  inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("filterinactive")));
204  auto *stalled = new QListWidgetItem(this);
205  stalled->setData(Qt::DisplayRole, tr("Stalled (0)"));
206  stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("filterstalled")));
207  auto *stalledUploading = new QListWidgetItem(this);
208  stalledUploading->setData(Qt::DisplayRole, tr("Stalled Uploading (0)"));
209  stalledUploading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("stalledUP")));
210  auto *stalledDownloading = new QListWidgetItem(this);
211  stalledDownloading->setData(Qt::DisplayRole, tr("Stalled Downloading (0)"));
212  stalledDownloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("stalledDL")));
213  auto *checking = new QListWidgetItem(this);
214  checking->setData(Qt::DisplayRole, tr("Checking (0)"));
215  checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("checking")));
216  auto *errored = new QListWidgetItem(this);
217  errored->setData(Qt::DisplayRole, tr("Errored (0)"));
218  errored->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(QLatin1String("error")));
219 
220  const Preferences *const pref = Preferences::instance();
221  setCurrentRow(pref->getTransSelFilter(), QItemSelectionModel::SelectCurrent);
223 }
224 
226 {
227  Preferences::instance()->setTransSelFilter(currentRow());
228 }
229 
231 {
232  int nbDownloading = 0;
233  int nbSeeding = 0;
234  int nbCompleted = 0;
235  int nbResumed = 0;
236  int nbPaused = 0;
237  int nbActive = 0;
238  int nbInactive = 0;
239  int nbStalled = 0;
240  int nbStalledUploading = 0;
241  int nbStalledDownloading = 0;
242  int nbChecking = 0;
243  int nbErrored = 0;
244 
245  const QVector<BitTorrent::Torrent *> torrents = BitTorrent::Session::instance()->torrents();
246  for (const BitTorrent::Torrent *torrent : torrents)
247  {
248  if (torrent->isDownloading())
249  ++nbDownloading;
250  if (torrent->isUploading())
251  ++nbSeeding;
252  if (torrent->isCompleted())
253  ++nbCompleted;
254  if (torrent->isResumed())
255  ++nbResumed;
256  if (torrent->isPaused())
257  ++nbPaused;
258  if (torrent->isActive())
259  ++nbActive;
260  if (torrent->isInactive())
261  ++nbInactive;
262  if (torrent->state() == BitTorrent::TorrentState::StalledUploading)
263  ++nbStalledUploading;
264  if (torrent->state() == BitTorrent::TorrentState::StalledDownloading)
265  ++nbStalledDownloading;
266  if (torrent->isChecking())
267  ++nbChecking;
268  if (torrent->isErrored())
269  ++nbErrored;
270  }
271 
272  nbStalled = nbStalledUploading + nbStalledDownloading;
273 
274  item(TorrentFilter::All)->setData(Qt::DisplayRole, tr("All (%1)").arg(torrents.count()));
275  item(TorrentFilter::Downloading)->setData(Qt::DisplayRole, tr("Downloading (%1)").arg(nbDownloading));
276  item(TorrentFilter::Seeding)->setData(Qt::DisplayRole, tr("Seeding (%1)").arg(nbSeeding));
277  item(TorrentFilter::Completed)->setData(Qt::DisplayRole, tr("Completed (%1)").arg(nbCompleted));
278  item(TorrentFilter::Resumed)->setData(Qt::DisplayRole, tr("Resumed (%1)").arg(nbResumed));
279  item(TorrentFilter::Paused)->setData(Qt::DisplayRole, tr("Paused (%1)").arg(nbPaused));
280  item(TorrentFilter::Active)->setData(Qt::DisplayRole, tr("Active (%1)").arg(nbActive));
281  item(TorrentFilter::Inactive)->setData(Qt::DisplayRole, tr("Inactive (%1)").arg(nbInactive));
282  item(TorrentFilter::Stalled)->setData(Qt::DisplayRole, tr("Stalled (%1)").arg(nbStalled));
283  item(TorrentFilter::StalledUploading)->setData(Qt::DisplayRole, tr("Stalled Uploading (%1)").arg(nbStalledUploading));
284  item(TorrentFilter::StalledDownloading)->setData(Qt::DisplayRole, tr("Stalled Downloading (%1)").arg(nbStalledDownloading));
285  item(TorrentFilter::Checking)->setData(Qt::DisplayRole, tr("Checking (%1)").arg(nbChecking));
286  item(TorrentFilter::Errored)->setData(Qt::DisplayRole, tr("Errored (%1)").arg(nbErrored));
287 }
288 
289 void StatusFilterWidget::showMenu(const QPoint &) {}
290 
292 {
294 }
295 
297 
299 
300 TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
301  : BaseFilterWidget(parent, transferList)
302  , m_totalTorrents(0)
303  , m_downloadTrackerFavicon(downloadFavicon)
304 {
305  auto *allTrackers = new QListWidgetItem(this);
306  allTrackers->setData(Qt::DisplayRole, tr("All (0)", "this is for the tracker filter"));
307  allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon("network-server"));
308  auto *noTracker = new QListWidgetItem(this);
309  noTracker->setData(Qt::DisplayRole, tr("Trackerless (0)"));
310  noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon("network-server"));
311  auto *errorTracker = new QListWidgetItem(this);
312  errorTracker->setData(Qt::DisplayRole, tr("Error (0)"));
313  errorTracker->setData(Qt::DecorationRole, style()->standardIcon(QStyle::SP_MessageBoxCritical));
314  auto *warningTracker = new QListWidgetItem(this);
315  warningTracker->setData(Qt::DisplayRole, tr("Warning (0)"));
316  warningTracker->setData(Qt::DecorationRole, style()->standardIcon(QStyle::SP_MessageBoxWarning));
317  m_trackers[NULL_HOST] = {};
318 
319  setCurrentRow(0, QItemSelectionModel::SelectCurrent);
320  toggleFilter(Preferences::instance()->getTrackerFilterState());
321 }
322 
324 {
325  for (const QString &iconPath : asConst(m_iconPaths))
326  Utils::Fs::forceRemove(iconPath);
327 }
328 
329 void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::TorrentID &id)
330 {
331  const QString host {getHost(tracker)};
332  const bool exists {m_trackers.contains(host)};
333  QListWidgetItem *trackerItem {nullptr};
334 
335  if (exists)
336  {
337  if (m_trackers.value(host).contains(id))
338  return;
339 
340  trackerItem = item((host == NULL_HOST)
342  : rowFromTracker(host));
343  }
344  else
345  {
346  trackerItem = new QListWidgetItem();
347  trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon("network-server"));
348 
349  const QString scheme = getScheme(tracker);
350  downloadFavicon(QString::fromLatin1("%1://%2/favicon.ico").arg((scheme.startsWith("http") ? scheme : "http"), host));
351  }
352  if (!trackerItem) return;
353 
354  QSet<BitTorrent::TorrentID> &torrentIDs {m_trackers[host]};
355  torrentIDs.insert(id);
356 
357  if (host == NULL_HOST)
358  {
359  trackerItem->setText(tr("Trackerless (%1)").arg(torrentIDs.size()));
360  if (currentRow() == TRACKERLESS_ROW)
362  return;
363  }
364 
365  trackerItem->setText(QString::fromLatin1("%1 (%2)").arg(host, QString::number(torrentIDs.size())));
366  if (exists)
367  {
368  if (currentRow() == rowFromTracker(host))
369  applyFilter(currentRow());
370  return;
371  }
372 
373  Q_ASSERT(count() >= 4);
375  int insPos = count();
376  for (int i = 4; i < count(); ++i)
377  {
378  if (naturalLessThan(host, item(i)->text()))
379  {
380  insPos = i;
381  break;
382  }
383  }
384  QListWidget::insertItem(insPos, trackerItem);
385  updateGeometry();
386 }
387 
388 void TrackerFiltersList::removeItem(const QString &tracker, const BitTorrent::TorrentID &id)
389 {
390  const QString host = getHost(tracker);
391  QSet<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host);
392 
393  if (torrentIDs.empty())
394  return;
395  torrentIDs.remove(id);
396 
397  int row = 0;
398  QListWidgetItem *trackerItem = nullptr;
399 
400  if (!host.isEmpty())
401  {
402  // Remove from 'Error' and 'Warning' view
403  trackerSuccess(id, tracker);
404  row = rowFromTracker(host);
405  trackerItem = item(row);
406 
407  if (torrentIDs.empty())
408  {
409  if (currentRow() == row)
410  setCurrentRow(0, QItemSelectionModel::SelectCurrent);
411  delete trackerItem;
412  m_trackers.remove(host);
413  updateGeometry();
414  return;
415  }
416 
417  if (trackerItem)
418  trackerItem->setText(QString::fromLatin1("%1 (%2)").arg(host, QString::number(torrentIDs.size())));
419  }
420  else
421  {
422  row = 1;
423  trackerItem = item(TRACKERLESS_ROW);
424  trackerItem->setText(tr("Trackerless (%1)").arg(torrentIDs.size()));
425  }
426 
427  m_trackers.insert(host, torrentIDs);
428 
429  if (currentRow() == row)
430  applyFilter(row);
431 }
432 
433 void TrackerFiltersList::changeTrackerless(const bool trackerless, const BitTorrent::TorrentID &id)
434 {
435  if (trackerless)
436  addItem(NULL_HOST, id);
437  else
438  removeItem(NULL_HOST, id);
439 }
440 
442 {
443  if (value == m_downloadTrackerFavicon) return;
445 
447  {
448  for (auto i = m_trackers.cbegin(); i != m_trackers.cend(); ++i)
449  {
450  const QString &tracker = i.key();
451  if (!tracker.isEmpty())
452  {
453  const QString scheme = getScheme(tracker);
454  downloadFavicon(QString("%1://%2/favicon.ico")
455  .arg((scheme.startsWith("http") ? scheme : "http"), getHost(tracker)));
456  }
457  }
458  }
459 }
460 
461 void TrackerFiltersList::trackerSuccess(const BitTorrent::TorrentID &id, const QString &tracker)
462 {
463  const auto errorHashesIter = m_errors.find(id);
464  if (errorHashesIter != m_errors.end())
465  {
466  QSet<QString> &errored = *errorHashesIter;
467  errored.remove(tracker);
468  if (errored.empty())
469  {
470  m_errors.erase(errorHashesIter);
471  item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size()));
472  if (currentRow() == ERROR_ROW)
474  }
475  }
476 
477  const auto warningHashesIter = m_warnings.find(id);
478  if (warningHashesIter != m_warnings.end())
479  {
480  QSet<QString> &warned = *warningHashesIter;
481  warned.remove(tracker);
482  if (warned.empty())
483  {
484  m_warnings.erase(warningHashesIter);
485  item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size()));
486  if (currentRow() == WARNING_ROW)
488  }
489  }
490 }
491 
492 void TrackerFiltersList::trackerError(const BitTorrent::TorrentID &id, const QString &tracker)
493 {
494  QSet<QString> &trackers {m_errors[id]};
495  if (trackers.contains(tracker))
496  return;
497 
498  trackers.insert(tracker);
499  item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size()));
500  if (currentRow() == ERROR_ROW)
502 }
503 
504 void TrackerFiltersList::trackerWarning(const BitTorrent::TorrentID &id, const QString &tracker)
505 {
506  QSet<QString> &trackers {m_warnings[id]};
507  if (trackers.contains(tracker))
508  return;
509 
510  trackers.insert(tracker);
511  item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size()));
512  if (currentRow() == WARNING_ROW)
514 }
515 
516 void TrackerFiltersList::downloadFavicon(const QString &url)
517 {
518  if (!m_downloadTrackerFavicon) return;
522 }
523 
525 {
526  if (result.status != Net::DownloadStatus::Success)
527  {
528  if (result.url.endsWith(".ico", Qt::CaseInsensitive))
529  downloadFavicon(result.url.left(result.url.size() - 4) + ".png");
530  return;
531  }
532 
533  const QString host = getHost(result.url);
534 
535  if (!m_trackers.contains(host))
536  {
538  return;
539  }
540 
541  QListWidgetItem *trackerItem = item(rowFromTracker(host));
542  if (!trackerItem) return;
543 
544  QIcon icon(result.filePath);
545  //Detect a non-decodable icon
546  QList<QSize> sizes = icon.availableSizes();
547  bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull());
548  if (invalid)
549  {
550  if (result.url.endsWith(".ico", Qt::CaseInsensitive))
551  downloadFavicon(result.url.left(result.url.size() - 4) + ".png");
553  }
554  else
555  {
556  trackerItem->setData(Qt::DecorationRole, QIcon(result.filePath));
557  m_iconPaths.append(result.filePath);
558  }
559 }
560 
561 void TrackerFiltersList::showMenu(const QPoint &)
562 {
563  QMenu *menu = new QMenu(this);
564  menu->setAttribute(Qt::WA_DeleteOnClose);
565 
566  menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
568  menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
570  menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
572 
573  menu->popup(QCursor::pos());
574 }
575 
577 {
578  if (row == ALL_ROW)
580  else if (isVisible())
582 }
583 
585 {
586  const BitTorrent::TorrentID torrentID {torrent->id()};
587  const QVector<BitTorrent::TrackerEntry> trackers {torrent->trackers()};
588  for (const BitTorrent::TrackerEntry &tracker : trackers)
589  addItem(tracker.url, torrentID);
590 
591  // Check for trackerless torrent
592  if (trackers.isEmpty())
593  addItem(NULL_HOST, torrentID);
594 
595  item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(++m_totalTorrents));
596 }
597 
599 {
600  const BitTorrent::TorrentID torrentID {torrent->id()};
601  const QVector<BitTorrent::TrackerEntry> trackers {torrent->trackers()};
602  for (const BitTorrent::TrackerEntry &tracker : trackers)
603  removeItem(tracker.url, torrentID);
604 
605  // Check for trackerless torrent
606  if (trackers.isEmpty())
607  removeItem(NULL_HOST, torrentID);
608 
609  item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(--m_totalTorrents));
610 }
611 
613 {
614  Q_ASSERT(row > 1);
615  const QString tracker = item(row)->text();
616  QStringList parts = tracker.split(' ');
617  Q_ASSERT(parts.size() >= 2);
618  parts.removeLast(); // Remove trailing number
619  return parts.join(' ');
620 }
621 
622 int TrackerFiltersList::rowFromTracker(const QString &tracker) const
623 {
624  Q_ASSERT(!tracker.isEmpty());
625  for (int i = 4; i < count(); ++i)
626  {
627  if (tracker == trackerFromRow(i))
628  return i;
629  }
630  return -1;
631 }
632 
633 QSet<BitTorrent::TorrentID> TrackerFiltersList::getTorrentIDs(const int row) const
634 {
635  switch (row)
636  {
637  case TRACKERLESS_ROW:
638  return m_trackers.value(NULL_HOST);
639  case ERROR_ROW:
640  return {m_errors.keyBegin(), m_errors.keyEnd()};
641  case WARNING_ROW:
642  return {m_warnings.keyBegin(), m_warnings.keyEnd()};
643  default:
644  return m_trackers.value(trackerFromRow(row));
645  }
646 }
647 
648 TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
649  : QFrame(parent)
650  , m_transferList(transferList)
651 {
652  Preferences *const pref = Preferences::instance();
653 
654  // Construct lists
655  auto *vLayout = new QVBoxLayout(this);
656  auto *scroll = new QScrollArea(this);
657  QFrame *frame = new QFrame(scroll);
658  auto *frameLayout = new QVBoxLayout(frame);
659  QFont font;
660  font.setBold(true);
661  font.setCapitalization(QFont::AllUppercase);
662 
663  scroll->setWidgetResizable(true);
664  scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
665 
666  setStyleSheet("QFrame {background: transparent;}");
667  scroll->setStyleSheet("QFrame {border: none;}");
668  vLayout->setContentsMargins(0, 0, 0, 0);
669  frameLayout->setContentsMargins(0, 2, 0, 0);
670  frameLayout->setSpacing(2);
671  frameLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
672 
673  frame->setLayout(frameLayout);
674  scroll->setWidget(frame);
675  vLayout->addWidget(scroll);
676  setLayout(vLayout);
677 
678  QCheckBox *statusLabel = new ArrowCheckBox(tr("Status"), this);
679  statusLabel->setChecked(pref->getStatusFilterState());
680  statusLabel->setFont(font);
681  frameLayout->addWidget(statusLabel);
682 
683  auto *statusFilters = new StatusFilterWidget(this, transferList);
684  frameLayout->addWidget(statusFilters);
685 
686  QCheckBox *categoryLabel = new ArrowCheckBox(tr("Categories"), this);
687  categoryLabel->setChecked(pref->getCategoryFilterState());
688  categoryLabel->setFont(font);
689  connect(categoryLabel, &QCheckBox::toggled, this
691  frameLayout->addWidget(categoryLabel);
692 
701  , transferList, &TransferListWidget::applyCategoryFilter);
703  frameLayout->addWidget(m_categoryFilterWidget);
704 
705  QCheckBox *tagsLabel = new ArrowCheckBox(tr("Tags"), this);
706  tagsLabel->setChecked(pref->getTagFilterState());
707  tagsLabel->setFont(font);
708  connect(tagsLabel, &QCheckBox::toggled, this, &TransferListFiltersWidget::onTagFilterStateChanged);
709  frameLayout->addWidget(tagsLabel);
710 
719  , transferList, &TransferListWidget::applyTagFilter);
721  frameLayout->addWidget(m_tagFilterWidget);
722 
723  QCheckBox *trackerLabel = new ArrowCheckBox(tr("Trackers"), this);
724  trackerLabel->setChecked(pref->getTrackerFilterState());
725  trackerLabel->setFont(font);
726  frameLayout->addWidget(trackerLabel);
727 
728  m_trackerFilters = new TrackerFiltersList(this, transferList, downloadFavicon);
729  frameLayout->addWidget(m_trackerFilters);
730 
731  connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFilterWidget::toggleFilter);
732  connect(statusLabel, &QCheckBox::toggled, pref, &Preferences::setStatusFilterState);
733  connect(trackerLabel, &QCheckBox::toggled, m_trackerFilters, &TrackerFiltersList::toggleFilter);
734  connect(trackerLabel, &QCheckBox::toggled, pref, &Preferences::setTrackerFilterState);
735 
736  connect(this, qOverload<const BitTorrent::TorrentID &, const QString &>(&TransferListFiltersWidget::trackerSuccess)
738  connect(this, qOverload<const BitTorrent::TorrentID &, const QString &>(&TransferListFiltersWidget::trackerError)
740  connect(this, qOverload<const BitTorrent::TorrentID &, const QString &>(&TransferListFiltersWidget::trackerWarning)
742 }
743 
745 {
747 }
748 
749 void TransferListFiltersWidget::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers)
750 {
751  for (const BitTorrent::TrackerEntry &tracker : trackers)
752  m_trackerFilters->addItem(tracker.url, torrent->id());
753 }
754 
755 void TransferListFiltersWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers)
756 {
757  for (const BitTorrent::TrackerEntry &tracker : trackers)
758  m_trackerFilters->removeItem(tracker.url, torrent->id());
759 }
760 
761 void TransferListFiltersWidget::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless)
762 {
763  m_trackerFilters->changeTrackerless(trackerless, torrent->id());
764 }
765 
766 void TransferListFiltersWidget::trackerSuccess(const BitTorrent::Torrent *torrent, const QString &tracker)
767 {
768  emit trackerSuccess(torrent->id(), tracker);
769 }
770 
771 void TransferListFiltersWidget::trackerWarning(const BitTorrent::Torrent *torrent, const QString &tracker)
772 {
773  emit trackerWarning(torrent->id(), tracker);
774 }
775 
776 void TransferListFiltersWidget::trackerError(const BitTorrent::Torrent *torrent, const QString &tracker)
777 {
778  emit trackerError(torrent->id(), tracker);
779 }
780 
782 {
783  toggleCategoryFilter(enabled);
785 }
786 
788 {
789  m_categoryFilterWidget->setVisible(enabled);
791 }
792 
794 {
795  toggleTagFilter(enabled);
797 }
798 
800 {
801  m_tagFilterWidget->setVisible(enabled);
802  m_transferList->applyTagFilter(enabled ? m_tagFilterWidget->currentTag() : QString());
803 }
virtual void handleNewTorrent(BitTorrent::Torrent *const)=0
void toggleFilter(bool checked)
BaseFilterWidget(QWidget *parent, TransferListWidget *transferList)
virtual void showMenu(const QPoint &)=0
virtual void applyFilter(int row)=0
virtual void torrentAboutToBeDeleted(BitTorrent::Torrent *const)=0
QSize minimumSizeHint() const override
TransferListWidget * transferList
QSize sizeHint() const override
QVector< Torrent * > torrents() const
Definition: session.cpp:1997
static Session * instance()
Definition: session.cpp:997
void torrentAboutToBeRemoved(Torrent *torrent)
void torrentsUpdated(const QVector< Torrent * > &torrents)
void torrentLoaded(Torrent *torrent)
TorrentID id() const
Definition: torrent.cpp:54
virtual QVector< TrackerEntry > trackers() const =0
void categoryChanged(const QString &categoryName)
void actionDeleteTorrentsTriggered()
QString currentCategory() const
void actionPauseTorrentsTriggered()
void actionResumeTorrentsTriggered()
DownloadHandler * download(const DownloadRequest &downloadRequest)
static DownloadManager * instance()
static Preferences * instance()
bool getStatusFilterState() const
bool getTrackerFilterState() const
int getTransSelFilter() const
void setStatusFilterState(bool checked)
void setTagFilterState(bool checked)
bool getCategoryFilterState() const
void setTrackerFilterState(bool checked)
bool getTagFilterState() const
void setTransSelFilter(int index)
void setCategoryFilterState(bool checked)
void showMenu(const QPoint &) override
void handleNewTorrent(BitTorrent::Torrent *const) override
StatusFilterWidget(QWidget *parent, TransferListWidget *transferList)
void torrentAboutToBeDeleted(BitTorrent::Torrent *const) override
void applyFilter(int row) override
void actionPauseTorrentsTriggered()
void actionResumeTorrentsTriggered()
void tagChanged(const QString &tag)
void actionDeleteTorrentsTriggered()
QString currentTag() const
void handleNewTorrent(BitTorrent::Torrent *const torrent) override
QSet< BitTorrent::TorrentID > getTorrentIDs(int row) const
void trackerError(const BitTorrent::TorrentID &id, const QString &tracker)
void removeItem(const QString &tracker, const BitTorrent::TorrentID &id)
void downloadFavicon(const QString &url)
void setDownloadTrackerFavicon(bool value)
QHash< BitTorrent::TorrentID, QSet< QString > > m_errors
TrackerFiltersList(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon)
void trackerSuccess(const BitTorrent::TorrentID &id, const QString &tracker)
void addItem(const QString &tracker, const BitTorrent::TorrentID &id)
void trackerWarning(const BitTorrent::TorrentID &id, const QString &tracker)
void applyFilter(int row) override
void handleFavicoDownloadFinished(const Net::DownloadResult &result)
QString trackerFromRow(int row) const
QHash< QString, QSet< BitTorrent::TorrentID > > m_trackers
void torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) override
QHash< BitTorrent::TorrentID, QSet< QString > > m_warnings
int rowFromTracker(const QString &tracker) const
void changeTrackerless(bool trackerless, const BitTorrent::TorrentID &id)
void showMenu(const QPoint &) override
void onCategoryFilterStateChanged(bool enabled)
void trackerWarning(const BitTorrent::Torrent *torrent, const QString &tracker)
void removeTrackers(const BitTorrent::Torrent *torrent, const QVector< BitTorrent::TrackerEntry > &trackers)
void trackerError(const BitTorrent::Torrent *torrent, const QString &tracker)
void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless)
void addTrackers(const BitTorrent::Torrent *torrent, const QVector< BitTorrent::TrackerEntry > &trackers)
void trackerSuccess(const BitTorrent::Torrent *torrent, const QString &tracker)
CategoryFilterWidget * m_categoryFilterWidget
TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon)
void applyCategoryFilter(const QString &category)
void applyTrackerFilter(const QSet< BitTorrent::TorrentID > &torrentIDs)
void applyTagFilter(const QString &tag)
static UIThemeManager * instance()
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
bool forceRemove(const QString &filePath)
Definition: fs.cpp:173
QSize smallIconSize(const QWidget *widget=nullptr)
Definition: utils.cpp:97
nonstd::expected< void, QString > saveToFile(const QString &path, const QByteArray &data)
Definition: io.cpp:69
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
Definition: trackerentry.h:38
DownloadStatus status