Optional PC key blocks: function row, navigation, numeric keypad

- Template sections placed above or to the right of the typing keys;
  the user picks any mix in settings, typing keys included
- keysym keys send XKB keysyms on press, optionally repeating
- Geometry lays blocks side by side with one key unit; stretch keys are
  resolved at layout time so composed blocks line up
- Toggling a block resizes the window so keys keep their size

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 21:18:10 +02:00
co-authored by Claude Opus 5.5
parent e532f5c072
commit 88338fde22
22 changed files with 521 additions and 101 deletions
+1
View File
@@ -32,6 +32,7 @@ set(SLOPOSK_QML_FILES
AlternatesPopup.qml
DragHandle.qml
ResizeHandle.qml
SectionHeader.qml
SettingsButton.qml
SettingsPanel.qml
ToggleRow.qml
+28
View File
@@ -14,14 +14,18 @@ static_assert(int(KeyboardController::ShiftMode::Locked) == int(ShiftState::Mode
namespace {
// Enough history for the text rules; fields may report megabytes.
constexpr qsizetype MaxTextBefore = 256;
// Like a hardware key: no word mode, a faster rate than Backspace.
constexpr KeyRepeater::Timing KeysymRepeat{.intervalMs = 50, .wordModeAfterMs = std::numeric_limits<int>::max()};
}
KeyboardController::KeyboardController(InputEngine &engine, LayoutModel &model, QObject *parent)
: QObject(parent)
, m_engine(engine)
, m_model(model)
, m_keysymRepeater(KeysymRepeat)
{
connect(&m_backspaceRepeater, &KeyRepeater::repeat, this, &KeyboardController::deleteBackward);
connect(&m_keysymRepeater, &KeyRepeater::repeat, this, &KeyboardController::sendRepeatedKeysym);
}
KeyboardController::ShiftMode KeyboardController::shiftMode() const
@@ -43,6 +47,13 @@ void KeyboardController::keyDown(const KeyDef &key, qint64 timeMs)
m_layerBeforeSwitch = m_model.layerName();
m_model.setLayer(key.target);
break;
case KeyType::Keysym:
m_engine.sendKeysym(key.keysym);
if (key.repeat) {
m_repeatingKeysym = key.keysym;
m_keysymRepeater.start();
}
break;
default:
break; // everything else acts on release
}
@@ -57,6 +68,9 @@ void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QStrin
case KeyType::Backspace:
m_backspaceRepeater.stop();
break;
case KeyType::Keysym:
stopRepeating(key);
break;
case KeyType::Character:
if (output == KeyOutput::Alternate && !alternate.isEmpty())
typeText(alternate);
@@ -90,6 +104,8 @@ void KeyboardController::keyCancelled(const KeyDef &key)
changeShift([&] { m_shift.release(); });
else if (key.type == KeyType::Backspace)
m_backspaceRepeater.stop();
else if (key.type == KeyType::Keysym)
stopRepeating(key);
}
void KeyboardController::slideReleased(const KeyDef &target, qint64 timeMs)
@@ -156,6 +172,18 @@ void KeyboardController::deleteBackward(bool wholeWord)
autoCapitalize();
}
// Only the key that started the repeat stops it; another finger's key doesn't.
void KeyboardController::stopRepeating(const KeyDef &key)
{
if (key.keysym == m_repeatingKeysym)
m_keysymRepeater.stop();
}
void KeyboardController::sendRepeatedKeysym()
{
m_engine.sendKeysym(m_repeatingKeysym);
}
void KeyboardController::autoCapitalize()
{
const bool capitalize = m_options.autoCapitalize && m_context.allowsAutoCapitalization()
+4
View File
@@ -57,6 +57,8 @@ Q_SIGNALS:
private:
void typeText(const QString &text);
void deleteBackward(bool wholeWord);
void sendRepeatedKeysym();
void stopRepeating(const KeyDef &key);
void autoCapitalize();
template<typename Change>
void changeShift(Change change);
@@ -66,6 +68,8 @@ private:
TypingOptions m_options;
ShiftState m_shift;
KeyRepeater m_backspaceRepeater;
KeyRepeater m_keysymRepeater; // arrows, Delete, ...
quint32 m_repeatingKeysym = 0;
QString m_layerBeforeSwitch;
TextContext m_context;
+58 -18
View File
@@ -1,6 +1,30 @@
#include "KeyGeometry.h"
#include <algorithm>
#include <numeric>
namespace {
qreal blockUnits(const Block &block)
{
qreal units = 0;
for (const Row &row : block)
units = std::max(units, rowUnits(row));
return units;
}
// Key widths in units once stretch keys take up the row's slack.
QList<qreal> stretchedWidths(const Row &row, qreal targetUnits)
{
const auto stretchy = std::ranges::count_if(row, &KeyDef::stretch);
const qreal extra = stretchy ? (targetUnits - rowUnits(row)) / qreal(stretchy) : 0;
QList<qreal> widths;
for (const KeyDef &key : row)
widths.append(key.width + (key.stretch ? extra : 0));
return widths;
}
} // namespace
qreal rowUnits(const Row &row)
{
@@ -10,33 +34,49 @@ qreal rowUnits(const Row &row)
return units;
}
QList<QRectF> computeKeyRects(const Layer &layer, QSizeF size, KeyboardMetrics metrics)
qreal totalUnits(const QList<Block> &blocks)
{
qreal units = 0;
for (const Block &block : blocks)
units += blockUnits(block);
return units;
}
QList<QRectF> computeKeyRects(const QList<Block> &blocks, QSizeF size, KeyboardMetrics metrics)
{
QList<QRectF> rects;
if (layer.rows.isEmpty())
const qreal units = totalUnits(blocks);
qsizetype maxRows = 0;
for (const Block &block : blocks)
maxRows = std::max(maxRows, block.size());
if (units <= 0 || maxRows == 0)
return rects;
qreal maxUnits = 0;
for (const Row &row : layer.rows)
maxUnits = std::max(maxUnits, rowUnits(row));
const qreal innerWidth = size.width() - 2 * metrics.padding;
const qreal innerHeight = size.height() - 2 * metrics.padding;
const int rowCount = int(layer.rows.size());
const auto blockCount = qreal(blocks.size());
// Each cell includes one trailing gap; the last gap of a row is dropped.
const qreal unitWidth = (innerWidth + metrics.gap) / maxUnits;
const qreal rowHeight = (innerHeight - (rowCount - 1) * metrics.gap) / rowCount;
// Each cell includes one trailing gap; the last gap of a row is dropped,
// and blocks are separated by blockGap instead.
const qreal unitWidth = (innerWidth + blockCount * metrics.gap - (blockCount - 1) * metrics.blockGap) / units;
const qreal rowHeight = (innerHeight - qreal(maxRows - 1) * metrics.gap) / qreal(maxRows);
qreal y = metrics.padding;
for (const Row &row : layer.rows) {
qreal x = metrics.padding + (maxUnits - rowUnits(row)) * unitWidth / 2;
for (const KeyDef &key : row) {
const qreal cellWidth = key.width * unitWidth;
rects.append(QRectF(x, y, std::max<qreal>(0, cellWidth - metrics.gap), rowHeight));
x += cellWidth;
qreal left = metrics.padding;
for (const Block &block : blocks) {
const qreal width = blockUnits(block);
qreal y = metrics.padding + qreal(maxRows - block.size()) * (rowHeight + metrics.gap);
for (const Row &row : block) {
const QList<qreal> widths = stretchedWidths(row, width);
const qreal used = std::accumulate(widths.begin(), widths.end(), 0.0);
qreal x = left + (width - used) * unitWidth / 2;
for (qreal keyWidth : widths) {
const qreal cellWidth = keyWidth * unitWidth;
rects.append(QRectF(x, y, std::max<qreal>(0, cellWidth - metrics.gap), rowHeight));
x += cellWidth;
}
y += rowHeight + metrics.gap;
}
y += rowHeight + metrics.gap;
left += width * unitWidth - metrics.gap + metrics.blockGap;
}
return rects;
}
+19 -7
View File
@@ -6,14 +6,26 @@
#include <QSizeF>
struct KeyboardMetrics {
qreal padding = 6; // around the whole keyboard
qreal gap = 8; // between keys, horizontally and vertically
qreal padding = 6; // around the whole keyboard
qreal gap = 8; // between keys, horizontally and vertically
qreal blockGap = 24; // between side-by-side blocks (main, navigation, numpad)
};
// Lays out a layer's keys to fill `size`. Returns one rect per key, in row
// order (spacers included). One key unit has the same width in every row, so
// rows with equal unit totals line up; shorter rows are centered.
QList<QRectF> computeKeyRects(const Layer &layer, QSizeF size, KeyboardMetrics metrics = {});
// The keys on screen: blocks of rows, side by side.
using Block = QList<Row>;
// Total width of a row in key units.
// Lays out blocks to fill `size`. Returns one rect per key, block by block in
// row order (spacers included). One key unit has the same width everywhere;
// all rows share one height, and shorter blocks sit at the bottom. In each
// block, stretch keys grow to make their row as wide as the widest one, and
// remaining shorter rows are centered.
QList<QRectF> computeKeyRects(const QList<Block> &blocks, QSizeF size, KeyboardMetrics metrics = {});
inline QList<QRectF> computeKeyRects(const Layer &layer, QSizeF size, KeyboardMetrics metrics = {})
{
return computeKeyRects({layer.rows}, size, metrics);
}
// Total width of a row in key units, before stretching.
qreal rowUnits(const Row &row);
// Width of all blocks in key units, ignoring gaps.
qreal totalUnits(const QList<Block> &blocks);
+18 -2
View File
@@ -16,6 +16,7 @@ enum class KeyType {
Dismiss,
Spacer, // empty room, e.g. the half-key indent of the home row
Letters, // template only: a row of letters from the system keymap
Keysym, // a non-text key (Home, F5, arrows...): sends `keysym`
};
struct KeyDef {
@@ -26,7 +27,9 @@ struct KeyDef {
QStringList alternates; // long-press choices
QString target; // layer to switch to (LayerSwitch only)
qreal width = 1.0; // in key units
bool stretch = false; // grows so the row is as wide as the layer's widest
bool stretch = false; // grows so the row is as wide as its block's widest
quint32 keysym = 0; // Keysym keys only
bool repeat = false; // Keysym keys only: auto-repeat while held
// Letters placeholders only.
int lettersRow = -1; // which keymap row (0 = top)
@@ -48,10 +51,21 @@ struct Layer {
QList<Row> rows;
};
// An optional block of keys next to the main one, shown if the user wants it.
struct Section {
enum class Placement { Above, Right }; // above the main keys, or a column to their right
QString id; // "main" is reserved for the layers
QString name; // shown in settings
Placement placement = Placement::Right;
QList<Row> rows;
};
struct Layout {
QString id;
QString name; // human readable, e.g. "Slovak"
QList<Layer> layers; // the first one is shown by default
QList<Layer> layers; // the main keys; the first one is shown by default
QList<Section> sections;
// Template only: long-press choices for keymap letters, by lowercase letter.
QHash<QString, QStringList> alternates;
@@ -64,3 +78,5 @@ struct Layout {
return nullptr;
}
};
inline const QString MainSectionId = QStringLiteral("main");
-21
View File
@@ -1,9 +1,7 @@
#include "LayoutBuilder.h"
#include "KeyGeometry.h"
#include <QSet>
#include <algorithm>
namespace {
@@ -74,24 +72,6 @@ Row expandLetters(const Row &row, const KeymapLetters &letters, const QHash<QStr
return result;
}
void stretchRows(Layer &layer)
{
qreal widest = 0;
for (const Row &row : layer.rows)
widest = std::max(widest, rowUnits(row));
for (Row &row : layer.rows) {
const auto stretchy = std::ranges::count_if(row, &KeyDef::stretch);
if (stretchy == 0)
continue;
const qreal extra = (widest - rowUnits(row)) / qreal(stretchy);
for (KeyDef &key : row) {
if (key.stretch)
key.width += extra;
}
}
}
} // namespace
Layout buildLayout(const Layout &tmpl, const KeymapLetters &letters)
@@ -104,7 +84,6 @@ Layout buildLayout(const Layout &tmpl, const KeymapLetters &letters)
for (Layer &layer : layout.layers) {
for (Row &row : layer.rows)
row = expandLetters(row, letters, alternates);
stretchRows(layer);
}
return layout;
}
+1 -2
View File
@@ -4,6 +4,5 @@
#include "Layout.h"
// Turns the template into the layout shown: fills its letters placeholders
// from the keymap, gives each letter its flick and long-press choices, and
// stretches the marked keys so every row of a layer is equally wide.
// from the keymap and gives each letter its flick and long-press choices.
Layout buildLayout(const Layout &tmpl, const KeymapLetters &letters);
+64 -16
View File
@@ -4,6 +4,7 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <xkbcommon/xkbcommon.h>
namespace LayoutLoader {
@@ -32,6 +33,7 @@ const QHash<QString, KeyType> &keyTypesByName()
{QStringLiteral("dismiss"), KeyType::Dismiss},
{QStringLiteral("spacer"), KeyType::Spacer},
{QStringLiteral("letters"), KeyType::Letters},
{QStringLiteral("keysym"), KeyType::Keysym},
};
return types;
}
@@ -66,6 +68,18 @@ std::optional<KeyDef> parseKey(const QJsonValue &value, QString *error)
key.alternates = toStringList(obj.value(u"alternates"));
key.lettersRow = obj.value(u"row").toInt(-1);
key.flicks = toStringList(obj.value(u"flicks"));
key.repeat = obj.value(u"repeat").toBool();
if (key.type == KeyType::Keysym) {
const QString name = obj.value(u"keysym").toString();
key.keysym = xkb_keysym_from_name(qPrintable(name), XKB_KEYSYM_NO_FLAGS);
if (key.keysym == XKB_KEY_NoSymbol) {
*error = QStringLiteral("unknown keysym \"%1\"").arg(name);
return std::nullopt;
}
if (key.label.isEmpty())
key.label = name;
}
if (key.type == KeyType::Character && key.label.isEmpty()) {
*error = QStringLiteral("character key without a label");
@@ -82,6 +96,25 @@ std::optional<KeyDef> parseKey(const QJsonValue &value, QString *error)
return key;
}
bool parseRows(const QJsonValue &value, QList<Row> *rows, QString *error)
{
for (const QJsonValue &rowValue : value.toArray()) {
Row row;
for (const QJsonValue &keyValue : rowValue.toArray()) {
auto key = parseKey(keyValue, error);
if (!key)
return false;
row.append(*key);
}
rows->append(row);
}
if (rows->isEmpty()) {
*error = QStringLiteral("no rows");
return false;
}
return true;
}
std::optional<Layer> parseLayer(const QJsonObject &obj, QString *error)
{
Layer layer;
@@ -90,27 +123,35 @@ std::optional<Layer> parseLayer(const QJsonObject &obj, QString *error)
*error = QStringLiteral("layer without a name");
return std::nullopt;
}
for (const QJsonValue &rowValue : obj.value(u"rows").toArray()) {
Row row;
for (const QJsonValue &keyValue : rowValue.toArray()) {
auto key = parseKey(keyValue, error);
if (!key) {
*error = QStringLiteral("layer \"%1\": %2").arg(layer.name, *error);
return std::nullopt;
}
row.append(*key);
}
layer.rows.append(row);
}
if (layer.rows.isEmpty()) {
*error = QStringLiteral("layer \"%1\" has no rows").arg(layer.name);
if (!parseRows(obj.value(u"rows"), &layer.rows, error)) {
*error = QStringLiteral("layer \"%1\": %2").arg(layer.name, *error);
return std::nullopt;
}
return layer;
}
std::optional<Section> parseSection(const QJsonObject &obj, QString *error)
{
Section section;
section.id = obj.value(u"id").toString();
section.name = obj.value(u"name").toString(section.id);
if (section.id.isEmpty() || section.id == MainSectionId) {
*error = QStringLiteral("section needs an id other than \"%1\"").arg(MainSectionId);
return std::nullopt;
}
const QString placement = obj.value(u"placement").toString(QStringLiteral("right"));
if (placement != u"right" && placement != u"above") {
*error = QStringLiteral("section \"%1\": placement must be \"above\" or \"right\"").arg(section.id);
return std::nullopt;
}
section.placement = placement == u"above" ? Section::Placement::Above : Section::Placement::Right;
if (!parseRows(obj.value(u"rows"), &section.rows, error)) {
*error = QStringLiteral("section \"%1\": %2").arg(section.id, *error);
return std::nullopt;
}
return section;
}
} // namespace
std::optional<Layout> parse(const QByteArray &json, QString *error)
@@ -142,6 +183,13 @@ std::optional<Layout> parse(const QByteArray &json, QString *error)
layout.layers.append(*layer);
}
for (const QJsonValue &sectionValue : root.value(u"sections").toArray()) {
auto section = parseSection(sectionValue.toObject(), error);
if (!section)
return std::nullopt;
layout.sections.append(*section);
}
if (layout.layers.isEmpty()) {
*error = QStringLiteral("layout has no layers");
return std::nullopt;
+60 -10
View File
@@ -16,6 +16,7 @@ QString keyTypeName(KeyType type)
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: break; // expanded before it reaches the model
}
return {};
@@ -89,6 +90,27 @@ void LayoutModel::setLayer(const QString &layerName)
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);
}
void LayoutModel::setSize(QSizeF size)
{
if (size == m_size)
@@ -140,12 +162,39 @@ int LayoutModel::keyAt(QPointF pos) const
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 += section.rows;
}
if (layer && shown(MainSectionId))
main += layer->rows;
QList<Block> blocks;
if (!main.isEmpty())
blocks.append(main);
for (const Section &section : m_layout.sections) {
if (section.placement == Section::Placement::Right && shown(section.id))
blocks.append(section.rows);
}
// Never show nothing.
if (blocks.isEmpty() && layer)
blocks.append(layer->rows);
return blocks;
}
void LayoutModel::rebuild()
{
beginResetModel();
m_blocks = composeBlocks();
m_keys.clear();
if (const Layer *layer = m_layout.layer(m_layerName)) {
for (const Row &row : layer->rows) {
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_keys.append(key);
@@ -161,17 +210,18 @@ void LayoutModel::rebuild()
void LayoutModel::relayout()
{
m_rects.clear();
const Layer *layer = m_layout.layer(m_layerName);
if (!layer || m_size.isEmpty())
if (m_size.isEmpty())
return;
const QList<QRectF> all = computeKeyRects(*layer, m_size);
const QList<QRectF> all = computeKeyRects(m_blocks, m_size);
qsizetype i = 0;
for (const Row &row : layer->rows) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_rects.append(all[i]);
++i;
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_rects.append(all[i]);
++i;
}
}
}
}
+13 -2
View File
@@ -6,8 +6,9 @@
#include <QAbstractListModel>
#include <QtQml/qqmlregistration.h>
// The keys of the current layer, positioned for the current size.
// Spacers take room but are not exposed as items.
// The keys on screen, positioned for the current size: the current layer
// plus the sections the user chose to show. Spacers take room but are not
// exposed as items.
class LayoutModel : public QAbstractListModel
{
Q_OBJECT
@@ -15,6 +16,8 @@ class LayoutModel : public QAbstractListModel
QML_UNCREATABLE("Created in C++")
Q_PROPERTY(QString layoutName READ layoutName NOTIFY layoutChanged)
Q_PROPERTY(QString layerName READ layerName NOTIFY layerChanged)
// [{id, name}] of the optional sections, for the settings panel.
Q_PROPERTY(QVariantList sections READ sections NOTIFY layoutChanged)
public:
enum Role {
@@ -37,9 +40,14 @@ public:
QString layoutName() const { return m_layout.name; }
QString layerName() const { return m_layerName; }
QVariantList sections() const;
// Width in key units of everything shown.
qreal totalUnits() const;
void setLayout(const Layout &layout);
void setLayer(const QString &layerName);
// Section ids to show; MainSectionId stands for the layers.
void setVisibleSections(const QStringList &ids);
void setSize(QSizeF size);
void setUppercase(bool uppercase);
void setPressed(int index, bool pressed);
@@ -56,11 +64,14 @@ Q_SIGNALS:
void layerChanged();
private:
QList<Block> composeBlocks() const;
void rebuild();
void relayout();
Layout m_layout;
QString m_layerName;
QStringList m_visibleSections{MainSectionId};
QList<Block> m_blocks;
QSizeF m_size;
bool m_uppercase = false;
+13
View File
@@ -52,6 +52,17 @@ void connectLayouts(const Layout &tmpl, WaylandKeymap &keymap, SystemLayouts &sy
rebuild();
}
// Showing or hiding a block resizes the window so keys keep their width.
void connectSections(Settings &settings, LayoutModel &model, FloatingWindow &placement)
{
QObject::connect(&settings, &Settings::visibleSectionsChanged, &model, [&] {
const qreal before = model.totalUnits();
model.setVisibleSections(settings.visibleSections());
if (before > 0)
placement.resizeBy(placement.geometry().width() * (model.totalUnits() / before - 1), 0);
});
}
void restoreAndPersistGeometry(FloatingWindow &placement, WindowSettings &settings)
{
if (const auto geometry = settings.load(placement.screenName()))
@@ -109,6 +120,7 @@ int main(int argc, char *argv[])
Settings settings;
controller.setOptions(settings.typingOptions());
model.setVisibleSections(settings.visibleSections());
QObject::connect(&settings, &Settings::typingOptionsChanged, &controller,
[&] { controller.setOptions(settings.typingOptions()); });
@@ -129,6 +141,7 @@ int main(int argc, char *argv[])
window->setProperty("placement", QVariant::fromValue(&placement));
WindowSettings windowSettings;
restoreAndPersistGeometry(placement, windowSettings);
connectSections(settings, model, placement);
VisibilityController visibility;
connectVisibility(visibility, backend, controller, placement);
+20
View File
@@ -1,10 +1,13 @@
#include "Settings.h"
#include "layout/Layout.h"
#include <QSettings>
namespace {
constexpr auto AutoCapitalizeKey = "typing/autoCapitalize";
constexpr auto DoubleSpacePeriodKey = "typing/doubleSpacePeriod";
constexpr auto VisibleSectionsKey = "layout/visibleSections";
}
Settings::Settings(QObject *parent)
@@ -14,6 +17,23 @@ Settings::Settings(QObject *parent)
const TypingOptions defaults;
m_typing.autoCapitalize = store.value(AutoCapitalizeKey, defaults.autoCapitalize).toBool();
m_typing.doubleSpacePeriod = store.value(DoubleSpacePeriodKey, defaults.doubleSpacePeriod).toBool();
m_visibleSections = store.value(VisibleSectionsKey, QStringList{MainSectionId}).toStringList();
if (m_visibleSections.isEmpty())
m_visibleSections = {MainSectionId};
}
void Settings::setSectionVisible(const QString &id, bool visible)
{
QStringList sections = m_visibleSections;
if (visible && !sections.contains(id))
sections.append(id);
else if (!visible)
sections.removeAll(id);
if (sections == m_visibleSections || sections.isEmpty())
return;
m_visibleSections = sections;
QSettings().setValue(VisibleSectionsKey, m_visibleSections);
Q_EMIT visibleSectionsChanged();
}
void Settings::setAutoCapitalize(bool enabled)
+9
View File
@@ -3,6 +3,7 @@
#include "input/TypingOptions.h"
#include <QObject>
#include <QStringList>
#include <QtQml/qqmlregistration.h>
// User preferences, persisted with QSettings (~/.config/sloposk/sloposk.conf)
@@ -14,6 +15,8 @@ class Settings : public QObject
QML_UNCREATABLE("Created in C++")
Q_PROPERTY(bool autoCapitalize READ autoCapitalize WRITE setAutoCapitalize NOTIFY typingOptionsChanged)
Q_PROPERTY(bool doubleSpacePeriod READ doubleSpacePeriod WRITE setDoubleSpacePeriod NOTIFY typingOptionsChanged)
// Which key blocks are shown: "main" (the typing keys) and template section ids.
Q_PROPERTY(QStringList visibleSections READ visibleSections NOTIFY visibleSectionsChanged)
public:
explicit Settings(QObject *parent = nullptr);
@@ -25,11 +28,17 @@ public:
bool doubleSpacePeriod() const { return m_typing.doubleSpacePeriod; }
void setDoubleSpacePeriod(bool enabled);
QStringList visibleSections() const { return m_visibleSections; }
// Hiding the last visible section is refused: something must stay on screen.
Q_INVOKABLE void setSectionVisible(const QString &id, bool visible);
Q_SIGNALS:
void typingOptionsChanged();
void visibleSectionsChanged();
private:
void setTypingOptions(const TypingOptions &options);
TypingOptions m_typing;
QStringList m_visibleSections;
};