mirror of
https://github.com/qbittorrent/qBittorrent
synced 2024-11-14 19:12:46 +01:00
Add option to auto hide zero status filters
* Extract transfer list filter classes into separate files * Add option to auto hide zero status filters PR #18801. Closes #13996.
This commit is contained in:
parent
d40be79c69
commit
0dcb65bb7c
@ -1551,6 +1551,16 @@ void Preferences::setTransSelFilter(const int index)
|
||||
setValue(u"TransferListFilters/selectedFilterIndex"_qs, index);
|
||||
}
|
||||
|
||||
bool Preferences::getHideZeroStatusFilters() const
|
||||
{
|
||||
return value<bool>(u"TransferListFilters/HideZeroStatusFilters"_qs, false);
|
||||
}
|
||||
|
||||
void Preferences::setHideZeroStatusFilters(const bool hide)
|
||||
{
|
||||
setValue(u"TransferListFilters/HideZeroStatusFilters"_qs, hide);
|
||||
}
|
||||
|
||||
QByteArray Preferences::getTransHeaderState() const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
|
@ -382,6 +382,8 @@ public:
|
||||
bool getTrackerFilterState() const;
|
||||
int getTransSelFilter() const;
|
||||
void setTransSelFilter(int index);
|
||||
bool getHideZeroStatusFilters() const;
|
||||
void setHideZeroStatusFilters(bool hide);
|
||||
QByteArray getTransHeaderState() const;
|
||||
void setTransHeaderState(const QByteArray &state);
|
||||
bool getRegexAsFilteringPatternForTransferList() const;
|
||||
|
@ -49,9 +49,6 @@ add_library(qbt_gui STATIC
|
||||
advancedsettings.h
|
||||
autoexpandabledialog.h
|
||||
banlistoptionsdialog.h
|
||||
categoryfiltermodel.h
|
||||
categoryfilterproxymodel.h
|
||||
categoryfilterwidget.h
|
||||
color.h
|
||||
cookiesdialog.h
|
||||
cookiesmodel.h
|
||||
@ -103,9 +100,6 @@ add_library(qbt_gui STATIC
|
||||
speedlimitdialog.h
|
||||
statsdialog.h
|
||||
statusbar.h
|
||||
tagfiltermodel.h
|
||||
tagfilterproxymodel.h
|
||||
tagfilterwidget.h
|
||||
torrentcategorydialog.h
|
||||
torrentcontentfiltermodel.h
|
||||
torrentcontentitemdelegate.h
|
||||
@ -119,6 +113,15 @@ add_library(qbt_gui STATIC
|
||||
torrenttagsdialog.h
|
||||
trackerentriesdialog.h
|
||||
transferlistdelegate.h
|
||||
transferlistfilters/basefilterwidget.h
|
||||
transferlistfilters/categoryfiltermodel.h
|
||||
transferlistfilters/categoryfilterproxymodel.h
|
||||
transferlistfilters/categoryfilterwidget.h
|
||||
transferlistfilters/statusfilterwidget.h
|
||||
transferlistfilters/tagfiltermodel.h
|
||||
transferlistfilters/tagfilterproxymodel.h
|
||||
transferlistfilters/tagfilterwidget.h
|
||||
transferlistfilters/trackersfilterwidget.h
|
||||
transferlistfilterswidget.h
|
||||
transferlistmodel.h
|
||||
transferlistsortmodel.h
|
||||
@ -140,9 +143,6 @@ add_library(qbt_gui STATIC
|
||||
advancedsettings.cpp
|
||||
autoexpandabledialog.cpp
|
||||
banlistoptionsdialog.cpp
|
||||
categoryfiltermodel.cpp
|
||||
categoryfilterproxymodel.cpp
|
||||
categoryfilterwidget.cpp
|
||||
cookiesdialog.cpp
|
||||
cookiesmodel.cpp
|
||||
deletionconfirmationdialog.cpp
|
||||
@ -192,9 +192,6 @@ add_library(qbt_gui STATIC
|
||||
speedlimitdialog.cpp
|
||||
statsdialog.cpp
|
||||
statusbar.cpp
|
||||
tagfiltermodel.cpp
|
||||
tagfilterproxymodel.cpp
|
||||
tagfilterwidget.cpp
|
||||
torrentcategorydialog.cpp
|
||||
torrentcontentfiltermodel.cpp
|
||||
torrentcontentitemdelegate.cpp
|
||||
@ -208,6 +205,15 @@ add_library(qbt_gui STATIC
|
||||
torrenttagsdialog.cpp
|
||||
trackerentriesdialog.cpp
|
||||
transferlistdelegate.cpp
|
||||
transferlistfilters/basefilterwidget.cpp
|
||||
transferlistfilters/categoryfiltermodel.cpp
|
||||
transferlistfilters/categoryfilterproxymodel.cpp
|
||||
transferlistfilters/categoryfilterwidget.cpp
|
||||
transferlistfilters/statusfilterwidget.cpp
|
||||
transferlistfilters/tagfiltermodel.cpp
|
||||
transferlistfilters/tagfilterproxymodel.cpp
|
||||
transferlistfilters/tagfilterwidget.cpp
|
||||
transferlistfilters/trackersfilterwidget.cpp
|
||||
transferlistfilterswidget.cpp
|
||||
transferlistmodel.cpp
|
||||
transferlistsortmodel.cpp
|
||||
|
@ -6,9 +6,6 @@ HEADERS += \
|
||||
$$PWD/advancedsettings.h \
|
||||
$$PWD/autoexpandabledialog.h \
|
||||
$$PWD/banlistoptionsdialog.h \
|
||||
$$PWD/categoryfiltermodel.h \
|
||||
$$PWD/categoryfilterproxymodel.h \
|
||||
$$PWD/categoryfilterwidget.h \
|
||||
$$PWD/color.h \
|
||||
$$PWD/cookiesdialog.h \
|
||||
$$PWD/cookiesmodel.h \
|
||||
@ -60,9 +57,6 @@ HEADERS += \
|
||||
$$PWD/speedlimitdialog.h \
|
||||
$$PWD/statsdialog.h \
|
||||
$$PWD/statusbar.h \
|
||||
$$PWD/tagfiltermodel.h \
|
||||
$$PWD/tagfilterproxymodel.h \
|
||||
$$PWD/tagfilterwidget.h \
|
||||
$$PWD/torrentcategorydialog.h \
|
||||
$$PWD/torrentcontentfiltermodel.h \
|
||||
$$PWD/torrentcontentitemdelegate.h \
|
||||
@ -76,6 +70,15 @@ HEADERS += \
|
||||
$$PWD/torrenttagsdialog.h \
|
||||
$$PWD/trackerentriesdialog.h \
|
||||
$$PWD/transferlistdelegate.h \
|
||||
$$PWD/transferlistfilters/basefilterwidget.h \
|
||||
$$PWD/transferlistfilters/categoryfiltermodel.h \
|
||||
$$PWD/transferlistfilters/categoryfilterproxymodel.h \
|
||||
$$PWD/transferlistfilters/categoryfilterwidget.h \
|
||||
$$PWD/transferlistfilters/statusfilterwidget.h \
|
||||
$$PWD/transferlistfilters/tagfiltermodel.h \
|
||||
$$PWD/transferlistfilters/tagfilterproxymodel.h \
|
||||
$$PWD/transferlistfilters/tagfilterwidget.h \
|
||||
$$PWD/transferlistfilters/trackersfilterwidget.h \
|
||||
$$PWD/transferlistfilterswidget.h \
|
||||
$$PWD/transferlistmodel.h \
|
||||
$$PWD/transferlistsortmodel.h \
|
||||
@ -97,9 +100,6 @@ SOURCES += \
|
||||
$$PWD/advancedsettings.cpp \
|
||||
$$PWD/autoexpandabledialog.cpp \
|
||||
$$PWD/banlistoptionsdialog.cpp \
|
||||
$$PWD/categoryfiltermodel.cpp \
|
||||
$$PWD/categoryfilterproxymodel.cpp \
|
||||
$$PWD/categoryfilterwidget.cpp \
|
||||
$$PWD/cookiesdialog.cpp \
|
||||
$$PWD/cookiesmodel.cpp \
|
||||
$$PWD/deletionconfirmationdialog.cpp \
|
||||
@ -149,9 +149,6 @@ SOURCES += \
|
||||
$$PWD/speedlimitdialog.cpp \
|
||||
$$PWD/statsdialog.cpp \
|
||||
$$PWD/statusbar.cpp \
|
||||
$$PWD/tagfiltermodel.cpp \
|
||||
$$PWD/tagfilterproxymodel.cpp \
|
||||
$$PWD/tagfilterwidget.cpp \
|
||||
$$PWD/torrentcategorydialog.cpp \
|
||||
$$PWD/torrentcontentfiltermodel.cpp \
|
||||
$$PWD/torrentcontentitemdelegate.cpp \
|
||||
@ -165,6 +162,15 @@ SOURCES += \
|
||||
$$PWD/torrenttagsdialog.cpp \
|
||||
$$PWD/trackerentriesdialog.cpp \
|
||||
$$PWD/transferlistdelegate.cpp \
|
||||
$$PWD/transferlistfilters/basefilterwidget.cpp \
|
||||
$$PWD/transferlistfilters/categoryfiltermodel.cpp \
|
||||
$$PWD/transferlistfilters/categoryfilterproxymodel.cpp \
|
||||
$$PWD/transferlistfilters/categoryfilterwidget.cpp \
|
||||
$$PWD/transferlistfilters/statusfilterwidget.cpp \
|
||||
$$PWD/transferlistfilters/tagfiltermodel.cpp \
|
||||
$$PWD/transferlistfilters/tagfilterproxymodel.cpp \
|
||||
$$PWD/transferlistfilters/tagfilterwidget.cpp \
|
||||
$$PWD/transferlistfilters/trackersfilterwidget.cpp \
|
||||
$$PWD/transferlistfilterswidget.cpp \
|
||||
$$PWD/transferlistmodel.cpp \
|
||||
$$PWD/transferlistsortmodel.cpp \
|
||||
|
@ -245,6 +245,8 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
actionSeeding = OPEN_DEST;
|
||||
m_ui->actionTorrentFnOnDblClBox->setCurrentIndex(m_ui->actionTorrentFnOnDblClBox->findData(actionSeeding));
|
||||
|
||||
m_ui->checkBoxHideZeroStatusFilters->setChecked(pref->getHideZeroStatusFilters());
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
m_ui->checkStartup->setVisible(false);
|
||||
#endif
|
||||
@ -344,6 +346,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
connect(m_ui->comboHideZero, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->actionTorrentDlOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->actionTorrentFnOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkBoxHideZeroStatusFilters, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
connect(m_ui->checkStartup, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
@ -417,6 +420,8 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
||||
pref->setActionOnDblClOnTorrentDl(m_ui->actionTorrentDlOnDblClBox->currentData().toInt());
|
||||
pref->setActionOnDblClOnTorrentFn(m_ui->actionTorrentFnOnDblClBox->currentData().toInt());
|
||||
|
||||
pref->setHideZeroStatusFilters(m_ui->checkBoxHideZeroStatusFilters->isChecked());
|
||||
|
||||
pref->setSplashScreenDisabled(isSplashScreenDisabled());
|
||||
pref->setConfirmOnExit(m_ui->checkProgramExitConfirm->isChecked());
|
||||
pref->setDontConfirmAutoExit(!m_ui->checkProgramAutoExitConfirm->isChecked());
|
||||
|
@ -384,6 +384,13 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxHideZeroStatusFilters">
|
||||
<property name="text">
|
||||
<string>Auto hide zero status filters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -3761,6 +3768,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
||||
<tabstop>checkAltRowColors</tabstop>
|
||||
<tabstop>actionTorrentDlOnDblClBox</tabstop>
|
||||
<tabstop>actionTorrentFnOnDblClBox</tabstop>
|
||||
<tabstop>checkBoxHideZeroStatusFilters</tabstop>
|
||||
<tabstop>checkStartup</tabstop>
|
||||
<tabstop>checkShowSplash</tabstop>
|
||||
<tabstop>windowStateComboBox</tabstop>
|
||||
|
92
src/gui/transferlistfilters/basefilterwidget.cpp
Normal file
92
src/gui/transferlistfilters/basefilterwidget.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "basefilterwidget.h"
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "gui/utils.h"
|
||||
|
||||
constexpr int ALL_ROW = 0;
|
||||
|
||||
BaseFilterWidget::BaseFilterWidget(QWidget *parent, TransferListWidget *transferList)
|
||||
: QListWidget(parent)
|
||||
, m_transferList {transferList}
|
||||
{
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
setUniformItemSizes(true);
|
||||
setSpacing(0);
|
||||
|
||||
setIconSize(Utils::Gui::smallIconSize());
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
#endif
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &BaseFilterWidget::customContextMenuRequested, this, &BaseFilterWidget::showMenu);
|
||||
connect(this, &BaseFilterWidget::currentRowChanged, this, &BaseFilterWidget::applyFilter);
|
||||
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsLoaded
|
||||
, this, &BaseFilterWidget::handleTorrentsLoaded);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved
|
||||
, this, &BaseFilterWidget::torrentAboutToBeDeleted);
|
||||
}
|
||||
|
||||
QSize BaseFilterWidget::sizeHint() const
|
||||
{
|
||||
return {
|
||||
// Width should be exactly the width of the content
|
||||
sizeHintForColumn(0),
|
||||
// Height should be exactly the height of the content
|
||||
static_cast<int>((sizeHintForRow(0) + 2 * spacing()) * (count() + 0.5))};
|
||||
}
|
||||
|
||||
QSize BaseFilterWidget::minimumSizeHint() const
|
||||
{
|
||||
QSize size = sizeHint();
|
||||
size.setWidth(6);
|
||||
return size;
|
||||
}
|
||||
|
||||
TransferListWidget *BaseFilterWidget::transferList() const
|
||||
{
|
||||
return m_transferList;
|
||||
}
|
||||
|
||||
void BaseFilterWidget::toggleFilter(const bool checked)
|
||||
{
|
||||
setVisible(checked);
|
||||
if (checked)
|
||||
applyFilter(currentRow());
|
||||
else
|
||||
applyFilter(ALL_ROW);
|
||||
}
|
63
src/gui/transferlistfilters/basefilterwidget.h
Normal file
63
src/gui/transferlistfilters/basefilterwidget.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QListWidget>
|
||||
|
||||
#include "base/bittorrent/torrent.h"
|
||||
|
||||
class TransferListWidget;
|
||||
|
||||
class BaseFilterWidget : public QListWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(BaseFilterWidget)
|
||||
|
||||
public:
|
||||
BaseFilterWidget(QWidget *parent, TransferListWidget *transferList);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
TransferListWidget *transferList() const;
|
||||
|
||||
public slots:
|
||||
void toggleFilter(bool checked);
|
||||
|
||||
private slots:
|
||||
virtual void showMenu() = 0;
|
||||
virtual void applyFilter(int row) = 0;
|
||||
virtual void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) = 0;
|
||||
virtual void torrentAboutToBeDeleted(BitTorrent::Torrent *const) = 0;
|
||||
|
||||
private:
|
||||
TransferListWidget *m_transferList = nullptr;
|
||||
};
|
@ -33,7 +33,7 @@
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "uithememanager.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
class CategoryModelItem
|
||||
{
|
@ -32,11 +32,11 @@
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "gui/torrentcategorydialog.h"
|
||||
#include "gui/uithememanager.h"
|
||||
#include "gui/utils.h"
|
||||
#include "categoryfiltermodel.h"
|
||||
#include "categoryfilterproxymodel.h"
|
||||
#include "torrentcategorydialog.h"
|
||||
#include "uithememanager.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace
|
||||
{
|
291
src/gui/transferlistfilters/statusfilterwidget.cpp
Normal file
291
src/gui/transferlistfilters/statusfilterwidget.cpp
Normal file
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "statusfilterwidget.h"
|
||||
|
||||
#include <QListWidgetItem>
|
||||
#include <QMenu>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "gui/transferlistwidget.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *transferList)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
{
|
||||
// Add status filters
|
||||
auto *all = new QListWidgetItem(this);
|
||||
all->setData(Qt::DisplayRole, tr("All (0)", "this is for the status filter"));
|
||||
all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-all"_qs, u"filterall"_qs));
|
||||
auto *downloading = new QListWidgetItem(this);
|
||||
downloading->setData(Qt::DisplayRole, tr("Downloading (0)"));
|
||||
downloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"downloading"_qs));
|
||||
auto *seeding = new QListWidgetItem(this);
|
||||
seeding->setData(Qt::DisplayRole, tr("Seeding (0)"));
|
||||
seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"upload"_qs, u"uploading"_qs));
|
||||
auto *completed = new QListWidgetItem(this);
|
||||
completed->setData(Qt::DisplayRole, tr("Completed (0)"));
|
||||
completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"checked-completed"_qs, u"completed"_qs));
|
||||
auto *resumed = new QListWidgetItem(this);
|
||||
resumed->setData(Qt::DisplayRole, tr("Resumed (0)"));
|
||||
resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs));
|
||||
auto *paused = new QListWidgetItem(this);
|
||||
paused->setData(Qt::DisplayRole, tr("Paused (0)"));
|
||||
paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stopped"_qs, u"media-playback-pause"_qs));
|
||||
auto *active = new QListWidgetItem(this);
|
||||
active->setData(Qt::DisplayRole, tr("Active (0)"));
|
||||
active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-active"_qs, u"filteractive"_qs));
|
||||
auto *inactive = new QListWidgetItem(this);
|
||||
inactive->setData(Qt::DisplayRole, tr("Inactive (0)"));
|
||||
inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-inactive"_qs, u"filterinactive"_qs));
|
||||
auto *stalled = new QListWidgetItem(this);
|
||||
stalled->setData(Qt::DisplayRole, tr("Stalled (0)"));
|
||||
stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-stalled"_qs, u"filterstalled"_qs));
|
||||
auto *stalledUploading = new QListWidgetItem(this);
|
||||
stalledUploading->setData(Qt::DisplayRole, tr("Stalled Uploading (0)"));
|
||||
stalledUploading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledUP"_qs));
|
||||
auto *stalledDownloading = new QListWidgetItem(this);
|
||||
stalledDownloading->setData(Qt::DisplayRole, tr("Stalled Downloading (0)"));
|
||||
stalledDownloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledDL"_qs));
|
||||
auto *checking = new QListWidgetItem(this);
|
||||
checking->setData(Qt::DisplayRole, tr("Checking (0)"));
|
||||
checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"checking"_qs));
|
||||
auto *moving = new QListWidgetItem(this);
|
||||
moving->setData(Qt::DisplayRole, tr("Moving (0)"));
|
||||
moving->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"set-location"_qs));
|
||||
auto *errored = new QListWidgetItem(this);
|
||||
errored->setData(Qt::DisplayRole, tr("Errored (0)"));
|
||||
errored->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"error"_qs));
|
||||
|
||||
const QVector<BitTorrent::Torrent *> torrents = BitTorrent::Session::instance()->torrents();
|
||||
update(torrents);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated
|
||||
, this, &StatusFilterWidget::update);
|
||||
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
connect(pref, &Preferences::changed, this, &StatusFilterWidget::configure);
|
||||
|
||||
const int storedRow = pref->getTransSelFilter();
|
||||
if (item((storedRow < count()) ? storedRow : 0)->isHidden())
|
||||
setCurrentRow(TorrentFilter::All, QItemSelectionModel::SelectCurrent);
|
||||
else
|
||||
setCurrentRow(storedRow, QItemSelectionModel::SelectCurrent);
|
||||
|
||||
toggleFilter(pref->getStatusFilterState());
|
||||
}
|
||||
|
||||
StatusFilterWidget::~StatusFilterWidget()
|
||||
{
|
||||
Preferences::instance()->setTransSelFilter(currentRow());
|
||||
}
|
||||
|
||||
QSize StatusFilterWidget::sizeHint() const
|
||||
{
|
||||
int numVisibleItems = 0;
|
||||
for (int i = 0; i < count(); ++i)
|
||||
{
|
||||
if (!item(i)->isHidden())
|
||||
++numVisibleItems;
|
||||
}
|
||||
|
||||
return {
|
||||
// Width should be exactly the width of the content
|
||||
sizeHintForColumn(0),
|
||||
// Height should be exactly the height of the content
|
||||
static_cast<int>((sizeHintForRow(0) + 2 * spacing()) * (numVisibleItems + 0.5))};
|
||||
}
|
||||
|
||||
void StatusFilterWidget::updateTorrentStatus(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
TorrentFilterBitset &torrentStatus = m_torrentsStatus[torrent];
|
||||
|
||||
const auto update = [torrent, &torrentStatus](const TorrentFilter::Type status, int &counter)
|
||||
{
|
||||
const bool hasStatus = torrentStatus[status];
|
||||
const bool needStatus = TorrentFilter(status).match(torrent);
|
||||
if (needStatus && !hasStatus)
|
||||
{
|
||||
++counter;
|
||||
torrentStatus.set(status);
|
||||
}
|
||||
else if (!needStatus && hasStatus)
|
||||
{
|
||||
--counter;
|
||||
torrentStatus.reset(status);
|
||||
}
|
||||
};
|
||||
|
||||
update(TorrentFilter::Downloading, m_nbDownloading);
|
||||
update(TorrentFilter::Seeding, m_nbSeeding);
|
||||
update(TorrentFilter::Completed, m_nbCompleted);
|
||||
update(TorrentFilter::Resumed, m_nbResumed);
|
||||
update(TorrentFilter::Paused, m_nbPaused);
|
||||
update(TorrentFilter::Active, m_nbActive);
|
||||
update(TorrentFilter::Inactive, m_nbInactive);
|
||||
update(TorrentFilter::StalledUploading, m_nbStalledUploading);
|
||||
update(TorrentFilter::StalledDownloading, m_nbStalledDownloading);
|
||||
update(TorrentFilter::Checking, m_nbChecking);
|
||||
update(TorrentFilter::Moving, m_nbMoving);
|
||||
update(TorrentFilter::Errored, m_nbErrored);
|
||||
|
||||
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading;
|
||||
}
|
||||
|
||||
void StatusFilterWidget::updateTexts()
|
||||
{
|
||||
const qsizetype torrentsCount = BitTorrent::Session::instance()->torrentsCount();
|
||||
item(TorrentFilter::All)->setData(Qt::DisplayRole, tr("All (%1)").arg(torrentsCount));
|
||||
item(TorrentFilter::Downloading)->setData(Qt::DisplayRole, tr("Downloading (%1)").arg(m_nbDownloading));
|
||||
item(TorrentFilter::Seeding)->setData(Qt::DisplayRole, tr("Seeding (%1)").arg(m_nbSeeding));
|
||||
item(TorrentFilter::Completed)->setData(Qt::DisplayRole, tr("Completed (%1)").arg(m_nbCompleted));
|
||||
item(TorrentFilter::Resumed)->setData(Qt::DisplayRole, tr("Resumed (%1)").arg(m_nbResumed));
|
||||
item(TorrentFilter::Paused)->setData(Qt::DisplayRole, tr("Paused (%1)").arg(m_nbPaused));
|
||||
item(TorrentFilter::Active)->setData(Qt::DisplayRole, tr("Active (%1)").arg(m_nbActive));
|
||||
item(TorrentFilter::Inactive)->setData(Qt::DisplayRole, tr("Inactive (%1)").arg(m_nbInactive));
|
||||
item(TorrentFilter::Stalled)->setData(Qt::DisplayRole, tr("Stalled (%1)").arg(m_nbStalled));
|
||||
item(TorrentFilter::StalledUploading)->setData(Qt::DisplayRole, tr("Stalled Uploading (%1)").arg(m_nbStalledUploading));
|
||||
item(TorrentFilter::StalledDownloading)->setData(Qt::DisplayRole, tr("Stalled Downloading (%1)").arg(m_nbStalledDownloading));
|
||||
item(TorrentFilter::Checking)->setData(Qt::DisplayRole, tr("Checking (%1)").arg(m_nbChecking));
|
||||
item(TorrentFilter::Moving)->setData(Qt::DisplayRole, tr("Moving (%1)").arg(m_nbMoving));
|
||||
item(TorrentFilter::Errored)->setData(Qt::DisplayRole, tr("Errored (%1)").arg(m_nbErrored));
|
||||
}
|
||||
|
||||
void StatusFilterWidget::hideZeroItems()
|
||||
{
|
||||
item(TorrentFilter::Downloading)->setHidden(m_nbDownloading == 0);
|
||||
item(TorrentFilter::Seeding)->setHidden(m_nbSeeding == 0);
|
||||
item(TorrentFilter::Completed)->setHidden(m_nbCompleted == 0);
|
||||
item(TorrentFilter::Resumed)->setHidden(m_nbResumed == 0);
|
||||
item(TorrentFilter::Paused)->setHidden(m_nbPaused == 0);
|
||||
item(TorrentFilter::Active)->setHidden(m_nbActive == 0);
|
||||
item(TorrentFilter::Inactive)->setHidden(m_nbInactive == 0);
|
||||
item(TorrentFilter::Stalled)->setHidden(m_nbStalled == 0);
|
||||
item(TorrentFilter::StalledUploading)->setHidden(m_nbStalledUploading == 0);
|
||||
item(TorrentFilter::StalledDownloading)->setHidden(m_nbStalledDownloading == 0);
|
||||
item(TorrentFilter::Checking)->setHidden(m_nbChecking == 0);
|
||||
item(TorrentFilter::Moving)->setHidden(m_nbMoving == 0);
|
||||
item(TorrentFilter::Errored)->setHidden(m_nbErrored == 0);
|
||||
|
||||
if (currentItem() && currentItem()->isHidden())
|
||||
setCurrentRow(TorrentFilter::All, QItemSelectionModel::SelectCurrent);
|
||||
}
|
||||
|
||||
void StatusFilterWidget::update(const QVector<BitTorrent::Torrent *> torrents)
|
||||
{
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
updateTorrentStatus(torrent);
|
||||
|
||||
updateTexts();
|
||||
|
||||
if (Preferences::instance()->getHideZeroStatusFilters())
|
||||
{
|
||||
hideZeroItems();
|
||||
updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusFilterWidget::showMenu()
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
|
||||
, transferList(), &TransferListWidget::startVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
|
||||
, transferList(), &TransferListWidget::pauseVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
|
||||
, transferList(), &TransferListWidget::deleteVisibleTorrents);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void StatusFilterWidget::applyFilter(int row)
|
||||
{
|
||||
transferList()->applyStatusFilter(row);
|
||||
}
|
||||
|
||||
void StatusFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
updateTorrentStatus(torrent);
|
||||
|
||||
updateTexts();
|
||||
}
|
||||
|
||||
void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const TorrentFilterBitset status = m_torrentsStatus.take(torrent);
|
||||
|
||||
if (status[TorrentFilter::Downloading])
|
||||
--m_nbDownloading;
|
||||
if (status[TorrentFilter::Seeding])
|
||||
--m_nbSeeding;
|
||||
if (status[TorrentFilter::Completed])
|
||||
--m_nbCompleted;
|
||||
if (status[TorrentFilter::Resumed])
|
||||
--m_nbResumed;
|
||||
if (status[TorrentFilter::Paused])
|
||||
--m_nbPaused;
|
||||
if (status[TorrentFilter::Active])
|
||||
--m_nbActive;
|
||||
if (status[TorrentFilter::Inactive])
|
||||
--m_nbInactive;
|
||||
if (status[TorrentFilter::StalledUploading])
|
||||
--m_nbStalledUploading;
|
||||
if (status[TorrentFilter::StalledDownloading])
|
||||
--m_nbStalledDownloading;
|
||||
if (status[TorrentFilter::Checking])
|
||||
--m_nbChecking;
|
||||
if (status[TorrentFilter::Moving])
|
||||
--m_nbMoving;
|
||||
if (status[TorrentFilter::Errored])
|
||||
--m_nbErrored;
|
||||
|
||||
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading;
|
||||
|
||||
updateTexts();
|
||||
}
|
||||
|
||||
void StatusFilterWidget::configure()
|
||||
{
|
||||
if (Preferences::instance()->getHideZeroStatusFilters())
|
||||
{
|
||||
hideZeroItems();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < count(); ++i)
|
||||
item(i)->setHidden(false);
|
||||
}
|
||||
|
||||
updateGeometry();
|
||||
}
|
81
src/gui/transferlistfilters/statusfilterwidget.h
Normal file
81
src/gui/transferlistfilters/statusfilterwidget.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QHash>
|
||||
|
||||
#include "base/torrentfilter.h"
|
||||
#include "basefilterwidget.h"
|
||||
|
||||
class StatusFilterWidget final : public BaseFilterWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(StatusFilterWidget)
|
||||
|
||||
public:
|
||||
StatusFilterWidget(QWidget *parent, TransferListWidget *transferList);
|
||||
~StatusFilterWidget() override;
|
||||
|
||||
private:
|
||||
QSize sizeHint() const override;
|
||||
|
||||
// These 4 methods are virtual slots in the base class.
|
||||
// No need to redeclare them here as slots.
|
||||
void showMenu() override;
|
||||
void applyFilter(int row) override;
|
||||
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *const) override;
|
||||
|
||||
void configure();
|
||||
|
||||
void update(const QVector<BitTorrent::Torrent *> torrents);
|
||||
void updateTorrentStatus(const BitTorrent::Torrent *torrent);
|
||||
void updateTexts();
|
||||
void hideZeroItems();
|
||||
|
||||
using TorrentFilterBitset = std::bitset<32>; // approximated size, this should be the number of TorrentFilter::Type elements
|
||||
QHash<const BitTorrent::Torrent *, TorrentFilterBitset> m_torrentsStatus;
|
||||
int m_nbDownloading = 0;
|
||||
int m_nbSeeding = 0;
|
||||
int m_nbCompleted = 0;
|
||||
int m_nbResumed = 0;
|
||||
int m_nbPaused = 0;
|
||||
int m_nbActive = 0;
|
||||
int m_nbInactive = 0;
|
||||
int m_nbStalled = 0;
|
||||
int m_nbStalledUploading = 0;
|
||||
int m_nbStalledDownloading = 0;
|
||||
int m_nbChecking = 0;
|
||||
int m_nbMoving = 0;
|
||||
int m_nbErrored = 0;
|
||||
};
|
@ -34,7 +34,7 @@
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "uithememanager.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
namespace
|
||||
{
|
@ -33,11 +33,11 @@
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "autoexpandabledialog.h"
|
||||
#include "gui/autoexpandabledialog.h"
|
||||
#include "gui/uithememanager.h"
|
||||
#include "gui/utils.h"
|
||||
#include "tagfiltermodel.h"
|
||||
#include "tagfilterproxymodel.h"
|
||||
#include "uithememanager.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace
|
||||
{
|
513
src/gui/transferlistfilters/trackersfilterwidget.cpp
Normal file
513
src/gui/transferlistfilters/trackersfilterwidget.cpp
Normal file
@ -0,0 +1,513 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "trackersfilterwidget.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMenu>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/compare.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "gui/transferlistwidget.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum TRACKER_FILTER_ROW
|
||||
{
|
||||
ALL_ROW,
|
||||
TRACKERLESS_ROW,
|
||||
ERROR_ROW,
|
||||
WARNING_ROW
|
||||
};
|
||||
|
||||
QString getScheme(const QString &tracker)
|
||||
{
|
||||
const QString scheme = QUrl(tracker).scheme();
|
||||
return !scheme.isEmpty() ? scheme : u"http"_qs;
|
||||
}
|
||||
|
||||
QString getHost(const QString &url)
|
||||
{
|
||||
// We want the domain + tld. Subdomains should be disregarded
|
||||
// If failed to parse the domain or IP address, original input should be returned
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
if (host.isEmpty())
|
||||
return url;
|
||||
|
||||
// host is in IP format
|
||||
if (!QHostAddress(host).isNull())
|
||||
return host;
|
||||
|
||||
return host.section(u'.', -2, -1);
|
||||
}
|
||||
|
||||
const QString NULL_HOST = u""_qs;
|
||||
}
|
||||
|
||||
TrackersFilterWidget::TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
, m_downloadTrackerFavicon(downloadFavicon)
|
||||
{
|
||||
auto *allTrackers = new QListWidgetItem(this);
|
||||
allTrackers->setData(Qt::DisplayRole, tr("All (0)", "this is for the tracker filter"));
|
||||
allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs));
|
||||
auto *noTracker = new QListWidgetItem(this);
|
||||
noTracker->setData(Qt::DisplayRole, tr("Trackerless (0)"));
|
||||
noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_qs, u"network-server"_qs));
|
||||
auto *errorTracker = new QListWidgetItem(this);
|
||||
errorTracker->setData(Qt::DisplayRole, tr("Error (0)"));
|
||||
errorTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_qs, u"dialog-error"_qs));
|
||||
auto *warningTracker = new QListWidgetItem(this);
|
||||
warningTracker->setData(Qt::DisplayRole, tr("Warning (0)"));
|
||||
warningTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_qs, u"dialog-warning"_qs));
|
||||
|
||||
m_trackers[NULL_HOST] = {{}, noTracker};
|
||||
|
||||
handleTorrentsLoaded(BitTorrent::Session::instance()->torrents());
|
||||
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
toggleFilter(Preferences::instance()->getTrackerFilterState());
|
||||
}
|
||||
|
||||
TrackersFilterWidget::~TrackersFilterWidget()
|
||||
{
|
||||
for (const Path &iconPath : asConst(m_iconPaths))
|
||||
Utils::Fs::removeFile(iconPath);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
addItems(tracker.url, {torrentID});
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
for (const QString &tracker : trackers)
|
||||
removeItem(tracker, torrentID);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::refreshTrackers(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
|
||||
m_errors.remove(torrentID);
|
||||
m_warnings.remove(torrentID);
|
||||
|
||||
Algorithm::removeIf(m_trackers, [this, &torrentID](const QString &host, TrackerData &trackerData)
|
||||
{
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackerData.torrents;
|
||||
if (!torrentIDs.remove(torrentID))
|
||||
return false;
|
||||
|
||||
QListWidgetItem *trackerItem = trackerData.item;
|
||||
|
||||
if (!host.isEmpty() && torrentIDs.isEmpty())
|
||||
{
|
||||
if (currentItem() == trackerItem)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerItem;
|
||||
return true;
|
||||
}
|
||||
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg((host.isEmpty() ? tr("Trackerless") : host), QString::number(torrentIDs.size())));
|
||||
return false;
|
||||
});
|
||||
|
||||
const QVector<BitTorrent::TrackerEntry> trackerEntries = torrent->trackers();
|
||||
const bool isTrackerless = trackerEntries.isEmpty();
|
||||
if (isTrackerless)
|
||||
{
|
||||
addItems(NULL_HOST, {torrentID});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const BitTorrent::TrackerEntry &trackerEntry : trackerEntries)
|
||||
addItems(trackerEntry.url, {torrentID});
|
||||
}
|
||||
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless)
|
||||
{
|
||||
if (trackerless)
|
||||
addItems(NULL_HOST, {torrent->id()});
|
||||
else
|
||||
removeItem(NULL_HOST, torrent->id());
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
auto trackersIt = m_trackers.find(host);
|
||||
const bool exists = (trackersIt != m_trackers.end());
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
|
||||
if (exists)
|
||||
{
|
||||
trackerItem = trackersIt->item;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem = new QListWidgetItem();
|
||||
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs));
|
||||
|
||||
const TrackerData trackerData {{}, trackerItem};
|
||||
trackersIt = m_trackers.insert(host, trackerData);
|
||||
|
||||
const QString scheme = getScheme(trackerURL);
|
||||
downloadFavicon(u"%1://%2/favicon.ico"_qs.arg((scheme.startsWith(u"http") ? scheme : u"http"_qs), host));
|
||||
}
|
||||
|
||||
Q_ASSERT(trackerItem);
|
||||
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents;
|
||||
for (const BitTorrent::TorrentID &torrentID : torrents)
|
||||
torrentIDs.insert(torrentID);
|
||||
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg(((host == NULL_HOST) ? tr("Trackerless") : host), QString::number(torrentIDs.size())));
|
||||
if (exists)
|
||||
{
|
||||
if (item(currentRow()) == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(count() >= 4);
|
||||
const Utils::Compare::NaturalLessThan<Qt::CaseSensitive> naturalLessThan {};
|
||||
int insPos = count();
|
||||
for (int i = 4; i < count(); ++i)
|
||||
{
|
||||
if (naturalLessThan(host, item(i)->text()))
|
||||
{
|
||||
insPos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
QListWidget::insertItem(insPos, trackerItem);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
|
||||
QSet<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host).torrents;
|
||||
torrentIDs.remove(id);
|
||||
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
|
||||
if (!host.isEmpty())
|
||||
{
|
||||
// Remove from 'Error' and 'Warning' view
|
||||
const auto errorHashesIt = m_errors.find(id);
|
||||
if (errorHashesIt != m_errors.end())
|
||||
{
|
||||
QSet<QString> &errored = errorHashesIt.value();
|
||||
errored.remove(trackerURL);
|
||||
if (errored.isEmpty())
|
||||
{
|
||||
m_errors.erase(errorHashesIt);
|
||||
item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size()));
|
||||
if (currentRow() == ERROR_ROW)
|
||||
applyFilter(ERROR_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
const auto warningHashesIt = m_warnings.find(id);
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
{
|
||||
QSet<QString> &warned = *warningHashesIt;
|
||||
warned.remove(trackerURL);
|
||||
if (warned.isEmpty())
|
||||
{
|
||||
m_warnings.erase(warningHashesIt);
|
||||
item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size()));
|
||||
if (currentRow() == WARNING_ROW)
|
||||
applyFilter(WARNING_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
trackerItem = m_trackers.value(host).item;
|
||||
|
||||
if (torrentIDs.isEmpty())
|
||||
{
|
||||
if (currentItem() == trackerItem)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerItem;
|
||||
m_trackers.remove(host);
|
||||
updateGeometry();
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackerItem)
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg(host, QString::number(torrentIDs.size())));
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem = item(TRACKERLESS_ROW);
|
||||
trackerItem->setText(tr("Trackerless (%1)").arg(torrentIDs.size()));
|
||||
}
|
||||
|
||||
m_trackers.insert(host, {torrentIDs, trackerItem});
|
||||
|
||||
if (currentItem() == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::setDownloadTrackerFavicon(bool value)
|
||||
{
|
||||
if (value == m_downloadTrackerFavicon) return;
|
||||
m_downloadTrackerFavicon = value;
|
||||
|
||||
if (m_downloadTrackerFavicon)
|
||||
{
|
||||
for (auto i = m_trackers.cbegin(); i != m_trackers.cend(); ++i)
|
||||
{
|
||||
const QString &tracker = i.key();
|
||||
if (!tracker.isEmpty())
|
||||
{
|
||||
const QString scheme = getScheme(tracker);
|
||||
downloadFavicon(u"%1://%2/favicon.ico"_qs
|
||||
.arg((scheme.startsWith(u"http") ? scheme : u"http"_qs), getHost(tracker)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries)
|
||||
{
|
||||
const BitTorrent::TorrentID id = torrent->id();
|
||||
|
||||
auto errorHashesIt = m_errors.find(id);
|
||||
auto warningHashesIt = m_warnings.find(id);
|
||||
|
||||
for (const BitTorrent::TrackerEntry &trackerEntry : updatedTrackerEntries)
|
||||
{
|
||||
if (trackerEntry.status == BitTorrent::TrackerEntry::Working)
|
||||
{
|
||||
if (errorHashesIt != m_errors.end())
|
||||
{
|
||||
QSet<QString> &errored = errorHashesIt.value();
|
||||
errored.remove(trackerEntry.url);
|
||||
}
|
||||
|
||||
if (trackerEntry.message.isEmpty())
|
||||
{
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
{
|
||||
QSet<QString> &warned = *warningHashesIt;
|
||||
warned.remove(trackerEntry.url);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (warningHashesIt == m_warnings.end())
|
||||
warningHashesIt = m_warnings.insert(id, {});
|
||||
warningHashesIt.value().insert(trackerEntry.url);
|
||||
}
|
||||
}
|
||||
else if (trackerEntry.status == BitTorrent::TrackerEntry::NotWorking)
|
||||
{
|
||||
if (errorHashesIt == m_errors.end())
|
||||
errorHashesIt = m_errors.insert(id, {});
|
||||
errorHashesIt.value().insert(trackerEntry.url);
|
||||
}
|
||||
}
|
||||
|
||||
if ((errorHashesIt != m_errors.end()) && errorHashesIt.value().isEmpty())
|
||||
m_errors.erase(errorHashesIt);
|
||||
if ((warningHashesIt != m_warnings.end()) && warningHashesIt.value().isEmpty())
|
||||
m_warnings.erase(warningHashesIt);
|
||||
|
||||
item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size()));
|
||||
item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size()));
|
||||
|
||||
if (currentRow() == ERROR_ROW)
|
||||
applyFilter(ERROR_ROW);
|
||||
else if (currentRow() == WARNING_ROW)
|
||||
applyFilter(WARNING_ROW);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::downloadFavicon(const QString &url)
|
||||
{
|
||||
if (!m_downloadTrackerFavicon) return;
|
||||
Net::DownloadManager::instance()->download(
|
||||
Net::DownloadRequest(url).saveToFile(true), Preferences::instance()->useProxyForGeneralPurposes()
|
||||
, this, &TrackersFilterWidget::handleFavicoDownloadFinished);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleFavicoDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
if (result.status != Net::DownloadStatus::Success)
|
||||
{
|
||||
if (result.url.endsWith(u".ico", Qt::CaseInsensitive))
|
||||
downloadFavicon(result.url.left(result.url.size() - 4) + u".png");
|
||||
return;
|
||||
}
|
||||
|
||||
const QString host = getHost(result.url);
|
||||
|
||||
if (!m_trackers.contains(host))
|
||||
{
|
||||
Utils::Fs::removeFile(result.filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
QListWidgetItem *trackerItem = item(rowFromTracker(host));
|
||||
if (!trackerItem) return;
|
||||
|
||||
const QIcon icon {result.filePath.data()};
|
||||
//Detect a non-decodable icon
|
||||
QList<QSize> sizes = icon.availableSizes();
|
||||
bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull());
|
||||
if (invalid)
|
||||
{
|
||||
if (result.url.endsWith(u".ico", Qt::CaseInsensitive))
|
||||
downloadFavicon(result.url.left(result.url.size() - 4) + u".png");
|
||||
Utils::Fs::removeFile(result.filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem->setData(Qt::DecorationRole, QIcon(result.filePath.data()));
|
||||
m_iconPaths.append(result.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::showMenu()
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
|
||||
, transferList(), &TransferListWidget::startVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
|
||||
, transferList(), &TransferListWidget::pauseVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
|
||||
, transferList(), &TransferListWidget::deleteVisibleTorrents);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::applyFilter(const int row)
|
||||
{
|
||||
if (row == ALL_ROW)
|
||||
transferList()->applyTrackerFilterAll();
|
||||
else if (isVisible())
|
||||
transferList()->applyTrackerFilter(getTorrentIDs(row));
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
QHash<QString, QVector<BitTorrent::TorrentID>> torrentsPerTracker;
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
torrentsPerTracker[tracker.url].append(torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
torrentsPerTracker[NULL_HOST].append(torrentID);
|
||||
}
|
||||
|
||||
for (auto it = torrentsPerTracker.cbegin(); it != torrentsPerTracker.cend(); ++it)
|
||||
{
|
||||
const QString &trackerURL = it.key();
|
||||
const QVector<BitTorrent::TorrentID> &torrents = it.value();
|
||||
addItems(trackerURL, torrents);
|
||||
}
|
||||
|
||||
m_totalTorrents += torrents.count();
|
||||
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(m_totalTorrents));
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
removeItem(tracker.url, torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
removeItem(NULL_HOST, torrentID);
|
||||
|
||||
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(--m_totalTorrents));
|
||||
}
|
||||
|
||||
QString TrackersFilterWidget::trackerFromRow(int row) const
|
||||
{
|
||||
Q_ASSERT(row > 1);
|
||||
const QString tracker = item(row)->text();
|
||||
QStringList parts = tracker.split(u' ');
|
||||
Q_ASSERT(parts.size() >= 2);
|
||||
parts.removeLast(); // Remove trailing number
|
||||
return parts.join(u' ');
|
||||
}
|
||||
|
||||
int TrackersFilterWidget::rowFromTracker(const QString &tracker) const
|
||||
{
|
||||
Q_ASSERT(!tracker.isEmpty());
|
||||
for (int i = 4; i < count(); ++i)
|
||||
{
|
||||
if (tracker == trackerFromRow(i))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QSet<BitTorrent::TorrentID> TrackersFilterWidget::getTorrentIDs(const int row) const
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case TRACKERLESS_ROW:
|
||||
return m_trackers.value(NULL_HOST).torrents;
|
||||
case ERROR_ROW:
|
||||
return {m_errors.keyBegin(), m_errors.keyEnd()};
|
||||
case WARNING_ROW:
|
||||
return {m_warnings.keyBegin(), m_warnings.keyEnd()};
|
||||
default:
|
||||
return m_trackers.value(trackerFromRow(row)).torrents;
|
||||
}
|
||||
}
|
93
src/gui/transferlistfilters/trackersfilterwidget.h
Normal file
93
src/gui/transferlistfilters/trackersfilterwidget.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QHash>
|
||||
|
||||
#include "base/bittorrent/trackerentry.h"
|
||||
#include "base/path.h"
|
||||
#include "basefilterwidget.h"
|
||||
|
||||
class TransferListWidget;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
struct DownloadResult;
|
||||
}
|
||||
|
||||
class TrackersFilterWidget final : public BaseFilterWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TrackersFilterWidget)
|
||||
|
||||
public:
|
||||
TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon);
|
||||
~TrackersFilterWidget() override;
|
||||
|
||||
void addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers);
|
||||
void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void refreshTrackers(const BitTorrent::Torrent *torrent);
|
||||
void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless);
|
||||
void handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries);
|
||||
void setDownloadTrackerFavicon(bool value);
|
||||
|
||||
private slots:
|
||||
void handleFavicoDownloadFinished(const Net::DownloadResult &result);
|
||||
|
||||
private:
|
||||
// These 4 methods are virtual slots in the base class.
|
||||
// No need to redeclare them here as slots.
|
||||
void showMenu() override;
|
||||
void applyFilter(int row) override;
|
||||
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) override;
|
||||
|
||||
void addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents);
|
||||
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id);
|
||||
QString trackerFromRow(int row) const;
|
||||
int rowFromTracker(const QString &tracker) const;
|
||||
QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const;
|
||||
void downloadFavicon(const QString &url);
|
||||
|
||||
struct TrackerData
|
||||
{
|
||||
QSet<BitTorrent::TorrentID> torrents;
|
||||
QListWidgetItem *item = nullptr;
|
||||
};
|
||||
|
||||
QHash<QString, TrackerData> m_trackers; // <tracker host, tracker data>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_errors; // <torrent ID, tracker hosts>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_warnings; // <torrent ID, tracker hosts>
|
||||
PathList m_iconPaths;
|
||||
int m_totalTorrents = 0;
|
||||
bool m_downloadTrackerFavicon = false;
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -48,44 +49,16 @@
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/utils/compare.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "categoryfilterwidget.h"
|
||||
#include "tagfilterwidget.h"
|
||||
#include "transferlistfilters/categoryfilterwidget.h"
|
||||
#include "transferlistfilters/statusfilterwidget.h"
|
||||
#include "transferlistfilters/tagfilterwidget.h"
|
||||
#include "transferlistfilters/trackersfilterwidget.h"
|
||||
#include "transferlistwidget.h"
|
||||
#include "uithememanager.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum TRACKER_FILTER_ROW
|
||||
{
|
||||
ALL_ROW,
|
||||
TRACKERLESS_ROW,
|
||||
ERROR_ROW,
|
||||
WARNING_ROW
|
||||
};
|
||||
|
||||
QString getScheme(const QString &tracker)
|
||||
{
|
||||
const QString scheme = QUrl(tracker).scheme();
|
||||
return !scheme.isEmpty() ? scheme : u"http"_qs;
|
||||
}
|
||||
|
||||
QString getHost(const QString &url)
|
||||
{
|
||||
// We want the domain + tld. Subdomains should be disregarded
|
||||
// If failed to parse the domain or IP address, original input should be returned
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
if (host.isEmpty())
|
||||
return url;
|
||||
|
||||
// host is in IP format
|
||||
if (!QHostAddress(host).isNull())
|
||||
return host;
|
||||
|
||||
return host.section(u'.', -2, -1);
|
||||
}
|
||||
|
||||
class ArrowCheckBox final : public QCheckBox
|
||||
{
|
||||
public:
|
||||
@ -109,693 +82,6 @@ namespace
|
||||
style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &painter, this);
|
||||
}
|
||||
};
|
||||
|
||||
const QString NULL_HOST = u""_qs;
|
||||
}
|
||||
|
||||
BaseFilterWidget::BaseFilterWidget(QWidget *parent, TransferListWidget *transferList)
|
||||
: QListWidget(parent)
|
||||
, transferList(transferList)
|
||||
{
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
setUniformItemSizes(true);
|
||||
setSpacing(0);
|
||||
|
||||
setIconSize(Utils::Gui::smallIconSize());
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
#endif
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &BaseFilterWidget::customContextMenuRequested, this, &BaseFilterWidget::showMenu);
|
||||
connect(this, &BaseFilterWidget::currentRowChanged, this, &BaseFilterWidget::applyFilter);
|
||||
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsLoaded
|
||||
, this, &BaseFilterWidget::handleTorrentsLoaded);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved
|
||||
, this, &BaseFilterWidget::torrentAboutToBeDeleted);
|
||||
}
|
||||
|
||||
QSize BaseFilterWidget::sizeHint() const
|
||||
{
|
||||
return
|
||||
{
|
||||
// Width should be exactly the width of the content
|
||||
sizeHintForColumn(0),
|
||||
// Height should be exactly the height of the content
|
||||
static_cast<int>((sizeHintForRow(0) + 2 * spacing()) * (count() + 0.5)),
|
||||
};
|
||||
}
|
||||
|
||||
QSize BaseFilterWidget::minimumSizeHint() const
|
||||
{
|
||||
QSize size = sizeHint();
|
||||
size.setWidth(6);
|
||||
return size;
|
||||
}
|
||||
|
||||
void BaseFilterWidget::toggleFilter(bool checked)
|
||||
{
|
||||
setVisible(checked);
|
||||
if (checked)
|
||||
applyFilter(currentRow());
|
||||
else
|
||||
applyFilter(ALL_ROW);
|
||||
}
|
||||
|
||||
StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *transferList)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
{
|
||||
// Add status filters
|
||||
auto *all = new QListWidgetItem(this);
|
||||
all->setData(Qt::DisplayRole, tr("All (0)", "this is for the status filter"));
|
||||
all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-all"_qs, u"filterall"_qs));
|
||||
auto *downloading = new QListWidgetItem(this);
|
||||
downloading->setData(Qt::DisplayRole, tr("Downloading (0)"));
|
||||
downloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"downloading"_qs));
|
||||
auto *seeding = new QListWidgetItem(this);
|
||||
seeding->setData(Qt::DisplayRole, tr("Seeding (0)"));
|
||||
seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"upload"_qs, u"uploading"_qs));
|
||||
auto *completed = new QListWidgetItem(this);
|
||||
completed->setData(Qt::DisplayRole, tr("Completed (0)"));
|
||||
completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"checked-completed"_qs, u"completed"_qs));
|
||||
auto *resumed = new QListWidgetItem(this);
|
||||
resumed->setData(Qt::DisplayRole, tr("Resumed (0)"));
|
||||
resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs));
|
||||
auto *paused = new QListWidgetItem(this);
|
||||
paused->setData(Qt::DisplayRole, tr("Paused (0)"));
|
||||
paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stopped"_qs, u"media-playback-pause"_qs));
|
||||
auto *active = new QListWidgetItem(this);
|
||||
active->setData(Qt::DisplayRole, tr("Active (0)"));
|
||||
active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-active"_qs, u"filteractive"_qs));
|
||||
auto *inactive = new QListWidgetItem(this);
|
||||
inactive->setData(Qt::DisplayRole, tr("Inactive (0)"));
|
||||
inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-inactive"_qs, u"filterinactive"_qs));
|
||||
auto *stalled = new QListWidgetItem(this);
|
||||
stalled->setData(Qt::DisplayRole, tr("Stalled (0)"));
|
||||
stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-stalled"_qs, u"filterstalled"_qs));
|
||||
auto *stalledUploading = new QListWidgetItem(this);
|
||||
stalledUploading->setData(Qt::DisplayRole, tr("Stalled Uploading (0)"));
|
||||
stalledUploading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledUP"_qs));
|
||||
auto *stalledDownloading = new QListWidgetItem(this);
|
||||
stalledDownloading->setData(Qt::DisplayRole, tr("Stalled Downloading (0)"));
|
||||
stalledDownloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledDL"_qs));
|
||||
auto *checking = new QListWidgetItem(this);
|
||||
checking->setData(Qt::DisplayRole, tr("Checking (0)"));
|
||||
checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"checking"_qs));
|
||||
auto *moving = new QListWidgetItem(this);
|
||||
moving->setData(Qt::DisplayRole, tr("Moving (0)"));
|
||||
moving->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"set-location"_qs));
|
||||
auto *errored = new QListWidgetItem(this);
|
||||
errored->setData(Qt::DisplayRole, tr("Errored (0)"));
|
||||
errored->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"error"_qs));
|
||||
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
setCurrentRow(pref->getTransSelFilter(), QItemSelectionModel::SelectCurrent);
|
||||
toggleFilter(pref->getStatusFilterState());
|
||||
|
||||
populate();
|
||||
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated
|
||||
, this, &StatusFilterWidget::handleTorrentsUpdated);
|
||||
}
|
||||
|
||||
StatusFilterWidget::~StatusFilterWidget()
|
||||
{
|
||||
Preferences::instance()->setTransSelFilter(currentRow());
|
||||
}
|
||||
|
||||
void StatusFilterWidget::populate()
|
||||
{
|
||||
m_torrentsStatus.clear();
|
||||
|
||||
const QVector<BitTorrent::Torrent *> torrents = BitTorrent::Session::instance()->torrents();
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
updateTorrentStatus(torrent);
|
||||
|
||||
updateTexts();
|
||||
}
|
||||
|
||||
void StatusFilterWidget::updateTorrentStatus(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
TorrentFilterBitset &torrentStatus = m_torrentsStatus[torrent];
|
||||
|
||||
const auto update = [torrent, &torrentStatus](const TorrentFilter::Type status, int &counter)
|
||||
{
|
||||
const bool hasStatus = torrentStatus[status];
|
||||
const bool needStatus = TorrentFilter(status).match(torrent);
|
||||
if (needStatus && !hasStatus)
|
||||
{
|
||||
++counter;
|
||||
torrentStatus.set(status);
|
||||
}
|
||||
else if (!needStatus && hasStatus)
|
||||
{
|
||||
--counter;
|
||||
torrentStatus.reset(status);
|
||||
}
|
||||
};
|
||||
|
||||
update(TorrentFilter::Downloading, m_nbDownloading);
|
||||
update(TorrentFilter::Seeding, m_nbSeeding);
|
||||
update(TorrentFilter::Completed, m_nbCompleted);
|
||||
update(TorrentFilter::Resumed, m_nbResumed);
|
||||
update(TorrentFilter::Paused, m_nbPaused);
|
||||
update(TorrentFilter::Active, m_nbActive);
|
||||
update(TorrentFilter::Inactive, m_nbInactive);
|
||||
update(TorrentFilter::StalledUploading, m_nbStalledUploading);
|
||||
update(TorrentFilter::StalledDownloading, m_nbStalledDownloading);
|
||||
update(TorrentFilter::Checking, m_nbChecking);
|
||||
update(TorrentFilter::Moving, m_nbMoving);
|
||||
update(TorrentFilter::Errored, m_nbErrored);
|
||||
|
||||
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading;
|
||||
}
|
||||
|
||||
void StatusFilterWidget::updateTexts()
|
||||
{
|
||||
const qsizetype torrentsCount = BitTorrent::Session::instance()->torrentsCount();
|
||||
item(TorrentFilter::All)->setData(Qt::DisplayRole, tr("All (%1)").arg(torrentsCount));
|
||||
item(TorrentFilter::Downloading)->setData(Qt::DisplayRole, tr("Downloading (%1)").arg(m_nbDownloading));
|
||||
item(TorrentFilter::Seeding)->setData(Qt::DisplayRole, tr("Seeding (%1)").arg(m_nbSeeding));
|
||||
item(TorrentFilter::Completed)->setData(Qt::DisplayRole, tr("Completed (%1)").arg(m_nbCompleted));
|
||||
item(TorrentFilter::Resumed)->setData(Qt::DisplayRole, tr("Resumed (%1)").arg(m_nbResumed));
|
||||
item(TorrentFilter::Paused)->setData(Qt::DisplayRole, tr("Paused (%1)").arg(m_nbPaused));
|
||||
item(TorrentFilter::Active)->setData(Qt::DisplayRole, tr("Active (%1)").arg(m_nbActive));
|
||||
item(TorrentFilter::Inactive)->setData(Qt::DisplayRole, tr("Inactive (%1)").arg(m_nbInactive));
|
||||
item(TorrentFilter::Stalled)->setData(Qt::DisplayRole, tr("Stalled (%1)").arg(m_nbStalled));
|
||||
item(TorrentFilter::StalledUploading)->setData(Qt::DisplayRole, tr("Stalled Uploading (%1)").arg(m_nbStalledUploading));
|
||||
item(TorrentFilter::StalledDownloading)->setData(Qt::DisplayRole, tr("Stalled Downloading (%1)").arg(m_nbStalledDownloading));
|
||||
item(TorrentFilter::Checking)->setData(Qt::DisplayRole, tr("Checking (%1)").arg(m_nbChecking));
|
||||
item(TorrentFilter::Moving)->setData(Qt::DisplayRole, tr("Moving (%1)").arg(m_nbMoving));
|
||||
item(TorrentFilter::Errored)->setData(Qt::DisplayRole, tr("Errored (%1)").arg(m_nbErrored));
|
||||
}
|
||||
|
||||
void StatusFilterWidget::handleTorrentsUpdated(const QVector<BitTorrent::Torrent *> torrents)
|
||||
{
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
updateTorrentStatus(torrent);
|
||||
|
||||
updateTexts();
|
||||
}
|
||||
|
||||
void StatusFilterWidget::showMenu()
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
|
||||
, transferList, &TransferListWidget::pauseVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void StatusFilterWidget::applyFilter(int row)
|
||||
{
|
||||
transferList->applyStatusFilter(row);
|
||||
}
|
||||
|
||||
void StatusFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
updateTorrentStatus(torrent);
|
||||
|
||||
updateTexts();
|
||||
}
|
||||
|
||||
void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const TorrentFilterBitset status = m_torrentsStatus.take(torrent);
|
||||
|
||||
if (status[TorrentFilter::Downloading])
|
||||
--m_nbDownloading;
|
||||
if (status[TorrentFilter::Seeding])
|
||||
--m_nbSeeding;
|
||||
if (status[TorrentFilter::Completed])
|
||||
--m_nbCompleted;
|
||||
if (status[TorrentFilter::Resumed])
|
||||
--m_nbResumed;
|
||||
if (status[TorrentFilter::Paused])
|
||||
--m_nbPaused;
|
||||
if (status[TorrentFilter::Active])
|
||||
--m_nbActive;
|
||||
if (status[TorrentFilter::Inactive])
|
||||
--m_nbInactive;
|
||||
if (status[TorrentFilter::StalledUploading])
|
||||
--m_nbStalledUploading;
|
||||
if (status[TorrentFilter::StalledDownloading])
|
||||
--m_nbStalledDownloading;
|
||||
if (status[TorrentFilter::Checking])
|
||||
--m_nbChecking;
|
||||
if (status[TorrentFilter::Moving])
|
||||
--m_nbMoving;
|
||||
if (status[TorrentFilter::Errored])
|
||||
--m_nbErrored;
|
||||
|
||||
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading;
|
||||
|
||||
updateTexts();
|
||||
}
|
||||
|
||||
TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
, m_downloadTrackerFavicon(downloadFavicon)
|
||||
{
|
||||
auto *allTrackers = new QListWidgetItem(this);
|
||||
allTrackers->setData(Qt::DisplayRole, tr("All (0)", "this is for the tracker filter"));
|
||||
allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs));
|
||||
auto *noTracker = new QListWidgetItem(this);
|
||||
noTracker->setData(Qt::DisplayRole, tr("Trackerless (0)"));
|
||||
noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_qs, u"network-server"_qs));
|
||||
auto *errorTracker = new QListWidgetItem(this);
|
||||
errorTracker->setData(Qt::DisplayRole, tr("Error (0)"));
|
||||
errorTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_qs, u"dialog-error"_qs));
|
||||
auto *warningTracker = new QListWidgetItem(this);
|
||||
warningTracker->setData(Qt::DisplayRole, tr("Warning (0)"));
|
||||
warningTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_qs, u"dialog-warning"_qs));
|
||||
|
||||
m_trackers[NULL_HOST] = {{}, noTracker};
|
||||
|
||||
handleTorrentsLoaded(BitTorrent::Session::instance()->torrents());
|
||||
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
toggleFilter(Preferences::instance()->getTrackerFilterState());
|
||||
}
|
||||
|
||||
TrackerFiltersList::~TrackerFiltersList()
|
||||
{
|
||||
for (const Path &iconPath : asConst(m_iconPaths))
|
||||
Utils::Fs::removeFile(iconPath);
|
||||
}
|
||||
|
||||
void TrackerFiltersList::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
addItems(tracker.url, {torrentID});
|
||||
}
|
||||
|
||||
void TrackerFiltersList::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
for (const QString &tracker : trackers)
|
||||
removeItem(tracker, torrentID);
|
||||
}
|
||||
|
||||
void TrackerFiltersList::refreshTrackers(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
|
||||
m_errors.remove(torrentID);
|
||||
m_warnings.remove(torrentID);
|
||||
|
||||
Algorithm::removeIf(m_trackers, [this, &torrentID](const QString &host, TrackerData &trackerData)
|
||||
{
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackerData.torrents;
|
||||
if (!torrentIDs.remove(torrentID))
|
||||
return false;
|
||||
|
||||
QListWidgetItem *trackerItem = trackerData.item;
|
||||
|
||||
if (!host.isEmpty() && torrentIDs.isEmpty())
|
||||
{
|
||||
if (currentItem() == trackerItem)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerItem;
|
||||
return true;
|
||||
}
|
||||
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg((host.isEmpty() ? tr("Trackerless") : host), QString::number(torrentIDs.size())));
|
||||
return false;
|
||||
});
|
||||
|
||||
const QVector<BitTorrent::TrackerEntry> trackerEntries = torrent->trackers();
|
||||
const bool isTrackerless = trackerEntries.isEmpty();
|
||||
if (isTrackerless)
|
||||
{
|
||||
addItems(NULL_HOST, {torrentID});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const BitTorrent::TrackerEntry &trackerEntry : trackerEntries)
|
||||
addItems(trackerEntry.url, {torrentID});
|
||||
}
|
||||
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackerFiltersList::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless)
|
||||
{
|
||||
if (trackerless)
|
||||
addItems(NULL_HOST, {torrent->id()});
|
||||
else
|
||||
removeItem(NULL_HOST, torrent->id());
|
||||
}
|
||||
|
||||
void TrackerFiltersList::addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
auto trackersIt = m_trackers.find(host);
|
||||
const bool exists = (trackersIt != m_trackers.end());
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
|
||||
if (exists)
|
||||
{
|
||||
trackerItem = trackersIt->item;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem = new QListWidgetItem();
|
||||
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs));
|
||||
|
||||
const TrackerData trackerData {{}, trackerItem};
|
||||
trackersIt = m_trackers.insert(host, trackerData);
|
||||
|
||||
const QString scheme = getScheme(trackerURL);
|
||||
downloadFavicon(u"%1://%2/favicon.ico"_qs.arg((scheme.startsWith(u"http") ? scheme : u"http"_qs), host));
|
||||
}
|
||||
|
||||
Q_ASSERT(trackerItem);
|
||||
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents;
|
||||
for (const BitTorrent::TorrentID &torrentID : torrents)
|
||||
torrentIDs.insert(torrentID);
|
||||
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg(((host == NULL_HOST) ? tr("Trackerless") : host), QString::number(torrentIDs.size())));
|
||||
if (exists)
|
||||
{
|
||||
if (item(currentRow()) == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(count() >= 4);
|
||||
const Utils::Compare::NaturalLessThan<Qt::CaseSensitive> naturalLessThan {};
|
||||
int insPos = count();
|
||||
for (int i = 4; i < count(); ++i)
|
||||
{
|
||||
if (naturalLessThan(host, item(i)->text()))
|
||||
{
|
||||
insPos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
QListWidget::insertItem(insPos, trackerItem);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackerFiltersList::removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
|
||||
QSet<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host).torrents;
|
||||
torrentIDs.remove(id);
|
||||
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
|
||||
if (!host.isEmpty())
|
||||
{
|
||||
// Remove from 'Error' and 'Warning' view
|
||||
const auto errorHashesIt = m_errors.find(id);
|
||||
if (errorHashesIt != m_errors.end())
|
||||
{
|
||||
QSet<QString> &errored = errorHashesIt.value();
|
||||
errored.remove(trackerURL);
|
||||
if (errored.isEmpty())
|
||||
{
|
||||
m_errors.erase(errorHashesIt);
|
||||
item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size()));
|
||||
if (currentRow() == ERROR_ROW)
|
||||
applyFilter(ERROR_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
const auto warningHashesIt = m_warnings.find(id);
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
{
|
||||
QSet<QString> &warned = *warningHashesIt;
|
||||
warned.remove(trackerURL);
|
||||
if (warned.isEmpty())
|
||||
{
|
||||
m_warnings.erase(warningHashesIt);
|
||||
item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size()));
|
||||
if (currentRow() == WARNING_ROW)
|
||||
applyFilter(WARNING_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
trackerItem = m_trackers.value(host).item;
|
||||
|
||||
if (torrentIDs.isEmpty())
|
||||
{
|
||||
if (currentItem() == trackerItem)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerItem;
|
||||
m_trackers.remove(host);
|
||||
updateGeometry();
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackerItem)
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg(host, QString::number(torrentIDs.size())));
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem = item(TRACKERLESS_ROW);
|
||||
trackerItem->setText(tr("Trackerless (%1)").arg(torrentIDs.size()));
|
||||
}
|
||||
|
||||
m_trackers.insert(host, {torrentIDs, trackerItem});
|
||||
|
||||
if (currentItem() == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
}
|
||||
|
||||
void TrackerFiltersList::setDownloadTrackerFavicon(bool value)
|
||||
{
|
||||
if (value == m_downloadTrackerFavicon) return;
|
||||
m_downloadTrackerFavicon = value;
|
||||
|
||||
if (m_downloadTrackerFavicon)
|
||||
{
|
||||
for (auto i = m_trackers.cbegin(); i != m_trackers.cend(); ++i)
|
||||
{
|
||||
const QString &tracker = i.key();
|
||||
if (!tracker.isEmpty())
|
||||
{
|
||||
const QString scheme = getScheme(tracker);
|
||||
downloadFavicon(u"%1://%2/favicon.ico"_qs
|
||||
.arg((scheme.startsWith(u"http") ? scheme : u"http"_qs), getHost(tracker)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackerFiltersList::handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries)
|
||||
{
|
||||
const BitTorrent::TorrentID id = torrent->id();
|
||||
|
||||
auto errorHashesIt = m_errors.find(id);
|
||||
auto warningHashesIt = m_warnings.find(id);
|
||||
|
||||
for (const BitTorrent::TrackerEntry &trackerEntry : updatedTrackerEntries)
|
||||
{
|
||||
if (trackerEntry.status == BitTorrent::TrackerEntry::Working)
|
||||
{
|
||||
if (errorHashesIt != m_errors.end())
|
||||
{
|
||||
QSet<QString> &errored = errorHashesIt.value();
|
||||
errored.remove(trackerEntry.url);
|
||||
}
|
||||
|
||||
if (trackerEntry.message.isEmpty())
|
||||
{
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
{
|
||||
QSet<QString> &warned = *warningHashesIt;
|
||||
warned.remove(trackerEntry.url);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (warningHashesIt == m_warnings.end())
|
||||
warningHashesIt = m_warnings.insert(id, {});
|
||||
warningHashesIt.value().insert(trackerEntry.url);
|
||||
}
|
||||
}
|
||||
else if (trackerEntry.status == BitTorrent::TrackerEntry::NotWorking)
|
||||
{
|
||||
if (errorHashesIt == m_errors.end())
|
||||
errorHashesIt = m_errors.insert(id, {});
|
||||
errorHashesIt.value().insert(trackerEntry.url);
|
||||
}
|
||||
}
|
||||
|
||||
if ((errorHashesIt != m_errors.end()) && errorHashesIt.value().isEmpty())
|
||||
m_errors.erase(errorHashesIt);
|
||||
if ((warningHashesIt != m_warnings.end()) && warningHashesIt.value().isEmpty())
|
||||
m_warnings.erase(warningHashesIt);
|
||||
|
||||
item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size()));
|
||||
item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size()));
|
||||
|
||||
if (currentRow() == ERROR_ROW)
|
||||
applyFilter(ERROR_ROW);
|
||||
else if (currentRow() == WARNING_ROW)
|
||||
applyFilter(WARNING_ROW);
|
||||
}
|
||||
|
||||
void TrackerFiltersList::downloadFavicon(const QString &url)
|
||||
{
|
||||
if (!m_downloadTrackerFavicon) return;
|
||||
Net::DownloadManager::instance()->download(
|
||||
Net::DownloadRequest(url).saveToFile(true), Preferences::instance()->useProxyForGeneralPurposes()
|
||||
, this, &TrackerFiltersList::handleFavicoDownloadFinished);
|
||||
}
|
||||
|
||||
void TrackerFiltersList::handleFavicoDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
if (result.status != Net::DownloadStatus::Success)
|
||||
{
|
||||
if (result.url.endsWith(u".ico", Qt::CaseInsensitive))
|
||||
downloadFavicon(result.url.left(result.url.size() - 4) + u".png");
|
||||
return;
|
||||
}
|
||||
|
||||
const QString host = getHost(result.url);
|
||||
|
||||
if (!m_trackers.contains(host))
|
||||
{
|
||||
Utils::Fs::removeFile(result.filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
QListWidgetItem *trackerItem = item(rowFromTracker(host));
|
||||
if (!trackerItem) return;
|
||||
|
||||
const QIcon icon {result.filePath.data()};
|
||||
//Detect a non-decodable icon
|
||||
QList<QSize> sizes = icon.availableSizes();
|
||||
bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull());
|
||||
if (invalid)
|
||||
{
|
||||
if (result.url.endsWith(u".ico", Qt::CaseInsensitive))
|
||||
downloadFavicon(result.url.left(result.url.size() - 4) + u".png");
|
||||
Utils::Fs::removeFile(result.filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem->setData(Qt::DecorationRole, QIcon(result.filePath.data()));
|
||||
m_iconPaths.append(result.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackerFiltersList::showMenu()
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
|
||||
, transferList, &TransferListWidget::pauseVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void TrackerFiltersList::applyFilter(const int row)
|
||||
{
|
||||
if (row == ALL_ROW)
|
||||
transferList->applyTrackerFilterAll();
|
||||
else if (isVisible())
|
||||
transferList->applyTrackerFilter(getTorrentIDs(row));
|
||||
}
|
||||
|
||||
void TrackerFiltersList::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
QHash<QString, QVector<BitTorrent::TorrentID>> torrentsPerTracker;
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
torrentsPerTracker[tracker.url].append(torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
torrentsPerTracker[NULL_HOST].append(torrentID);
|
||||
}
|
||||
|
||||
for (auto it = torrentsPerTracker.cbegin(); it != torrentsPerTracker.cend(); ++it)
|
||||
{
|
||||
const QString &trackerURL = it.key();
|
||||
const QVector<BitTorrent::TorrentID> &torrents = it.value();
|
||||
addItems(trackerURL, torrents);
|
||||
}
|
||||
|
||||
m_totalTorrents += torrents.count();
|
||||
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(m_totalTorrents));
|
||||
}
|
||||
|
||||
void TrackerFiltersList::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
removeItem(tracker.url, torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
removeItem(NULL_HOST, torrentID);
|
||||
|
||||
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(--m_totalTorrents));
|
||||
}
|
||||
|
||||
QString TrackerFiltersList::trackerFromRow(int row) const
|
||||
{
|
||||
Q_ASSERT(row > 1);
|
||||
const QString tracker = item(row)->text();
|
||||
QStringList parts = tracker.split(u' ');
|
||||
Q_ASSERT(parts.size() >= 2);
|
||||
parts.removeLast(); // Remove trailing number
|
||||
return parts.join(u' ');
|
||||
}
|
||||
|
||||
int TrackerFiltersList::rowFromTracker(const QString &tracker) const
|
||||
{
|
||||
Q_ASSERT(!tracker.isEmpty());
|
||||
for (int i = 4; i < count(); ++i)
|
||||
{
|
||||
if (tracker == trackerFromRow(i))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QSet<BitTorrent::TorrentID> TrackerFiltersList::getTorrentIDs(const int row) const
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case TRACKERLESS_ROW:
|
||||
return m_trackers.value(NULL_HOST).torrents;
|
||||
case ERROR_ROW:
|
||||
return {m_errors.keyBegin(), m_errors.keyEnd()};
|
||||
case WARNING_ROW:
|
||||
return {m_warnings.keyBegin(), m_warnings.keyEnd()};
|
||||
default:
|
||||
return m_trackers.value(trackerFromRow(row)).torrents;
|
||||
}
|
||||
}
|
||||
|
||||
TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
|
||||
@ -878,44 +164,44 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||
trackerLabel->setFont(font);
|
||||
frameLayout->addWidget(trackerLabel);
|
||||
|
||||
m_trackerFilters = new TrackerFiltersList(this, transferList, downloadFavicon);
|
||||
frameLayout->addWidget(m_trackerFilters);
|
||||
m_trackersFilterWidget = new TrackersFilterWidget(this, transferList, downloadFavicon);
|
||||
frameLayout->addWidget(m_trackersFilterWidget);
|
||||
|
||||
connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFilterWidget::toggleFilter);
|
||||
connect(statusLabel, &QCheckBox::toggled, pref, &Preferences::setStatusFilterState);
|
||||
connect(trackerLabel, &QCheckBox::toggled, m_trackerFilters, &TrackerFiltersList::toggleFilter);
|
||||
connect(trackerLabel, &QCheckBox::toggled, m_trackersFilterWidget, &TrackersFilterWidget::toggleFilter);
|
||||
connect(trackerLabel, &QCheckBox::toggled, pref, &Preferences::setTrackerFilterState);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::setDownloadTrackerFavicon(bool value)
|
||||
{
|
||||
m_trackerFilters->setDownloadTrackerFavicon(value);
|
||||
m_trackersFilterWidget->setDownloadTrackerFavicon(value);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers)
|
||||
{
|
||||
m_trackerFilters->addTrackers(torrent, trackers);
|
||||
m_trackersFilterWidget->addTrackers(torrent, trackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
{
|
||||
m_trackerFilters->removeTrackers(torrent, trackers);
|
||||
m_trackersFilterWidget->removeTrackers(torrent, trackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::refreshTrackers(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
m_trackerFilters->refreshTrackers(torrent);
|
||||
m_trackersFilterWidget->refreshTrackers(torrent);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless)
|
||||
{
|
||||
m_trackerFilters->changeTrackerless(torrent, trackerless);
|
||||
m_trackersFilterWidget->changeTrackerless(torrent, trackerless);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::trackerEntriesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries)
|
||||
{
|
||||
m_trackerFilters->handleTrackerEntriesUpdated(torrent, updatedTrackerEntries);
|
||||
m_trackersFilterWidget->handleTrackerEntriesUpdated(torrent, updatedTrackerEntries);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::onCategoryFilterStateChanged(bool enabled)
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -28,145 +29,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QFrame>
|
||||
#include <QHash>
|
||||
#include <QListWidget>
|
||||
#include <QtContainerFwd>
|
||||
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/bittorrent/trackerentry.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/path.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QResizeEvent;
|
||||
|
||||
class CategoryFilterWidget;
|
||||
class StatusFilterWidget;
|
||||
class TagFilterWidget;
|
||||
class TrackersFilterWidget;
|
||||
class TransferListWidget;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
struct DownloadResult;
|
||||
}
|
||||
|
||||
class BaseFilterWidget : public QListWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(BaseFilterWidget)
|
||||
|
||||
public:
|
||||
BaseFilterWidget(QWidget *parent, TransferListWidget *transferList);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
public slots:
|
||||
void toggleFilter(bool checked);
|
||||
|
||||
protected:
|
||||
TransferListWidget *transferList = nullptr;
|
||||
|
||||
private slots:
|
||||
virtual void showMenu() = 0;
|
||||
virtual void applyFilter(int row) = 0;
|
||||
virtual void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) = 0;
|
||||
virtual void torrentAboutToBeDeleted(BitTorrent::Torrent *const) = 0;
|
||||
};
|
||||
|
||||
class StatusFilterWidget final : public BaseFilterWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(StatusFilterWidget)
|
||||
|
||||
public:
|
||||
StatusFilterWidget(QWidget *parent, TransferListWidget *transferList);
|
||||
~StatusFilterWidget() override;
|
||||
|
||||
private slots:
|
||||
void handleTorrentsUpdated(const QVector<BitTorrent::Torrent *> torrents);
|
||||
|
||||
private:
|
||||
// These 4 methods are virtual slots in the base class.
|
||||
// No need to redeclare them here as slots.
|
||||
void showMenu() override;
|
||||
void applyFilter(int row) override;
|
||||
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *const) override;
|
||||
|
||||
void populate();
|
||||
void updateTorrentStatus(const BitTorrent::Torrent *torrent);
|
||||
void updateTexts();
|
||||
|
||||
using TorrentFilterBitset = std::bitset<32>; // approximated size, this should be the number of TorrentFilter::Type elements
|
||||
QHash<const BitTorrent::Torrent *, TorrentFilterBitset> m_torrentsStatus;
|
||||
int m_nbDownloading = 0;
|
||||
int m_nbSeeding = 0;
|
||||
int m_nbCompleted = 0;
|
||||
int m_nbResumed = 0;
|
||||
int m_nbPaused = 0;
|
||||
int m_nbActive = 0;
|
||||
int m_nbInactive = 0;
|
||||
int m_nbStalled = 0;
|
||||
int m_nbStalledUploading = 0;
|
||||
int m_nbStalledDownloading = 0;
|
||||
int m_nbChecking = 0;
|
||||
int m_nbMoving = 0;
|
||||
int m_nbErrored = 0;
|
||||
};
|
||||
|
||||
class TrackerFiltersList final : public BaseFilterWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TrackerFiltersList)
|
||||
|
||||
public:
|
||||
TrackerFiltersList(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon);
|
||||
~TrackerFiltersList() override;
|
||||
|
||||
void addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers);
|
||||
void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void refreshTrackers(const BitTorrent::Torrent *torrent);
|
||||
void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless);
|
||||
void handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries);
|
||||
void setDownloadTrackerFavicon(bool value);
|
||||
|
||||
private slots:
|
||||
void handleFavicoDownloadFinished(const Net::DownloadResult &result);
|
||||
|
||||
private:
|
||||
// These 4 methods are virtual slots in the base class.
|
||||
// No need to redeclare them here as slots.
|
||||
void showMenu() override;
|
||||
void applyFilter(int row) override;
|
||||
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) override;
|
||||
|
||||
void addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents);
|
||||
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id);
|
||||
QString trackerFromRow(int row) const;
|
||||
int rowFromTracker(const QString &tracker) const;
|
||||
QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const;
|
||||
void downloadFavicon(const QString &url);
|
||||
|
||||
struct TrackerData
|
||||
{
|
||||
QSet<BitTorrent::TorrentID> torrents;
|
||||
QListWidgetItem *item = nullptr;
|
||||
};
|
||||
|
||||
QHash<QString, TrackerData> m_trackers; // <tracker host, tracker data>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_errors; // <torrent ID, tracker hosts>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_warnings; // <torrent ID, tracker hosts>
|
||||
PathList m_iconPaths;
|
||||
int m_totalTorrents = 0;
|
||||
bool m_downloadTrackerFavicon = false;
|
||||
};
|
||||
|
||||
class TransferListFiltersWidget final : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -193,7 +68,7 @@ private:
|
||||
void toggleTagFilter(bool enabled);
|
||||
|
||||
TransferListWidget *m_transferList = nullptr;
|
||||
TrackerFiltersList *m_trackerFilters = nullptr;
|
||||
TrackersFilterWidget *m_trackersFilterWidget = nullptr;
|
||||
CategoryFilterWidget *m_categoryFilterWidget = nullptr;
|
||||
TagFilterWidget *m_tagFilterWidget = nullptr;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user