199 lines
5.6 KiB
C++
199 lines
5.6 KiB
C++
#include "gitstatusmodel.h"
|
|
|
|
#include <QFont>
|
|
#include <QIcon>
|
|
|
|
GitStatusModel::GitStatusModel(QObject *parent)
|
|
: QAbstractItemModel(parent)
|
|
{
|
|
m_nodeNames[NodeStaged] = QStringLiteral("Staged");
|
|
m_nodeNames[NodeChanged] = QStringLiteral("Changed");
|
|
m_nodeNames[NodeUntracked] = QStringLiteral("Untracked");
|
|
|
|
m_nodeIcons[NodeStaged] = QIcon::fromTheme(QStringLiteral("vcs-added"));
|
|
m_nodeIcons[NodeChanged] = QIcon::fromTheme(QStringLiteral("vcs-locally-modified"));
|
|
m_nodeIcons[NodeUntracked] = QIcon::fromTheme(QStringLiteral("vcs-added"));
|
|
}
|
|
|
|
QModelIndex GitStatusModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (column != 0)
|
|
return {};
|
|
|
|
if (!parent.isValid()) {
|
|
// Top-level nodes
|
|
if (row >= 0 && row < NodeCount)
|
|
return createIndex(row, 0, TopLevelId);
|
|
return {};
|
|
}
|
|
|
|
// Child items — parent's internalId is TopLevelId, parent's row gives the node type
|
|
if (parent.internalId() == TopLevelId) {
|
|
int nodeType = parent.row();
|
|
if (nodeType >= 0 && nodeType < NodeCount && row >= 0 && row < m_entries[nodeType].size())
|
|
return createIndex(row, 0, quintptr(nodeType));
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QModelIndex GitStatusModel::parent(const QModelIndex &child) const
|
|
{
|
|
if (!child.isValid())
|
|
return {};
|
|
|
|
if (child.internalId() == TopLevelId)
|
|
return {}; // top-level has no parent
|
|
|
|
// internalId is the node type
|
|
int nodeType = int(child.internalId());
|
|
if (nodeType >= 0 && nodeType < NodeCount)
|
|
return createIndex(nodeType, 0, TopLevelId);
|
|
|
|
return {};
|
|
}
|
|
|
|
int GitStatusModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid())
|
|
return NodeCount;
|
|
|
|
if (parent.internalId() == TopLevelId) {
|
|
int nodeType = parent.row();
|
|
if (nodeType >= 0 && nodeType < NodeCount)
|
|
return m_entries[nodeType].size();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int GitStatusModel::columnCount(const QModelIndex &) const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
QVariant GitStatusModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid())
|
|
return {};
|
|
|
|
// Top-level nodes
|
|
if (index.internalId() == TopLevelId) {
|
|
int nodeType = index.row();
|
|
if (nodeType < 0 || nodeType >= NodeCount)
|
|
return {};
|
|
|
|
switch (role) {
|
|
case Qt::DisplayRole:
|
|
return QStringLiteral("%1 (%2)").arg(m_nodeNames[nodeType]).arg(m_entries[nodeType].size());
|
|
case Qt::DecorationRole:
|
|
return m_nodeIcons[nodeType];
|
|
case Qt::FontRole: {
|
|
QFont f;
|
|
f.setBold(true);
|
|
return f;
|
|
}
|
|
case NodeTypeRole:
|
|
return nodeType;
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// File entries
|
|
int nodeType = int(index.internalId());
|
|
if (nodeType < 0 || nodeType >= NodeCount)
|
|
return {};
|
|
|
|
int row = index.row();
|
|
if (row < 0 || row >= m_entries[nodeType].size())
|
|
return {};
|
|
|
|
const auto &entry = m_entries[nodeType][row];
|
|
|
|
switch (role) {
|
|
case Qt::DisplayRole:
|
|
return entry.file;
|
|
case Qt::DecorationRole: {
|
|
// Status char icon
|
|
QChar sc = (nodeType == NodeStaged) ? entry.indexStatus : entry.workStatus;
|
|
if (sc == QLatin1Char('M'))
|
|
return QIcon::fromTheme(QStringLiteral("vcs-locally-modified"));
|
|
if (sc == QLatin1Char('A') || sc == QLatin1Char('?'))
|
|
return QIcon::fromTheme(QStringLiteral("vcs-added"));
|
|
if (sc == QLatin1Char('D'))
|
|
return QIcon::fromTheme(QStringLiteral("vcs-removed"));
|
|
if (sc == QLatin1Char('R'))
|
|
return QIcon::fromTheme(QStringLiteral("vcs-locally-modified"));
|
|
return QIcon::fromTheme(QStringLiteral("text-plain"));
|
|
}
|
|
case Qt::ToolTipRole: {
|
|
QChar sc = (nodeType == NodeStaged) ? entry.indexStatus : entry.workStatus;
|
|
return QStringLiteral("[%1] %2").arg(sc).arg(entry.file);
|
|
}
|
|
case FilePathRole:
|
|
return entry.file;
|
|
case NodeTypeRole:
|
|
return nodeType;
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
Qt::ItemFlags GitStatusModel::flags(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return Qt::NoItemFlags;
|
|
|
|
if (index.internalId() == TopLevelId)
|
|
return Qt::ItemIsEnabled; // top-level nodes not selectable
|
|
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
|
}
|
|
|
|
void GitStatusModel::parseStatus(const QByteArray &raw)
|
|
{
|
|
if (raw == m_lastRaw)
|
|
return;
|
|
m_lastRaw = raw;
|
|
|
|
beginResetModel();
|
|
|
|
for (int i = 0; i < NodeCount; ++i)
|
|
m_entries[i].clear();
|
|
|
|
const auto lines = raw.split('\n');
|
|
for (const auto &line : lines) {
|
|
if (line.size() < 4)
|
|
continue;
|
|
|
|
// Format: XY <space> filename
|
|
// or XY <space> old -> new (for renames)
|
|
QChar x = QLatin1Char(line.at(0));
|
|
QChar y = QLatin1Char(line.at(1));
|
|
// line[2] is space
|
|
QString file = QString::fromUtf8(line.mid(3));
|
|
|
|
// Handle renames: "R old -> new"
|
|
int arrowIdx = file.indexOf(QStringLiteral(" -> "));
|
|
if (arrowIdx >= 0)
|
|
file = file.mid(arrowIdx + 4);
|
|
|
|
GitStatusEntry entry{file, x, y};
|
|
|
|
// Staged: X is not ' ', '?' or '!'
|
|
if (x != QLatin1Char(' ') && x != QLatin1Char('?') && x != QLatin1Char('!')) {
|
|
m_entries[NodeStaged].append(entry);
|
|
}
|
|
|
|
// Changed (working tree): Y is not ' ', '?' or '!'
|
|
if (y == QLatin1Char('?')) {
|
|
m_entries[NodeUntracked].append(entry);
|
|
} else if (y != QLatin1Char(' ') && y != QLatin1Char('!')) {
|
|
m_entries[NodeChanged].append(entry);
|
|
}
|
|
}
|
|
|
|
endResetModel();
|
|
}
|