Browse Source

Fix transfer list architecture

Model returns string for DisplayRole.
Text alignment is set by Model (using TextAlignmentRole).
Delegate performs custom painting only where necessary
(i.e. for Progress bar).
adaptive-webui-19844
Vladimir Golovnev (Glassez) 5 years ago
parent
commit
766cfb67df
No known key found for this signature in database
GPG Key ID: 52A2C7DEE2DFA6F7
  1. 197
      src/gui/transferlistdelegate.cpp
  2. 8
      src/gui/transferlistdelegate.h
  3. 265
      src/gui/transferlistmodel.cpp
  4. 15
      src/gui/transferlistmodel.h
  5. 188
      src/gui/transferlistsortmodel.cpp
  6. 12
      src/gui/transferlistsortmodel.h

197
src/gui/transferlistdelegate.cpp

@ -29,7 +29,6 @@
#include "transferlistdelegate.h" #include "transferlistdelegate.h"
#include <QApplication> #include <QApplication>
#include <QDateTime>
#include <QModelIndex> #include <QModelIndex>
#include <QPainter> #include <QPainter>
#include <QStyleOptionViewItem> #include <QStyleOptionViewItem>
@ -38,11 +37,6 @@
#include <QProxyStyle> #include <QProxyStyle>
#endif #endif
#include "base/bittorrent/torrenthandle.h"
#include "base/preferences.h"
#include "base/types.h"
#include "base/unicodestrings.h"
#include "base/utils/misc.h"
#include "base/utils/string.h" #include "base/utils/string.h"
#include "transferlistmodel.h" #include "transferlistmodel.h"
@ -53,121 +47,20 @@ TransferListDelegate::TransferListDelegate(QObject *parent)
void TransferListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const void TransferListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{ {
painter->save(); if (index.column() != TransferListModel::TR_PROGRESS) {
QItemDelegate::paint(painter, option, index);
bool isHideState = true; return;
if (Preferences::instance()->getHideZeroComboValues() == 1) { // paused torrents only
const QModelIndex stateIndex = index.sibling(index.row(), TransferListModel::TR_STATUS);
if (stateIndex.data().value<BitTorrent::TorrentState>() != BitTorrent::TorrentState::PausedDownloading)
isHideState = false;
} }
const bool hideValues = Preferences::instance()->getHideZeroValues() && isHideState;
painter->save();
QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option); QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option);
QItemDelegate::drawBackground(painter, opt, index); QItemDelegate::drawBackground(painter, opt, index);
switch (index.column()) {
case TransferListModel::TR_AMOUNT_DOWNLOADED:
case TransferListModel::TR_AMOUNT_UPLOADED:
case TransferListModel::TR_AMOUNT_DOWNLOADED_SESSION:
case TransferListModel::TR_AMOUNT_UPLOADED_SESSION:
case TransferListModel::TR_AMOUNT_LEFT:
case TransferListModel::TR_COMPLETED:
case TransferListModel::TR_SIZE:
case TransferListModel::TR_TOTAL_SIZE: {
qlonglong size = index.data().toLongLong();
if (hideValues && !size)
break;
opt.displayAlignment = (Qt::AlignRight | Qt::AlignVCenter);
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(size));
}
break;
case TransferListModel::TR_ETA: {
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::userFriendlyDuration(index.data().toLongLong(), MAX_ETA));
}
break;
case TransferListModel::TR_SEEDS:
case TransferListModel::TR_PEERS: {
qlonglong value = index.data().toLongLong();
qlonglong total = index.data(Qt::UserRole).toLongLong();
if (hideValues && (!value && !total))
break;
QString display = QString::number(value) + " (" + QString::number(total) + ')';
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
QItemDelegate::drawDisplay(painter, opt, opt.rect, display);
}
break;
case TransferListModel::TR_STATUS: {
const auto state = index.data().value<BitTorrent::TorrentState>();
const QString errorMsg = index.data(Qt::UserRole).toString();
QString display = getStatusString(state);
if (state == BitTorrent::TorrentState::Error)
display += (": " + errorMsg);
QItemDelegate::drawDisplay(painter, opt, opt.rect, display);
}
break;
case TransferListModel::TR_UPSPEED:
case TransferListModel::TR_DLSPEED: {
const int speed = index.data().toInt();
if (hideValues && !speed)
break;
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
QItemDelegate::drawDisplay(painter, opt, opt.rect, Utils::Misc::friendlyUnit(speed, true));
}
break;
case TransferListModel::TR_UPLIMIT:
case TransferListModel::TR_DLLIMIT: {
const qlonglong limit = index.data().toLongLong();
if (hideValues && !limit)
break;
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
QItemDelegate::drawDisplay(painter, opt, opt.rect, limit > 0 ? Utils::Misc::friendlyUnit(limit, true) : QString::fromUtf8(C_INFINITY));
}
break;
case TransferListModel::TR_TIME_ELAPSED: {
const qlonglong elapsedTime = index.data().toLongLong();
const qlonglong seedingTime = index.data(Qt::UserRole).toLongLong();
const QString txt = (seedingTime > 0)
? tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)")
.arg(Utils::Misc::userFriendlyDuration(elapsedTime)
, Utils::Misc::userFriendlyDuration(seedingTime))
: Utils::Misc::userFriendlyDuration(elapsedTime);
QItemDelegate::drawDisplay(painter, opt, opt.rect, txt);
}
break;
case TransferListModel::TR_ADD_DATE:
case TransferListModel::TR_SEED_DATE:
QItemDelegate::drawDisplay(painter, opt, opt.rect, index.data().toDateTime().toLocalTime().toString(Qt::DefaultLocaleShortDate));
break;
case TransferListModel::TR_RATIO_LIMIT:
case TransferListModel::TR_RATIO: {
const qreal ratio = index.data().toDouble();
if (hideValues && (ratio <= 0))
break;
QString str = ((ratio == -1) || (ratio > BitTorrent::TorrentHandle::MAX_RATIO)) ? QString::fromUtf8(C_INFINITY) : Utils::String::fromDouble(ratio, 2);
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
QItemDelegate::drawDisplay(painter, opt, opt.rect, str);
}
break;
case TransferListModel::TR_QUEUE_POSITION: {
const int queuePos = index.data().toInt();
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
if (queuePos > 0)
QItemDelegate::paint(painter, opt, index);
else
QItemDelegate::drawDisplay(painter, opt, opt.rect, "*");
}
break;
case TransferListModel::TR_PROGRESS: {
const qreal progress = index.data().toDouble() * 100;
QStyleOptionProgressBar newopt; QStyleOptionProgressBar newopt;
newopt.rect = opt.rect; newopt.rect = opt.rect;
newopt.text = ((progress == 100) newopt.text = index.data().toString();
? QString("100%") newopt.progress = static_cast<int>(index.data(TransferListModel::UnderlyingDataRole).toReal());
: (Utils::String::fromDouble(progress, 1) + '%'));
newopt.progress = static_cast<int>(progress);
newopt.maximum = 100; newopt.maximum = 100;
newopt.minimum = 0; newopt.minimum = 0;
newopt.state |= QStyle::State_Enabled; newopt.state |= QStyle::State_Enabled;
@ -180,41 +73,6 @@ void TransferListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
#else #else
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter); QApplication::style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter);
#endif #endif
}
break;
case TransferListModel::TR_LAST_ACTIVITY: {
qlonglong elapsed = index.data().toLongLong();
if (hideValues && ((elapsed < 0) || (elapsed >= MAX_ETA)))
break;
// Show '< 1m ago' when elapsed time is 0
if (elapsed == 0)
elapsed = 1;
QString elapsedString = (elapsed >= 0)
? tr("%1 ago", "e.g.: 1h 20m ago").arg(Utils::Misc::userFriendlyDuration(elapsed))
: Utils::Misc::userFriendlyDuration(elapsed);
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
QItemDelegate::drawDisplay(painter, opt, option.rect, elapsedString);
}
break;
case TransferListModel::TR_AVAILABILITY: {
const qreal availability = index.data().toReal();
if (hideValues && (availability <= 0))
break;
const QString availabilityStr = Utils::String::fromDouble(availability, 3);
opt.displayAlignment = (Qt::AlignRight | Qt::AlignVCenter);
QItemDelegate::drawDisplay(painter, opt, option.rect, availabilityStr);
}
break;
default:
QItemDelegate::paint(painter, option, index);
}
painter->restore(); painter->restore();
} }
@ -241,44 +99,3 @@ QSize TransferListDelegate::sizeHint(const QStyleOptionViewItem &option, const Q
size.setHeight(std::max(nameColHeight, size.height())); size.setHeight(std::max(nameColHeight, size.height()));
return size; return size;
} }
QString TransferListDelegate::getStatusString(const BitTorrent::TorrentState state) const
{
switch (state) {
case BitTorrent::TorrentState::Downloading:
return tr("Downloading");
case BitTorrent::TorrentState::StalledDownloading:
return tr("Stalled", "Torrent is waiting for download to begin");
case BitTorrent::TorrentState::DownloadingMetadata:
return tr("Downloading metadata", "Used when loading a magnet link");
case BitTorrent::TorrentState::ForcedDownloading:
return tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.");
case BitTorrent::TorrentState::Allocating:
return tr("Allocating", "qBittorrent is allocating the files on disk");
case BitTorrent::TorrentState::Uploading:
case BitTorrent::TorrentState::StalledUploading:
return tr("Seeding", "Torrent is complete and in upload-only mode");
case BitTorrent::TorrentState::ForcedUploading:
return tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.");
case BitTorrent::TorrentState::QueuedDownloading:
case BitTorrent::TorrentState::QueuedUploading:
return tr("Queued", "Torrent is queued");
case BitTorrent::TorrentState::CheckingDownloading:
case BitTorrent::TorrentState::CheckingUploading:
return tr("Checking", "Torrent local data is being checked");
case BitTorrent::TorrentState::CheckingResumeData:
return tr("Checking resume data", "Used when loading the torrents from disk after qbt is launched. It checks the correctness of the .fastresume file. Normally it is completed in a fraction of a second, unless loading many many torrents.");
case BitTorrent::TorrentState::PausedDownloading:
return tr("Paused");
case BitTorrent::TorrentState::PausedUploading:
return tr("Completed");
case BitTorrent::TorrentState::Moving:
return tr("Moving", "Torrent local data are being moved/relocated");
case BitTorrent::TorrentState::MissingFiles:
return tr("Missing Files");
case BitTorrent::TorrentState::Error:
return tr("Errored", "Torrent status, the torrent has an error");
default:
return {};
}
}

