#include "LayoutModel.h" #include #include 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 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 §ion : 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::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 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 §ion : 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 blocks; if (!main.rows.isEmpty()) blocks.append(main); for (const Section §ion : 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(m_keys.size(), 0); m_flickProgress = QList(m_keys.size(), 0.0); relayout(); endResetModel(); } void LayoutModel::relayout() { m_rects.clear(); if (m_size.isEmpty()) return; const QList 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; } } } }