diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 11:27:00 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 11:27:00 +0200 |
| commit | 0e56395aaa46359974a5eb21acaab32b2ea02a0f (patch) | |
| tree | a245b641d3ff6deeabe411fa052d6b22b635726f | |
| parent | 48d1781fed3f9ea5cfd2b270bd92fd2826605636 (diff) | |
| download | krino-10261eb4eb785b49e57e2142228c49dcdf4592b0.tar.gz krino-10261eb4eb785b49e57e2142228c49dcdf4592b0.zip | |
gui: an exclude is not a rule - the form no longer assumes onev0.0.11
Selecting the exclude row in Forms panicked: newFormEditor read the
form's rule for its when, name, action and stop, and an exclude has
only the when. The editor now takes the conditions from whichever of
the two the form holds and leaves out the fields an exclude has not
got. Checklist item 49 covers it.
Also the 0.0.11 changelog and README, and header floors wide enough
that the age heading is not clipped.
| -rw-r--r-- | CHANGELOG.md | 44 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | docs/gui-checklist.md | 2 | ||||
| -rw-r--r-- | gui/internal/ui/forms.go | 26 | ||||
| -rw-r--r-- | gui/internal/ui/plan.go | 4 |
5 files changed, 68 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a981b39..5e4dd69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,53 @@ ## Unreleased +## 0.0.11 โ 2026-09-17 + +The window, worked over from a fortnight's worth of using it: the plan reads +like a table, a filter and a sort over it, choices that act on everything +checked, the file itself shown beside its explanation, and rules edited as a +tree. The command line is unchanged. + +- The plan is a table with headings: file, size, age, action, where it would + go, rule, and an outcome column once it has been applied. The action is in + capitals and carries the running GTK theme's own colours - accent for a + move, the selection colour for a rename, success, warning and error for the + rest - and stays readable on a selected row. Size, age and rule can each be + turned off. +- A filter over the plan: the letters of a name or a rule, in order, capitals + and accents ignored as `(fold yes)` does. `Select all` then takes what is + shown, and the window says how many checked files the filter is hiding. +- A sort picker: as scanned, by name, size, age, action or rule. +- `With checked` sets every checked file to the Trash, or - after a + confirmation naming the count - to a permanent delete. A row's own menu + still does one file at a time. +- A file krino found another copy of names that copy in full, and offers to + keep this one instead: it takes the other's place and the other goes to the + Trash, which `krino undo` reverses. +- The file behind the selected row is shown: a picture for an image, the + first page for a PDF, the first lines of anything that is text. The divider + above it sets how big it is, and the page is rendered to suit. +- The explanation is laid out rather than printed: the name, the size and + age, the copy it duplicates, then a line per step with its action coloured. +- Rules: the conditions of a rule are a tree - `and`, `or` and `not` holding + the conditions under them - the text has line numbers and colours, a + `Check` button, and `Test rule` lists the files a rule would take. + `Add directory` writes `dirs/NAME.conf` and adds it to the include, as + `krino new` does. +- `Settings`, at the top right: krino's own defaults, written to + `krino.conf` with the same care a rules file is saved with, and how the + window behaves - the layout (the file list beside the explanation or above + it), which columns are shown, the sort, the preview, and whether a scan + starts with every file checked. Every setting says what it does. The window + keeps where each divider was left. +- Nothing on disk changes until Apply, Undo or Save: a review choice says so, + and closing with an unapplied plan asks first. - `FileMatch.DuplicateOf`: the file a `(duplicate)` test matched, absolute, so a front end can say where the other copy is and act on it. The reason text is unchanged. +- Fixed: selecting the exclude row in Forms took the window down with it + (0.0.10). An exclude has conditions but no name, action or `(stop)`, and + the form now opens with only what an exclude has. ## 0.0.10 โ 2026-09-16 @@ -8,7 +8,7 @@ shows the plan before touching anything, lets you approve all of it or file by file, logs every step, and can undo a run. Version 0.0.9 is the engine and this command-line interface: per-directory rule files, placeholders in destinations and new names, a keyword cache that makes a rerun fast, and -builds for Linux, FreeBSD and OpenBSD. Version 0.0.10 adds `krino-gui`, an +builds for Linux, FreeBSD and OpenBSD. Version 0.0.11 has `krino-gui`, an optional GTK 4 window onto the same engine (see below). It is not a watch mode, OCR, EXIF dates, macOS or Windows support, a shell-command action, or a way to look inside archives. @@ -260,7 +260,7 @@ release that changes it. ## Status -**0.0.10, local only.** The module path is `krino`; `go install` only +**0.0.11, local only.** The module path is `krino`; `go install` only works once a remote is chosen and the imports are renamed to match (`docs/design.md`, ยง18). Until then, build from a clone. `krino -n --json`'s output shape is unstable before krino 1.0 โ don't diff --git a/docs/gui-checklist.md b/docs/gui-checklist.md index 3eaeb60..4b646b2 100644 --- a/docs/gui-checklist.md +++ b/docs/gui-checklist.md @@ -146,3 +146,5 @@ A dialog is its own window: take it by its own id, not the main window's. 48. A rule's conditions are a tree: and / or / not hold the conditions indented under them, "+" adds a test or an operator under a line, "-" takes one out, and saving writes the same nesting back. +49. Selecting the exclude row in Forms opens its conditions like a rule's, + with no name, action or `(stop)`, and the window does not fall over. diff --git a/gui/internal/ui/forms.go b/gui/internal/ui/forms.go index 18a8b13..fd97546 100644 --- a/gui/internal/ui/forms.go +++ b/gui/internal/ui/forms.go @@ -386,7 +386,7 @@ func (r *settingRow) set(args string) { // formLabel is one line of the list: what the form is, and what it does. func formLabel(f model.Form) string { - if f.Kind == model.ExcludeForm { + if f.Kind == model.ExcludeForm || f.Rule == nil { return "exclude: " + f.Label } var what []string @@ -677,7 +677,7 @@ func newFormEditor(form model.Form, changed func()) *formEditor { fe.root.SetMarginTop(6) fe.root.SetMarginBottom(6) - if form.Kind == model.RuleForm { + if form.Kind == model.RuleForm && form.Rule != nil { fe.name = gtk.NewEntry() fe.name.SetText(form.Rule.Name) fe.name.SetHExpand(true) @@ -692,18 +692,25 @@ func newFormEditor(form model.Form, changed func()) *formEditor { fe.conditions = append(fe.conditions, &model.Cond{Kind: kind}) }))) - when := form.Rule.When - if form.Kind == model.ExcludeForm { - when = form.Exclude.When - } else if !form.Rule.HasWhen { - when = nil + // An exclude has no rule behind it and a rule may have no (when ...) at + // all, so neither pointer is followed without asking first: reading + // form.Rule for an exclude crashed the window (found by the release + // checklist, 2026-09-17). + var when []*sexp.Node + switch { + case form.Kind == model.ExcludeForm: + if form.Exclude != nil { + when = form.Exclude.When + } + case form.Rule != nil && form.Rule.HasWhen: + when = form.Rule.When } for _, c := range when { fe.conditions = append(fe.conditions, model.ParseCond(c)) } fe.drawConds() - if form.Kind == model.RuleForm { + if form.Kind == model.RuleForm && form.Rule != nil { fe.acts = gtk.NewBox(gtk.OrientationVertical, 4) fe.root.Append(heading("Actions - in order")) fe.root.Append(fe.acts) @@ -756,6 +763,9 @@ func (fe *formEditor) text() (string, error) { } return config.PrintExclude(&config.Exclude{When: when}), nil } + if fe.name == nil || fe.stop == nil { + return "", fmt.Errorf("this form has no rule behind it") + } rule := &config.Rule{ Name: fe.name.Text(), HasWhen: len(when) > 0, diff --git a/gui/internal/ui/plan.go b/gui/internal/ui/plan.go index 0adc6b1..5daba6a 100644 --- a/gui/internal/ui/plan.go +++ b/gui/internal/ui/plan.go @@ -672,7 +672,7 @@ func (p *planView) fillList() { // shown whole rather than cut to an ellipsis. The list scrolls sideways // when the total does not fit (his report, 2026-09-16). func (p *planView) widths() [7]int { - w := [7]int{16, 4, 3, 6, 16, 8, 6} + w := [7]int{16, 5, 4, 6, 16, 8, 6} root := p.dirRoot() now := time.Now() for _, r := range p.tab.Rows { @@ -761,7 +761,7 @@ func (p *planView) showOutcome() bool { // headerFloors are how narrow each heading may become, matching the cells // under it. -var headerFloors = [7]int{12, 4, 3, 6, 16, 10, 8} +var headerFloors = [7]int{12, 5, 4, 6, 16, 10, 8} // columnTitles name the columns of the plan. var columnTitles = [7]string{"file", "size", "age", "action", "where it would go", "rule", "outcome"} |
