Add an org path setting with a directory picker to Settings
Let the user change the server's scanned org directory from the GUI, backed by the server's /cfg file. The server expands a leading ~ and rescans, so the client only sends a path. OrkClient: - orgPath() reads /cfg and extracts the org_path value (basic TOML string, with escapes). - setOrgPath() writes org_path to /cfg with proper TOML escaping, and emits orgPathChanged() on success. On failure the server's message is left in error(). SettingsDialog: - New "Org Path" group: a text field prefilled from orgPath() when the dialog opens, a Browse… button opening a FolderDialog (QtQuick.Dialogs), and an Apply button. The folder picker's file:// URL is converted to a local path. A status line shows success or the server error. The field accepts a raw path too, so ~ and typed paths work. Main.qml: - On orgPathChanged, clear the selection and refresh the document list so the sidebar reflects the newly scanned directory. Verified over 9P against a live server using the exact TOML setApply writes: empty initial config, ~/org expanded to $HOME/org, and an absolute Browse-style path each switch the served documents. GUI builds clean (QML cache compiles SettingsDialog).
This commit is contained in:
parent
7c9779b2fc
commit
c4dc18cbfc
|
|
@ -83,6 +83,14 @@ ApplicationWindow {
|
|||
Connections {
|
||||
target: orkClient
|
||||
function onConnectedChanged() { root.refreshDocs() }
|
||||
// When the org path changes via Settings, the server has rescanned a
|
||||
// new directory: drop the current selection and reload the doc list.
|
||||
function onOrgPathChanged() {
|
||||
root.currentDoc = ""
|
||||
docModel.document = ""
|
||||
docModel.reload()
|
||||
root.refreshDocs()
|
||||
}
|
||||
}
|
||||
|
||||
// Settings dialog
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
Dialog {
|
||||
id: settingsDialog
|
||||
|
|
@ -9,10 +10,46 @@ Dialog {
|
|||
standardButtons: Dialog.Close
|
||||
width: 450
|
||||
height: 400
|
||||
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
// Load the current org path from the server whenever the dialog opens.
|
||||
onOpened: {
|
||||
orgPathField.text = orkClient.orgPath()
|
||||
orgPathStatus.clear()
|
||||
}
|
||||
|
||||
// Native folder picker. Writes the chosen local path into the field; the
|
||||
// user still presses Apply to send it to the server.
|
||||
FolderDialog {
|
||||
id: folderDialog
|
||||
title: qsTr("Select Org Directory")
|
||||
onAccepted: {
|
||||
// selectedFolder is a file:// URL; convert to a local path.
|
||||
orgPathField.text = decodeURIComponent(
|
||||
selectedFolder.toString().replace(/^file:\/\//, ""))
|
||||
}
|
||||
}
|
||||
|
||||
// Send the field's path to the server via /cfg. On success, refresh the
|
||||
// document list; on failure, show the server's error message.
|
||||
function applyOrgPath() {
|
||||
var path = orgPathField.text.trim()
|
||||
if (path.length === 0) {
|
||||
orgPathStatus.ok = false
|
||||
orgPathStatus.text = qsTr("Path is empty.")
|
||||
return
|
||||
}
|
||||
if (orkClient.setOrgPath(path)) {
|
||||
orgPathStatus.ok = true
|
||||
orgPathStatus.text = qsTr("Applied. Scanning %1").arg(path)
|
||||
} else {
|
||||
orgPathStatus.ok = false
|
||||
orgPathStatus.text = qsTr("Failed: %1").arg(orkClient.error)
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: theme.windowColor
|
||||
border.color: theme.midColor
|
||||
|
|
@ -46,6 +83,75 @@ Dialog {
|
|||
contentItem: ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
// --- Org Path ---
|
||||
GroupBox {
|
||||
title: qsTr("Org Path")
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
border.color: theme.midColor
|
||||
border.width: 1
|
||||
radius: 4
|
||||
y: 12
|
||||
}
|
||||
|
||||
label: Label {
|
||||
x: 8
|
||||
text: parent.title
|
||||
color: theme.textColor
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: qsTr("Directory the server scans for .org files. A leading ~ expands to your home directory.")
|
||||
color: theme.placeholderTextColor
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.85 : 10
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
TextField {
|
||||
id: orgPathField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. ~/org")
|
||||
selectByMouse: true
|
||||
onAccepted: applyOrgPath()
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Browse…")
|
||||
onClicked: folderDialog.open()
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Apply")
|
||||
onClicked: applyOrgPath()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: orgPathStatus
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
visible: text.length > 0
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.85 : 10
|
||||
// ok/err color set by applyOrgPath via property.
|
||||
property bool ok: true
|
||||
color: ok ? theme.doneColor : theme.deadlineColor
|
||||
function clear() { text = "" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Color Scheme ---
|
||||
GroupBox {
|
||||
title: qsTr("Color Scheme")
|
||||
|
|
|
|||
|
|
@ -61,6 +61,51 @@ QString OrkClient::rdwr(const QString &path, const QString &text) {
|
|||
return QString::fromUtf8(data);
|
||||
}
|
||||
|
||||
// Extract the org_path value from a TOML config body. Handles a basic
|
||||
// `org_path = "..."` assignment with standard TOML string escapes. Returns an
|
||||
// empty string if the key is absent.
|
||||
static QString parseOrgPath(const QString &toml) {
|
||||
const QStringList lines = toml.split('\n');
|
||||
for (QString line : lines) {
|
||||
line = line.trimmed();
|
||||
if (line.startsWith('#')) continue;
|
||||
if (!line.startsWith("org_path")) continue;
|
||||
const int eq = line.indexOf('=');
|
||||
if (eq < 0) continue;
|
||||
QString val = line.mid(eq + 1).trimmed();
|
||||
if (val.size() < 2 || !val.startsWith('"') || !val.endsWith('"')) continue;
|
||||
val = val.mid(1, val.size() - 2);
|
||||
// Unescape the common TOML basic-string escapes we emit.
|
||||
val.replace("\\\\", "\\");
|
||||
val.replace("\\\"", "\"");
|
||||
return val;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Escape a string for a TOML basic string (double-quoted).
|
||||
static QString tomlEscape(const QString &s) {
|
||||
QString out;
|
||||
out.reserve(s.size());
|
||||
for (const QChar c : s) {
|
||||
if (c == '\\' || c == '"') out.append('\\');
|
||||
out.append(c);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString OrkClient::orgPath() {
|
||||
return parseOrgPath(read("/cfg"));
|
||||
}
|
||||
|
||||
bool OrkClient::setOrgPath(const QString &path) {
|
||||
const QString body = QStringLiteral("org_path = \"%1\"\n").arg(tomlEscape(path));
|
||||
const bool ok = write("/cfg", body);
|
||||
syncError();
|
||||
if (ok) emit orgPathChanged();
|
||||
return ok;
|
||||
}
|
||||
|
||||
QVariantList OrkClient::docs() {
|
||||
QVariantList out;
|
||||
const QString idx = read("/idx");
|
||||
|
|
|
|||
|
|
@ -45,6 +45,16 @@ public:
|
|||
// Write-then-read against a control/query file (/ctl, /query, .../new).
|
||||
Q_INVOKABLE QString rdwr(const QString &path, const QString &text);
|
||||
|
||||
// Read the server's org_path setting from /cfg (empty if unset). This is
|
||||
// the directory the server scans for org files.
|
||||
Q_INVOKABLE QString orgPath();
|
||||
|
||||
// Set the server's org_path in /cfg. The server validates and applies it
|
||||
// (switching the scanned directory, expanding a leading ~), then persists
|
||||
// the config. Returns true on success; on failure the reason is in error().
|
||||
// Emits orgPathChanged() when the write succeeds.
|
||||
Q_INVOKABLE bool setOrgPath(const QString &path);
|
||||
|
||||
// Parse /idx into a list of {name, title, path} maps for the doc sidebar.
|
||||
Q_INVOKABLE QVariantList docs();
|
||||
|
||||
|
|
@ -54,6 +64,7 @@ public:
|
|||
signals:
|
||||
void connectedChanged();
|
||||
void errorChanged();
|
||||
void orgPathChanged();
|
||||
|
||||
private:
|
||||
void syncError();
|
||||
|
|
|
|||
Loading…
Reference in New Issue