Fix transfer list architecture

Model returns string for DisplayRole.
Text alignment is set by Model (using TextAlignmentRole).
Delegate performs custom painting only where necessary
(i.e. for Progress bar).
This commit is contained in:
Vladimir Golovnev (Glassez)
2019-12-17 21:57:36 +03:00
parent 18de63f743
commit 766cfb67df
6 changed files with 375 additions and 346 deletions

View File

@@ -37,8 +37,9 @@
#include "transferlistmodel.h"
TransferListSortModel::TransferListSortModel(QObject *parent)
: QSortFilterProxyModel(parent)
: QSortFilterProxyModel {parent}
{
QMetaType::registerComparators<BitTorrent::TorrentState>();
}
void TransferListSortModel::setStatusFilter(TorrentFilter::Type filter)
@@ -85,56 +86,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: {
@@ -152,8 +197,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) {
@@ -164,85 +211,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