diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 00:23:51 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 00:23:51 +0200 |
| commit | 04b4243ad31d144c4caf9be4c5096a0f27a2648e (patch) | |
| tree | 056515d145ba8e4fba29a2150b2c58199133b0c1 /gui/internal/ui/settings.go | |
| parent | 9db67b201b80e9b7f824989df8517cefc587036d (diff) | |
| download | krino-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.go | 191 |
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] +} |
