mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-10 23:07:59 +00:00
Add a Tags (multi-label) feature to the GUI. Closes #13.
See https://github.com/qbittorrent/qBittorrent/issues/13 for details.
This commit is contained in:
parent
ff80208534
commit
467e516801
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ namespace BitTorrent
|
|||||||
{
|
{
|
||||||
QString name;
|
QString name;
|
||||||
QString category;
|
QString category;
|
||||||
|
QSet<QString> tags;
|
||||||
QString savePath;
|
QString savePath;
|
||||||
bool disableTempPath = false; // e.g. for imported torrents
|
bool disableTempPath = false; // e.g. for imported torrents
|
||||||
bool sequential = false;
|
bool sequential = false;
|
||||||
|
@ -127,6 +127,43 @@ namespace
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Entry>
|
||||||
|
QSet<QString> entryListToSetImpl(const Entry &entry)
|
||||||
|
{
|
||||||
|
Q_ASSERT(entry.type() == Entry::list_t);
|
||||||
|
QSet<QString> output;
|
||||||
|
for (int i = 0; i < entry.list_size(); ++i) {
|
||||||
|
const QString tag = QString::fromStdString(entry.list_string_value_at(i));
|
||||||
|
if (Session::isValidTag(tag))
|
||||||
|
output.insert(tag);
|
||||||
|
else
|
||||||
|
qWarning() << QString("Dropping invalid stored tag: %1").arg(tag);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
|
bool isList(const libt::lazy_entry *entry)
|
||||||
|
{
|
||||||
|
return entry && (entry->type() == libt::lazy_entry::list_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> entryListToSet(const libt::lazy_entry *entry)
|
||||||
|
{
|
||||||
|
return entry ? entryListToSetImpl(*entry) : QSet<QString>();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool isList(const libt::bdecode_node &entry)
|
||||||
|
{
|
||||||
|
return entry.type() == libt::bdecode_node::list_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> entryListToSet(const libt::bdecode_node &entry)
|
||||||
|
{
|
||||||
|
return entryListToSetImpl(entry);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QString normalizePath(const QString &path)
|
QString normalizePath(const QString &path)
|
||||||
{
|
{
|
||||||
QString tmp = Utils::Fs::fromNativePath(path.trimmed());
|
QString tmp = Utils::Fs::fromNativePath(path.trimmed());
|
||||||
@ -260,6 +297,7 @@ Session::Session(QObject *parent)
|
|||||||
, m_isForceProxyEnabled(BITTORRENT_SESSION_KEY("ForceProxy"), true)
|
, m_isForceProxyEnabled(BITTORRENT_SESSION_KEY("ForceProxy"), true)
|
||||||
, m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false)
|
, m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false)
|
||||||
, m_storedCategories(BITTORRENT_SESSION_KEY("Categories"))
|
, m_storedCategories(BITTORRENT_SESSION_KEY("Categories"))
|
||||||
|
, m_storedTags(BITTORRENT_SESSION_KEY("Tags"))
|
||||||
, m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause)
|
, m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause)
|
||||||
, m_defaultSavePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), normalizePath)
|
, m_defaultSavePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), normalizePath)
|
||||||
, m_tempPath(BITTORRENT_SESSION_KEY("TempPath"), defaultSavePath() + "temp/", normalizePath)
|
, m_tempPath(BITTORRENT_SESSION_KEY("TempPath"), defaultSavePath() + "temp/", normalizePath)
|
||||||
@ -400,6 +438,8 @@ Session::Session(QObject *parent)
|
|||||||
m_storedCategories = map_cast(m_categories);
|
m_storedCategories = map_cast(m_categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_tags = QSet<QString>::fromList(m_storedTags.value());
|
||||||
|
|
||||||
m_refreshTimer = new QTimer(this);
|
m_refreshTimer = new QTimer(this);
|
||||||
m_refreshTimer->setInterval(refreshInterval());
|
m_refreshTimer->setInterval(refreshInterval());
|
||||||
connect(m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
|
connect(m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
|
||||||
@ -724,6 +764,47 @@ void Session::setSubcategoriesEnabled(bool value)
|
|||||||
emit subcategoriesSupportChanged();
|
emit subcategoriesSupportChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSet<QString> Session::tags() const
|
||||||
|
{
|
||||||
|
return m_tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::isValidTag(const QString &tag)
|
||||||
|
{
|
||||||
|
return (!tag.trimmed().isEmpty() && !tag.contains(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::hasTag(const QString &tag) const
|
||||||
|
{
|
||||||
|
return m_tags.contains(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::addTag(const QString &tag)
|
||||||
|
{
|
||||||
|
if (!isValidTag(tag))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hasTag(tag)) {
|
||||||
|
m_tags.insert(tag);
|
||||||
|
m_storedTags = m_tags.toList();
|
||||||
|
emit tagAdded(tag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::removeTag(const QString &tag)
|
||||||
|
{
|
||||||
|
if (m_tags.remove(tag)) {
|
||||||
|
foreach (TorrentHandle *const torrent, torrents())
|
||||||
|
torrent->removeTag(tag);
|
||||||
|
m_storedTags = m_tags.toList();
|
||||||
|
emit tagRemoved(tag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool Session::isAutoTMMDisabledByDefault() const
|
bool Session::isAutoTMMDisabledByDefault() const
|
||||||
{
|
{
|
||||||
return m_isAutoTMMDisabledByDefault;
|
return m_isAutoTMMDisabledByDefault;
|
||||||
@ -2997,6 +3078,16 @@ void Session::handleTorrentCategoryChanged(TorrentHandle *const torrent, const Q
|
|||||||
emit torrentCategoryChanged(torrent, oldCategory);
|
emit torrentCategoryChanged(torrent, oldCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Session::handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag)
|
||||||
|
{
|
||||||
|
emit torrentTagAdded(torrent, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::handleTorrentTagRemoved(TorrentHandle *const torrent, const QString &tag)
|
||||||
|
{
|
||||||
|
emit torrentTagRemoved(torrent, tag);
|
||||||
|
}
|
||||||
|
|
||||||
void Session::handleTorrentSavingModeChanged(TorrentHandle * const torrent)
|
void Session::handleTorrentSavingModeChanged(TorrentHandle * const torrent)
|
||||||
{
|
{
|
||||||
emit torrentSavingModeChanged(torrent);
|
emit torrentSavingModeChanged(torrent);
|
||||||
@ -3930,6 +4021,10 @@ namespace
|
|||||||
if (torrentData.category.isEmpty())
|
if (torrentData.category.isEmpty())
|
||||||
// **************************************************************************************
|
// **************************************************************************************
|
||||||
torrentData.category = QString::fromStdString(fast.dict_find_string_value("qBt-category"));
|
torrentData.category = QString::fromStdString(fast.dict_find_string_value("qBt-category"));
|
||||||
|
// auto because the return type depends on the #if above.
|
||||||
|
const auto tagsEntry = fast.dict_find_list("qBt-tags");
|
||||||
|
if (isList(tagsEntry))
|
||||||
|
torrentData.tags = entryListToSet(tagsEntry);
|
||||||
torrentData.name = QString::fromStdString(fast.dict_find_string_value("qBt-name"));
|
torrentData.name = QString::fromStdString(fast.dict_find_string_value("qBt-name"));
|
||||||
torrentData.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus");
|
torrentData.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus");
|
||||||
torrentData.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled");
|
torrentData.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled");
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <QNetworkConfigurationManager>
|
#include <QNetworkConfigurationManager>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <QSet>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QWaitCondition>
|
#include <QWaitCondition>
|
||||||
@ -223,6 +224,12 @@ namespace BitTorrent
|
|||||||
bool isSubcategoriesEnabled() const;
|
bool isSubcategoriesEnabled() const;
|
||||||
void setSubcategoriesEnabled(bool value);
|
void setSubcategoriesEnabled(bool value);
|
||||||
|
|
||||||
|
static bool isValidTag(const QString &tag);
|
||||||
|
QSet<QString> tags() const;
|
||||||
|
bool hasTag(const QString &tag) const;
|
||||||
|
bool addTag(const QString &tag);
|
||||||
|
bool removeTag(const QString &tag);
|
||||||
|
|
||||||
// Torrent Management Mode subsystem (TMM)
|
// Torrent Management Mode subsystem (TMM)
|
||||||
//
|
//
|
||||||
// Each torrent can be either in Manual mode or in Automatic mode
|
// Each torrent can be either in Manual mode or in Automatic mode
|
||||||
@ -400,6 +407,8 @@ namespace BitTorrent
|
|||||||
void handleTorrentShareLimitChanged(TorrentHandle *const torrent);
|
void handleTorrentShareLimitChanged(TorrentHandle *const torrent);
|
||||||
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
|
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
|
||||||
void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory);
|
void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory);
|
||||||
|
void handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag);
|
||||||
|
void handleTorrentTagRemoved(TorrentHandle *const torrent, const QString &tag);
|
||||||
void handleTorrentSavingModeChanged(TorrentHandle *const torrent);
|
void handleTorrentSavingModeChanged(TorrentHandle *const torrent);
|
||||||
void handleTorrentMetadataReceived(TorrentHandle *const torrent);
|
void handleTorrentMetadataReceived(TorrentHandle *const torrent);
|
||||||
void handleTorrentPaused(TorrentHandle *const torrent);
|
void handleTorrentPaused(TorrentHandle *const torrent);
|
||||||
@ -431,6 +440,8 @@ namespace BitTorrent
|
|||||||
void torrentFinishedChecking(BitTorrent::TorrentHandle *const torrent);
|
void torrentFinishedChecking(BitTorrent::TorrentHandle *const torrent);
|
||||||
void torrentSavePathChanged(BitTorrent::TorrentHandle *const torrent);
|
void torrentSavePathChanged(BitTorrent::TorrentHandle *const torrent);
|
||||||
void torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory);
|
void torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory);
|
||||||
|
void torrentTagAdded(TorrentHandle *const torrent, const QString &tag);
|
||||||
|
void torrentTagRemoved(TorrentHandle *const torrent, const QString &tag);
|
||||||
void torrentSavingModeChanged(BitTorrent::TorrentHandle *const torrent);
|
void torrentSavingModeChanged(BitTorrent::TorrentHandle *const torrent);
|
||||||
void allTorrentsFinished();
|
void allTorrentsFinished();
|
||||||
void metadataLoaded(const BitTorrent::TorrentInfo &info);
|
void metadataLoaded(const BitTorrent::TorrentInfo &info);
|
||||||
@ -452,6 +463,8 @@ namespace BitTorrent
|
|||||||
void categoryAdded(const QString &categoryName);
|
void categoryAdded(const QString &categoryName);
|
||||||
void categoryRemoved(const QString &categoryName);
|
void categoryRemoved(const QString &categoryName);
|
||||||
void subcategoriesSupportChanged();
|
void subcategoriesSupportChanged();
|
||||||
|
void tagAdded(const QString &tag);
|
||||||
|
void tagRemoved(const QString &tag);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void configureDeferred();
|
void configureDeferred();
|
||||||
@ -606,6 +619,7 @@ namespace BitTorrent
|
|||||||
CachedSettingValue<bool> m_isForceProxyEnabled;
|
CachedSettingValue<bool> m_isForceProxyEnabled;
|
||||||
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
|
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
|
||||||
CachedSettingValue<QVariantMap> m_storedCategories;
|
CachedSettingValue<QVariantMap> m_storedCategories;
|
||||||
|
CachedSettingValue<QStringList> m_storedTags;
|
||||||
CachedSettingValue<int> m_maxRatioAction;
|
CachedSettingValue<int> m_maxRatioAction;
|
||||||
CachedSettingValue<QString> m_defaultSavePath;
|
CachedSettingValue<QString> m_defaultSavePath;
|
||||||
CachedSettingValue<QString> m_tempPath;
|
CachedSettingValue<QString> m_tempPath;
|
||||||
@ -650,6 +664,7 @@ namespace BitTorrent
|
|||||||
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
||||||
TorrentStatusReport m_torrentStatusReport;
|
TorrentStatusReport m_torrentStatusReport;
|
||||||
QStringMap m_categories;
|
QStringMap m_categories;
|
||||||
|
QSet<QString> m_tags;
|
||||||
|
|
||||||
#if LIBTORRENT_VERSION_NUM < 10100
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
QMutex m_alertsMutex;
|
QMutex m_alertsMutex;
|
||||||
|
@ -68,6 +68,19 @@ const QString QB_EXT {".!qB"};
|
|||||||
namespace libt = libtorrent;
|
namespace libt = libtorrent;
|
||||||
using namespace BitTorrent;
|
using namespace BitTorrent;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using ListType = libt::entry::list_type;
|
||||||
|
|
||||||
|
ListType setToEntryList(const QSet<QString> &input)
|
||||||
|
{
|
||||||
|
ListType entryList;
|
||||||
|
foreach (const QString &setValue, input)
|
||||||
|
entryList.emplace_back(setValue.toStdString());
|
||||||
|
return entryList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AddTorrentData
|
// AddTorrentData
|
||||||
|
|
||||||
AddTorrentData::AddTorrentData()
|
AddTorrentData::AddTorrentData()
|
||||||
@ -89,6 +102,7 @@ AddTorrentData::AddTorrentData(const AddTorrentParams ¶ms)
|
|||||||
: resumed(false)
|
: resumed(false)
|
||||||
, name(params.name)
|
, name(params.name)
|
||||||
, category(params.category)
|
, category(params.category)
|
||||||
|
, tags(params.tags)
|
||||||
, savePath(params.savePath)
|
, savePath(params.savePath)
|
||||||
, disableTempPath(params.disableTempPath)
|
, disableTempPath(params.disableTempPath)
|
||||||
, sequential(params.sequential)
|
, sequential(params.sequential)
|
||||||
@ -213,6 +227,7 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
|||||||
, m_name(data.name)
|
, m_name(data.name)
|
||||||
, m_savePath(Utils::Fs::toNativePath(data.savePath))
|
, m_savePath(Utils::Fs::toNativePath(data.savePath))
|
||||||
, m_category(data.category)
|
, m_category(data.category)
|
||||||
|
, m_tags(data.tags)
|
||||||
, m_hasSeedStatus(data.hasSeedStatus)
|
, m_hasSeedStatus(data.hasSeedStatus)
|
||||||
, m_ratioLimit(data.ratioLimit)
|
, m_ratioLimit(data.ratioLimit)
|
||||||
, m_seedingTimeLimit(data.seedingTimeLimit)
|
, m_seedingTimeLimit(data.seedingTimeLimit)
|
||||||
@ -578,6 +593,50 @@ bool TorrentHandle::belongsToCategory(const QString &category) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSet<QString> TorrentHandle::tags() const
|
||||||
|
{
|
||||||
|
return m_tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TorrentHandle::hasTag(const QString &tag) const
|
||||||
|
{
|
||||||
|
return m_tags.contains(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TorrentHandle::addTag(const QString &tag)
|
||||||
|
{
|
||||||
|
if (!Session::isValidTag(tag))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hasTag(tag)) {
|
||||||
|
if (!m_session->hasTag(tag))
|
||||||
|
if (!m_session->addTag(tag))
|
||||||
|
return false;
|
||||||
|
m_tags.insert(tag);
|
||||||
|
m_session->handleTorrentTagAdded(this, tag);
|
||||||
|
m_needSaveResumeData = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TorrentHandle::removeTag(const QString &tag)
|
||||||
|
{
|
||||||
|
if (m_tags.remove(tag)) {
|
||||||
|
m_session->handleTorrentTagRemoved(this, tag);
|
||||||
|
m_needSaveResumeData = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TorrentHandle::removeAllTags()
|
||||||
|
{
|
||||||
|
// QT automatically copies the container in foreach, so it's safe to mutate it.
|
||||||
|
foreach (const QString &tag, m_tags)
|
||||||
|
removeTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
QDateTime TorrentHandle::addedTime() const
|
QDateTime TorrentHandle::addedTime() const
|
||||||
{
|
{
|
||||||
return QDateTime::fromTime_t(m_nativeStatus.added_time);
|
return QDateTime::fromTime_t(m_nativeStatus.added_time);
|
||||||
@ -1617,6 +1676,7 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert
|
|||||||
resumeData["qBt-ratioLimit"] = QString::number(m_ratioLimit).toStdString();
|
resumeData["qBt-ratioLimit"] = QString::number(m_ratioLimit).toStdString();
|
||||||
resumeData["qBt-seedingTimeLimit"] = QString::number(m_seedingTimeLimit).toStdString();
|
resumeData["qBt-seedingTimeLimit"] = QString::number(m_seedingTimeLimit).toStdString();
|
||||||
resumeData["qBt-category"] = m_category.toStdString();
|
resumeData["qBt-category"] = m_category.toStdString();
|
||||||
|
resumeData["qBt-tags"] = setToEntryList(m_tags);
|
||||||
resumeData["qBt-name"] = m_name.toStdString();
|
resumeData["qBt-name"] = m_name.toStdString();
|
||||||
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
|
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
|
||||||
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
|
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
|
||||||
|
@ -30,12 +30,13 @@
|
|||||||
#ifndef BITTORRENT_TORRENTHANDLE_H
|
#ifndef BITTORRENT_TORRENTHANDLE_H
|
||||||
#define BITTORRENT_TORRENTHANDLE_H
|
#define BITTORRENT_TORRENTHANDLE_H
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QQueue>
|
|
||||||
#include <QVector>
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQueue>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
#include <libtorrent/torrent_handle.hpp>
|
#include <libtorrent/torrent_handle.hpp>
|
||||||
#include <libtorrent/version.hpp>
|
#include <libtorrent/version.hpp>
|
||||||
@ -93,6 +94,7 @@ namespace BitTorrent
|
|||||||
// for both new and resumed torrents
|
// for both new and resumed torrents
|
||||||
QString name;
|
QString name;
|
||||||
QString category;
|
QString category;
|
||||||
|
QSet<QString> tags;
|
||||||
QString savePath;
|
QString savePath;
|
||||||
bool disableTempPath;
|
bool disableTempPath;
|
||||||
bool sequential;
|
bool sequential;
|
||||||
@ -248,6 +250,12 @@ namespace BitTorrent
|
|||||||
bool belongsToCategory(const QString &category) const;
|
bool belongsToCategory(const QString &category) const;
|
||||||
bool setCategory(const QString &category);
|
bool setCategory(const QString &category);
|
||||||
|
|
||||||
|
QSet<QString> tags() const;
|
||||||
|
bool hasTag(const QString &tag) const;
|
||||||
|
bool addTag(const QString &tag);
|
||||||
|
bool removeTag(const QString &tag);
|
||||||
|
void removeAllTags();
|
||||||
|
|
||||||
bool hasRootFolder() const;
|
bool hasRootFolder() const;
|
||||||
|
|
||||||
int filesCount() const;
|
int filesCount() const;
|
||||||
@ -445,6 +453,7 @@ namespace BitTorrent
|
|||||||
QString m_name;
|
QString m_name;
|
||||||
QString m_savePath;
|
QString m_savePath;
|
||||||
QString m_category;
|
QString m_category;
|
||||||
|
QSet<QString> m_tags;
|
||||||
bool m_hasSeedStatus;
|
bool m_hasSeedStatus;
|
||||||
qreal m_ratioLimit;
|
qreal m_ratioLimit;
|
||||||
int m_seedingTimeLimit;
|
int m_seedingTimeLimit;
|
||||||
|
@ -1064,6 +1064,16 @@ void Preferences::setConfirmTorrentRecheck(bool enabled)
|
|||||||
setValue("Preferences/Advanced/confirmTorrentRecheck", enabled);
|
setValue("Preferences/Advanced/confirmTorrentRecheck", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::confirmRemoveAllTags() const
|
||||||
|
{
|
||||||
|
return value("Preferences/Advanced/confirmRemoveAllTags", true).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setConfirmRemoveAllTags(bool enabled)
|
||||||
|
{
|
||||||
|
setValue("Preferences/Advanced/confirmRemoveAllTags", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
TrayIcon::Style Preferences::trayIconStyle() const
|
TrayIcon::Style Preferences::trayIconStyle() const
|
||||||
{
|
{
|
||||||
return TrayIcon::Style(value("Preferences/Advanced/TrayIconStyle", TrayIcon::NORMAL).toInt());
|
return TrayIcon::Style(value("Preferences/Advanced/TrayIconStyle", TrayIcon::NORMAL).toInt());
|
||||||
@ -1327,6 +1337,16 @@ void Preferences::setCategoryFilterState(const bool checked)
|
|||||||
setValue("TransferListFilters/CategoryFilterState", checked);
|
setValue("TransferListFilters/CategoryFilterState", checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::getTagFilterState() const
|
||||||
|
{
|
||||||
|
return value("TransferListFilters/TagFilterState", true).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setTagFilterState(const bool checked)
|
||||||
|
{
|
||||||
|
setValue("TransferListFilters/TagFilterState", checked);
|
||||||
|
}
|
||||||
|
|
||||||
bool Preferences::getTrackerFilterState() const
|
bool Preferences::getTrackerFilterState() const
|
||||||
{
|
{
|
||||||
return value("TransferListFilters/trackerFilterState", true).toBool();
|
return value("TransferListFilters/trackerFilterState", true).toBool();
|
||||||
|
@ -260,6 +260,8 @@ public:
|
|||||||
void setConfirmTorrentDeletion(bool enabled);
|
void setConfirmTorrentDeletion(bool enabled);
|
||||||
bool confirmTorrentRecheck() const;
|
bool confirmTorrentRecheck() const;
|
||||||
void setConfirmTorrentRecheck(bool enabled);
|
void setConfirmTorrentRecheck(bool enabled);
|
||||||
|
bool confirmRemoveAllTags() const;
|
||||||
|
void setConfirmRemoveAllTags(bool enabled);
|
||||||
TrayIcon::Style trayIconStyle() const;
|
TrayIcon::Style trayIconStyle() const;
|
||||||
void setTrayIconStyle(TrayIcon::Style style);
|
void setTrayIconStyle(TrayIcon::Style style);
|
||||||
|
|
||||||
@ -313,6 +315,7 @@ public:
|
|||||||
void setTorImportGeometry(const QByteArray &geometry);
|
void setTorImportGeometry(const QByteArray &geometry);
|
||||||
bool getStatusFilterState() const;
|
bool getStatusFilterState() const;
|
||||||
bool getCategoryFilterState() const;
|
bool getCategoryFilterState() const;
|
||||||
|
bool getTagFilterState() const;
|
||||||
bool getTrackerFilterState() const;
|
bool getTrackerFilterState() const;
|
||||||
int getTransSelFilter() const;
|
int getTransSelFilter() const;
|
||||||
void setTransSelFilter(const int &index);
|
void setTransSelFilter(const int &index);
|
||||||
@ -340,6 +343,7 @@ public:
|
|||||||
public slots:
|
public slots:
|
||||||
void setStatusFilterState(bool checked);
|
void setStatusFilterState(bool checked);
|
||||||
void setCategoryFilterState(bool checked);
|
void setCategoryFilterState(bool checked);
|
||||||
|
void setTagFilterState(bool checked);
|
||||||
void setTrackerFilterState(bool checked);
|
void setTrackerFilterState(bool checked);
|
||||||
|
|
||||||
void apply();
|
void apply();
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
const QString TorrentFilter::AnyCategory;
|
const QString TorrentFilter::AnyCategory;
|
||||||
const QStringSet TorrentFilter::AnyHash = (QStringSet() << QString());
|
const QStringSet TorrentFilter::AnyHash = (QStringSet() << QString());
|
||||||
|
const QString TorrentFilter::AnyTag;
|
||||||
|
|
||||||
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
||||||
const TorrentFilter TorrentFilter::SeedingTorrent(TorrentFilter::Seeding);
|
const TorrentFilter TorrentFilter::SeedingTorrent(TorrentFilter::Seeding);
|
||||||
@ -49,16 +50,18 @@ TorrentFilter::TorrentFilter()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const Type type, const QStringSet &hashSet, const QString &category)
|
TorrentFilter::TorrentFilter(const Type type, const QStringSet &hashSet, const QString &category, const QString &tag)
|
||||||
: m_type(type)
|
: m_type(type)
|
||||||
, m_category(category)
|
, m_category(category)
|
||||||
|
, m_tag(tag)
|
||||||
, m_hashSet(hashSet)
|
, m_hashSet(hashSet)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const QString &filter, const QStringSet &hashSet, const QString &category)
|
TorrentFilter::TorrentFilter(const QString &filter, const QStringSet &hashSet, const QString &category, const QString &tag)
|
||||||
: m_type(All)
|
: m_type(All)
|
||||||
, m_category(category)
|
, m_category(category)
|
||||||
|
, m_tag(tag)
|
||||||
, m_hashSet(hashSet)
|
, m_hashSet(hashSet)
|
||||||
{
|
{
|
||||||
setTypeByName(filter);
|
setTypeByName(filter);
|
||||||
@ -121,11 +124,24 @@ bool TorrentFilter::setCategory(const QString &category)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TorrentFilter::setTag(const QString &tag)
|
||||||
|
{
|
||||||
|
// QString::operator==() doesn't distinguish between empty and null strings.
|
||||||
|
if ((m_tag != tag)
|
||||||
|
|| (m_tag.isNull() && !tag.isNull())
|
||||||
|
|| (!m_tag.isNull() && tag.isNull())) {
|
||||||
|
m_tag = tag;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool TorrentFilter::match(TorrentHandle *const torrent) const
|
bool TorrentFilter::match(TorrentHandle *const torrent) const
|
||||||
{
|
{
|
||||||
if (!torrent) return false;
|
if (!torrent) return false;
|
||||||
|
|
||||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent));
|
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentFilter::matchState(BitTorrent::TorrentHandle *const torrent) const
|
bool TorrentFilter::matchState(BitTorrent::TorrentHandle *const torrent) const
|
||||||
@ -165,3 +181,11 @@ bool TorrentFilter::matchCategory(BitTorrent::TorrentHandle *const torrent) cons
|
|||||||
if (m_category.isNull()) return true;
|
if (m_category.isNull()) return true;
|
||||||
else return (torrent->belongsToCategory(m_category));
|
else return (torrent->belongsToCategory(m_category));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TorrentFilter::matchTag(BitTorrent::TorrentHandle *const torrent) const
|
||||||
|
{
|
||||||
|
// Empty tag is a special value to indicate we're filtering for untagged torrents.
|
||||||
|
if (m_tag.isNull()) return true;
|
||||||
|
else if (m_tag.isEmpty()) return torrent->tags().isEmpty();
|
||||||
|
else return (torrent->hasTag(m_tag));
|
||||||
|
}
|
||||||
|
@ -58,8 +58,10 @@ public:
|
|||||||
Errored
|
Errored
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// These mean any permutation, including no category / tag.
|
||||||
static const QString AnyCategory;
|
static const QString AnyCategory;
|
||||||
static const QStringSet AnyHash;
|
static const QStringSet AnyHash;
|
||||||
|
static const QString AnyTag;
|
||||||
|
|
||||||
static const TorrentFilter DownloadingTorrent;
|
static const TorrentFilter DownloadingTorrent;
|
||||||
static const TorrentFilter SeedingTorrent;
|
static const TorrentFilter SeedingTorrent;
|
||||||
@ -71,14 +73,16 @@ public:
|
|||||||
static const TorrentFilter ErroredTorrent;
|
static const TorrentFilter ErroredTorrent;
|
||||||
|
|
||||||
TorrentFilter();
|
TorrentFilter();
|
||||||
// category: pass empty string for "no category" or null string (QString()) for "any category"
|
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||||
TorrentFilter(const Type type, const QStringSet &hashSet = AnyHash, const QString &category = AnyCategory);
|
// Pass null string (QString()) to disable filtering (i.e. all torrents).
|
||||||
TorrentFilter(const QString &filter, const QStringSet &hashSet = AnyHash, const QString &category = AnyCategory);
|
TorrentFilter(const Type type, const QStringSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tag = AnyTag);
|
||||||
|
TorrentFilter(const QString &filter, const QStringSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tags = AnyTag);
|
||||||
|
|
||||||
bool setType(Type type);
|
bool setType(Type type);
|
||||||
bool setTypeByName(const QString &filter);
|
bool setTypeByName(const QString &filter);
|
||||||
bool setHashSet(const QStringSet &hashSet);
|
bool setHashSet(const QStringSet &hashSet);
|
||||||
bool setCategory(const QString &category);
|
bool setCategory(const QString &category);
|
||||||
|
bool setTag(const QString &tag);
|
||||||
|
|
||||||
bool match(BitTorrent::TorrentHandle *const torrent) const;
|
bool match(BitTorrent::TorrentHandle *const torrent) const;
|
||||||
|
|
||||||
@ -86,9 +90,11 @@ private:
|
|||||||
bool matchState(BitTorrent::TorrentHandle *const torrent) const;
|
bool matchState(BitTorrent::TorrentHandle *const torrent) const;
|
||||||
bool matchHash(BitTorrent::TorrentHandle *const torrent) const;
|
bool matchHash(BitTorrent::TorrentHandle *const torrent) const;
|
||||||
bool matchCategory(BitTorrent::TorrentHandle *const torrent) const;
|
bool matchCategory(BitTorrent::TorrentHandle *const torrent) const;
|
||||||
|
bool matchTag(BitTorrent::TorrentHandle *const torrent) const;
|
||||||
|
|
||||||
Type m_type;
|
Type m_type;
|
||||||
QString m_category;
|
QString m_category;
|
||||||
|
QString m_tag;
|
||||||
QStringSet m_hashSet;
|
QStringSet m_hashSet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,6 +57,9 @@ shutdownconfirmdlg.h
|
|||||||
speedlimitdlg.h
|
speedlimitdlg.h
|
||||||
statsdialog.h
|
statsdialog.h
|
||||||
statusbar.h
|
statusbar.h
|
||||||
|
tagfiltermodel.h
|
||||||
|
tagfilterproxymodel.h
|
||||||
|
tagfilterwidget.h
|
||||||
torrentcontentfiltermodel.h
|
torrentcontentfiltermodel.h
|
||||||
torrentcontentmodel.h
|
torrentcontentmodel.h
|
||||||
torrentcontentmodelfile.h
|
torrentcontentmodelfile.h
|
||||||
@ -98,6 +101,9 @@ shutdownconfirmdlg.cpp
|
|||||||
speedlimitdlg.cpp
|
speedlimitdlg.cpp
|
||||||
statsdialog.cpp
|
statsdialog.cpp
|
||||||
statusbar.cpp
|
statusbar.cpp
|
||||||
|
tagfiltermodel.cpp
|
||||||
|
tagfilterproxymodel.cpp
|
||||||
|
tagfilterwidget.cpp
|
||||||
torrentcontentfiltermodel.cpp
|
torrentcontentfiltermodel.cpp
|
||||||
torrentcontentmodel.cpp
|
torrentcontentmodel.cpp
|
||||||
torrentcontentmodelfile.cpp
|
torrentcontentmodelfile.cpp
|
||||||
|
@ -66,6 +66,7 @@ enum AdvSettingsRows
|
|||||||
RESOLVE_COUNTRIES,
|
RESOLVE_COUNTRIES,
|
||||||
PROGRAM_NOTIFICATIONS,
|
PROGRAM_NOTIFICATIONS,
|
||||||
TORRENT_ADDED_NOTIFICATIONS,
|
TORRENT_ADDED_NOTIFICATIONS,
|
||||||
|
CONFIRM_REMOVE_ALL_TAGS,
|
||||||
DOWNLOAD_TRACKER_FAVICON,
|
DOWNLOAD_TRACKER_FAVICON,
|
||||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||||
USE_ICON_THEME,
|
USE_ICON_THEME,
|
||||||
@ -185,6 +186,9 @@ void AdvancedSettings::saveAdvancedSettings()
|
|||||||
pref->useSystemIconTheme(cb_use_icon_theme.isChecked());
|
pref->useSystemIconTheme(cb_use_icon_theme.isChecked());
|
||||||
#endif
|
#endif
|
||||||
pref->setConfirmTorrentRecheck(cb_confirm_torrent_recheck.isChecked());
|
pref->setConfirmTorrentRecheck(cb_confirm_torrent_recheck.isChecked());
|
||||||
|
|
||||||
|
pref->setConfirmRemoveAllTags(cb_confirm_remove_all_tags.isChecked());
|
||||||
|
|
||||||
session->setAnnounceToAllTrackers(cb_announce_all_trackers.isChecked());
|
session->setAnnounceToAllTrackers(cb_announce_all_trackers.isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,6 +381,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||||||
// Torrent recheck confirmation
|
// Torrent recheck confirmation
|
||||||
cb_confirm_torrent_recheck.setChecked(pref->confirmTorrentRecheck());
|
cb_confirm_torrent_recheck.setChecked(pref->confirmTorrentRecheck());
|
||||||
addRow(CONFIRM_RECHECK_TORRENT, tr("Confirm torrent recheck"), &cb_confirm_torrent_recheck);
|
addRow(CONFIRM_RECHECK_TORRENT, tr("Confirm torrent recheck"), &cb_confirm_torrent_recheck);
|
||||||
|
|
||||||
|
// Remove all tags confirmation
|
||||||
|
cb_confirm_remove_all_tags.setChecked(pref->confirmRemoveAllTags());
|
||||||
|
addRow(CONFIRM_REMOVE_ALL_TAGS, tr("Confirm remove all tags"), &cb_confirm_remove_all_tags);
|
||||||
|
|
||||||
// Announce to all trackers
|
// Announce to all trackers
|
||||||
cb_announce_all_trackers.setChecked(session->announceToAllTrackers());
|
cb_announce_all_trackers.setChecked(session->announceToAllTrackers());
|
||||||
addRow(ANNOUNCE_ALL_TRACKERS, tr("Always announce to all trackers"), &cb_announce_all_trackers);
|
addRow(ANNOUNCE_ALL_TRACKERS, tr("Always announce to all trackers"), &cb_announce_all_trackers);
|
||||||
|
@ -79,7 +79,7 @@ private:
|
|||||||
QSpinBox spin_cache, spin_save_resume_data_interval, outgoing_ports_min, outgoing_ports_max, spin_list_refresh, spin_maxhalfopen, spin_tracker_port, spin_cache_ttl;
|
QSpinBox spin_cache, spin_save_resume_data_interval, outgoing_ports_min, outgoing_ports_max, spin_list_refresh, spin_maxhalfopen, spin_tracker_port, spin_cache_ttl;
|
||||||
QCheckBox cb_os_cache, cb_recheck_completed, cb_resolve_countries, cb_resolve_hosts, cb_super_seeding,
|
QCheckBox cb_os_cache, cb_recheck_completed, cb_resolve_countries, cb_resolve_hosts, cb_super_seeding,
|
||||||
cb_program_notifications, cb_torrent_added_notifications, cb_tracker_favicon, cb_tracker_status,
|
cb_program_notifications, cb_torrent_added_notifications, cb_tracker_favicon, cb_tracker_status,
|
||||||
cb_confirm_torrent_recheck, cb_listen_ipv6, cb_announce_all_trackers;
|
cb_confirm_torrent_recheck, cb_confirm_remove_all_tags, cb_listen_ipv6, cb_announce_all_trackers;
|
||||||
QComboBox combo_iface, combo_iface_address;
|
QComboBox combo_iface, combo_iface_address;
|
||||||
QLineEdit txtAnnounceIP;
|
QLineEdit txtAnnounceIP;
|
||||||
|
|
||||||
|
@ -52,6 +52,9 @@ HEADERS += \
|
|||||||
$$PWD/categoryfiltermodel.h \
|
$$PWD/categoryfiltermodel.h \
|
||||||
$$PWD/categoryfilterproxymodel.h \
|
$$PWD/categoryfilterproxymodel.h \
|
||||||
$$PWD/categoryfilterwidget.h \
|
$$PWD/categoryfilterwidget.h \
|
||||||
|
$$PWD/tagfiltermodel.h \
|
||||||
|
$$PWD/tagfilterproxymodel.h \
|
||||||
|
$$PWD/tagfilterwidget.h \
|
||||||
$$PWD/banlistoptions.h \
|
$$PWD/banlistoptions.h \
|
||||||
$$PWD/rss/rsswidget.h \
|
$$PWD/rss/rsswidget.h \
|
||||||
$$PWD/rss/articlelistwidget.h \
|
$$PWD/rss/articlelistwidget.h \
|
||||||
@ -103,6 +106,9 @@ SOURCES += \
|
|||||||
$$PWD/categoryfiltermodel.cpp \
|
$$PWD/categoryfiltermodel.cpp \
|
||||||
$$PWD/categoryfilterproxymodel.cpp \
|
$$PWD/categoryfilterproxymodel.cpp \
|
||||||
$$PWD/categoryfilterwidget.cpp \
|
$$PWD/categoryfilterwidget.cpp \
|
||||||
|
$$PWD/tagfiltermodel.cpp \
|
||||||
|
$$PWD/tagfilterproxymodel.cpp \
|
||||||
|
$$PWD/tagfilterwidget.cpp \
|
||||||
$$PWD/banlistoptions.cpp \
|
$$PWD/banlistoptions.cpp \
|
||||||
$$PWD/rss/rsswidget.cpp \
|
$$PWD/rss/rsswidget.cpp \
|
||||||
$$PWD/rss/articlelistwidget.cpp \
|
$$PWD/rss/articlelistwidget.cpp \
|
||||||
|
337
src/gui/tagfiltermodel.cpp
Normal file
337
src/gui/tagfiltermodel.cpp
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 "tagfiltermodel.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
|
#include "base/bittorrent/session.h"
|
||||||
|
#include "base/bittorrent/torrenthandle.h"
|
||||||
|
#include "guiiconprovider.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
QString getSpecialAllTag()
|
||||||
|
{
|
||||||
|
static const QString *const ALL_TAG = new QString(" ");
|
||||||
|
Q_ASSERT(!BitTorrent::Session::isValidTag(*ALL_TAG));
|
||||||
|
return *ALL_TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getSpecialUntaggedTag()
|
||||||
|
{
|
||||||
|
static const QString *const UNTAGGED_TAG = new QString(" ");
|
||||||
|
Q_ASSERT(!BitTorrent::Session::isValidTag(*UNTAGGED_TAG));
|
||||||
|
return *UNTAGGED_TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagModelItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TagModelItem(const QString &tag, int torrentsCount = 0)
|
||||||
|
: m_tag(tag)
|
||||||
|
, m_torrentsCount(torrentsCount)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString tag() const
|
||||||
|
{
|
||||||
|
return m_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
int torrentsCount() const
|
||||||
|
{
|
||||||
|
return m_torrentsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void increaseTorrentsCount()
|
||||||
|
{
|
||||||
|
++m_torrentsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decreaseTorrentsCount()
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_torrentsCount > 0);
|
||||||
|
--m_torrentsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QString m_tag;
|
||||||
|
int m_torrentsCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
TagFilterModel::TagFilterModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
using Session = BitTorrent::Session;
|
||||||
|
auto session = Session::instance();
|
||||||
|
|
||||||
|
connect(session, &Session::tagAdded, this, &TagFilterModel::tagAdded);
|
||||||
|
connect(session, &Session::tagRemoved, this, &TagFilterModel::tagRemoved);
|
||||||
|
connect(session, &Session::torrentTagAdded, this, &TagFilterModel::torrentTagAdded);
|
||||||
|
connect(session, &Session::torrentTagRemoved, this, &TagFilterModel::torrentTagRemoved);
|
||||||
|
connect(session, &Session::torrentAdded, this, &TagFilterModel::torrentAdded);
|
||||||
|
connect(session, &Session::torrentAboutToBeRemoved, this, &TagFilterModel::torrentAboutToBeRemoved);
|
||||||
|
populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
TagFilterModel::~TagFilterModel() = default;
|
||||||
|
|
||||||
|
bool TagFilterModel::isSpecialItem(const QModelIndex &index)
|
||||||
|
{
|
||||||
|
// the first two items are special items: 'All' and 'Untagged'
|
||||||
|
return (!index.parent().isValid() && (index.row() <= 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TagFilterModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.column() != 0)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
const int row = index.internalId();
|
||||||
|
Q_ASSERT(isValidRow(row));
|
||||||
|
const TagModelItem &item = m_tagItems[row];
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DecorationRole:
|
||||||
|
return GuiIconProvider::instance()->getIcon("inode-directory");
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
return QString(QLatin1String("%1 (%2)"))
|
||||||
|
.arg(tagDisplayName(item.tag())).arg(item.torrentsCount());
|
||||||
|
case Qt::UserRole:
|
||||||
|
return item.torrentsCount();
|
||||||
|
default:
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags TagFilterModel::flags(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return 0;
|
||||||
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TagFilterModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole))
|
||||||
|
if (section == 0)
|
||||||
|
return tr("Tags");
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex TagFilterModel::index(int row, int, const QModelIndex &) const
|
||||||
|
{
|
||||||
|
if (!isValidRow(row))
|
||||||
|
return QModelIndex();
|
||||||
|
return createIndex(row, 0, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TagFilterModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (!parent.isValid())
|
||||||
|
return m_tagItems.count();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagFilterModel::isValidRow(int row) const
|
||||||
|
{
|
||||||
|
return (row >= 0) && (row < m_tagItems.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex TagFilterModel::index(const QString &tag) const
|
||||||
|
{
|
||||||
|
const int row = findRow(tag);
|
||||||
|
if (!isValidRow(row))
|
||||||
|
return QModelIndex();
|
||||||
|
return index(row, 0, QModelIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TagFilterModel::tag(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QString();
|
||||||
|
const int row = index.internalId();
|
||||||
|
Q_ASSERT(isValidRow(row));
|
||||||
|
return m_tagItems[row].tag();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::tagAdded(const QString &tag)
|
||||||
|
{
|
||||||
|
const int row = m_tagItems.count();
|
||||||
|
beginInsertRows(QModelIndex(), row, row);
|
||||||
|
addToModel(tag, 0);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::tagRemoved(const QString &tag)
|
||||||
|
{
|
||||||
|
QModelIndex i = index(tag);
|
||||||
|
beginRemoveRows(i.parent(), i.row(), i.row());
|
||||||
|
removeFromModel(i.row());
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::torrentTagAdded(BitTorrent::TorrentHandle *const torrent, const QString &tag)
|
||||||
|
{
|
||||||
|
if (torrent->tags().count() == 1)
|
||||||
|
untaggedItem()->decreaseTorrentsCount();
|
||||||
|
|
||||||
|
const int row = findRow(tag);
|
||||||
|
Q_ASSERT(isValidRow(row));
|
||||||
|
TagModelItem &item = m_tagItems[row];
|
||||||
|
|
||||||
|
item.increaseTorrentsCount();
|
||||||
|
const QModelIndex i = index(row, 0, QModelIndex());
|
||||||
|
emit dataChanged(i, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::torrentTagRemoved(BitTorrent::TorrentHandle* const torrent, const QString &tag)
|
||||||
|
{
|
||||||
|
Q_ASSERT(torrent->tags().count() >= 0);
|
||||||
|
if (torrent->tags().count() == 0)
|
||||||
|
untaggedItem()->increaseTorrentsCount();
|
||||||
|
|
||||||
|
const int row = findRow(tag);
|
||||||
|
Q_ASSERT(isValidRow(row));
|
||||||
|
TagModelItem &item = m_tagItems[row];
|
||||||
|
|
||||||
|
item.decreaseTorrentsCount();
|
||||||
|
const QModelIndex i = index(row, 0, QModelIndex());
|
||||||
|
emit dataChanged(i, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::torrentAdded(BitTorrent::TorrentHandle *const torrent)
|
||||||
|
{
|
||||||
|
allTagsItem()->increaseTorrentsCount();
|
||||||
|
|
||||||
|
const QVector<TagModelItem *> items = findItems(torrent->tags());
|
||||||
|
if (items.isEmpty())
|
||||||
|
untaggedItem()->increaseTorrentsCount();
|
||||||
|
|
||||||
|
foreach (TagModelItem *item, items)
|
||||||
|
item->increaseTorrentsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const torrent)
|
||||||
|
{
|
||||||
|
allTagsItem()->decreaseTorrentsCount();
|
||||||
|
|
||||||
|
if (torrent->tags().isEmpty())
|
||||||
|
untaggedItem()->decreaseTorrentsCount();
|
||||||
|
|
||||||
|
foreach (TagModelItem *item, findItems(torrent->tags()))
|
||||||
|
item->decreaseTorrentsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TagFilterModel::tagDisplayName(const QString &tag)
|
||||||
|
{
|
||||||
|
if (tag == getSpecialAllTag())
|
||||||
|
return tr("All");
|
||||||
|
if (tag == getSpecialUntaggedTag())
|
||||||
|
return tr("Untagged");
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::populate()
|
||||||
|
{
|
||||||
|
using Torrent = BitTorrent::TorrentHandle;
|
||||||
|
|
||||||
|
auto session = BitTorrent::Session::instance();
|
||||||
|
auto torrents = session->torrents();
|
||||||
|
|
||||||
|
// All torrents
|
||||||
|
addToModel(getSpecialAllTag(), torrents.count());
|
||||||
|
|
||||||
|
const int untaggedCount = std::count_if(torrents.begin(), torrents.end(),
|
||||||
|
[](Torrent *torrent) { return torrent->tags().isEmpty(); });
|
||||||
|
addToModel(getSpecialUntaggedTag(), untaggedCount);
|
||||||
|
|
||||||
|
foreach (const QString &tag, session->tags()) {
|
||||||
|
const int count = std::count_if(torrents.begin(), torrents.end(),
|
||||||
|
[tag](Torrent *torrent) { return torrent->hasTag(tag); });
|
||||||
|
addToModel(tag, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::addToModel(const QString &tag, int count)
|
||||||
|
{
|
||||||
|
m_tagItems.append(TagModelItem(tag, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterModel::removeFromModel(int row)
|
||||||
|
{
|
||||||
|
Q_ASSERT(isValidRow(row));
|
||||||
|
m_tagItems.removeAt(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TagFilterModel::findRow(const QString &tag) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_tagItems.size(); ++i) {
|
||||||
|
if (m_tagItems[i].tag() == tag)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TagModelItem *TagFilterModel::findItem(const QString &tag)
|
||||||
|
{
|
||||||
|
const int row = findRow(tag);
|
||||||
|
if (!isValidRow(row))
|
||||||
|
return nullptr;
|
||||||
|
return &m_tagItems[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<TagModelItem *> TagFilterModel::findItems(const QSet<QString> &tags)
|
||||||
|
{
|
||||||
|
QVector<TagModelItem *> items;
|
||||||
|
items.reserve(tags.size());
|
||||||
|
foreach (const QString &tag, tags) {
|
||||||
|
TagModelItem *item = findItem(tag);
|
||||||
|
if (item)
|
||||||
|
items.push_back(item);
|
||||||
|
else
|
||||||
|
qWarning() << QString("Requested tag '%1' missing from the model.").arg(tag);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
TagModelItem *TagFilterModel::allTagsItem()
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_tagItems.size() > 0);
|
||||||
|
return &m_tagItems[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
TagModelItem *TagFilterModel::untaggedItem()
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_tagItems.size() > 1);
|
||||||
|
return &m_tagItems[1];
|
||||||
|
}
|
88
src/gui/tagfiltermodel.h
Normal file
88
src/gui/tagfiltermodel.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TAGFILTERMODEL_H
|
||||||
|
#define TAGFILTERMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QModelIndex>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
namespace BitTorrent
|
||||||
|
{
|
||||||
|
class TorrentHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagModelItem;
|
||||||
|
|
||||||
|
class TagFilterModel: public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TagFilterModel(QObject *parent = nullptr);
|
||||||
|
~TagFilterModel();
|
||||||
|
|
||||||
|
static bool isSpecialItem(const QModelIndex &index);
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
QModelIndex index(const QString &tag) const;
|
||||||
|
QString tag(const QModelIndex &index) const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void tagAdded(const QString &tag);
|
||||||
|
void tagRemoved(const QString &tag);
|
||||||
|
void torrentTagAdded(BitTorrent::TorrentHandle *const torrent, const QString &tag);
|
||||||
|
void torrentTagRemoved(BitTorrent::TorrentHandle *const, const QString &tag);
|
||||||
|
void torrentAdded(BitTorrent::TorrentHandle *const torrent);
|
||||||
|
void torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const torrent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString tagDisplayName(const QString &tag);
|
||||||
|
|
||||||
|
void populate();
|
||||||
|
void addToModel(const QString &tag, int count);
|
||||||
|
void removeFromModel(int row);
|
||||||
|
bool isValidRow(int row) const;
|
||||||
|
int findRow(const QString &tag) const;
|
||||||
|
TagModelItem *findItem(const QString &tag);
|
||||||
|
QVector<TagModelItem *> findItems(const QSet<QString> &tags);
|
||||||
|
TagModelItem *allTagsItem();
|
||||||
|
TagModelItem *untaggedItem();
|
||||||
|
|
||||||
|
QList<TagModelItem> m_tagItems; // Index corresponds to its row
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGFILTERMODEL_H
|
56
src/gui/tagfilterproxymodel.cpp
Normal file
56
src/gui/tagfilterproxymodel.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 "tagfilterproxymodel.h"
|
||||||
|
|
||||||
|
#include "base/utils/string.h"
|
||||||
|
#include "tagfiltermodel.h"
|
||||||
|
|
||||||
|
TagFilterProxyModel::TagFilterProxyModel(QObject *parent)
|
||||||
|
: QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex TagFilterProxyModel::index(const QString &tag) const
|
||||||
|
{
|
||||||
|
return mapFromSource(static_cast<TagFilterModel *>(sourceModel())->index(tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TagFilterProxyModel::tag(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
return static_cast<TagFilterModel *>(sourceModel())->tag(mapToSource(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||||
|
{
|
||||||
|
// "All" and "Untagged" must be left in place
|
||||||
|
if (TagFilterModel::isSpecialItem(left) || TagFilterModel::isSpecialItem(right))
|
||||||
|
return left.row() < right.row();
|
||||||
|
return Utils::String::naturalCompareCaseInsensitive(
|
||||||
|
left.data().toString(), right.data().toString());
|
||||||
|
}
|
52
src/gui/tagfilterproxymodel.h
Normal file
52
src/gui/tagfilterproxymodel.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TAGFILTERPROXYMODEL_H
|
||||||
|
#define TAGFILTERPROXYMODEL_H
|
||||||
|
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class TagFilterProxyModel: public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TagFilterProxyModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// TagFilterModel methods which we need to relay
|
||||||
|
QModelIndex index(const QString &tag) const;
|
||||||
|
QString tag(const QModelIndex &index) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// we added another overload of index(), hence this using directive:
|
||||||
|
using QSortFilterProxyModel::index;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGFILTERPROXYMODEL_H
|
224
src/gui/tagfilterwidget.cpp
Normal file
224
src/gui/tagfilterwidget.cpp
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 "tagfilterwidget.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "base/bittorrent/session.h"
|
||||||
|
#include "base/utils/misc.h"
|
||||||
|
#include "autoexpandabledialog.h"
|
||||||
|
#include "guiiconprovider.h"
|
||||||
|
#include "tagfiltermodel.h"
|
||||||
|
#include "tagfilterproxymodel.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
QString getTagFilter(const TagFilterProxyModel *const model, const QModelIndex &index)
|
||||||
|
{
|
||||||
|
QString tagFilter; // Defaults to All
|
||||||
|
if (index.isValid()) {
|
||||||
|
if (index.row() == 1)
|
||||||
|
tagFilter = ""; // Untagged
|
||||||
|
else if (index.row() > 1)
|
||||||
|
tagFilter = model->tag(index);
|
||||||
|
}
|
||||||
|
return tagFilter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TagFilterWidget::TagFilterWidget(QWidget *parent)
|
||||||
|
: QTreeView(parent)
|
||||||
|
{
|
||||||
|
TagFilterProxyModel *proxyModel = new TagFilterProxyModel(this);
|
||||||
|
proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
proxyModel->setSourceModel(new TagFilterModel(this));
|
||||||
|
setModel(proxyModel);
|
||||||
|
setFrameShape(QFrame::NoFrame);
|
||||||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
setUniformRowHeights(true);
|
||||||
|
setHeaderHidden(true);
|
||||||
|
setIconSize(Utils::Misc::smallIconSize());
|
||||||
|
#if defined(Q_OS_MAC)
|
||||||
|
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||||
|
#endif
|
||||||
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
sortByColumn(0, Qt::AscendingOrder);
|
||||||
|
setCurrentIndex(model()->index(0, 0));
|
||||||
|
|
||||||
|
connect(this, &TagFilterWidget::collapsed, this, &TagFilterWidget::callUpdateGeometry);
|
||||||
|
connect(this, &TagFilterWidget::expanded, this, &TagFilterWidget::callUpdateGeometry);
|
||||||
|
connect(this, &TagFilterWidget::customContextMenuRequested, this, &TagFilterWidget::showMenu);
|
||||||
|
connect(selectionModel(), &QItemSelectionModel::currentRowChanged, this
|
||||||
|
, &TagFilterWidget::onCurrentRowChanged);
|
||||||
|
connect(model(), &QAbstractItemModel::modelReset, this, &TagFilterWidget::callUpdateGeometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TagFilterWidget::currentTag() const
|
||||||
|
{
|
||||||
|
QModelIndex current;
|
||||||
|
auto selectedRows = selectionModel()->selectedRows();
|
||||||
|
if (!selectedRows.isEmpty())
|
||||||
|
current = selectedRows.first();
|
||||||
|
|
||||||
|
return getTagFilter(static_cast<TagFilterProxyModel *>(model()), current);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterWidget::onCurrentRowChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||||
|
{
|
||||||
|
Q_UNUSED(previous);
|
||||||
|
|
||||||
|
emit tagChanged(getTagFilter(static_cast<TagFilterProxyModel *>(model()), current));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterWidget::showMenu(QPoint)
|
||||||
|
{
|
||||||
|
QMenu menu(this);
|
||||||
|
|
||||||
|
QAction *addAct = menu.addAction(
|
||||||
|
GuiIconProvider::instance()->getIcon("list-add")
|
||||||
|
, tr("Add tag..."));
|
||||||
|
connect(addAct, &QAction::triggered, this, &TagFilterWidget::addTag);
|
||||||
|
|
||||||
|
auto selectedRows = selectionModel()->selectedRows();
|
||||||
|
if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first())) {
|
||||||
|
QAction *removeAct = menu.addAction(
|
||||||
|
GuiIconProvider::instance()->getIcon("list-remove")
|
||||||
|
, tr("Remove tag"));
|
||||||
|
connect(removeAct, &QAction::triggered, this, &TagFilterWidget::removeTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *removeUnusedAct = menu.addAction(
|
||||||
|
GuiIconProvider::instance()->getIcon("list-remove")
|
||||||
|
, tr("Remove unused tags"));
|
||||||
|
connect(removeUnusedAct, &QAction::triggered, this, &TagFilterWidget::removeUnusedTags);
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
QAction *startAct = menu.addAction(
|
||||||
|
GuiIconProvider::instance()->getIcon("media-playback-start")
|
||||||
|
, tr("Resume torrents"));
|
||||||
|
connect(startAct, &QAction::triggered
|
||||||
|
, this, &TagFilterWidget::actionResumeTorrentsTriggered);
|
||||||
|
|
||||||
|
QAction *pauseAct = menu.addAction(
|
||||||
|
GuiIconProvider::instance()->getIcon("media-playback-pause")
|
||||||
|
, tr("Pause torrents"));
|
||||||
|
connect(pauseAct, &QAction::triggered, this
|
||||||
|
, &TagFilterWidget::actionPauseTorrentsTriggered);
|
||||||
|
|
||||||
|
QAction *deleteTorrentsAct = menu.addAction(
|
||||||
|
GuiIconProvider::instance()->getIcon("edit-delete")
|
||||||
|
, tr("Delete torrents"));
|
||||||
|
connect(deleteTorrentsAct, &QAction::triggered, this
|
||||||
|
, &TagFilterWidget::actionDeleteTorrentsTriggered);
|
||||||
|
|
||||||
|
menu.exec(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterWidget::callUpdateGeometry()
|
||||||
|
{
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize TagFilterWidget::sizeHint() const
|
||||||
|
{
|
||||||
|
return viewportSizeHint();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize TagFilterWidget::minimumSizeHint() const
|
||||||
|
{
|
||||||
|
QSize size = sizeHint();
|
||||||
|
size.setWidth(6);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterWidget::rowsInserted(const QModelIndex &parent, int start, int end)
|
||||||
|
{
|
||||||
|
QTreeView::rowsInserted(parent, start, end);
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TagFilterWidget::askTagName()
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
QString tag = "";
|
||||||
|
bool invalid = true;
|
||||||
|
while (invalid) {
|
||||||
|
invalid = false;
|
||||||
|
tag = AutoExpandableDialog::getText(
|
||||||
|
this, tr("New Tag"), tr("Tag:"), QLineEdit::Normal, tag, &ok).trimmed();
|
||||||
|
if (ok && !tag.isEmpty()) {
|
||||||
|
if (!BitTorrent::Session::isValidTag(tag)) {
|
||||||
|
QMessageBox::warning(
|
||||||
|
this, tr("Invalid tag name")
|
||||||
|
, tr("Tag name '%1' is invalid").arg(tag));
|
||||||
|
invalid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok ? tag : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterWidget::addTag()
|
||||||
|
{
|
||||||
|
const QString tag = askTagName();
|
||||||
|
if (tag.isEmpty()) return;
|
||||||
|
|
||||||
|
if (BitTorrent::Session::instance()->tags().contains(tag))
|
||||||
|
QMessageBox::warning(this, tr("Tag exists"), tr("Tag name already exists."));
|
||||||
|
else
|
||||||
|
BitTorrent::Session::instance()->addTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterWidget::removeTag()
|
||||||
|
{
|
||||||
|
auto selectedRows = selectionModel()->selectedRows();
|
||||||
|
if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first())) {
|
||||||
|
BitTorrent::Session::instance()->removeTag(
|
||||||
|
static_cast<TagFilterProxyModel *>(model())->tag(selectedRows.first()));
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagFilterWidget::removeUnusedTags()
|
||||||
|
{
|
||||||
|
auto session = BitTorrent::Session::instance();
|
||||||
|
foreach (const QString &tag, session->tags())
|
||||||
|
if (model()->data(static_cast<TagFilterProxyModel *>(model())->index(tag), Qt::UserRole) == 0)
|
||||||
|
session->removeTag(tag);
|
||||||
|
updateGeometry();
|
||||||
|
}
|
64
src/gui/tagfilterwidget.h
Normal file
64
src/gui/tagfilterwidget.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TAGFILTERWIDGET_H
|
||||||
|
#define TAGFILTERWIDGET_H
|
||||||
|
|
||||||
|
#include <QTreeView>
|
||||||
|
|
||||||
|
class TagFilterWidget: public QTreeView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TagFilterWidget(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
QString currentTag() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void tagChanged(const QString &tag);
|
||||||
|
void actionResumeTorrentsTriggered();
|
||||||
|
void actionPauseTorrentsTriggered();
|
||||||
|
void actionDeleteTorrentsTriggered();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCurrentRowChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||||
|
void showMenu(QPoint);
|
||||||
|
void callUpdateGeometry();
|
||||||
|
void addTag();
|
||||||
|
void removeTag();
|
||||||
|
void removeUnusedTags();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSize sizeHint() const override;
|
||||||
|
QSize minimumSizeHint() const override;
|
||||||
|
void rowsInserted(const QModelIndex &parent, int start, int end) override;
|
||||||
|
QString askTagName();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGFILTERWIDGET_H
|
@ -105,6 +105,7 @@ QVariant TorrentModel::headerData(int section, Qt::Orientation orientation, int
|
|||||||
case TR_RATIO: return tr("Ratio", "Share ratio");
|
case TR_RATIO: return tr("Ratio", "Share ratio");
|
||||||
case TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
|
case TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
|
||||||
case TR_CATEGORY: return tr("Category");
|
case TR_CATEGORY: return tr("Category");
|
||||||
|
case TR_TAGS: return tr("Tags");
|
||||||
case TR_ADD_DATE: return tr("Added On", "Torrent was added to transfer list on 01/01/2010 08:00");
|
case TR_ADD_DATE: return tr("Added On", "Torrent was added to transfer list on 01/01/2010 08:00");
|
||||||
case TR_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
|
case TR_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
|
||||||
case TR_TRACKER: return tr("Tracker");
|
case TR_TRACKER: return tr("Tracker");
|
||||||
@ -198,6 +199,11 @@ QVariant TorrentModel::data(const QModelIndex &index, int role) const
|
|||||||
return torrent->realRatio();
|
return torrent->realRatio();
|
||||||
case TR_CATEGORY:
|
case TR_CATEGORY:
|
||||||
return torrent->category();
|
return torrent->category();
|
||||||
|
case TR_TAGS: {
|
||||||
|
QStringList tagsList = torrent->tags().toList();
|
||||||
|
tagsList.sort();
|
||||||
|
return tagsList.join(", ");
|
||||||
|
}
|
||||||
case TR_ADD_DATE:
|
case TR_ADD_DATE:
|
||||||
return torrent->addedTime();
|
return torrent->addedTime();
|
||||||
case TR_SEED_DATE:
|
case TR_SEED_DATE:
|
||||||
|
@ -62,6 +62,7 @@ public:
|
|||||||
TR_ETA,
|
TR_ETA,
|
||||||
TR_RATIO,
|
TR_RATIO,
|
||||||
TR_CATEGORY,
|
TR_CATEGORY,
|
||||||
|
TR_TAGS,
|
||||||
TR_ADD_DATE,
|
TR_ADD_DATE,
|
||||||
TR_SEED_DATE,
|
TR_SEED_DATE,
|
||||||
TR_TRACKER,
|
TR_TRACKER,
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
#include "autoexpandabledialog.h"
|
#include "autoexpandabledialog.h"
|
||||||
#include "categoryfilterwidget.h"
|
#include "categoryfilterwidget.h"
|
||||||
#include "guiiconprovider.h"
|
#include "guiiconprovider.h"
|
||||||
|
#include "tagfilterwidget.h"
|
||||||
#include "torrentmodel.h"
|
#include "torrentmodel.h"
|
||||||
#include "transferlistdelegate.h"
|
#include "transferlistdelegate.h"
|
||||||
#include "transferlistwidget.h"
|
#include "transferlistwidget.h"
|
||||||
@ -75,11 +76,13 @@ FiltersBase::FiltersBase(QWidget *parent, TransferListWidget *transferList)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showMenu(QPoint)));
|
connect(this, &FiltersBase::customContextMenuRequested, this, &FiltersBase::showMenu);
|
||||||
connect(this, SIGNAL(currentRowChanged(int)), SLOT(applyFilter(int)));
|
connect(this, &FiltersBase::currentRowChanged, this, &FiltersBase::applyFilter);
|
||||||
|
|
||||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentAdded(BitTorrent::TorrentHandle *const)), SLOT(handleNewTorrent(BitTorrent::TorrentHandle *const)));
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAdded
|
||||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const)), SLOT(torrentAboutToBeDeleted(BitTorrent::TorrentHandle *const)));
|
, this, &FiltersBase::handleNewTorrent);
|
||||||
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved
|
||||||
|
, this, &FiltersBase::torrentAboutToBeDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize FiltersBase::sizeHint() const
|
QSize FiltersBase::sizeHint() const
|
||||||
@ -111,7 +114,8 @@ void FiltersBase::toggleFilter(bool checked)
|
|||||||
StatusFiltersWidget::StatusFiltersWidget(QWidget *parent, TransferListWidget *transferList)
|
StatusFiltersWidget::StatusFiltersWidget(QWidget *parent, TransferListWidget *transferList)
|
||||||
: FiltersBase(parent, transferList)
|
: FiltersBase(parent, transferList)
|
||||||
{
|
{
|
||||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentsUpdated()), SLOT(updateTorrentNumbers()));
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated
|
||||||
|
, this, &StatusFiltersWidget::updateTorrentNumbers);
|
||||||
|
|
||||||
// Add status filters
|
// Add status filters
|
||||||
QListWidgetItem *all = new QListWidgetItem(this);
|
QListWidgetItem *all = new QListWidgetItem(this);
|
||||||
@ -387,8 +391,11 @@ void TrackerFiltersList::downloadFavicon(const QString& url)
|
|||||||
{
|
{
|
||||||
if (!m_downloadTrackerFavicon) return;
|
if (!m_downloadTrackerFavicon) return;
|
||||||
Net::DownloadHandler *h = Net::DownloadManager::instance()->downloadUrl(url, true);
|
Net::DownloadHandler *h = Net::DownloadManager::instance()->downloadUrl(url, true);
|
||||||
connect(h, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFavicoDownload(QString, QString)));
|
using Func = void (Net::DownloadHandler::*)(const QString &, const QString &);
|
||||||
connect(h, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleFavicoFailure(QString, QString)));
|
connect(h, static_cast<Func>(&Net::DownloadHandler::downloadFinished), this
|
||||||
|
, &TrackerFiltersList::handleFavicoDownload);
|
||||||
|
connect(h, static_cast<Func>(&Net::DownloadHandler::downloadFailed), this
|
||||||
|
, &TrackerFiltersList::handleFavicoFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackerFiltersList::handleFavicoDownload(const QString& url, const QString& filePath)
|
void TrackerFiltersList::handleFavicoDownload(const QString& url, const QString& filePath)
|
||||||
@ -568,21 +575,40 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
|||||||
QCheckBox *categoryLabel = new QCheckBox(tr("Categories"), this);
|
QCheckBox *categoryLabel = new QCheckBox(tr("Categories"), this);
|
||||||
categoryLabel->setChecked(pref->getCategoryFilterState());
|
categoryLabel->setChecked(pref->getCategoryFilterState());
|
||||||
categoryLabel->setFont(font);
|
categoryLabel->setFont(font);
|
||||||
connect(categoryLabel, SIGNAL(toggled(bool)), SLOT(onCategoryFilterStateChanged(bool)));
|
connect(categoryLabel, &QCheckBox::toggled, this
|
||||||
|
, &TransferListFiltersWidget::onCategoryFilterStateChanged);
|
||||||
frameLayout->addWidget(categoryLabel);
|
frameLayout->addWidget(categoryLabel);
|
||||||
|
|
||||||
m_categoryFilterWidget = new CategoryFilterWidget(this);
|
m_categoryFilterWidget = new CategoryFilterWidget(this);
|
||||||
connect(m_categoryFilterWidget, SIGNAL(actionDeleteTorrentsTriggered())
|
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionDeleteTorrentsTriggered
|
||||||
, transferList, SLOT(deleteVisibleTorrents()));
|
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||||
connect(m_categoryFilterWidget, SIGNAL(actionPauseTorrentsTriggered())
|
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionPauseTorrentsTriggered
|
||||||
, transferList, SLOT(pauseVisibleTorrents()));
|
, transferList, &TransferListWidget::pauseVisibleTorrents);
|
||||||
connect(m_categoryFilterWidget, SIGNAL(actionResumeTorrentsTriggered())
|
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionResumeTorrentsTriggered
|
||||||
, transferList, SLOT(startVisibleTorrents()));
|
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||||
connect(m_categoryFilterWidget, SIGNAL(categoryChanged(QString))
|
connect(m_categoryFilterWidget, &CategoryFilterWidget::categoryChanged
|
||||||
, transferList, SLOT(applyCategoryFilter(QString)));
|
, transferList, &TransferListWidget::applyCategoryFilter);
|
||||||
toggleCategoryFilter(pref->getCategoryFilterState());
|
toggleCategoryFilter(pref->getCategoryFilterState());
|
||||||
frameLayout->addWidget(m_categoryFilterWidget);
|
frameLayout->addWidget(m_categoryFilterWidget);
|
||||||
|
|
||||||
|
QCheckBox *tagsLabel = new QCheckBox(tr("Tags"), this);
|
||||||
|
tagsLabel->setChecked(pref->getTagFilterState());
|
||||||
|
tagsLabel->setFont(font);
|
||||||
|
connect(tagsLabel, &QCheckBox::toggled, this, &TransferListFiltersWidget::onTagFilterStateChanged);
|
||||||
|
frameLayout->addWidget(tagsLabel);
|
||||||
|
|
||||||
|
m_tagFilterWidget = new TagFilterWidget(this);
|
||||||
|
connect(m_tagFilterWidget, &TagFilterWidget::actionDeleteTorrentsTriggered
|
||||||
|
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||||
|
connect(m_tagFilterWidget, &TagFilterWidget::actionPauseTorrentsTriggered
|
||||||
|
, transferList, &TransferListWidget::pauseVisibleTorrents);
|
||||||
|
connect(m_tagFilterWidget, &TagFilterWidget::actionResumeTorrentsTriggered
|
||||||
|
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||||
|
connect(m_tagFilterWidget, &TagFilterWidget::tagChanged
|
||||||
|
, transferList, &TransferListWidget::applyTagFilter);
|
||||||
|
toggleTagFilter(pref->getTagFilterState());
|
||||||
|
frameLayout->addWidget(m_tagFilterWidget);
|
||||||
|
|
||||||
QCheckBox *trackerLabel = new QCheckBox(tr("Trackers"), this);
|
QCheckBox *trackerLabel = new QCheckBox(tr("Trackers"), this);
|
||||||
trackerLabel->setChecked(pref->getTrackerFilterState());
|
trackerLabel->setChecked(pref->getTrackerFilterState());
|
||||||
trackerLabel->setFont(font);
|
trackerLabel->setFont(font);
|
||||||
@ -591,13 +617,18 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
|||||||
m_trackerFilters = new TrackerFiltersList(this, transferList);
|
m_trackerFilters = new TrackerFiltersList(this, transferList);
|
||||||
frameLayout->addWidget(m_trackerFilters);
|
frameLayout->addWidget(m_trackerFilters);
|
||||||
|
|
||||||
connect(statusLabel, SIGNAL(toggled(bool)), statusFilters, SLOT(toggleFilter(bool)));
|
connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFiltersWidget::toggleFilter);
|
||||||
connect(statusLabel, SIGNAL(toggled(bool)), pref, SLOT(setStatusFilterState(const bool)));
|
connect(statusLabel, &QCheckBox::toggled, pref, &Preferences::setStatusFilterState);
|
||||||
connect(trackerLabel, SIGNAL(toggled(bool)), m_trackerFilters, SLOT(toggleFilter(bool)));
|
connect(trackerLabel, &QCheckBox::toggled, m_trackerFilters, &TrackerFiltersList::toggleFilter);
|
||||||
connect(trackerLabel, SIGNAL(toggled(bool)), pref, SLOT(setTrackerFilterState(const bool)));
|
connect(trackerLabel, &QCheckBox::toggled, pref, &Preferences::setTrackerFilterState);
|
||||||
connect(this, SIGNAL(trackerSuccess(const QString &, const QString &)), m_trackerFilters, SLOT(trackerSuccess(const QString &, const QString &)));
|
|
||||||
connect(this, SIGNAL(trackerError(const QString &, const QString &)), m_trackerFilters, SLOT(trackerError(const QString &, const QString &)));
|
using Func = void (TransferListFiltersWidget::*)(const QString&, const QString&);
|
||||||
connect(this, SIGNAL(trackerWarning(const QString &, const QString &)), m_trackerFilters, SLOT(trackerWarning(const QString &, const QString &)));
|
connect(this, static_cast<Func>(&TransferListFiltersWidget::trackerSuccess)
|
||||||
|
, m_trackerFilters, &TrackerFiltersList::trackerSuccess);
|
||||||
|
connect(this, static_cast<Func>(&TransferListFiltersWidget::trackerError)
|
||||||
|
, m_trackerFilters, &TrackerFiltersList::trackerError);
|
||||||
|
connect(this, static_cast<Func>(&TransferListFiltersWidget::trackerWarning)
|
||||||
|
, m_trackerFilters, &TrackerFiltersList::trackerWarning);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransferListFiltersWidget::setDownloadTrackerFavicon(bool value)
|
void TransferListFiltersWidget::setDownloadTrackerFavicon(bool value)
|
||||||
@ -648,3 +679,15 @@ void TransferListFiltersWidget::toggleCategoryFilter(bool enabled)
|
|||||||
m_categoryFilterWidget->setVisible(enabled);
|
m_categoryFilterWidget->setVisible(enabled);
|
||||||
m_transferList->applyCategoryFilter(enabled ? m_categoryFilterWidget->currentCategory() : QString());
|
m_transferList->applyCategoryFilter(enabled ? m_categoryFilterWidget->currentCategory() : QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransferListFiltersWidget::onTagFilterStateChanged(bool enabled)
|
||||||
|
{
|
||||||
|
toggleTagFilter(enabled);
|
||||||
|
Preferences::instance()->setTagFilterState(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferListFiltersWidget::toggleTagFilter(bool enabled)
|
||||||
|
{
|
||||||
|
m_tagFilterWidget->setVisible(enabled);
|
||||||
|
m_transferList->applyTagFilter(enabled ? m_tagFilterWidget->currentTag() : QString());
|
||||||
|
}
|
||||||
|
@ -136,6 +136,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class CategoryFilterWidget;
|
class CategoryFilterWidget;
|
||||||
|
class TagFilterWidget;
|
||||||
|
|
||||||
class TransferListFiltersWidget: public QFrame
|
class TransferListFiltersWidget: public QFrame
|
||||||
{
|
{
|
||||||
@ -160,13 +161,16 @@ signals:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onCategoryFilterStateChanged(bool enabled);
|
void onCategoryFilterStateChanged(bool enabled);
|
||||||
|
void onTagFilterStateChanged(bool enabled);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void toggleCategoryFilter(bool enabled);
|
void toggleCategoryFilter(bool enabled);
|
||||||
|
void toggleTagFilter(bool enabled);
|
||||||
|
|
||||||
TransferListWidget *m_transferList;
|
TransferListWidget *m_transferList;
|
||||||
TrackerFiltersList *m_trackerFilters;
|
TrackerFiltersList *m_trackerFilters;
|
||||||
CategoryFilterWidget *m_categoryFilterWidget;
|
CategoryFilterWidget *m_categoryFilterWidget;
|
||||||
|
TagFilterWidget *m_tagFilterWidget;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TRANSFERLISTFILTERSWIDGET_H
|
#endif // TRANSFERLISTFILTERSWIDGET_H
|
||||||
|
@ -59,6 +59,18 @@ void TransferListSortModel::disableCategoryFilter()
|
|||||||
invalidateFilter();
|
invalidateFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransferListSortModel::setTagFilter(const QString &tag)
|
||||||
|
{
|
||||||
|
if (m_filter.setTag(tag))
|
||||||
|
invalidateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferListSortModel::disableTagFilter()
|
||||||
|
{
|
||||||
|
if (m_filter.setTag(TorrentFilter::AnyTag))
|
||||||
|
invalidateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
void TransferListSortModel::setTrackerFilter(const QStringList &hashes)
|
void TransferListSortModel::setTrackerFilter(const QStringList &hashes)
|
||||||
{
|
{
|
||||||
if (m_filter.setHashSet(hashes.toSet()))
|
if (m_filter.setHashSet(hashes.toSet()))
|
||||||
@ -75,6 +87,7 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex
|
|||||||
{
|
{
|
||||||
switch (sortColumn()) {
|
switch (sortColumn()) {
|
||||||
case TorrentModel::TR_CATEGORY:
|
case TorrentModel::TR_CATEGORY:
|
||||||
|
case TorrentModel::TR_TAGS:
|
||||||
case TorrentModel::TR_NAME: {
|
case TorrentModel::TR_NAME: {
|
||||||
QVariant vL = left.data();
|
QVariant vL = left.data();
|
||||||
QVariant vR = right.data();
|
QVariant vR = right.data();
|
||||||
|
@ -46,6 +46,8 @@ public:
|
|||||||
void setStatusFilter(TorrentFilter::Type filter);
|
void setStatusFilter(TorrentFilter::Type filter);
|
||||||
void setCategoryFilter(const QString &category);
|
void setCategoryFilter(const QString &category);
|
||||||
void disableCategoryFilter();
|
void disableCategoryFilter();
|
||||||
|
void setTagFilter(const QString &tag);
|
||||||
|
void disableTagFilter();
|
||||||
void setTrackerFilter(const QStringList &hashes);
|
void setTrackerFilter(const QStringList &hashes);
|
||||||
void disableTrackerFilter();
|
void disableTrackerFilter();
|
||||||
|
|
||||||
|
@ -607,6 +607,62 @@ void TransferListWidget::askNewCategoryForSelection()
|
|||||||
} while(invalid);
|
} while(invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::askAddTagsForSelection()
|
||||||
|
{
|
||||||
|
const QStringList tags = askTagsForSelection(tr("Add Tags"));
|
||||||
|
foreach (const QString &tag, tags)
|
||||||
|
addSelectionTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::askRemoveTagsForSelection()
|
||||||
|
{
|
||||||
|
const QStringList tags = askTagsForSelection(tr("Remove Tags"));
|
||||||
|
foreach (const QString &tag, tags)
|
||||||
|
removeSelectionTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::confirmRemoveAllTagsForSelection()
|
||||||
|
{
|
||||||
|
QMessageBox::StandardButton response = QMessageBox::question(
|
||||||
|
this, tr("Remove All Tags"), tr("Remove all tags from selected torrents?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (response == QMessageBox::Yes)
|
||||||
|
clearSelectionTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList TransferListWidget::askTagsForSelection(const QString &dialogTitle)
|
||||||
|
{
|
||||||
|
QStringList tags;
|
||||||
|
bool invalid = true;
|
||||||
|
while (invalid) {
|
||||||
|
bool ok = false;
|
||||||
|
invalid = false;
|
||||||
|
const QString tagsInput = AutoExpandableDialog::getText(
|
||||||
|
this, dialogTitle, tr("Comma-separated tags:"), QLineEdit::Normal, "", &ok).trimmed();
|
||||||
|
if (!ok || tagsInput.isEmpty())
|
||||||
|
return QStringList();
|
||||||
|
tags = tagsInput.split(',', QString::SkipEmptyParts);
|
||||||
|
for (QString &tag : tags) {
|
||||||
|
tag = tag.trimmed();
|
||||||
|
if (!BitTorrent::Session::isValidTag(tag)) {
|
||||||
|
QMessageBox::warning(this, tr("Invalid tag")
|
||||||
|
, tr("Tag name: '%1' is invalid").arg(tag));
|
||||||
|
invalid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::applyToSelectedTorrents(const std::function<void (BitTorrent::TorrentHandle *const)> &fn)
|
||||||
|
{
|
||||||
|
foreach (const QModelIndex &index, selectionModel()->selectedRows()) {
|
||||||
|
BitTorrent::TorrentHandle *const torrent = listModel->torrentHandle(mapToSource(index));
|
||||||
|
Q_ASSERT(torrent);
|
||||||
|
fn(torrent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TransferListWidget::renameSelectedTorrent()
|
void TransferListWidget::renameSelectedTorrent()
|
||||||
{
|
{
|
||||||
const QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
const QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
||||||
@ -632,6 +688,21 @@ void TransferListWidget::setSelectionCategory(QString category)
|
|||||||
listModel->setData(listModel->index(mapToSource(index).row(), TorrentModel::TR_CATEGORY), category, Qt::DisplayRole);
|
listModel->setData(listModel->index(mapToSource(index).row(), TorrentModel::TR_CATEGORY), category, Qt::DisplayRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::addSelectionTag(const QString &tag)
|
||||||
|
{
|
||||||
|
applyToSelectedTorrents([&tag](BitTorrent::TorrentHandle *const torrent) { torrent->addTag(tag); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::removeSelectionTag(const QString &tag)
|
||||||
|
{
|
||||||
|
applyToSelectedTorrents([&tag](BitTorrent::TorrentHandle *const torrent) { torrent->removeTag(tag); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::clearSelectionTags()
|
||||||
|
{
|
||||||
|
applyToSelectedTorrents([](BitTorrent::TorrentHandle *const torrent) { torrent->removeAllTags(); });
|
||||||
|
}
|
||||||
|
|
||||||
void TransferListWidget::displayListMenu(const QPoint&)
|
void TransferListWidget::displayListMenu(const QPoint&)
|
||||||
{
|
{
|
||||||
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
||||||
@ -701,6 +772,7 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
|||||||
bool firstAutoTMM = false;
|
bool firstAutoTMM = false;
|
||||||
QString firstCategory;
|
QString firstCategory;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
QSet<QString> tagsInSelection;
|
||||||
|
|
||||||
BitTorrent::TorrentHandle *torrent;
|
BitTorrent::TorrentHandle *torrent;
|
||||||
qDebug("Displaying menu");
|
qDebug("Displaying menu");
|
||||||
@ -715,6 +787,8 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
|||||||
if (firstCategory != torrent->category())
|
if (firstCategory != torrent->category())
|
||||||
allSameCategory = false;
|
allSameCategory = false;
|
||||||
|
|
||||||
|
tagsInSelection.unite(torrent->tags());
|
||||||
|
|
||||||
if (first)
|
if (first)
|
||||||
firstAutoTMM = torrent->isAutoTMMEnabled();
|
firstAutoTMM = torrent->isAutoTMMEnabled();
|
||||||
if (firstAutoTMM != torrent->isAutoTMMEnabled())
|
if (firstAutoTMM != torrent->isAutoTMMEnabled())
|
||||||
@ -798,6 +872,25 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
|||||||
categoryActions << cat;
|
categoryActions << cat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tag Menu
|
||||||
|
QStringList tags(BitTorrent::Session::instance()->tags().toList());
|
||||||
|
std::sort(tags.begin(), tags.end(), Utils::String::naturalCompareCaseInsensitive);
|
||||||
|
QList<QAction *> tagsActions;
|
||||||
|
QMenu *tagsMenu = listMenu.addMenu(GuiIconProvider::instance()->getIcon("view-categories"), tr("Tags"));
|
||||||
|
tagsActions << tagsMenu->addAction(GuiIconProvider::instance()->getIcon("list-add"), tr("Add...", "Add / assign multiple tags..."));
|
||||||
|
tagsActions << tagsMenu->addAction(GuiIconProvider::instance()->getIcon("edit-clear"), tr("Remove...", "Remove multiple tags..."));
|
||||||
|
tagsActions << tagsMenu->addAction(GuiIconProvider::instance()->getIcon("edit-clear"), tr("Remove All", "Remove all tags"));
|
||||||
|
tagsMenu->addSeparator();
|
||||||
|
foreach (QString tag, tags) {
|
||||||
|
const bool setChecked = tagsInSelection.contains(tag);
|
||||||
|
tag.replace('&', "&&"); // avoid '&' becomes accelerator key
|
||||||
|
QAction *tagSelection = new QAction(GuiIconProvider::instance()->getIcon("inode-directory"), tag, tagsMenu);
|
||||||
|
tagSelection->setCheckable(true);
|
||||||
|
tagSelection->setChecked(setChecked);
|
||||||
|
tagsMenu->addAction(tagSelection);
|
||||||
|
tagsActions << tagSelection;
|
||||||
|
}
|
||||||
|
|
||||||
if (allSameAutoTMM) {
|
if (allSameAutoTMM) {
|
||||||
actionAutoTMM.setChecked(firstAutoTMM);
|
actionAutoTMM.setChecked(firstAutoTMM);
|
||||||
listMenu.addAction(&actionAutoTMM);
|
listMenu.addAction(&actionAutoTMM);
|
||||||
@ -853,7 +946,7 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
|||||||
QAction *act = 0;
|
QAction *act = 0;
|
||||||
act = listMenu.exec(QCursor::pos());
|
act = listMenu.exec(QCursor::pos());
|
||||||
if (act) {
|
if (act) {
|
||||||
// Parse category actions only (others have slots assigned)
|
// Parse category & tag actions only (others have slots assigned)
|
||||||
int i = categoryActions.indexOf(act);
|
int i = categoryActions.indexOf(act);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
// Category action
|
// Category action
|
||||||
@ -869,6 +962,29 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
|||||||
setSelectionCategory(category);
|
setSelectionCategory(category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i = tagsActions.indexOf(act);
|
||||||
|
if (i >= 0) {
|
||||||
|
if (i == 0) {
|
||||||
|
askAddTagsForSelection();
|
||||||
|
}
|
||||||
|
else if (i == 1) {
|
||||||
|
askRemoveTagsForSelection();
|
||||||
|
}
|
||||||
|
else if (i == 2) {
|
||||||
|
if (Preferences::instance()->confirmRemoveAllTags())
|
||||||
|
confirmRemoveAllTagsForSelection();
|
||||||
|
else
|
||||||
|
clearSelectionTags();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Individual tag toggles.
|
||||||
|
const QString &tag = tags.at(i - 3);
|
||||||
|
if (act->isChecked())
|
||||||
|
addSelectionTag(tag);
|
||||||
|
else
|
||||||
|
removeSelectionTag(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -892,6 +1008,14 @@ void TransferListWidget::applyCategoryFilter(QString category)
|
|||||||
nameFilterModel->setCategoryFilter(category);
|
nameFilterModel->setCategoryFilter(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransferListWidget::applyTagFilter(const QString &tag)
|
||||||
|
{
|
||||||
|
if (tag.isNull())
|
||||||
|
nameFilterModel->disableTagFilter();
|
||||||
|
else
|
||||||
|
nameFilterModel->setTagFilter(tag);
|
||||||
|
}
|
||||||
|
|
||||||
void TransferListWidget::applyTrackerFilterAll()
|
void TransferListWidget::applyTrackerFilterAll()
|
||||||
{
|
{
|
||||||
nameFilterModel->disableTrackerFilter();
|
nameFilterModel->disableTrackerFilter();
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#ifndef TRANSFERLISTWIDGET_H
|
#ifndef TRANSFERLISTWIDGET_H
|
||||||
#define TRANSFERLISTWIDGET_H
|
#define TRANSFERLISTWIDGET_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
@ -60,6 +61,9 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setSelectionCategory(QString category);
|
void setSelectionCategory(QString category);
|
||||||
|
void addSelectionTag(const QString &tag);
|
||||||
|
void removeSelectionTag(const QString &tag);
|
||||||
|
void clearSelectionTags();
|
||||||
void setSelectedTorrentsLocation();
|
void setSelectedTorrentsLocation();
|
||||||
void pauseAllTorrents();
|
void pauseAllTorrents();
|
||||||
void resumeAllTorrents();
|
void resumeAllTorrents();
|
||||||
@ -89,6 +93,7 @@ public slots:
|
|||||||
void applyNameFilter(const QString& name);
|
void applyNameFilter(const QString& name);
|
||||||
void applyStatusFilter(int f);
|
void applyStatusFilter(int f);
|
||||||
void applyCategoryFilter(QString category);
|
void applyCategoryFilter(QString category);
|
||||||
|
void applyTagFilter(const QString &tag);
|
||||||
void applyTrackerFilterAll();
|
void applyTrackerFilterAll();
|
||||||
void applyTrackerFilter(const QStringList &hashes);
|
void applyTrackerFilter(const QStringList &hashes);
|
||||||
void previewFile(QString filePath);
|
void previewFile(QString filePath);
|
||||||
@ -116,6 +121,11 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
|
void askAddTagsForSelection();
|
||||||
|
void askRemoveTagsForSelection();
|
||||||
|
void confirmRemoveAllTagsForSelection();
|
||||||
|
QStringList askTagsForSelection(const QString &dialogTitle);
|
||||||
|
void applyToSelectedTorrents(const std::function<void (BitTorrent::TorrentHandle *const)> &fn);
|
||||||
|
|
||||||
TransferListDelegate *listDelegate;
|
TransferListDelegate *listDelegate;
|
||||||
TorrentModel *listModel;
|
TorrentModel *listModel;
|
||||||
|
Loading…
Reference in New Issue
Block a user