mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-07-14 01:03:08 -07: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
27 changed files with 1315 additions and 35 deletions
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue