mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-20 15:37:26 -06: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:
@@ -57,6 +57,9 @@ shutdownconfirmdlg.h
|
||||
speedlimitdlg.h
|
||||
statsdialog.h
|
||||
statusbar.h
|
||||
tagfiltermodel.h
|
||||
tagfilterproxymodel.h
|
||||
tagfilterwidget.h
|
||||
torrentcontentfiltermodel.h
|
||||
torrentcontentmodel.h
|
||||
torrentcontentmodelfile.h
|
||||
@@ -98,6 +101,9 @@ shutdownconfirmdlg.cpp
|
||||
speedlimitdlg.cpp
|
||||
statsdialog.cpp
|
||||
statusbar.cpp
|
||||
tagfiltermodel.cpp
|
||||
tagfilterproxymodel.cpp
|
||||
tagfilterwidget.cpp
|
||||
torrentcontentfiltermodel.cpp
|
||||
torrentcontentmodel.cpp
|
||||
torrentcontentmodelfile.cpp
|
||||
|
||||
@@ -66,6 +66,7 @@ enum AdvSettingsRows
|
||||
RESOLVE_COUNTRIES,
|
||||
PROGRAM_NOTIFICATIONS,
|
||||
TORRENT_ADDED_NOTIFICATIONS,
|
||||
CONFIRM_REMOVE_ALL_TAGS,
|
||||
DOWNLOAD_TRACKER_FAVICON,
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||
USE_ICON_THEME,
|
||||
@@ -185,6 +186,9 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
pref->useSystemIconTheme(cb_use_icon_theme.isChecked());
|
||||
#endif
|
||||
pref->setConfirmTorrentRecheck(cb_confirm_torrent_recheck.isChecked());
|
||||
|
||||
pref->setConfirmRemoveAllTags(cb_confirm_remove_all_tags.isChecked());
|
||||
|
||||
session->setAnnounceToAllTrackers(cb_announce_all_trackers.isChecked());
|
||||
}
|
||||
|
||||
@@ -377,6 +381,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
// Torrent recheck confirmation
|
||||
cb_confirm_torrent_recheck.setChecked(pref->confirmTorrentRecheck());
|
||||
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
|
||||
cb_announce_all_trackers.setChecked(session->announceToAllTrackers());
|
||||
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;
|
||||
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_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;
|
||||
QLineEdit txtAnnounceIP;
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ HEADERS += \
|
||||
$$PWD/categoryfiltermodel.h \
|
||||
$$PWD/categoryfilterproxymodel.h \
|
||||
$$PWD/categoryfilterwidget.h \
|
||||
$$PWD/tagfiltermodel.h \
|
||||
$$PWD/tagfilterproxymodel.h \
|
||||
$$PWD/tagfilterwidget.h \
|
||||
$$PWD/banlistoptions.h \
|
||||
$$PWD/rss/rsswidget.h \
|
||||
$$PWD/rss/articlelistwidget.h \
|
||||
@@ -103,6 +106,9 @@ SOURCES += \
|
||||
$$PWD/categoryfiltermodel.cpp \
|
||||
$$PWD/categoryfilterproxymodel.cpp \
|
||||
$$PWD/categoryfilterwidget.cpp \
|
||||
$$PWD/tagfiltermodel.cpp \
|
||||
$$PWD/tagfilterproxymodel.cpp \
|
||||
$$PWD/tagfilterwidget.cpp \
|
||||
$$PWD/banlistoptions.cpp \
|
||||
$$PWD/rss/rsswidget.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_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
|
||||
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_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
|
||||
case TR_TRACKER: return tr("Tracker");
|
||||
@@ -198,6 +199,11 @@ QVariant TorrentModel::data(const QModelIndex &index, int role) const
|
||||
return torrent->realRatio();
|
||||
case TR_CATEGORY:
|
||||
return torrent->category();
|
||||
case TR_TAGS: {
|
||||
QStringList tagsList = torrent->tags().toList();
|
||||
tagsList.sort();
|
||||
return tagsList.join(", ");
|
||||
}
|
||||
case TR_ADD_DATE:
|
||||
return torrent->addedTime();
|
||||
case TR_SEED_DATE:
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
TR_ETA,
|
||||
TR_RATIO,
|
||||
TR_CATEGORY,
|
||||
TR_TAGS,
|
||||
TR_ADD_DATE,
|
||||
TR_SEED_DATE,
|
||||
TR_TRACKER,
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include "autoexpandabledialog.h"
|
||||
#include "categoryfilterwidget.h"
|
||||
#include "guiiconprovider.h"
|
||||
#include "tagfilterwidget.h"
|
||||
#include "torrentmodel.h"
|
||||
#include "transferlistdelegate.h"
|
||||
#include "transferlistwidget.h"
|
||||
@@ -75,11 +76,13 @@ FiltersBase::FiltersBase(QWidget *parent, TransferListWidget *transferList)
|
||||
#endif
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showMenu(QPoint)));
|
||||
connect(this, SIGNAL(currentRowChanged(int)), SLOT(applyFilter(int)));
|
||||
connect(this, &FiltersBase::customContextMenuRequested, this, &FiltersBase::showMenu);
|
||||
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(), SIGNAL(torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const)), SLOT(torrentAboutToBeDeleted(BitTorrent::TorrentHandle *const)));
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAdded
|
||||
, this, &FiltersBase::handleNewTorrent);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved
|
||||
, this, &FiltersBase::torrentAboutToBeDeleted);
|
||||
}
|
||||
|
||||
QSize FiltersBase::sizeHint() const
|
||||
@@ -111,7 +114,8 @@ void FiltersBase::toggleFilter(bool checked)
|
||||
StatusFiltersWidget::StatusFiltersWidget(QWidget *parent, TransferListWidget *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
|
||||
QListWidgetItem *all = new QListWidgetItem(this);
|
||||
@@ -387,8 +391,11 @@ void TrackerFiltersList::downloadFavicon(const QString& url)
|
||||
{
|
||||
if (!m_downloadTrackerFavicon) return;
|
||||
Net::DownloadHandler *h = Net::DownloadManager::instance()->downloadUrl(url, true);
|
||||
connect(h, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFavicoDownload(QString, QString)));
|
||||
connect(h, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleFavicoFailure(QString, QString)));
|
||||
using Func = void (Net::DownloadHandler::*)(const QString &, const 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)
|
||||
@@ -568,21 +575,40 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||
QCheckBox *categoryLabel = new QCheckBox(tr("Categories"), this);
|
||||
categoryLabel->setChecked(pref->getCategoryFilterState());
|
||||
categoryLabel->setFont(font);
|
||||
connect(categoryLabel, SIGNAL(toggled(bool)), SLOT(onCategoryFilterStateChanged(bool)));
|
||||
connect(categoryLabel, &QCheckBox::toggled, this
|
||||
, &TransferListFiltersWidget::onCategoryFilterStateChanged);
|
||||
frameLayout->addWidget(categoryLabel);
|
||||
|
||||
m_categoryFilterWidget = new CategoryFilterWidget(this);
|
||||
connect(m_categoryFilterWidget, SIGNAL(actionDeleteTorrentsTriggered())
|
||||
, transferList, SLOT(deleteVisibleTorrents()));
|
||||
connect(m_categoryFilterWidget, SIGNAL(actionPauseTorrentsTriggered())
|
||||
, transferList, SLOT(pauseVisibleTorrents()));
|
||||
connect(m_categoryFilterWidget, SIGNAL(actionResumeTorrentsTriggered())
|
||||
, transferList, SLOT(startVisibleTorrents()));
|
||||
connect(m_categoryFilterWidget, SIGNAL(categoryChanged(QString))
|
||||
, transferList, SLOT(applyCategoryFilter(QString)));
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionPauseTorrentsTriggered
|
||||
, transferList, &TransferListWidget::pauseVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionResumeTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::categoryChanged
|
||||
, transferList, &TransferListWidget::applyCategoryFilter);
|
||||
toggleCategoryFilter(pref->getCategoryFilterState());
|
||||
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);
|
||||
trackerLabel->setChecked(pref->getTrackerFilterState());
|
||||
trackerLabel->setFont(font);
|
||||
@@ -591,13 +617,18 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||
m_trackerFilters = new TrackerFiltersList(this, transferList);
|
||||
frameLayout->addWidget(m_trackerFilters);
|
||||
|
||||
connect(statusLabel, SIGNAL(toggled(bool)), statusFilters, SLOT(toggleFilter(bool)));
|
||||
connect(statusLabel, SIGNAL(toggled(bool)), pref, SLOT(setStatusFilterState(const bool)));
|
||||
connect(trackerLabel, SIGNAL(toggled(bool)), m_trackerFilters, SLOT(toggleFilter(bool)));
|
||||
connect(trackerLabel, SIGNAL(toggled(bool)), pref, SLOT(setTrackerFilterState(const bool)));
|
||||
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 &)));
|
||||
connect(this, SIGNAL(trackerWarning(const QString &, const QString &)), m_trackerFilters, SLOT(trackerWarning(const QString &, const QString &)));
|
||||
connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFiltersWidget::toggleFilter);
|
||||
connect(statusLabel, &QCheckBox::toggled, pref, &Preferences::setStatusFilterState);
|
||||
connect(trackerLabel, &QCheckBox::toggled, m_trackerFilters, &TrackerFiltersList::toggleFilter);
|
||||
connect(trackerLabel, &QCheckBox::toggled, pref, &Preferences::setTrackerFilterState);
|
||||
|
||||
using Func = void (TransferListFiltersWidget::*)(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)
|
||||
@@ -648,3 +679,15 @@ void TransferListFiltersWidget::toggleCategoryFilter(bool enabled)
|
||||
m_categoryFilterWidget->setVisible(enabled);
|
||||
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 TagFilterWidget;
|
||||
|
||||
class TransferListFiltersWidget: public QFrame
|
||||
{
|
||||
@@ -160,13 +161,16 @@ signals:
|
||||
|
||||
private slots:
|
||||
void onCategoryFilterStateChanged(bool enabled);
|
||||
void onTagFilterStateChanged(bool enabled);
|
||||
|
||||
private:
|
||||
void toggleCategoryFilter(bool enabled);
|
||||
void toggleTagFilter(bool enabled);
|
||||
|
||||
TransferListWidget *m_transferList;
|
||||
TrackerFiltersList *m_trackerFilters;
|
||||
CategoryFilterWidget *m_categoryFilterWidget;
|
||||
TagFilterWidget *m_tagFilterWidget;
|
||||
};
|
||||
|
||||
#endif // TRANSFERLISTFILTERSWIDGET_H
|
||||
|
||||
@@ -59,6 +59,18 @@ void TransferListSortModel::disableCategoryFilter()
|
||||
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)
|
||||
{
|
||||
if (m_filter.setHashSet(hashes.toSet()))
|
||||
@@ -75,6 +87,7 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex
|
||||
{
|
||||
switch (sortColumn()) {
|
||||
case TorrentModel::TR_CATEGORY:
|
||||
case TorrentModel::TR_TAGS:
|
||||
case TorrentModel::TR_NAME: {
|
||||
QVariant vL = left.data();
|
||||
QVariant vR = right.data();
|
||||
|
||||
@@ -46,6 +46,8 @@ public:
|
||||
void setStatusFilter(TorrentFilter::Type filter);
|
||||
void setCategoryFilter(const QString &category);
|
||||
void disableCategoryFilter();
|
||||
void setTagFilter(const QString &tag);
|
||||
void disableTagFilter();
|
||||
void setTrackerFilter(const QStringList &hashes);
|
||||
void disableTrackerFilter();
|
||||
|
||||
|
||||
@@ -607,6 +607,62 @@ void TransferListWidget::askNewCategoryForSelection()
|
||||
} 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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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&)
|
||||
{
|
||||
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
||||
@@ -701,6 +772,7 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
||||
bool firstAutoTMM = false;
|
||||
QString firstCategory;
|
||||
bool first = true;
|
||||
QSet<QString> tagsInSelection;
|
||||
|
||||
BitTorrent::TorrentHandle *torrent;
|
||||
qDebug("Displaying menu");
|
||||
@@ -715,6 +787,8 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
||||
if (firstCategory != torrent->category())
|
||||
allSameCategory = false;
|
||||
|
||||
tagsInSelection.unite(torrent->tags());
|
||||
|
||||
if (first)
|
||||
firstAutoTMM = torrent->isAutoTMMEnabled();
|
||||
if (firstAutoTMM != torrent->isAutoTMMEnabled())
|
||||
@@ -798,6 +872,25 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
||||
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) {
|
||||
actionAutoTMM.setChecked(firstAutoTMM);
|
||||
listMenu.addAction(&actionAutoTMM);
|
||||
@@ -853,7 +946,7 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
||||
QAction *act = 0;
|
||||
act = listMenu.exec(QCursor::pos());
|
||||
if (act) {
|
||||
// Parse category actions only (others have slots assigned)
|
||||
// Parse category & tag actions only (others have slots assigned)
|
||||
int i = categoryActions.indexOf(act);
|
||||
if (i >= 0) {
|
||||
// Category action
|
||||
@@ -869,6 +962,29 @@ void TransferListWidget::displayListMenu(const QPoint&)
|
||||
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);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyTagFilter(const QString &tag)
|
||||
{
|
||||
if (tag.isNull())
|
||||
nameFilterModel->disableTagFilter();
|
||||
else
|
||||
nameFilterModel->setTagFilter(tag);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyTrackerFilterAll()
|
||||
{
|
||||
nameFilterModel->disableTrackerFilter();
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#ifndef TRANSFERLISTWIDGET_H
|
||||
#define TRANSFERLISTWIDGET_H
|
||||
|
||||
#include <functional>
|
||||
#include <QTreeView>
|
||||
|
||||
namespace BitTorrent
|
||||
@@ -60,6 +61,9 @@ public:
|
||||
|
||||
public slots:
|
||||
void setSelectionCategory(QString category);
|
||||
void addSelectionTag(const QString &tag);
|
||||
void removeSelectionTag(const QString &tag);
|
||||
void clearSelectionTags();
|
||||
void setSelectedTorrentsLocation();
|
||||
void pauseAllTorrents();
|
||||
void resumeAllTorrents();
|
||||
@@ -89,6 +93,7 @@ public slots:
|
||||
void applyNameFilter(const QString& name);
|
||||
void applyStatusFilter(int f);
|
||||
void applyCategoryFilter(QString category);
|
||||
void applyTagFilter(const QString &tag);
|
||||
void applyTrackerFilterAll();
|
||||
void applyTrackerFilter(const QStringList &hashes);
|
||||
void previewFile(QString filePath);
|
||||
@@ -116,6 +121,11 @@ signals:
|
||||
|
||||
private:
|
||||
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;
|
||||
TorrentModel *listModel;
|
||||
|
||||
Reference in New Issue
Block a user