8
src/gui/transferlistdelegate.h

@ -35,11 +35,6 @@ class QModelIndex;
class QPainter; class QPainter;
class QStyleOptionViewItem; class QStyleOptionViewItem;
namespace BitTorrent
{
enum class TorrentState;
}
class TransferListDelegate : public QItemDelegate class TransferListDelegate : public QItemDelegate
{ {
Q_OBJECT Q_OBJECT
@ -49,9 +44,6 @@ public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override; QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private:
QString getStatusString(const BitTorrent::TorrentState state) const;
}; };
#endif // TRANSFERLISTDELEGATE_H #endif // TRANSFERLISTDELEGATE_H

265
src/gui/transferlistmodel.cpp

@ -38,7 +38,11 @@
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h" #include "base/bittorrent/torrenthandle.h"
#include "base/global.h" #include "base/global.h"
#include "base/preferences.h"
#include "base/unicodestrings.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
static QIcon getIconByState(BitTorrent::TorrentState state); static QIcon getIconByState(BitTorrent::TorrentState state);
static QColor getColorByState(BitTorrent::TorrentState state); static QColor getColorByState(BitTorrent::TorrentState state);
@ -58,7 +62,27 @@ static bool isDarkTheme();
// TransferListModel // TransferListModel
TransferListModel::TransferListModel(QObject *parent) TransferListModel::TransferListModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel {parent}
, m_statusStrings {
{BitTorrent::TorrentState::Downloading, tr("Downloading")},
{BitTorrent::TorrentState::StalledDownloading, tr("Stalled", "Torrent is waiting for download to begin")},
{BitTorrent::TorrentState::DownloadingMetadata, tr("Downloading metadata", "Used when loading a magnet link")},
{BitTorrent::TorrentState::ForcedDownloading, tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
{BitTorrent::TorrentState::Allocating, tr("Allocating", "qBittorrent is allocating the files on disk")},
{BitTorrent::TorrentState::Uploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
{BitTorrent::TorrentState::StalledUploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
{BitTorrent::TorrentState::ForcedUploading, tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
{BitTorrent::TorrentState::QueuedDownloading, tr("Queued", "Torrent is queued")},
{BitTorrent::TorrentState::QueuedUploading, tr("Queued", "Torrent is queued")},
{BitTorrent::TorrentState::CheckingDownloading, tr("Checking", "Torrent local data is being checked")},
{BitTorrent::TorrentState::CheckingUploading, tr("Checking", "Torrent local data is being checked")},
{BitTorrent::TorrentState::CheckingResumeData, tr("Checking resume data", "Used when loading the torrents from disk after qbt is launched. It checks the correctness of the .fastresume file. Normally it is completed in a fraction of a second, unless loading many many torrents.")},
{BitTorrent::TorrentState::PausedDownloading, tr("Paused")},
{BitTorrent::TorrentState::PausedUploading, tr("Completed")},
{BitTorrent::TorrentState::Moving, tr("Moving", "Torrent local data are being moved/relocated")},
{BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
{BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}
}
, m_stateForegroundColors { , m_stateForegroundColors {
{BitTorrent::TorrentState::Unknown, getColorByState(BitTorrent::TorrentState::Unknown)}, {BitTorrent::TorrentState::Unknown, getColorByState(BitTorrent::TorrentState::Unknown)},
{BitTorrent::TorrentState::ForcedDownloading, getColorByState(BitTorrent::TorrentState::ForcedDownloading)}, {BitTorrent::TorrentState::ForcedDownloading, getColorByState(BitTorrent::TorrentState::ForcedDownloading)},
@ -179,23 +203,172 @@ QVariant TransferListModel::headerData(int section, Qt::Orientation orientation,
return {}; return {};
} }
QVariant TransferListModel::data(const QModelIndex &index, const int role) const QString TransferListModel::displayValue(const BitTorrent::TorrentHandle *torrent, const int column) const
{ {
if (!index.isValid()) return {}; const bool isHideState = (Preferences::instance()->getHideZeroComboValues() == 1)
&& (torrent->state() == BitTorrent::TorrentState::PausedDownloading); // paused torrents only
const bool hideValues = Preferences::instance()->getHideZeroValues() && isHideState;
const BitTorrent::TorrentHandle *torrent = m_torrentList.value(index.row()); const auto availabilityString = [hideValues](const qreal value) -> QString
if (!torrent) return {}; {
return ((value <= 0) && hideValues)
? QString {} : Utils::String::fromDouble(value, 3);
};
if ((role == Qt::DecorationRole) && (index.column() == TR_NAME)) const auto unitString = [hideValues](const qint64 value, const bool isSpeedUnit = false) -> QString
return getIconByState(torrent->state()); {
return ((value == 0) && hideValues)
? QString {} : Utils::Misc::friendlyUnit(value, isSpeedUnit);
};
if (role == Qt::ForegroundRole) const auto limitString = [hideValues](const qint64 value) -> QString
return stateForeground(torrent->state()); {
if ((value == 0) && hideValues)
return {};
return (value > 0)
? Utils::Misc::friendlyUnit(value, true)
: QString::fromUtf8(C_INFINITY);
};
if ((role != Qt::DisplayRole) && (role != Qt::UserRole)) const auto amountString = [hideValues](const qint64 value, const qint64 total) -> QString
{
return ((value == 0) && (total == 0) && hideValues)
? QString {}
: QString::number(value) + " (" + QString::number(total) + ')';
};
const auto ratioString = [hideValues](const qreal value) -> QString
{
if ((value <= 0) && hideValues)
return {}; return {};
switch (index.column()) { return ((static_cast<int>(value) == -1) || (value > BitTorrent::TorrentHandle::MAX_RATIO))
? QString::fromUtf8(C_INFINITY) : Utils::String::fromDouble(value, 2);
};
const auto queuePositionString = [](const qint64 value) -> QString
{
return (value > 0) ? QString::number(value) : "*";
};
const auto lastActivityString = [hideValues](qint64 value) -> QString
{
if (hideValues && ((value < 0) || (value >= MAX_ETA)))
return QString {};
// Show '< 1m ago' when elapsed time is 0
if (value == 0)
value = 1;
return (value >= 0)
? tr("%1 ago", "e.g.: 1h 20m ago").arg(Utils::Misc::userFriendlyDuration(value))
: Utils::Misc::userFriendlyDuration(value);
};
const auto timeElapsedString = [](const qint64 elapsedTime, const qint64 seedingTime) -> QString
{
if (seedingTime <= 0)
return Utils::Misc::userFriendlyDuration(elapsedTime);
return tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)")
.arg(Utils::Misc::userFriendlyDuration(elapsedTime)
, Utils::Misc::userFriendlyDuration(seedingTime));
};
const auto tagsString = [](const QSet<QString> &tags) -> QString
{
QStringList tagsList = tags.values();
tagsList.sort();
return tagsList.join(", ");
};
const auto progressString = [](qreal progress) -> QString
{
progress *= 100;
return (static_cast<int>(progress) == 100)
? QString {QLatin1String {"100%"}}
: Utils::String::fromDouble(progress, 1) + '%';
};
const auto statusString = [this](const BitTorrent::TorrentState state, const QString &errorMessage) -> QString
{
return (state == BitTorrent::TorrentState::Error)
? m_statusStrings[state] + ": " + errorMessage
: m_statusStrings[state];
};
switch (column) {
case TR_NAME:
return torrent->name();
case TR_QUEUE_POSITION:
return queuePositionString(torrent->queuePosition());
case TR_SIZE:
return unitString(torrent->wantedSize());
case TR_PROGRESS:
return progressString(torrent->progress());
case TR_STATUS:
return statusString(torrent->state(), torrent->error());
case TR_SEEDS:
return amountString(torrent->seedsCount(), torrent->totalSeedsCount());
case TR_PEERS:
return amountString(torrent->leechsCount(), torrent->totalLeechersCount());
case TR_DLSPEED:
return unitString(torrent->downloadPayloadRate(), true);
case TR_UPSPEED:
return unitString(torrent->uploadPayloadRate(), true);
case TR_ETA:
return Utils::Misc::userFriendlyDuration(torrent->eta(), MAX_ETA);
case TR_RATIO:
return ratioString(torrent->realRatio());
case TR_RATIO_LIMIT:
return ratioString(torrent->maxRatio());
case TR_CATEGORY:
return torrent->category();
case TR_TAGS:
return tagsString(torrent->tags());
case TR_ADD_DATE:
return torrent->addedTime().toLocalTime().toString(Qt::DefaultLocaleShortDate);
case TR_SEED_DATE:
return torrent->completedTime().toLocalTime().toString(Qt::DefaultLocaleShortDate);
case TR_TRACKER:
return torrent->currentTracker();
case TR_DLLIMIT:
return limitString(torrent->downloadLimit());
case TR_UPLIMIT:
return limitString(torrent->uploadLimit());
case TR_AMOUNT_DOWNLOADED:
return unitString(torrent->totalDownload());
case TR_AMOUNT_UPLOADED:
return unitString(torrent->totalUpload());
case TR_AMOUNT_DOWNLOADED_SESSION:
return unitString(torrent->totalPayloadDownload());
case TR_AMOUNT_UPLOADED_SESSION:
return unitString(torrent->totalPayloadUpload());
case TR_AMOUNT_LEFT:
return unitString(torrent->incompletedSize());
case TR_TIME_ELAPSED:
return timeElapsedString(torrent->activeTime(), torrent->seedingTime());
case TR_SAVE_PATH:
return Utils::Fs::toNativePath(torrent->savePath());
case TR_COMPLETED:
return unitString(torrent->completedSize());
case TR_SEEN_COMPLETE_DATE:
return torrent->lastSeenComplete().toString();
case TR_LAST_ACTIVITY:
return lastActivityString((torrent->isPaused() || torrent->isChecking()) ? -1 : torrent->timeSinceActivity());
case TR_AVAILABILITY:
return availabilityString(torrent->distributedCopies());
case TR_TOTAL_SIZE:
return unitString(torrent->totalSize());
}
return {};
}
QVariant TransferListModel::internalValue(const BitTorrent::TorrentHandle *torrent, const int column, const bool alt) const
{
switch (column) {
case TR_NAME: case TR_NAME:
return torrent->name(); return torrent->name();
case TR_QUEUE_POSITION: case TR_QUEUE_POSITION:
@ -203,13 +376,13 @@ QVariant TransferListModel::data(const QModelIndex &index, const int role) const
case TR_SIZE: case TR_SIZE:
return torrent->wantedSize(); return torrent->wantedSize();
case TR_PROGRESS: case TR_PROGRESS:
return torrent->progress(); return torrent->progress() * 100;
case TR_STATUS: case TR_STATUS:
return (role == Qt::DisplayRole) ? QVariant::fromValue(torrent->state()) : torrent->error(); return QVariant::fromValue(torrent->state());
case TR_SEEDS: case TR_SEEDS:
return (role == Qt::DisplayRole) ? torrent->seedsCount() : torrent->totalSeedsCount(); return !alt ? torrent->seedsCount() : torrent->totalSeedsCount();
case TR_PEERS: case TR_PEERS:
return (role == Qt::DisplayRole) ? torrent->leechsCount() : torrent->totalLeechersCount(); return !alt ? torrent->leechsCount() : torrent->totalLeechersCount();
case TR_DLSPEED: case TR_DLSPEED:
return torrent->downloadPayloadRate(); return torrent->downloadPayloadRate();
case TR_UPSPEED: case TR_UPSPEED:
@ -220,11 +393,8 @@ QVariant TransferListModel::data(const QModelIndex &index, const int role) const
return torrent->realRatio(); return torrent->realRatio();
case TR_CATEGORY: case TR_CATEGORY:
return torrent->category(); return torrent->category();
case TR_TAGS: { case TR_TAGS:
QStringList tagsList = torrent->tags().values(); return QStringList {torrent->tags().values()};
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:
@ -246,7 +416,7 @@ QVariant TransferListModel::data(const QModelIndex &index, const int role) const
case TR_AMOUNT_LEFT: case TR_AMOUNT_LEFT:
return torrent->incompletedSize(); return torrent->incompletedSize();
case TR_TIME_ELAPSED: case TR_TIME_ELAPSED:
return (role == Qt::DisplayRole) ? torrent->activeTime() : torrent->seedingTime(); return !alt ? torrent->activeTime() : torrent->seedingTime();
case TR_SAVE_PATH: case TR_SAVE_PATH:
return Utils::Fs::toNativePath(torrent->savePath()); return Utils::Fs::toNativePath(torrent->savePath());
case TR_COMPLETED: case TR_COMPLETED:
@ -256,9 +426,7 @@ QVariant TransferListModel::data(const QModelIndex &index, const int role) const
case TR_SEEN_COMPLETE_DATE: case TR_SEEN_COMPLETE_DATE:
return torrent->lastSeenComplete(); return torrent->lastSeenComplete();
case TR_LAST_ACTIVITY: case TR_LAST_ACTIVITY:
if (torrent->isPaused() || torrent->isChecking()) return (torrent->isPaused() || torrent->isChecking()) ? -1 : torrent->timeSinceActivity();
return -1;
return torrent->timeSinceActivity();
case TR_AVAILABILITY: case TR_AVAILABILITY:
return torrent->distributedCopies(); return torrent->distributedCopies();
case TR_TOTAL_SIZE: case TR_TOTAL_SIZE:
@ -268,6 +436,55 @@ QVariant TransferListModel::data(const QModelIndex &index, const int role) const
return {}; return {};
} }
QVariant TransferListModel::data(const QModelIndex &index, const int role) const
{
if (!index.isValid()) return {};
const BitTorrent::TorrentHandle *torrent = m_torrentList.value(index.row());
if (!torrent) return {};
switch (role) {
case Qt::ForegroundRole:
return stateForeground(torrent->state());
case Qt::DisplayRole:
return displayValue(torrent, index.column());
case UnderlyingDataRole:
return internalValue(torrent, index.column());
case AdditionalUnderlyingDataRole:
return internalValue(torrent, index.column(), true);
case Qt::DecorationRole:
if (index.column() == TR_NAME)
return getIconByState(torrent->state());
break;
case Qt::TextAlignmentRole:
switch (index.column()) {
case TR_AMOUNT_DOWNLOADED:
case TR_AMOUNT_UPLOADED:
case TR_AMOUNT_DOWNLOADED_SESSION:
case TR_AMOUNT_UPLOADED_SESSION:
case TR_AMOUNT_LEFT:
case TR_COMPLETED:
case TR_SIZE:
case TR_TOTAL_SIZE:
case TR_ETA:
case TR_SEEDS:
case TR_PEERS:
case TR_UPSPEED:
case TR_DLSPEED:
case TR_UPLIMIT:
case TR_DLLIMIT:
case TR_RATIO_LIMIT:
case TR_RATIO:
case TR_QUEUE_POSITION:
case TR_LAST_ACTIVITY:
case TR_AVAILABILITY:
return QVariant {Qt::AlignRight | Qt::AlignVCenter};
}
}
return {};
}
bool TransferListModel::setData(const QModelIndex &index, const QVariant &value, int role) bool TransferListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{ {
if (!index.isValid() || (role != Qt::DisplayRole)) return false; if (!index.isValid() || (role != Qt::DisplayRole)) return false;

15
src/gui/transferlistmodel.h

@ -34,11 +34,11 @@
#include <QColor> #include <QColor>
#include <QList> #include <QList>
#include "base/bittorrent/torrenthandle.h"
namespace BitTorrent namespace BitTorrent
{ {
class InfoHash; class InfoHash;
class TorrentHandle;
enum class TorrentState;
} }
class TransferListModel : public QAbstractListModel class TransferListModel : public QAbstractListModel
@ -84,6 +84,12 @@ public:
NB_COLUMNS NB_COLUMNS
}; };
enum DataRole
{
UnderlyingDataRole = Qt::UserRole,
AdditionalUnderlyingDataRole
};
explicit TransferListModel(QObject *parent = nullptr); explicit TransferListModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = {}) const override; int rowCount(const QModelIndex &parent = {}) const override;
@ -105,9 +111,12 @@ private slots:
void handleTorrentsUpdated(const QVector<BitTorrent::TorrentHandle *> &torrents); void handleTorrentsUpdated(const QVector<BitTorrent::TorrentHandle *> &torrents);
private: private:
QString displayValue(const BitTorrent::TorrentHandle *torrent, int column) const;
QVariant internalValue(const BitTorrent::TorrentHandle *torrent, int column, bool alt = false) const;
QList<BitTorrent::TorrentHandle *> m_torrentList; // maps row number to torrent handle QList<BitTorrent::TorrentHandle *> m_torrentList; // maps row number to torrent handle
QHash<BitTorrent::TorrentHandle *, int> m_torrentMap; // maps torrent handle to row number QHash<BitTorrent::TorrentHandle *, int> m_torrentMap; // maps torrent handle to row number
const QHash<BitTorrent::TorrentState, QString> m_statusStrings;
// row text colors // row text colors
QHash<BitTorrent::TorrentState, QColor> m_stateForegroundColors; QHash<BitTorrent::TorrentState, QColor> m_stateForegroundColors;
}; };

188
src/gui/transferlistsortmodel.cpp

@ -37,8 +37,9 @@
#include "transferlistmodel.h" #include "transferlistmodel.h"
TransferListSortModel::TransferListSortModel(QObject *parent) TransferListSortModel::TransferListSortModel(QObject *parent)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel {parent}
{ {
QMetaType::registerComparators<BitTorrent::TorrentState>();
} }
void TransferListSortModel::setStatusFilter(TorrentFilter::Type filter) void TransferListSortModel::setStatusFilter(TorrentFilter::Type filter)
@ -85,56 +86,100 @@ void TransferListSortModel::disableTrackerFilter()
bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{ {
switch (sortColumn()) { return lessThan_impl(left, right);
}
bool TransferListSortModel::lessThan_impl(const QModelIndex &left, const QModelIndex &right) const
{
Q_ASSERT(left.column() == right.column());
const auto invokeLessThanForColumn = [this, &left, &right](const int column) -> bool
{
return lessThan_impl(left.sibling(left.row(), column), right.sibling(left.row(), column));
};
const int sortColumn = left.column();
const QVariant leftValue = left.data(TransferListModel::UnderlyingDataRole);
const QVariant rightValue = right.data(TransferListModel::UnderlyingDataRole);
switch (sortColumn) {
case TransferListModel::TR_CATEGORY: case TransferListModel::TR_CATEGORY:
case TransferListModel::TR_TAGS: case TransferListModel::TR_TAGS:
case TransferListModel::TR_NAME: { case TransferListModel::TR_NAME:
const QVariant vL = left.data(); if (!leftValue.isValid() || !rightValue.isValid() || (leftValue == rightValue))
const QVariant vR = right.data(); return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
if (!vL.isValid() || !vR.isValid() || (vL == vR)) return (Utils::String::naturalCompare(leftValue.toString(), rightValue.toString(), Qt::CaseInsensitive) < 0);
return lowerPositionThan(left, right);
const int result = Utils::String::naturalCompare(vL.toString(), vR.toString(), Qt::CaseInsensitive);
return (result < 0);
}
case TransferListModel::TR_STATUS: { case TransferListModel::TR_STATUS:
// QSortFilterProxyModel::lessThan() uses the < operator only for specific QVariant types // QSortFilterProxyModel::lessThan() uses the < operator only for specific QVariant types
// so our custom type is outside that list. // so our custom type is outside that list.
// In this case QSortFilterProxyModel::lessThan() converts other types to QString and // In this case QSortFilterProxyModel::lessThan() converts other types to QString and
// sorts them. // sorts them.
// Thus we can't use the code in the default label. // Thus we can't use the code in the default label.
const auto leftValue = left.data().value<BitTorrent::TorrentState>();
const auto rightValue = right.data().value<BitTorrent::TorrentState>();
if (leftValue != rightValue) if (leftValue != rightValue)
return leftValue < rightValue; return leftValue < rightValue;
return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
return lowerPositionThan(left, right);
}
case TransferListModel::TR_ADD_DATE: case TransferListModel::TR_ADD_DATE:
case TransferListModel::TR_SEED_DATE: case TransferListModel::TR_SEED_DATE:
case TransferListModel::TR_SEEN_COMPLETE_DATE: case TransferListModel::TR_SEEN_COMPLETE_DATE: {
return dateLessThan(sortColumn(), left, right, true); const QDateTime dateL = leftValue.toDateTime();
const QDateTime dateR = rightValue.toDateTime();
case TransferListModel::TR_QUEUE_POSITION: if (dateL.isValid() && dateR.isValid()) {
return lowerPositionThan(left, right); if (dateL != dateR)
return dateL < dateR;
}
else if (dateL.isValid()) {
return true;
}
else if (dateR.isValid()) {
return false;
}
}
break;
case TransferListModel::TR_QUEUE_POSITION: {
// QVariant has comparators for all basic types
if ((leftValue > 0) || (rightValue > 0)) {
if ((leftValue > 0) && (rightValue > 0))
return leftValue < rightValue;
return leftValue != 0;
}
// Sort according to TR_SEED_DATE
const QDateTime dateL = left.sibling(left.row(), TransferListModel::TR_SEED_DATE)
.data(TransferListModel::UnderlyingDataRole).toDateTime();
const QDateTime dateR = right.sibling(right.row(), TransferListModel::TR_SEED_DATE)
.data(TransferListModel::UnderlyingDataRole).toDateTime();
if (dateL.isValid() && dateR.isValid()) {
if (dateL != dateR)
return dateL < dateR;
}
else if (dateL.isValid()) {
return false;
}
else if (dateR.isValid()) {
return true;
}
}
break;
case TransferListModel::TR_SEEDS: case TransferListModel::TR_SEEDS:
case TransferListModel::TR_PEERS: { case TransferListModel::TR_PEERS: {
const int leftActive = left.data().toInt(); // QVariant has comparators for all basic types
const int leftTotal = left.data(Qt::UserRole).toInt();
const int rightActive = right.data().toInt();
const int rightTotal = right.data(Qt::UserRole).toInt();
// Active peers/seeds take precedence over total peers/seeds. // Active peers/seeds take precedence over total peers/seeds.
if (leftActive != rightActive) if (leftValue != rightValue)
return (leftActive < rightActive); return (leftValue < rightValue);
if (leftTotal != rightTotal) const QVariant leftValueTotal = left.data(TransferListModel::AdditionalUnderlyingDataRole);
return (leftTotal < rightTotal); const QVariant rightValueTotal = right.data(TransferListModel::AdditionalUnderlyingDataRole);
if (leftValueTotal != rightValueTotal)
return (leftValueTotal < rightValueTotal);
return lowerPositionThan(left, right); return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
} }
case TransferListModel::TR_ETA: { case TransferListModel::TR_ETA: {
@ -152,8 +197,10 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex
if (isActiveL != isActiveR) if (isActiveL != isActiveR)
return isActiveL; return isActiveL;
const int queuePosL = left.sibling(left.row(), TransferListModel::TR_QUEUE_POSITION).data().toInt(); const int queuePosL = left.sibling(left.row(), TransferListModel::TR_QUEUE_POSITION)
const int queuePosR = right.sibling(right.row(), TransferListModel::TR_QUEUE_POSITION).data().toInt(); .data(TransferListModel::UnderlyingDataRole).toInt();
const int queuePosR = right.sibling(right.row(), TransferListModel::TR_QUEUE_POSITION)
.data(TransferListModel::UnderlyingDataRole).toInt();
const bool isSeedingL = (queuePosL < 0); const bool isSeedingL = (queuePosL < 0);
const bool isSeedingR = (queuePosR < 0); const bool isSeedingR = (queuePosR < 0);
if (isSeedingL != isSeedingR) { if (isSeedingL != isSeedingR) {
@ -164,85 +211,36 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex
return isAscendingOrder; return isAscendingOrder;
} }
const qlonglong etaL = left.data().toLongLong(); const qlonglong etaL = leftValue.toLongLong();
const qlonglong etaR = right.data().toLongLong(); const qlonglong etaR = rightValue.toLongLong();
const bool isInvalidL = ((etaL < 0) || (etaL >= MAX_ETA)); const bool isInvalidL = ((etaL < 0) || (etaL >= MAX_ETA));
const bool isInvalidR = ((etaR < 0) || (etaR >= MAX_ETA)); const bool isInvalidR = ((etaR < 0) || (etaR >= MAX_ETA));
if (isInvalidL && isInvalidR) { if (isInvalidL && isInvalidR) {
if (isSeedingL) // Both seeding if (isSeedingL) // Both seeding
return dateLessThan(TransferListModel::TR_SEED_DATE, left, right, true); return invokeLessThanForColumn(TransferListModel::TR_SEED_DATE);
return (queuePosL < queuePosR); return (queuePosL < queuePosR);
} }
if (!isInvalidL && !isInvalidR) {
if (!isInvalidL && !isInvalidR)
return (etaL < etaR); return (etaL < etaR);
}
return !isInvalidL; return !isInvalidL;
} }
case TransferListModel::TR_LAST_ACTIVITY: { case TransferListModel::TR_LAST_ACTIVITY:
const int vL = left.data().toInt(); case TransferListModel::TR_RATIO_LIMIT:
const int vR = right.data().toInt(); // QVariant has comparators for all basic types
if (leftValue < 0) return false;
if (vL < 0) return false; if (rightValue < 0) return true;
if (vR < 0) return true;
return (vL < vR);
}
case TransferListModel::TR_RATIO_LIMIT: { return (leftValue < rightValue);
const qreal vL = left.data().toReal();
const qreal vR = right.data().toReal();
if (vL < 0) return false;
if (vR < 0) return true;
return (vL < vR);
}
default: default:
if (left.data() != right.data()) if (leftValue != rightValue)
return QSortFilterProxyModel::lessThan(left, right); return QSortFilterProxyModel::lessThan(left, right);
return lowerPositionThan(left, right); return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
}
}
bool TransferListSortModel::lowerPositionThan(const QModelIndex &left, const QModelIndex &right) const
{
// Sort according to TR_QUEUE_POSITION
const int queueL = left.sibling(left.row(), TransferListModel::TR_QUEUE_POSITION).data().toInt();
const int queueR = right.sibling(right.row(), TransferListModel::TR_QUEUE_POSITION).data().toInt();
if ((queueL > 0) || (queueR > 0)) {
if ((queueL > 0) && (queueR > 0))
return queueL < queueR;
return queueL != 0;
}
// Sort according to TR_SEED_DATE
return dateLessThan(TransferListModel::TR_SEED_DATE, left, right, false);
}
// Every time we compare QDateTimes we need a fallback comparison in case both
// values are empty. This is a workaround for unstable sort in QSortFilterProxyModel
// (detailed discussion in #2526 and #2158).
bool TransferListSortModel::dateLessThan(const int dateColumn, const QModelIndex &left, const QModelIndex &right, bool sortInvalidInBottom) const
{
const QDateTime dateL = left.sibling(left.row(), dateColumn).data().toDateTime();
const QDateTime dateR = right.sibling(right.row(), dateColumn).data().toDateTime();
if (dateL.isValid() && dateR.isValid()) {
if (dateL != dateR)
return dateL < dateR;
}
else if (dateL.isValid()) {
return sortInvalidInBottom;
}
else if (dateR.isValid()) {
return !sortInvalidInBottom;
} }
// Finally, sort by hash // Finally, sort by hash

12
src/gui/transferlistsortmodel.h

@ -26,8 +26,7 @@
* exception statement from your version. * exception statement from your version.
*/ */
#ifndef TRANSFERLISTSORTMODEL_H #pragma once
#define TRANSFERLISTSORTMODEL_H
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include "base/torrentfilter.h" #include "base/torrentfilter.h"
@ -37,9 +36,10 @@ class QStringList;
class TransferListSortModel : public QSortFilterProxyModel class TransferListSortModel : public QSortFilterProxyModel
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(TransferListSortModel)
public: public:
TransferListSortModel(QObject *parent = nullptr); explicit TransferListSortModel(QObject *parent = nullptr);
void setStatusFilter(TorrentFilter::Type filter); void setStatusFilter(TorrentFilter::Type filter);
void setCategoryFilter(const QString &category); void setCategoryFilter(const QString &category);
@ -51,13 +51,9 @@ public:
private: private:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
bool lowerPositionThan(const QModelIndex &left, const QModelIndex &right) const;
bool dateLessThan(int dateColumn, const QModelIndex &left, const QModelIndex &right, bool sortInvalidInBottom) const;
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool matchFilter(int sourceRow, const QModelIndex &sourceParent) const; bool matchFilter(int sourceRow, const QModelIndex &sourceParent) const;
bool lessThan_impl(const QModelIndex &left, const QModelIndex &right) const;
private:
TorrentFilter m_filter; TorrentFilter m_filter;
}; };
#endif // TRANSFERLISTSORTMODEL_H

Loading…
Cancel
Save