kate-gitplusplus/gitstatusmodel.cpp

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();
}