mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-07-10 15:32:48 -07: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
26 changed files with 1222 additions and 891 deletions
|
@ -1551,6 +1551,16 @@ void Preferences::setTransSelFilter(const int index)
|
||||||
setValue(u"TransferListFilters/selectedFilterIndex"_qs, 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
|
QByteArray Preferences::getTransHeaderState() const
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
|
|
@ -382,6 +382,8 @@ public:
|
||||||
bool getTrackerFilterState() const;
|
bool getTrackerFilterState() const;
|
||||||
int getTransSelFilter() const;
|
int getTransSelFilter() const;
|
||||||
void setTransSelFilter(int index);
|
void setTransSelFilter(int index);
|
||||||
|
bool getHideZeroStatusFilters() const;
|
||||||
|
void setHideZeroStatusFilters(bool hide);
|
||||||
QByteArray getTransHeaderState() const;
|
QByteArray getTransHeaderState() const;
|
||||||
void setTransHeaderState(const QByteArray &state);
|
void setTransHeaderState(const QByteArray &state);
|
||||||
bool getRegexAsFilteringPatternForTransferList() const;
|
bool getRegexAsFilteringPatternForTransferList() const;
|
||||||
|
|
|
@ -49,9 +49,6 @@ add_library(qbt_gui STATIC
|
||||||
advancedsettings.h
|
advancedsettings.h
|
||||||
autoexpandabledialog.h
|
autoexpandabledialog.h
|
||||||
banlistoptionsdialog.h
|
banlistoptionsdialog.h
|
||||||
categoryfiltermodel.h
|
|
||||||
categoryfilterproxymodel.h
|
|
||||||
categoryfilterwidget.h
|
|
||||||
color.h
|
color.h
|
||||||
cookiesdialog.h
|
cookiesdialog.h
|
||||||
cookiesmodel.h
|
cookiesmodel.h
|
||||||
|
@ -103,9 +100,6 @@ add_library(qbt_gui STATIC
|
||||||
speedlimitdialog.h
|
speedlimitdialog.h
|
||||||
statsdialog.h
|
statsdialog.h
|
||||||
statusbar.h
|
statusbar.h
|
||||||
tagfiltermodel.h
|
|
||||||
tagfilterproxymodel.h
|
|
||||||
tagfilterwidget.h
|
|
||||||
torrentcategorydialog.h
|
torrentcategorydialog.h
|
||||||
torrentcontentfiltermodel.h
|
torrentcontentfiltermodel.h
|
||||||
torrentcontentitemdelegate.h
|
torrentcontentitemdelegate.h
|
||||||
|
@ -119,6 +113,15 @@ add_library(qbt_gui STATIC
|
||||||
torrenttagsdialog.h
|
torrenttagsdialog.h
|
||||||
trackerentriesdialog.h
|
trackerentriesdialog.h
|
||||||
transferlistdelegate.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
|
transferlistfilterswidget.h
|
||||||
transferlistmodel.h
|
transferlistmodel.h
|
||||||
transferlistsortmodel.h
|
transferlistsortmodel.h
|
||||||
|
@ -140,9 +143,6 @@ add_library(qbt_gui STATIC
|
||||||
advancedsettings.cpp
|
advancedsettings.cpp
|
||||||
autoexpandabledialog.cpp
|
autoexpandabledialog.cpp
|
||||||
banlistoptionsdialog.cpp
|
banlistoptionsdialog.cpp
|
||||||
categoryfiltermodel.cpp
|
|
||||||
categoryfilterproxymodel.cpp
|
|
||||||
categoryfilterwidget.cpp
|
|
||||||
cookiesdialog.cpp
|
cookiesdialog.cpp
|
||||||
cookiesmodel.cpp
|
cookiesmodel.cpp
|
||||||
deletionconfirmationdialog.cpp
|
deletionconfirmationdialog.cpp
|
||||||
|
@ -192,9 +192,6 @@ add_library(qbt_gui STATIC
|
||||||
speedlimitdialog.cpp
|
speedlimitdialog.cpp
|
||||||
statsdialog.cpp
|
statsdialog.cpp
|
||||||
statusbar.cpp
|
statusbar.cpp
|
||||||
tagfiltermodel.cpp
|
|
||||||
tagfilterproxymodel.cpp
|
|
||||||
tagfilterwidget.cpp
|
|
||||||
torrentcategorydialog.cpp
|
torrentcategorydialog.cpp
|
||||||
torrentcontentfiltermodel.cpp
|
torrentcontentfiltermodel.cpp
|
||||||
torrentcontentitemdelegate.cpp
|
torrentcontentitemdelegate.cpp
|
||||||
|
@ -208,6 +205,15 @@ add_library(qbt_gui STATIC
|
||||||
torrenttagsdialog.cpp
|
torrenttagsdialog.cpp
|
||||||
trackerentriesdialog.cpp
|
trackerentriesdialog.cpp
|
||||||
transferlistdelegate.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
|
transferlistfilterswidget.cpp
|
||||||
transferlistmodel.cpp
|
transferlistmodel.cpp
|
||||||
transferlistsortmodel.cpp
|
transferlistsortmodel.cpp
|
||||||
|
|
|
@ -6,9 +6,6 @@ HEADERS += \
|
||||||
$$PWD/advancedsettings.h \
|
$$PWD/advancedsettings.h \
|
||||||
$$PWD/autoexpandabledialog.h \
|
$$PWD/autoexpandabledialog.h \
|
||||||
$$PWD/banlistoptionsdialog.h \
|
$$PWD/banlistoptionsdialog.h \
|
||||||
$$PWD/categoryfiltermodel.h \
|
|
||||||
$$PWD/categoryfilterproxymodel.h \
|
|
||||||
$$PWD/categoryfilterwidget.h \
|
|
||||||
$$PWD/color.h \
|
$$PWD/color.h \
|
||||||
$$PWD/cookiesdialog.h \
|
$$PWD/cookiesdialog.h \
|
||||||
$$PWD/cookiesmodel.h \
|
$$PWD/cookiesmodel.h \
|
||||||
|
@ -60,9 +57,6 @@ HEADERS += \
|
||||||
$$PWD/speedlimitdialog.h \
|
$$PWD/speedlimitdialog.h \
|
||||||
$$PWD/statsdialog.h \
|
$$PWD/statsdialog.h \
|
||||||
$$PWD/statusbar.h \
|
$$PWD/statusbar.h \
|
||||||
$$PWD/tagfiltermodel.h \
|
|
||||||
$$PWD/tagfilterproxymodel.h \
|
|
||||||
$$PWD/tagfilterwidget.h \
|
|
||||||
$$PWD/torrentcategorydialog.h \
|
$$PWD/torrentcategorydialog.h \
|
||||||
$$PWD/torrentcontentfiltermodel.h \
|
$$PWD/torrentcontentfiltermodel.h \
|
||||||
$$PWD/torrentcontentitemdelegate.h \
|
$$PWD/torrentcontentitemdelegate.h \
|
||||||
|
@ -76,6 +70,15 @@ HEADERS += \
|
||||||
$$PWD/torrenttagsdialog.h \
|
$$PWD/torrenttagsdialog.h \
|
||||||
$$PWD/trackerentriesdialog.h \
|
$$PWD/trackerentriesdialog.h \
|
||||||
$$PWD/transferlistdelegate.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/transferlistfilterswidget.h \
|
||||||
$$PWD/transferlistmodel.h \
|
$$PWD/transferlistmodel.h \
|
||||||
$$PWD/transferlistsortmodel.h \
|
$$PWD/transferlistsortmodel.h \
|
||||||
|
@ -97,9 +100,6 @@ SOURCES += \
|
||||||
$$PWD/advancedsettings.cpp \
|
$$PWD/advancedsettings.cpp \
|
||||||
$$PWD/autoexpandabledialog.cpp \
|
$$PWD/autoexpandabledialog.cpp \
|
||||||
$$PWD/banlistoptionsdialog.cpp \
|
$$PWD/banlistoptionsdialog.cpp \
|
||||||
$$PWD/categoryfiltermodel.cpp \
|
|
||||||
$$PWD/categoryfilterproxymodel.cpp \
|
|
||||||
$$PWD/categoryfilterwidget.cpp \
|
|
||||||
$$PWD/cookiesdialog.cpp \
|
$$PWD/cookiesdialog.cpp \
|
||||||
$$PWD/cookiesmodel.cpp \
|
$$PWD/cookiesmodel.cpp \
|
||||||
$$PWD/deletionconfirmationdialog.cpp \
|
$$PWD/deletionconfirmationdialog.cpp \
|
||||||
|
@ -149,9 +149,6 @@ SOURCES += \
|
||||||
$$PWD/speedlimitdialog.cpp \
|
$$PWD/speedlimitdialog.cpp \
|
||||||
$$PWD/statsdialog.cpp \
|
$$PWD/statsdialog.cpp \
|
||||||
$$PWD/statusbar.cpp \
|
$$PWD/statusbar.cpp \
|
||||||
$$PWD/tagfiltermodel.cpp \
|
|
||||||
$$PWD/tagfilterproxymodel.cpp \
|
|
||||||
$$PWD/tagfilterwidget.cpp \
|
|
||||||
$$PWD/torrentcategorydialog.cpp \
|
$$PWD/torrentcategorydialog.cpp \
|
||||||
$$PWD/torrentcontentfiltermodel.cpp \
|
$$PWD/torrentcontentfiltermodel.cpp \
|
||||||
$$PWD/torrentcontentitemdelegate.cpp \
|
$$PWD/torrentcontentitemdelegate.cpp \
|
||||||
|
@ -165,6 +162,15 @@ SOURCES += \
|
||||||
$$PWD/torrenttagsdialog.cpp \
|
$$PWD/torrenttagsdialog.cpp \
|
||||||
$$PWD/trackerentriesdialog.cpp \
|
$$PWD/trackerentriesdialog.cpp \
|
||||||
$$PWD/transferlistdelegate.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/transferlistfilterswidget.cpp \
|
||||||
$$PWD/transferlistmodel.cpp \
|
$$PWD/transferlistmodel.cpp \
|
||||||
$$PWD/transferlistsortmodel.cpp \
|
$$PWD/transferlistsortmodel.cpp \
|
||||||
|
|
|
@ -245,6 +245,8 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||||
actionSeeding = OPEN_DEST;
|
actionSeeding = OPEN_DEST;
|
||||||
m_ui->actionTorrentFnOnDblClBox->setCurrentIndex(m_ui->actionTorrentFnOnDblClBox->findData(actionSeeding));
|
m_ui->actionTorrentFnOnDblClBox->setCurrentIndex(m_ui->actionTorrentFnOnDblClBox->findData(actionSeeding));
|
||||||
|
|
||||||
|
m_ui->checkBoxHideZeroStatusFilters->setChecked(pref->getHideZeroStatusFilters());
|
||||||
|
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
m_ui->checkStartup->setVisible(false);
|
m_ui->checkStartup->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
|
@ -344,6 +346,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||||
connect(m_ui->comboHideZero, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
connect(m_ui->comboHideZero, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||||
connect(m_ui->actionTorrentDlOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
connect(m_ui->actionTorrentDlOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||||
connect(m_ui->actionTorrentFnOnDblClBox, 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
|
#ifdef Q_OS_WIN
|
||||||
connect(m_ui->checkStartup, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
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->setActionOnDblClOnTorrentDl(m_ui->actionTorrentDlOnDblClBox->currentData().toInt());
|
||||||
pref->setActionOnDblClOnTorrentFn(m_ui->actionTorrentFnOnDblClBox->currentData().toInt());
|
pref->setActionOnDblClOnTorrentFn(m_ui->actionTorrentFnOnDblClBox->currentData().toInt());
|
||||||
|
|
||||||
|
pref->setHideZeroStatusFilters(m_ui->checkBoxHideZeroStatusFilters->isChecked());
|
||||||
|
|
||||||
pref->setSplashScreenDisabled(isSplashScreenDisabled());
|
pref->setSplashScreenDisabled(isSplashScreenDisabled());
|
||||||
pref->setConfirmOnExit(m_ui->checkProgramExitConfirm->isChecked());
|
pref->setConfirmOnExit(m_ui->checkProgramExitConfirm->isChecked());
|
||||||
pref->setDontConfirmAutoExit(!m_ui->checkProgramAutoExitConfirm->isChecked());
|
pref->setDontConfirmAutoExit(!m_ui->checkProgramAutoExitConfirm->isChecked());
|
||||||
|
|
|
@ -384,6 +384,13 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBoxHideZeroStatusFilters">
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto hide zero status filters</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -3761,6 +3768,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
||||||
<tabstop>checkAltRowColors</tabstop>
|
<tabstop>checkAltRowColors</tabstop>
|
||||||
<tabstop>actionTorrentDlOnDblClBox</tabstop>
|
<tabstop>actionTorrentDlOnDblClBox</tabstop>
|
||||||
<tabstop>actionTorrentFnOnDblClBox</tabstop>
|
<tabstop>actionTorrentFnOnDblClBox</tabstop>
|
||||||
|
<tabstop>checkBoxHideZeroStatusFilters</tabstop>
|
||||||
<tabstop>checkStartup</tabstop>
|
<tabstop>checkStartup</tabstop>
|
||||||
<tabstop>checkShowSplash</tabstop>
|
<tabstop>checkShowSplash</tabstop>
|
||||||
<tabstop>windowStateComboBox</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/bittorrent/session.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
|
|
||||||
class CategoryModelItem
|
class CategoryModelItem
|
||||||
{
|
{
|
|
@ -32,11 +32,11 @@
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
|
#include "gui/torrentcategorydialog.h"
|
||||||
|
#include "gui/uithememanager.h"
|
||||||
|
#include "gui/utils.h"
|
||||||
#include "categoryfiltermodel.h"
|
#include "categoryfiltermodel.h"
|
||||||
#include "categoryfilterproxymodel.h"
|
#include "categoryfilterproxymodel.h"
|
||||||
#include "torrentcategorydialog.h"
|
|
||||||
#include "uithememanager.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
namespace
|
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/bittorrent/session.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
|
@ -33,11 +33,11 @@
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "autoexpandabledialog.h"
|
#include "gui/autoexpandabledialog.h"
|
||||||
|
#include "gui/uithememanager.h"
|
||||||
|
#include "gui/utils.h"
|
||||||
#include "tagfiltermodel.h"
|
#include "tagfiltermodel.h"
|
||||||
#include "tagfilterproxymodel.h"
|
#include "tagfilterproxymodel.h"
|
||||||
#include "uithememanager.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
namespace
|
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.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -48,44 +49,16 @@
|
||||||
#include "base/torrentfilter.h"
|
#include "base/torrentfilter.h"
|
||||||
#include "base/utils/compare.h"
|
#include "base/utils/compare.h"
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "categoryfilterwidget.h"
|
#include "transferlistfilters/categoryfilterwidget.h"
|
||||||
#include "tagfilterwidget.h"
|
#include "transferlistfilters/statusfilterwidget.h"
|
||||||
|
#include "transferlistfilters/tagfilterwidget.h"
|
||||||
|
#include "transferlistfilters/trackersfilterwidget.h"
|
||||||
#include "transferlistwidget.h"
|
#include "transferlistwidget.h"
|
||||||
#include "uithememanager.h"
|
#include "uithememanager.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
namespace
|
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
|
class ArrowCheckBox final : public QCheckBox
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -109,693 +82,6 @@ namespace
|
||||||
style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &painter, this);
|
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)
|
TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
|
||||||
|
@ -878,44 +164,44 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||||
trackerLabel->setFont(font);
|
trackerLabel->setFont(font);
|
||||||
frameLayout->addWidget(trackerLabel);
|
frameLayout->addWidget(trackerLabel);
|
||||||
|
|
||||||
m_trackerFilters = new TrackerFiltersList(this, transferList, downloadFavicon);
|
m_trackersFilterWidget = new TrackersFilterWidget(this, transferList, downloadFavicon);
|
||||||
frameLayout->addWidget(m_trackerFilters);
|
frameLayout->addWidget(m_trackersFilterWidget);
|
||||||
|
|
||||||
connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFilterWidget::toggleFilter);
|
connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFilterWidget::toggleFilter);
|
||||||
connect(statusLabel, &QCheckBox::toggled, pref, &Preferences::setStatusFilterState);
|
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);
|
connect(trackerLabel, &QCheckBox::toggled, pref, &Preferences::setTrackerFilterState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransferListFiltersWidget::setDownloadTrackerFavicon(bool value)
|
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)
|
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)
|
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)
|
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)
|
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
|
void TransferListFiltersWidget::trackerEntriesUpdated(const BitTorrent::Torrent *torrent
|
||||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries)
|
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries)
|
||||||
{
|
{
|
||||||
m_trackerFilters->handleTrackerEntriesUpdated(torrent, updatedTrackerEntries);
|
m_trackersFilterWidget->handleTrackerEntriesUpdated(torrent, updatedTrackerEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransferListFiltersWidget::onCategoryFilterStateChanged(bool enabled)
|
void TransferListFiltersWidget::onCategoryFilterStateChanged(bool enabled)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -28,145 +29,19 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <bitset>
|
#include <QtContainerFwd>
|
||||||
|
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QListWidget>
|
|
||||||
#include <QtContainerFwd>
|
|
||||||
|
|
||||||
#include "base/bittorrent/infohash.h"
|
#include "base/bittorrent/torrent.h"
|
||||||
#include "base/bittorrent/session.h"
|
|
||||||
#include "base/bittorrent/trackerentry.h"
|
#include "base/bittorrent/trackerentry.h"
|
||||||
#include "base/torrentfilter.h"
|
|
||||||
#include "base/path.h"
|
|
||||||
|
|
||||||
class QCheckBox;
|
|
||||||
class QResizeEvent;
|
|
||||||
|
|
||||||
class CategoryFilterWidget;
|
class CategoryFilterWidget;
|
||||||
|
class StatusFilterWidget;
|
||||||
class TagFilterWidget;
|
class TagFilterWidget;
|
||||||
|
class TrackersFilterWidget;
|
||||||
class TransferListWidget;
|
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
|
class TransferListFiltersWidget final : public QFrame
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -193,7 +68,7 @@ private:
|
||||||
void toggleTagFilter(bool enabled);
|
void toggleTagFilter(bool enabled);
|
||||||
|
|
||||||
TransferListWidget *m_transferList = nullptr;
|
TransferListWidget *m_transferList = nullptr;
|
||||||
TrackerFiltersList *m_trackerFilters = nullptr;
|
TrackersFilterWidget *m_trackersFilterWidget = nullptr;
|
||||||
CategoryFilterWidget *m_categoryFilterWidget = nullptr;
|
CategoryFilterWidget *m_categoryFilterWidget = nullptr;
|
||||||
TagFilterWidget *m_tagFilterWidget = nullptr;
|
TagFilterWidget *m_tagFilterWidget = nullptr;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue