Cycle checkboxes with the mouse wheel
Scrolling over a checkbox glyph cycles its state unchecked -> partial -> checked (and back), respecting scroll direction: wheel up steps forward, wheel down steps in reverse. Left-click still toggles checked/unchecked. The shared rewriteCheckbox helper applies a state-mapping function to the Nth cookie; toggleCheckbox and the new cycleCheckbox are thin callers.
This commit is contained in:
parent
3d3ccd1e11
commit
39ab7108ff
|
|
@ -473,7 +473,8 @@ ListView {
|
||||||
font.pointSize: outline.baseFontSize + 1
|
font.pointSize: outline.baseFontSize + 1
|
||||||
font.bold: cb !== ""
|
font.bold: cb !== ""
|
||||||
|
|
||||||
// Clickable only for checkbox items; toggles via the model.
|
// Checkbox interaction: left-click toggles,
|
||||||
|
// mouse wheel cycles ( ) -> (-) -> (X).
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: cbGlyph.cb !== ""
|
enabled: cbGlyph.cb !== ""
|
||||||
|
|
@ -481,6 +482,15 @@ ListView {
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: docModel.toggleCheckbox(
|
onClicked: docModel.toggleCheckbox(
|
||||||
del.index, modelData.checkboxOrdinal ?? -1)
|
del.index, modelData.checkboxOrdinal ?? -1)
|
||||||
|
onWheel: (wheel) => {
|
||||||
|
// Scroll up = forward, down = reverse.
|
||||||
|
let forward = wheel.angleDelta.y > 0
|
||||||
|
docModel.cycleCheckbox(
|
||||||
|
del.index,
|
||||||
|
modelData.checkboxOrdinal ?? -1,
|
||||||
|
forward)
|
||||||
|
wheel.accepted = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,9 @@ bool DocumentModel::isLanguageSupported(const QString &lang)
|
||||||
return m_supportedLanguages.contains(lang.trimmed().toLower());
|
return m_supportedLanguages.contains(lang.trimmed().toLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocumentModel::toggleCheckbox(int row, int checkboxOrdinal)
|
bool DocumentModel::rewriteCheckbox(
|
||||||
|
int row, int checkboxOrdinal,
|
||||||
|
const std::function<QString(const QString &)> &nextState)
|
||||||
{
|
{
|
||||||
if (!m_client || row < 0 || row >= m_visibleRows.size())
|
if (!m_client || row < 0 || row >= m_visibleRows.size())
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -210,28 +212,24 @@ bool DocumentModel::toggleCheckbox(int row, int checkboxOrdinal)
|
||||||
out.reserve(lines.size());
|
out.reserve(lines.size());
|
||||||
|
|
||||||
int seen = -1;
|
int seen = -1;
|
||||||
bool toggled = false;
|
bool changed = false;
|
||||||
for (const QString &line : lines) {
|
for (const QString &line : lines) {
|
||||||
QRegularExpressionMatch m = cookieRe.match(line);
|
QRegularExpressionMatch m = cookieRe.match(line);
|
||||||
if (m.hasMatch()) {
|
if (m.hasMatch()) {
|
||||||
++seen;
|
++seen;
|
||||||
if (seen == checkboxOrdinal) {
|
if (seen == checkboxOrdinal) {
|
||||||
const QString state = m.captured(2);
|
const QString next = nextState(m.captured(2));
|
||||||
const QString next = (state == QLatin1String("X") ||
|
|
||||||
state == QLatin1String("x"))
|
|
||||||
? QStringLiteral(" ")
|
|
||||||
: QStringLiteral("X");
|
|
||||||
QString updated = line;
|
QString updated = line;
|
||||||
updated.replace(m.capturedStart(2), m.capturedLength(2), next);
|
updated.replace(m.capturedStart(2), m.capturedLength(2), next);
|
||||||
out.append(updated);
|
out.append(updated);
|
||||||
toggled = true;
|
changed = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.append(line);
|
out.append(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toggled)
|
if (!changed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const QString newBody = out.join('\n');
|
const QString newBody = out.join('\n');
|
||||||
|
|
@ -247,6 +245,37 @@ bool DocumentModel::toggleCheckbox(int row, int checkboxOrdinal)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DocumentModel::toggleCheckbox(int row, int checkboxOrdinal)
|
||||||
|
{
|
||||||
|
// Checked -> unchecked; unchecked/partial -> checked.
|
||||||
|
return rewriteCheckbox(row, checkboxOrdinal, [](const QString &state) {
|
||||||
|
const bool checked = (state == QLatin1String("X") ||
|
||||||
|
state == QLatin1String("x"));
|
||||||
|
return checked ? QStringLiteral(" ") : QStringLiteral("X");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DocumentModel::cycleCheckbox(int row, int checkboxOrdinal, bool forward)
|
||||||
|
{
|
||||||
|
// Cycle order: unchecked ( ) -> partial (-) -> checked (X) -> wrap.
|
||||||
|
return rewriteCheckbox(row, checkboxOrdinal, [forward](const QString &state) {
|
||||||
|
// Normalize current state to an index in the cycle.
|
||||||
|
int cur;
|
||||||
|
if (state == QLatin1String("-"))
|
||||||
|
cur = 1; // partial
|
||||||
|
else if (state == QLatin1String("X") || state == QLatin1String("x"))
|
||||||
|
cur = 2; // checked
|
||||||
|
else
|
||||||
|
cur = 0; // unchecked
|
||||||
|
const int next = forward ? (cur + 1) % 3 : (cur + 2) % 3;
|
||||||
|
switch (next) {
|
||||||
|
case 1: return QStringLiteral("-");
|
||||||
|
case 2: return QStringLiteral("X");
|
||||||
|
default: return QStringLiteral(" ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void DocumentModel::loadDocument()
|
void DocumentModel::loadDocument()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class OrkClient;
|
class OrkClient;
|
||||||
|
|
||||||
class DocumentModel : public QAbstractListModel {
|
class DocumentModel : public QAbstractListModel {
|
||||||
|
|
@ -76,6 +78,11 @@ public:
|
||||||
// back via /body, and refreshes the row. Returns true on success.
|
// back via /body, and refreshes the row. Returns true on success.
|
||||||
Q_INVOKABLE bool toggleCheckbox(int row, int checkboxOrdinal);
|
Q_INVOKABLE bool toggleCheckbox(int row, int checkboxOrdinal);
|
||||||
|
|
||||||
|
// Cycle the Nth checkbox through unchecked -> checked -> partial and back.
|
||||||
|
// `forward` steps unchecked->checked->partial; otherwise the reverse. Writes
|
||||||
|
// the section body back via /body and refreshes the row. Returns success.
|
||||||
|
Q_INVOKABLE bool cycleCheckbox(int row, int checkboxOrdinal, bool forward);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void documentChanged();
|
void documentChanged();
|
||||||
|
|
||||||
|
|
@ -123,6 +130,12 @@ private:
|
||||||
// Rebuild the visible row list based on expansion state
|
// Rebuild the visible row list based on expansion state
|
||||||
void rebuildVisibleRows();
|
void rebuildVisibleRows();
|
||||||
|
|
||||||
|
// Rewrite the Nth checkbox cookie (document order) in the given row's
|
||||||
|
// section body, mapping the current state char (' '/'x'/'X'/'-') to a new
|
||||||
|
// one via `nextState`. Writes back via /body and refreshes. Returns success.
|
||||||
|
bool rewriteCheckbox(int row, int checkboxOrdinal,
|
||||||
|
const std::function<QString(const QString &)> &nextState);
|
||||||
|
|
||||||
OrkClient *m_client = nullptr;
|
OrkClient *m_client = nullptr;
|
||||||
QString m_document;
|
QString m_document;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue