aboutsummaryrefslogtreecommitdiff
path: root/gui/internal/ui/settings.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 00:23:51 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 00:23:51 +0200
commit04b4243ad31d144c4caf9be4c5096a0f27a2648e (patch)
tree056515d145ba8e4fba29a2150b2c58199133b0c1 /gui/internal/ui/settings.go
parent9db67b201b80e9b7f824989df8517cefc587036d (diff)
downloadkrino-04b4243ad31d144c4caf9be4c5096a0f27a2648e.tar.gz
krino-04b4243ad31d144c4caf9be4c5096a0f27a2648e.zip
gui: syntax colours, a file preview, and a settings window
Diffstat (limited to 'gui/internal/ui/settings.go')
-rw-r--r--gui/internal/ui/settings.go191
1 files changed, 191 insertions, 0 deletions
diff --git a/gui/internal/ui/settings.go b/gui/internal/ui/settings.go
new file mode 100644
index 0000000..2db3450
--- /dev/null
+++ b/gui/internal/ui/settings.go
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package ui
+
+import (
+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
+
+ "krino/gui/internal/model"
+ "krino/internal/xdg"
+)
+
+// settingsWindow is Settings: krino's defaults, which every directory
+// inherits, and how this window behaves. The first are written to
+// krino.conf with the care a rules file is written with - checked first,
+// the previous text kept as krino.conf.bak - and the second to gui.json,
+// which krino itself never reads (his request, 2026-09-16).
+type settingsWindow struct {
+ w *Window
+ win *gtk.Window
+ main *model.MainConf
+
+ rows []*settingRow
+ check *gtk.Label
+ save *gtk.Button
+ toSave bool
+}
+
+// showSettings opens it, one at a time.
+func (w *Window) showSettings() {
+ main, err := model.OpenMain(w.engine)
+ if err != nil {
+ w.setStatus("settings: %v", err)
+ return
+ }
+ s := &settingsWindow{w: w, main: main}
+ s.win = gtk.NewWindow()
+ s.win.SetTitle("krino settings")
+ s.win.SetTransientFor(&w.win.Window)
+ s.win.SetModal(true)
+ s.win.SetDefaultSize(560, 640)
+
+ box := gtk.NewBox(gtk.OrientationVertical, 8)
+ box.SetMarginStart(12)
+ box.SetMarginEnd(12)
+ box.SetMarginTop(12)
+ box.SetMarginBottom(12)
+
+ box.Append(heading("Defaults for every directory - " + xdg.Abbrev(main.File)))
+ note := gtk.NewLabel("An empty field is one krino.conf does not set, and krino's own default applies. A directory's file overrides these.")
+ note.SetXAlign(0)
+ note.SetWrap(true)
+ note.AddCSSClass("dim-label")
+ box.Append(note)
+ for _, head := range append(append([]string{}, model.MainSettings...), "log") {
+ args, _, err := main.Setting(head)
+ if err != nil {
+ w.setStatus("settings: %v", err)
+ return
+ }
+ row := newSettingRow(head, args, s.armSave)
+ s.rows = append(s.rows, row)
+ box.Append(row.root)
+ }
+ s.check = gtk.NewLabel("")
+ s.check.SetXAlign(0)
+ s.check.SetWrap(true)
+ box.Append(s.check)
+
+ box.Append(heading("This window"))
+ prefs := w.prefs
+ font := gtk.NewSpinButtonWithRange(0, 32, 1)
+ font.SetValue(float64(prefs.FontSize))
+ font.SetTooltipText("the editor's font size in points; 0 keeps the theme's")
+ colours := gtk.NewCheckButtonWithLabel("colour the configuration in the Text tab")
+ colours.SetActive(prefs.Colours)
+ preview := gtk.NewCheckButtonWithLabel("show the file behind the selected row")
+ preview.SetActive(prefs.Preview)
+ selectAll := gtk.NewCheckButtonWithLabel("a scanned plan starts with every file checked")
+ selectAll.SetActive(prefs.SelectAll)
+ box.Append(field("editor font", font))
+ box.Append(colours)
+ box.Append(preview)
+ box.Append(selectAll)
+
+ apply := func() {
+ p := model.Prefs{
+ FontSize: int(font.Value()),
+ Colours: colours.Active(),
+ Preview: preview.Active(),
+ SelectAll: selectAll.Active(),
+ }
+ w.applyPrefs(p)
+ if err := p.Save(); err != nil {
+ w.setStatus("settings: %v", err)
+ }
+ }
+ font.ConnectValueChanged(apply)
+ colours.ConnectToggled(apply)
+ preview.ConnectToggled(apply)
+ selectAll.ConnectToggled(apply)
+
+ s.save = gtk.NewButtonWithLabel("Save krino.conf")
+ s.save.AddCSSClass("suggested-action")
+ s.save.SetSensitive(false)
+ s.save.ConnectClicked(s.onSave)
+ closeBtn := gtk.NewButtonWithLabel("Close")
+ closeBtn.ConnectClicked(func() { s.win.Close() })
+ // The buttons sit outside the scrolled area: on a short screen they
+ // would otherwise be below the fold, which is where he found them.
+ buttons := gtk.NewBox(gtk.OrientationHorizontal, 6)
+ buttons.SetHAlign(gtk.AlignEnd)
+ buttons.SetMarginStart(12)
+ buttons.SetMarginEnd(12)
+ buttons.SetMarginTop(8)
+ buttons.SetMarginBottom(12)
+ buttons.Append(closeBtn)
+ buttons.Append(s.save)
+
+ scroll := gtk.NewScrolledWindow()
+ scroll.SetChild(box)
+ scroll.SetVExpand(true)
+ outer := gtk.NewBox(gtk.OrientationVertical, 0)
+ outer.Append(scroll)
+ outer.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
+ outer.Append(buttons)
+ s.win.SetChild(outer)
+ s.win.Show()
+}
+
+// armSave writes what the fields hold into the text and checks it, without
+// touching the file: Save is what writes.
+func (s *settingsWindow) armSave() {
+ for _, row := range s.rows {
+ value := row.value()
+ was, _, err := s.main.Setting(row.head)
+ if err != nil {
+ s.check.SetText(escape(err.Error()))
+ return
+ }
+ if value == was {
+ continue
+ }
+ if err := s.main.SetSetting(row.head, value); err != nil {
+ s.check.SetText(escape(err.Error()))
+ row.set(was)
+ continue
+ }
+ s.toSave = true
+ }
+ diags := s.main.Check()
+ switch {
+ case len(diags) == 0:
+ s.check.SetText("")
+ s.save.SetSensitive(s.toSave && s.main.Modified())
+ default:
+ s.check.SetText(escape(diags[0].Error()))
+ s.check.AddCSSClass("error")
+ s.save.SetSensitive(false)
+ }
+}
+
+// onSave writes krino.conf and makes the new defaults the ones every tab
+// works from.
+func (s *settingsWindow) onSave() {
+ if err := s.main.Save(); err != nil {
+ s.check.SetText(escape(err.Error()))
+ return
+ }
+ s.check.RemoveCSSClass("error")
+ s.check.SetText("saved; the previous text is beside it as krino.conf.bak")
+ s.save.SetSensitive(false)
+ if err := s.w.reloadEngine(); err != nil {
+ s.w.setStatus("saved, but the new configuration does not load: %v", err)
+ return
+ }
+ s.w.setStatus("saved %s", escape(xdg.Abbrev(s.main.File)))
+}
+
+// settingHintsMain is the example shown in an empty krino.conf field, for
+// the settings the directory form does not already describe.
+var settingHintsMain = map[string]string{
+ "log": `"~/.local/state/krino/krino.log"`,
+}
+
+// mainHint is the example for a krino.conf setting.
+func mainHint(head string) string {
+ if h, ok := settingHintsMain[head]; ok {
+ return h
+ }
+ return settingHints[head]
+}