Pith - whittle
whittle/src/ui/DiffTableModel.cpp [6.6 kb]
Modified: 19:51:45 130 026 (27 Jul 026)
0 Days Ago
#include "ui/DiffTableModel.h"
#include "ui/Palette.h"
#include <algorithm>
#include <cstdlib>
#include <QDateTime>
#include <QLocale>
#include <QStringList>

namespace whittle {
namespace {

QString humanSize(std::int64_t bytes) {
    return QLocale().formattedDataSize(bytes, 1,
               QLocale::DataSizeTraditionalFormat);
}

QString humanDuration(std::int64_t ns) {
    ns = std::llabs(ns);
    if (ns == 0) return QStringLiteral("0s");
    const std::int64_t s = ns / 1'000'000'000LL;
    if (s == 0) {
        if (ns >= 1'000'000LL) return QStringLiteral("%1ms").arg(ns / 1'000'000LL);
        if (ns >= 1'000LL)     return QStringLiteral("%1us").arg(ns / 1'000LL);
        return QStringLiteral("%1ns").arg(ns);
    }
    const std::int64_t d = s / 86400, h = (s % 86400) / 3600,
                       m = (s % 3600) / 60, sec = s % 60;
    QStringList p;
    if (d) p << QStringLiteral("%1d").arg(d);
    if (h) p << QStringLiteral("%1h").arg(h);
    if (m) p << QStringLiteral("%1m").arg(m);
    if (sec || p.isEmpty()) p << QStringLiteral("%1s").arg(sec);
    while (p.size() > 2) p.removeLast();
    return p.join(' ');
}

QString displayName(const QString& relPath) {
    const int last = relPath.lastIndexOf('/');
    if (last <= 0) return relPath;
    const int prev = relPath.lastIndexOf('/', last - 1);
    return relPath.mid(prev + 1);
}

} 

DiffTableModel::DiffTableModel(bool checkable, QObject* parent)
    : QAbstractTableModel(parent), m_checkable(checkable) {}

void DiffTableModel::setRows(QList<FileRecord> rows,
                             QHash<QString, FileRecord> peer) {
    beginResetModel();
    m_rows = std::move(rows);
    m_peer = std::move(peer);
    m_skipped.clear();
    m_checked.clear();
    if (m_checkable)
        for (const auto& r : m_rows) m_checked.insert(r.relPath);
    applySort();
    endResetModel();
}

void DiffTableModel::sort(int column, Qt::SortOrder order) {
    if (column < 0 || column >= ColumnCount) return;
    m_sortColumn = column;
    m_sortOrder  = order;
    beginResetModel();
    applySort();
    endResetModel();
}

void DiffTableModel::applySort() {
    const int  col = m_sortColumn;
    const bool asc = (m_sortOrder == Qt::AscendingOrder);

    const auto delta = [this](const FileRecord& r) -> std::int64_t {
        const auto it = m_peer.constFind(r.relPath);
        if (it == m_peer.constEnd()) return -1;
        return std::llabs(r.mtimeNs - it->mtimeNs);
    };

    std::stable_sort(m_rows.begin(), m_rows.end(),
        [&](const FileRecord& a, const FileRecord& b) {
            int cmp = 0;
            switch (col) {
            case Size:
                cmp = (a.size < b.size) ? -1 : (a.size > b.size ? 1 : 0);
                break;
            case Modified:
                cmp = (a.mtimeNs < b.mtimeNs) ? -1 : (a.mtimeNs > b.mtimeNs ? 1 : 0);
                break;
            case TimeDiff: {
                const auto da = delta(a), db = delta(b);
                cmp = (da < db) ? -1 : (da > db ? 1 : 0);
                break;
            }
            case Name:
            default:
                cmp = displayName(a.relPath).compare(displayName(b.relPath),
                                                     Qt::CaseInsensitive);
                break;
            }
            if (cmp == 0) cmp = a.relPath.compare(b.relPath);
            return asc ? cmp < 0 : cmp > 0;
        });
}

void DiffTableModel::setSkipped(const QSet<QString>& relPaths) {
    m_skipped = relPaths;
    if (!m_rows.isEmpty())
        emit dataChanged(index(0, 0), index(m_rows.size() - 1, ColumnCount - 1));
}

const FileRecord* DiffTableModel::recordAt(int row) const {
    if (row < 0 || row >= m_rows.size()) return nullptr;
    return &m_rows[row];
}

void DiffTableModel::removeRecord(const QString& relPath) {
    const QString prefix = relPath + '/';
    for (int i = m_rows.size() - 1; i >= 0; --i) {
        const QString& key = m_rows[i].relPath;
        if (key != relPath && !key.startsWith(prefix)) continue;
        beginRemoveRows({}, i, i);
        m_checked.remove(key);
        m_skipped.remove(key);
        m_rows.removeAt(i);
        endRemoveRows();
    }
    m_peer.remove(relPath);
    for (auto it = m_peer.begin(); it != m_peer.end(); )
        it = it.key().startsWith(prefix) ? m_peer.erase(it) : ++it;
}

int DiffTableModel::rowCount(const QModelIndex&) const { return m_rows.size(); }
int DiffTableModel::columnCount(const QModelIndex&) const { return ColumnCount; }

QVariant DiffTableModel::data(const QModelIndex& ix, int role) const {
    if (!ix.isValid() || ix.row() >= m_rows.size()) return {};
    const FileRecord& r = m_rows[ix.row()];
    const bool inPeer = m_peer.contains(r.relPath);

    if (role == Qt::CheckStateRole && m_checkable && ix.column() == Name)
        return m_checked.contains(r.relPath) ? Qt::Checked : Qt::Unchecked;

    if (role == Qt::ForegroundRole) {
        if (m_skipped.contains(r.relPath)) return Palette::Skipped;
        if (m_checkable  && !inPeer)       return Palette::NewOnUsb;
        if (!m_checkable && !inPeer)       return Palette::UsbOnly;
        return {};
    }

    if (role != Qt::DisplayRole) return {};
    switch (ix.column()) {
    case Name: return displayName(r.relPath) + (r.isDir ? QStringLiteral("/") : QString());
    case Size: return r.isDir ? QStringLiteral("--") : humanSize(r.size);
    case Modified:
        return QDateTime::fromMSecsSinceEpoch(r.mtimeNs / 1'000'000LL)
                   .toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
    case TimeDiff:
        if (inPeer)
            return humanDuration(r.mtimeNs - m_peer.value(r.relPath).mtimeNs);
        return m_checkable ? tr("new") : tr("no file");
    }
    return {};
}

QVariant DiffTableModel::headerData(int s, Qt::Orientation o, int role) const {
    if (role != Qt::DisplayRole || o != Qt::Horizontal) return {};
    switch (s) {
    case Name:     return QStringLiteral("File");
    case Size:     return QStringLiteral("Size");
    case Modified: return QStringLiteral("Modified");
    case TimeDiff: return QStringLiteral("Diff");
    }
    return {};
}

Qt::ItemFlags DiffTableModel::flags(const QModelIndex& ix) const {
    Qt::ItemFlags f = Qt::ItemIsEnabled;
    if (m_checkable && ix.column() == Name) f |= Qt::ItemIsUserCheckable;
    return f;
}

bool DiffTableModel::setData(const QModelIndex& ix, const QVariant& v, int role) {
    if (role != Qt::CheckStateRole || !m_checkable || ix.column() != Name)
        return false;
    const QString& key = m_rows[ix.row()].relPath;
    if (v.toInt() == Qt::Checked) m_checked.insert(key);
    else                          m_checked.remove(key);
    emit dataChanged(ix, ix, {Qt::CheckStateRole});
    return true;
}

} 
Updates
Whittle - Linux 130.026
Wedge - Android 126.026
Wedge - Linux 124.026
Shim - Android 117.026
Miter - 114.026
Dev
TVShow (227) 'CSA'
TVShow (228) 'APT'
TVProgram (83) 'BXT'
Miter Update(s)
Peen (Messaging)

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)