Files
sloposk/src/layout/LayoutModel.cpp
T
ericek111andClaude Opus 5.5 23e957660f Reword code comments in plain technical language
Comments only, no code changes. Replaces vague or personified phrasing
with explicit statements of what the code does and why, uses bullet
lists for multi-case behavior, and fixes the settings path in
WindowSettings.h (~/.config/sloposk/sloposk.conf).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:58:59 +01:00

248 lines
7.3 KiB
C++

#include "LayoutModel.h"
#include <algorithm>
#include <limits>
namespace {
QString keyTypeName(KeyType type)
{
switch (type) {
case KeyType::Character: return QStringLiteral("char");
case KeyType::Space: return QStringLiteral("space");
case KeyType::Backspace: return QStringLiteral("backspace");
case KeyType::Enter: return QStringLiteral("enter");
case KeyType::Shift: return QStringLiteral("shift");
case KeyType::CapsLock: return QStringLiteral("capslock");
case KeyType::Modifier: return QStringLiteral("modifier");
case KeyType::LayerSwitch: return QStringLiteral("layer");
case KeyType::Globe: return QStringLiteral("globe");
case KeyType::Dismiss: return QStringLiteral("dismiss");
case KeyType::Spacer: return QStringLiteral("spacer");
case KeyType::Keysym: return QStringLiteral("keysym");
case KeyType::Letters:
case KeyType::Keys: break; // expanded before it reaches the model
}
return {};
}
qreal distanceSquared(const QRectF &rect, QPointF p)
{
const qreal dx = std::max({rect.left() - p.x(), 0.0, p.x() - rect.right()});
const qreal dy = std::max({rect.top() - p.y(), 0.0, p.y() - rect.bottom()});
return dx * dx + dy * dy;
}
} // namespace
int LayoutModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : int(m_keys.size());
}
QVariant LayoutModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return {};
const KeyDef &key = m_keys.at(index.row());
const QRectF rect = m_rects.value(index.row());
switch (role) {
case LabelRole: return key.text(m_keyLevel);
// Hide the flick symbol if the main label already shows it (shifted or AltGr: ! on 1).
case FlickLabelRole: return key.flick == key.text(m_keyLevel) ? QString() : key.flick;
case ModifierRole: return key.modifier;
case KeyTypeRole: return keyTypeName(key.type);
case KeyXRole: return rect.x();
case KeyYRole: return rect.y();
case KeyWidthRole: return rect.width();
case KeyHeightRole: return rect.height();
case PressedRole: return m_pressCount.at(index.row()) > 0;
case FlickProgressRole: return m_flickProgress.at(index.row());
}
return {};
}
QHash<int, QByteArray> LayoutModel::roleNames() const
{
return {
{LabelRole, "label"},
{FlickLabelRole, "flickLabel"},
{KeyTypeRole, "keyType"},
{KeyXRole, "keyX"},
{KeyYRole, "keyY"},
{KeyWidthRole, "keyWidth"},
{KeyHeightRole, "keyHeight"},
{PressedRole, "pressed"},
{FlickProgressRole, "flickProgress"},
{ModifierRole, "modifier"},
};
}
void LayoutModel::setLayout(const Layout &layout)
{
m_layout = layout;
m_layerName = layout.layers.isEmpty() ? QString() : layout.layers.first().name;
rebuild();
Q_EMIT layoutChanged();
Q_EMIT layerChanged();
}
void LayoutModel::setLayer(const QString &layerName)
{
if (layerName == m_layerName || !m_layout.layer(layerName))
return;
m_layerName = layerName;
rebuild();
Q_EMIT layerChanged();
}
void LayoutModel::setVisibleSections(const QStringList &ids)
{
if (ids == m_visibleSections)
return;
m_visibleSections = ids;
rebuild();
}
QVariantList LayoutModel::sections() const
{
QVariantList list;
for (const Section &section : m_layout.sections)
list.append(QVariantMap{{QStringLiteral("id"), section.id}, {QStringLiteral("name"), section.name}});
return list;
}
qreal LayoutModel::totalUnits() const
{
return ::totalUnits(m_blocks);
}
qsizetype LayoutModel::totalRows() const
{
return ::totalRows(m_blocks);
}
void LayoutModel::setSize(QSizeF size)
{
if (size == m_size)
return;
m_size = size;
relayout();
if (!m_keys.isEmpty())
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {KeyXRole, KeyYRole, KeyWidthRole, KeyHeightRole});
}
void LayoutModel::setKeyLevel(KeyLevel level)
{
if (level == m_keyLevel)
return;
m_keyLevel = level;
if (!m_keys.isEmpty())
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {LabelRole, FlickLabelRole});
}
void LayoutModel::setPressed(int row, bool pressed)
{
if (row < 0 || row >= m_pressCount.size())
return;
const bool wasPressed = m_pressCount[row] > 0;
m_pressCount[row] = std::max(0, m_pressCount[row] + (pressed ? 1 : -1));
if (wasPressed != (m_pressCount[row] > 0))
Q_EMIT dataChanged(index(row), index(row), {PressedRole});
}
void LayoutModel::setFlickProgress(int row, qreal progress)
{
if (row < 0 || row >= m_flickProgress.size() || qFuzzyCompare(m_flickProgress[row], progress))
return;
m_flickProgress[row] = progress;
Q_EMIT dataChanged(index(row), index(row), {FlickProgressRole});
}
int LayoutModel::keyAt(QPointF pos) const
{
int nearest = -1;
qreal best = std::numeric_limits<qreal>::max();
for (qsizetype i = 0; i < m_rects.size(); ++i) {
const qreal d = distanceSquared(m_rects[i], pos);
if (d < best) {
best = d;
nearest = int(i);
}
}
return nearest;
}
QList<Block> LayoutModel::composeBlocks() const
{
const auto shown = [this](const QString &id) { return m_visibleSections.contains(id); };
const Layer *layer = m_layout.layer(m_layerName);
Block main;
for (const Section &section : m_layout.sections) {
if (section.placement == Section::Placement::Above && shown(section.id))
main.rows += section.rows;
}
if (layer && shown(MainSectionId)) {
// Reserve space for the largest layer, so switching layers doesn't move other blocks.
main.fixedRows = main.rows.size();
for (const Layer &other : m_layout.layers) {
main.minRows = std::max(main.minRows, main.fixedRows + other.rows.size());
for (const Row &row : other.rows)
main.minUnits = std::max(main.minUnits, rowUnits(row));
}
main.rows += layer->rows;
}
QList<Block> blocks;
if (!main.rows.isEmpty())
blocks.append(main);
for (const Section &section : m_layout.sections) {
if (section.placement == Section::Placement::Right && shown(section.id))
blocks.append({section.rows, section.fillHeight});
}
// If no block is shown, fall back to the current layer.
if (blocks.isEmpty() && layer)
blocks.append({layer->rows});
return blocks;
}
void LayoutModel::rebuild()
{
beginResetModel();
m_blocks = composeBlocks();
m_keys.clear();
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block.rows) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_keys.append(key);
}
}
}
m_pressCount = QList<int>(m_keys.size(), 0);
m_flickProgress = QList<qreal>(m_keys.size(), 0.0);
relayout();
endResetModel();
}
void LayoutModel::relayout()
{
m_rects.clear();
if (m_size.isEmpty())
return;
const QList<QRectF> all = computeKeyRects(m_blocks, m_size);
qsizetype i = 0;
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block.rows) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_rects.append(all[i]);
++i;
}
}
}
}