Merge pull request #11708 from glassez/transfer-list

Improve Transfer list architecture
This commit is contained in:
Vladimir Golovnev
2019-12-27 16:25:26 +03:00
committed by GitHub
6 changed files with 384 additions and 358 deletions

View File

@@ -38,8 +38,9 @@
#include "transferlistmodel.h"
TransferListSortModel::TransferListSortModel(QObject *parent)
: QSortFilterProxyModel(parent)
: QSortFilterProxyModel {parent}
{
QMetaType::registerComparators<BitTorrent::TorrentState>();
}
void TransferListSortModel::setStatusFilter(TorrentFilter::Type filter)
@@ -86,56 +87,100 @@ void TransferListSortModel::disableTrackerFilter()
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_TAGS:
case TransferListModel::TR_NAME: {
const QVariant vL = left.data();
const QVariant vR = right.data();
if (!vL.isValid() || !vR.isValid() || (vL == vR))
return lowerPositionThan(left, right);
case TransferListModel::TR_NAME:
if (!leftValue.isValid() || !rightValue.isValid() || (leftValue == rightValue))
return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
return (Utils::String::naturalCompare(leftValue.toString(), rightValue.toString(), Qt::CaseInsensitive) < 0);
const int result = Utils::String::naturalCompare(vL.toString(), vR.toString(), Qt::CaseInsensitive);
return (result < 0);
}
case TransferListModel::TR_STATUS: {
// 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
// sorts them.
// 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_STATUS:
// 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
// sorts them.
// Thus we can't use the code in the default label.
if (leftValue != rightValue)
return leftValue < rightValue;
return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
case TransferListModel::TR_ADD_DATE:
case TransferListModel::TR_SEED_DATE:
case TransferListModel::TR_SEEN_COMPLETE_DATE:
return dateLessThan(sortColumn(), left, right, true);
case TransferListModel::TR_SEEN_COMPLETE_DATE: {
const QDateTime dateL = leftValue.toDateTime();
const QDateTime dateR = rightValue.toDateTime();
case TransferListModel::TR_QUEUE_POSITION:
return lowerPositionThan(left, right);
if (dateL.isValid() && dateR.isValid()) {
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_PEERS: {
const int leftActive = left.data().toInt();
const int leftTotal = left.data(Qt::UserRole).toInt();
const int rightActive = right.data().toInt();
const int rightTotal = right.data(Qt::UserRole).toInt();
// QVariant has comparators for all basic types
// Active peers/seeds take precedence over total peers/seeds.
if (leftActive != rightActive)
return (leftActive < rightActive);
if (leftValue != rightValue)
return (leftValue < rightValue);
if (leftTotal != rightTotal)
return (leftTotal < rightTotal);
const QVariant leftValueTotal = left.data(TransferListModel::AdditionalUnderlyingDataRole);
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: {
@@ -153,8 +198,10 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex
if (isActiveL != isActiveR)
return isActiveL;
const int queuePosL = left.sibling(left.row(), TransferListModel::TR_QUEUE_POSITION).data().toInt();
const int queuePosR = right.sibling(right.row(), TransferListModel::TR_QUEUE_POSITION).data().toInt();
const int queuePosL = left.sibling(left.row(), TransferListModel::TR_QUEUE_POSITION)
.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 isSeedingR = (queuePosR < 0);
if (isSeedingL != isSeedingR) {
@@ -165,85 +212,36 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex
return isAscendingOrder;
}
const qlonglong etaL = left.data().toLongLong();
const qlonglong etaR = right.data().toLongLong();
const qlonglong etaL = leftValue.toLongLong();
const qlonglong etaR = rightValue.toLongLong();
const bool isInvalidL = ((etaL < 0) || (etaL >= MAX_ETA));
const bool isInvalidR = ((etaR < 0) || (etaR >= MAX_ETA));
if (isInvalidL && isInvalidR) {
if (isSeedingL) // Both seeding
return dateLessThan(TransferListModel::TR_SEED_DATE, left, right, true);
return invokeLessThanForColumn(TransferListModel::TR_SEED_DATE);
return (queuePosL < queuePosR);
}
if (!isInvalidL && !isInvalidR) {
if (!isInvalidL && !isInvalidR)
return (etaL < etaR);
}
return !isInvalidL;
}
case TransferListModel::TR_LAST_ACTIVITY: {
const int vL = left.data().toInt();
const int vR = right.data().toInt();
case TransferListModel::TR_LAST_ACTIVITY:
case TransferListModel::TR_RATIO_LIMIT:
// QVariant has comparators for all basic types
if (leftValue < 0) return false;
if (rightValue < 0) return true;
if (vL < 0) return false;
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:
if (left.data() != right.data())
if (leftValue != rightValue)
return QSortFilterProxyModel::lessThan(left, right);
return lowerPositionThan(left, right);
}
}
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;
return invokeLessThanForColumn(TransferListModel::TR_QUEUE_POSITION);
}
// Finally, sort by hash