aboutsummaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 11:44:09 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 11:44:09 +0200
commit629ec29e33506a9bf96f552dcc2e6b6cd0560916 (patch)
tree5d7ff9fd4b938764a4f9a98cc09b03b5fb2fdc6c /gui
parent0e56395aaa46359974a5eb21acaab32b2ea02a0f (diff)
downloadkrino-629ec29e33506a9bf96f552dcc2e6b6cd0560916.tar.gz
krino-629ec29e33506a9bf96f552dcc2e6b6cd0560916.zip
gui: the toolbar in the order things happen, and an About block
The Plan bar now reads left to right as the work does: which directory, how it should be listed, what of it, where it is on disk - then Scan, and only then what to do with what came back. Scan carries the theme's accent, being the button that starts everything. Settings ends with About: the program and the version it was built as, what it is in a sentence, the licence, and who to write to. The text is model.About so it is tested; ui.Version is stamped by main. leak-check knows krino's own contact address, so the address check stays useful without a per-clone setting.
Diffstat (limited to 'gui')
-rw-r--r--gui/cmd/krino-gui/main.go1
-rw-r--r--gui/internal/model/about.go27
-rw-r--r--gui/internal/model/about_test.go43
-rw-r--r--gui/internal/ui/plan.go20
-rw-r--r--gui/internal/ui/settings.go13
-rw-r--r--gui/internal/ui/window.go4
6 files changed, 101 insertions, 7 deletions
diff --git a/gui/cmd/krino-gui/main.go b/gui/cmd/krino-gui/main.go
index f9a9759..25f4837 100644
--- a/gui/cmd/krino-gui/main.go
+++ b/gui/cmd/krino-gui/main.go
@@ -35,6 +35,7 @@ func main() {
}
e.CacheDir = cacheDir()
+ ui.Version = version
app := gtk.NewApplication("xyz.labunix.krino", 0)
app.ConnectActivate(func() { ui.NewWindow(app, e).Show() })
if code := app.Run(nil); code != 0 {
diff --git a/gui/internal/model/about.go b/gui/internal/model/about.go
new file mode 100644
index 0000000..e92a0f4
--- /dev/null
+++ b/gui/internal/model/about.go
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package model
+
+// The people and terms behind the program, in one place so About and any
+// later --about agree. The repository line joins these once there is a
+// public one to name.
+const (
+ Author = "Lukasz Kasprzak"
+ Contact = "contact@labunix.xyz"
+ Licence = "GPL-3.0-or-later"
+)
+
+// About is the block Settings shows at its foot: what this is, which build
+// it is, and under what terms. The first line names the program and its
+// version; the rest are the small print.
+func About(version string) []string {
+ if version == "" {
+ version = "dev"
+ }
+ return []string{
+ "krino " + version,
+ "A rule-based file sorter: it reads a directory, works out what belongs where, shows you the plan, and only then moves anything.",
+ "Licence: " + Licence + " - free software, with no warranty.",
+ "By " + Author + ", " + Contact,
+ }
+}
diff --git a/gui/internal/model/about_test.go b/gui/internal/model/about_test.go
new file mode 100644
index 0000000..00a6718
--- /dev/null
+++ b/gui/internal/model/about_test.go
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package model_test
+
+import (
+ "strings"
+ "testing"
+
+ "krino/gui/internal/model"
+)
+
+// TestAboutSaysWhatItIs: the About block names the program, the version it
+// was built as, the licence, who wrote it and how to reach them.
+func TestAboutSaysWhatItIs(t *testing.T) {
+ lines := model.About("0.0.12")
+ if len(lines) == 0 {
+ t.Fatal("About returned nothing")
+ }
+ whole := strings.Join(lines, "\n")
+ for _, want := range []string{
+ "krino",
+ "0.0.12",
+ "GPL-3.0-or-later",
+ "Lukasz Kasprzak",
+ "contact@labunix.xyz",
+ } {
+ if !strings.Contains(whole, want) {
+ t.Errorf("About does not mention %q:\n%s", want, whole)
+ }
+ }
+ if !strings.HasPrefix(lines[0], "krino ") {
+ t.Errorf("the first line should name the program and version, got %q", lines[0])
+ }
+}
+
+// TestAboutWithoutAVersion: a plain `go build` leaves "dev", and About says
+// so rather than printing an empty version.
+func TestAboutWithoutAVersion(t *testing.T) {
+ lines := model.About("")
+ if !strings.Contains(lines[0], "dev") {
+ t.Errorf("an unstamped build should read as dev, got %q", lines[0])
+ }
+}
diff --git a/gui/internal/ui/plan.go b/gui/internal/ui/plan.go
index 5daba6a..fe99a18 100644
--- a/gui/internal/ui/plan.go
+++ b/gui/internal/ui/plan.go
@@ -92,6 +92,10 @@ func newPlanView(w *Window) *planView {
p.path.SetMaxWidthChars(20)
p.dirs.Connect("notify::selected", p.showPath)
p.scan = gtk.NewButtonWithLabel("Scan")
+ // The one button that starts everything, so it carries the theme's
+ // accent like Apply does (his request, 2026-09-17).
+ p.scan.AddCSSClass("suggested-action")
+ p.scan.SetTooltipText("read the directory and work out what would happen to each file; nothing is touched until Apply")
p.selAll = gtk.NewButtonWithLabel("Select all")
p.selNone = gtk.NewButtonWithLabel("None")
p.apply = gtk.NewButtonWithLabel("Apply")
@@ -104,26 +108,28 @@ func newPlanView(w *Window) *planView {
bar.SetMarginStart(6)
bar.SetMarginEnd(6)
bar.SetMarginBottom(6)
- bar.Append(gtk.NewLabel("Directory"))
- bar.Append(p.dirs)
- bar.Append(p.scan)
// A filter over the plan: type a few letters of a name, as fzf does,
- // and act on what is left. It sits by the Scan button, where the eye
- // starts (his request, 2026-09-17).
+ // and act on what is left.
p.filter = gtk.NewSearchEntry()
p.filter.SetPlaceholderText("filter")
p.filter.SetTooltipText("show only the files whose name or rule has these letters, in order; Select all then checks those")
p.filter.SetSizeRequest(200, -1)
- bar.Append(p.filter)
// The order the plan is read in. It starts as the settings say and can
// be changed for this window alone (his request, 2026-09-17).
p.sort = gtk.NewDropDownFromStrings(sortItems())
p.sort.SetTooltipText("the order the plan is listed in; Settings has the one a new window starts with")
+
+ // The bar reads as the order of operations: which directory, how it
+ // will be listed, what of it, where it is on disk - then Scan, and only
+ // then what to do with what comes back (his request, 2026-09-17).
+ bar.Append(gtk.NewLabel("Directory"))
+ bar.Append(p.dirs)
bar.Append(gtk.NewLabel("sort"))
bar.Append(p.sort)
-
+ bar.Append(p.filter)
bar.Append(p.path)
+ bar.Append(p.scan)
bar.Append(p.selAll)
bar.Append(p.selNone)
bar.Append(p.checkedMenu())
diff --git a/gui/internal/ui/settings.go b/gui/internal/ui/settings.go
index e15f60d..73c4072 100644
--- a/gui/internal/ui/settings.go
+++ b/gui/internal/ui/settings.go
@@ -124,6 +124,19 @@ func (w *Window) showSettings() {
box.Append(colours)
box.Append(preview)
+ box.Append(heading("About"))
+ for i, line := range model.About(Version) {
+ l := gtk.NewLabel(line)
+ l.SetXAlign(0)
+ l.SetWrap(true)
+ // Selectable so the address can be copied out of the window.
+ l.SetSelectable(true)
+ if i > 0 {
+ l.AddCSSClass("dim-label")
+ }
+ box.Append(l)
+ }
+
apply := func() {
which := model.LayoutSide
if layout.Selected() == 1 {
diff --git a/gui/internal/ui/window.go b/gui/internal/ui/window.go
index ce991d8..5132c12 100644
--- a/gui/internal/ui/window.go
+++ b/gui/internal/ui/window.go
@@ -20,6 +20,10 @@ import (
"krino/internal/xdg"
)
+// Version is what About shows. main stamps it at build time; "dev" is
+// what a plain `go build` leaves.
+var Version = "dev"
+
// Window is krino-gui's one window: three tabs, and a status bar.
type Window struct {
app *gtk.Application