Browse Source

Merge pull request #11708 from glassez/transfer-list

Improve Transfer list architecture
adaptive-webui-19844
Vladimir Golovnev 5 years ago committed by GitHub
parent
commit
37d7323ac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 215
      src/gui/transferlistdelegate.cpp
  2. 21
      src/gui/transferlistdelegate.h
  3. 267
      src/gui/transferlistmodel.cpp
  4. 15
      src/gui/transferlistmodel.h
  5. 188
      src/gui/transferlistsortmodel.cpp
  6. 12
      src/gui/transferlistsortmodel.h

215
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,183 +37,40 @@
#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"
TransferListDelegate::TransferListDelegate(QObject *parent) TransferListDelegate::TransferListDelegate(QObject *parent)
: QItemDelegate(parent) : QStyledItemDelegate {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) {
QStyledItemDelegate::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;
QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option);
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 = option.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 = option.state;
newopt.textVisible = true; newopt.textVisible = true;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
// XXX: To avoid having the progress text on the right of the bar // XXX: To avoid having the progress text on the right of the bar
QProxyStyle st("fusion"); QProxyStyle fusionStyle {"fusion"};
st.drawControl(QStyle::CE_ProgressBar, &newopt, painter); QStyle *style = &fusionStyle;
#else #else
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter); QStyle *style = option.widget ? option.widget->style() : QApplication::style();
#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->save();
style->drawControl(QStyle::CE_ProgressBar, &newopt, painter, option.widget);
painter->restore(); painter->restore();
} }
@ -234,51 +90,10 @@ QSize TransferListDelegate::sizeHint(const QStyleOptionViewItem &option, const Q
static int nameColHeight = -1; static int nameColHeight = -1;
if (nameColHeight == -1) { if (nameColHeight == -1) {
const QModelIndex nameColumn = index.sibling(index.row(), TransferListModel::TR_NAME); const QModelIndex nameColumn = index.sibling(index.row(), TransferListModel::TR_NAME);
nameColHeight = QItemDelegate::sizeHint(option, nameColumn).height(); nameColHeight = QStyledItemDelegate::sizeHint(option, nameColumn).height();
} }
QSize size = QItemDelegate::sizeHint(option, index); QSize size = QStyledItemDelegate::sizeHint(option, index);
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 {};
}
}

21
src/gui/transferlistdelegate.h

@ -26,32 +26,23 @@
* exception statement from your version. * exception statement from your version.
*/ */
#ifndef TRANSFERLISTDELEGATE_H #pragma once
#define TRANSFERLISTDELEGATE_H
#include <QItemDelegate> #include <QStyledItemDelegate>
class QModelIndex; class QModelIndex;
class QPainter; class QPainter;
class QStyleOptionViewItem; class QStyleOptionViewItem;
namespace BitTorrent class TransferListDelegate : public QStyledItemDelegate
{
enum class TorrentState;
}
class TransferListDelegate : public QItemDelegate
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(TransferListDelegate)
public: public:
TransferListDelegate(QObject *parent); explicit TransferListDelegate(QObject *parent);
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

267
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 BitTorrent::TorrentHandle *torrent = m_torrentList.value(index.row()); const bool hideValues = Preferences::instance()->getHideZeroValues() && isHideState;
if (!torrent) return {};
const auto availabilityString = [hideValues](const qreal value) -> QString
{
return ((value <= 0) && hideValues)
? QString {} : Utils::String::fromDouble(value, 3);
};
const auto unitString = [hideValues](const qint64 value, const bool isSpeedUnit = false) -> QString
{
return ((value == 0) && hideValues)
? QString {} : Utils::Misc::friendlyUnit(value, isSpeedUnit);
};
const auto limitString = [hideValues](const qint64 value) -> QString
{
if ((value == 0) && hideValues)
return {};
if ((role == Qt::DecorationRole) && (index.column() == TR_NAME)) return (value > 0)
return getIconByState(torrent->state()); ? Utils::Misc::friendlyUnit(value, true)
: QString::fromUtf8(C_INFINITY);
};
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 {};
if (role == Qt::ForegroundRole) return ((static_cast<int>(value) == -1) || (value > BitTorrent::TorrentHandle::MAX_RATIO))
return stateForeground(torrent->state()); ? 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());
}
if ((role != Qt::DisplayRole) && (role != Qt::UserRole))
return {}; return {};
}
switch (index.column()) { 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

@ -38,8 +38,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)
@ -86,56 +87,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: {
@ -153,8 +198,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) {
@ -165,85 +212,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: {
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); return (leftValue < rightValue);
}
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