Make GUI checkboxes clickable
Clicking a checkbox glyph toggles its state and writes the change back. DocumentModel::toggleCheckbox(row, ordinal) finds the Nth checkbox cookie in the section body (document order), flips [ ]/[-] -> [X] and [X] -> [ ], writes the body via /body, and refreshes the row. The body parser assigns each checkbox item a stable ordinal across the whole section, and the list delegate wires a pointer-cursor MouseArea on the glyph (checkbox items only) to the model toggle.
This commit is contained in:
parent
754051b3a4
commit
55856707c7
|
|
@ -94,7 +94,9 @@ ListView {
|
|||
|
||||
// Push a run of text, splitting out any contiguous list items into
|
||||
// dedicated "list" parts so they render with bullets/checkboxes.
|
||||
function pushText(text) {
|
||||
// `cbCounter` is a single-element array used as a mutable counter so
|
||||
// checkbox ordinals are stable across the whole section body.
|
||||
function pushText(text, cbCounter) {
|
||||
if (!text) return;
|
||||
let lines = text.split('\n');
|
||||
let buf = [];
|
||||
|
|
@ -116,6 +118,9 @@ ListView {
|
|||
let item = parseListItem(ln);
|
||||
if (item) {
|
||||
flushBuf();
|
||||
// Assign a stable ordinal to checkbox-bearing items.
|
||||
item.checkboxOrdinal = (item.checkbox !== "")
|
||||
? cbCounter[0]++ : -1;
|
||||
items.push(item);
|
||||
} else {
|
||||
flushItems();
|
||||
|
|
@ -126,6 +131,9 @@ ListView {
|
|||
flushBuf();
|
||||
}
|
||||
|
||||
// Running checkbox ordinal across the whole section body.
|
||||
let cbCounter = [0];
|
||||
|
||||
let lines = body.split('\n');
|
||||
let currentText = [];
|
||||
let inCode = false;
|
||||
|
|
@ -143,7 +151,7 @@ ListView {
|
|||
if (!inCode && !inTable && trimmed.toUpperCase().startsWith("#+BEGIN_SRC")) {
|
||||
// Flush pending text
|
||||
if (currentText.length > 0) {
|
||||
pushText(currentText.join('\n').trim());
|
||||
pushText(currentText.join('\n').trim(), cbCounter);
|
||||
currentText = [];
|
||||
}
|
||||
inCode = true;
|
||||
|
|
@ -162,7 +170,7 @@ ListView {
|
|||
else if (!inTable && trimmed.startsWith("|")) {
|
||||
// Flush pending text
|
||||
if (currentText.length > 0) {
|
||||
pushText(currentText.join('\n').trim());
|
||||
pushText(currentText.join('\n').trim(), cbCounter);
|
||||
currentText = [];
|
||||
}
|
||||
inTable = true;
|
||||
|
|
@ -212,7 +220,7 @@ ListView {
|
|||
}
|
||||
parts.push({type: "table", rows: rows, hasHeader: hasHeader});
|
||||
} else if (currentText.length > 0) {
|
||||
pushText(currentText.join('\n').trim());
|
||||
pushText(currentText.join('\n').trim(), cbCounter);
|
||||
}
|
||||
|
||||
return parts;
|
||||
|
|
@ -447,6 +455,7 @@ ListView {
|
|||
|
||||
// Checkbox glyph, or a plain bullet when no checkbox.
|
||||
Label {
|
||||
id: cbGlyph
|
||||
readonly property string cb: modelData.checkbox ?? ""
|
||||
text: {
|
||||
switch (cb) {
|
||||
|
|
@ -463,6 +472,16 @@ ListView {
|
|||
: theme.placeholderTextColor
|
||||
font.pointSize: outline.baseFontSize + 1
|
||||
font.bold: cb !== ""
|
||||
|
||||
// Clickable only for checkbox items; toggles via the model.
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: cbGlyph.cb !== ""
|
||||
visible: enabled
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: docModel.toggleCheckbox(
|
||||
del.index, modelData.checkboxOrdinal ?? -1)
|
||||
}
|
||||
}
|
||||
|
||||
// Item text; checked items are dimmed and struck through.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "orkclient.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
|
||||
static const QStringList DONE_KEYWORDS = {"DONE", "CANCELLED", "CANCELED"};
|
||||
|
||||
|
|
@ -186,6 +187,66 @@ bool DocumentModel::isLanguageSupported(const QString &lang)
|
|||
return m_supportedLanguages.contains(lang.trimmed().toLower());
|
||||
}
|
||||
|
||||
bool DocumentModel::toggleCheckbox(int row, int checkboxOrdinal)
|
||||
{
|
||||
if (!m_client || row < 0 || row >= m_visibleRows.size())
|
||||
return false;
|
||||
|
||||
const QString &id = m_visibleRows[row];
|
||||
if (!m_sections.contains(id))
|
||||
return false;
|
||||
|
||||
Section &sec = m_sections[id];
|
||||
if (!sec.bodyLoaded)
|
||||
loadBody(sec);
|
||||
|
||||
// Match a list item's checkbox cookie: leading indent, a bullet (- / +) or
|
||||
// ordered marker (N. / N)), then a [ ]/[x]/[X]/[-] cookie.
|
||||
static const QRegularExpression cookieRe(
|
||||
QStringLiteral("^(\\s*(?:[-+]|\\d+[.)])\\s+\\[)([ xX-])(\\])"));
|
||||
|
||||
const QStringList lines = sec.body.split('\n');
|
||||
QStringList out;
|
||||
out.reserve(lines.size());
|
||||
|
||||
int seen = -1;
|
||||
bool toggled = false;
|
||||
for (const QString &line : lines) {
|
||||
QRegularExpressionMatch m = cookieRe.match(line);
|
||||
if (m.hasMatch()) {
|
||||
++seen;
|
||||
if (seen == checkboxOrdinal) {
|
||||
const QString state = m.captured(2);
|
||||
const QString next = (state == QLatin1String("X") ||
|
||||
state == QLatin1String("x"))
|
||||
? QStringLiteral(" ")
|
||||
: QStringLiteral("X");
|
||||
QString updated = line;
|
||||
updated.replace(m.capturedStart(2), m.capturedLength(2), next);
|
||||
out.append(updated);
|
||||
toggled = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.append(line);
|
||||
}
|
||||
|
||||
if (!toggled)
|
||||
return false;
|
||||
|
||||
const QString newBody = out.join('\n');
|
||||
if (!m_client->write(sec.path + QStringLiteral("/body"), newBody))
|
||||
return false;
|
||||
|
||||
// Refresh from the server so the parsed state matches what was written.
|
||||
sec.bodyLoaded = false;
|
||||
loadBody(sec);
|
||||
|
||||
const QModelIndex idx = index(row);
|
||||
emit dataChanged(idx, idx, {BodyRole});
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocumentModel::loadDocument()
|
||||
{
|
||||
beginResetModel();
|
||||
|
|
|
|||
|
|
@ -70,6 +70,12 @@ public:
|
|||
// `/ctl languages` (loaded once and cached). Case-insensitive.
|
||||
Q_INVOKABLE bool isLanguageSupported(const QString &lang);
|
||||
|
||||
// Toggle the Nth checkbox (0-based, in document order, counting only list
|
||||
// items that carry a `[ ]`/`[X]`/`[-]` cookie) in the section at the given
|
||||
// visible row. Flips [ ]/[-] -> [X] and [X] -> [ ], writes the section body
|
||||
// back via /body, and refreshes the row. Returns true on success.
|
||||
Q_INVOKABLE bool toggleCheckbox(int row, int checkboxOrdinal);
|
||||
|
||||
signals:
|
||||
void documentChanged();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue