mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-24 05:25:37 +00:00
Merge pull request #11708 from glassez/transfer-list
Improve Transfer list architecture
This commit is contained in:
commit
37d7323ac0
@ -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);
|
QStyleOptionProgressBar newopt;
|
||||||
QItemDelegate::drawBackground(painter, opt, index);
|
newopt.rect = option.rect;
|
||||||
|
newopt.text = index.data().toString();
|
||||||
switch (index.column()) {
|
newopt.progress = static_cast<int>(index.data(TransferListModel::UnderlyingDataRole).toReal());
|
||||||
case TransferListModel::TR_AMOUNT_DOWNLOADED:
|
newopt.maximum = 100;
|
||||||
case TransferListModel::TR_AMOUNT_UPLOADED:
|
newopt.minimum = 0;
|
||||||
case TransferListModel::TR_AMOUNT_DOWNLOADED_SESSION:
|
newopt.state = option.state;
|
||||||
case TransferListModel::TR_AMOUNT_UPLOADED_SESSION:
|
newopt.textVisible = true;
|
||||||
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;
|
|
||||||
newopt.rect = opt.rect;
|
|
||||||
newopt.text = ((progress == 100)
|
|
||||||
? QString("100%")
|
|
||||||
: (Utils::String::fromDouble(progress, 1) + '%'));
|
|
||||||
newopt.progress = static_cast<int>(progress);
|
|
||||||
newopt.maximum = 100;
|
|
||||||
newopt.minimum = 0;
|
|
||||||
newopt.state |= QStyle::State_Enabled;
|
|
||||||
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 {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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 {};
|
||||||
|
|
||||||
if ((role != Qt::DisplayRole) && (role != Qt::UserRole))
|
return (value > 0)
|
||||||
return {};
|
? Utils::Misc::friendlyUnit(value, true)
|
||||||
|
: QString::fromUtf8(C_INFINITY);
|
||||||
|
};
|
||||||
|
|
||||||
switch (index.column()) {
|
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 ((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;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
case TransferListModel::TR_STATUS:
|
||||||
return (result < 0);
|
// QSortFilterProxyModel::lessThan() uses the < operator only for specific QVariant types
|
||||||
}
|
// so our custom type is outside that list.
|
||||||
|
// In this case QSortFilterProxyModel::lessThan() converts other types to QString and
|
||||||
case TransferListModel::TR_STATUS: {
|
// sorts them.
|
||||||
// QSortFilterProxyModel::lessThan() uses the < operator only for specific QVariant types
|
// Thus we can't use the code in the default label.
|
||||||
// so our custom type is outside that list.
|
if (leftValue != rightValue)
|
||||||
// In this case QSortFilterProxyModel::lessThan() converts other types to QString and
|
return leftValue < rightValue;
|
||||||
// sorts them.
|
return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
|
||||||
// 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)
|
|
||||||
return leftValue < rightValue;
|
|
||||||
|
|
||||||
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 (rightValue < 0) return true;
|
||||||
|
|
||||||
if (vL < 0) return false;
|
return (leftValue < rightValue);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user