diff options
| -rw-r--r-- | gui/internal/ui/history.go | 26 | ||||
| -rw-r--r-- | gui/internal/ui/plan.go | 58 |
2 files changed, 71 insertions, 13 deletions
diff --git a/gui/internal/ui/history.go b/gui/internal/ui/history.go index 7d4ee7b..bbdb27d 100644 --- a/gui/internal/ui/history.go +++ b/gui/internal/ui/history.go @@ -244,8 +244,9 @@ func (h *historyView) fillPlan() { h.header.SetText(fmt.Sprintf("%d files · %d to reverse · %d refused", c.Files, c.ToReverse, c.Refused)) } + w := h.widths() for i, r := range h.tab.Rows { - h.list.Append(h.undoRowWidget(i, r)) + h.list.Append(h.undoRowWidget(i, r, w)) } h.updateUndoButton() } @@ -258,8 +259,23 @@ func (h *historyView) clearPlan() { h.undo.SetSensitive(false) } +// widths sizes the undo columns to what this plan holds, as the plan tab's +// do, so a file or a reversal is not cut short for want of a few characters. +func (h *historyView) widths() [3]int { + w := [3]int{16, 16, 6} + for _, r := range h.tab.Rows { + w[0] = max(w[0], len([]rune(r.Dir+"/"+r.File))) + w[1] = max(w[1], len([]rune(undoWhat(r, h.w.dirRoot(r.Dir))))) + w[2] = max(w[2], len([]rune(r.Outcome))) + } + for i, cap := range [3]int{40, 52, 24} { + w[i] = min(w[i], cap) + } + return w +} + // undoRowWidget is one file of the reversal. -func (h *historyView) undoRowWidget(i int, r model.UndoRow) *gtk.ListBoxRow { +func (h *historyView) undoRowWidget(i int, r model.UndoRow, w [3]int) *gtk.ListBoxRow { box := gtk.NewBox(gtk.OrientationHorizontal, 8) box.SetMarginStart(6) box.SetMarginEnd(6) @@ -277,13 +293,13 @@ func (h *historyView) undoRowWidget(i int, r model.UndoRow) *gtk.ListBoxRow { }) box.Append(check) - box.Append(column(escape(r.Dir+"/"+r.File), 24, true)) + box.Append(column(escape(r.Dir+"/"+r.File), w[0], true)) // The outcome comes before the reversal: after an undo it is what the // user is looking for, and the reversal is the column that ellipsizes. if r.Outcome != "" { - box.Append(column(escape(r.Outcome), 12, false)) + box.Append(column(escape(r.Outcome), w[2], false)) } - what := column(escape(undoWhat(r, h.w.dirRoot(r.Dir))), 30, false) + what := columnMin(escape(undoWhat(r, h.w.dirRoot(r.Dir))), w[1], 18, true) if r.Refused != "" { what.AddCSSClass("error") } diff --git a/gui/internal/ui/plan.go b/gui/internal/ui/plan.go index fd2f563..f6f2952 100644 --- a/gui/internal/ui/plan.go +++ b/gui/internal/ui/plan.go @@ -112,7 +112,7 @@ func newPlanView(w *Window) *planView { panes.SetResizeStartChild(true) panes.SetResizeEndChild(false) panes.SetShrinkEndChild(false) - panes.SetPosition(640) + panes.SetPosition(780) panes.SetVExpand(true) p.root.Append(bar) @@ -346,15 +346,37 @@ func (p *planView) fillList() { if p.tab == nil { return } + w := p.widths() for i, r := range p.tab.Rows { - p.list.Append(p.rowWidget(i, r)) + p.list.Append(p.rowWidget(i, r, w)) } p.apply.SetLabel(fmt.Sprintf("Apply %d selected", p.tab.SelectedCount())) p.apply.SetSensitive(!p.tab.Applied && p.tab.SelectedCount() > 0) } +// widths is how wide each column has to be for this plan: enough for the +// longest value it holds, within limits, so a rule name or an outcome is +// 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() [4]int { + w := [4]int{16, 16, 8, 6} + root := p.dirRoot() + for _, r := range p.tab.Rows { + w[0] = max(w[0], len([]rune(r.Rel))) + w[1] = max(w[1], len([]rune(rowLabel(r, root)))) + w[2] = max(w[2], len([]rune(r.Rule))) + w[3] = max(w[3], len([]rune(r.Outcome))) + } + // Past these a single long value would push every other column off the + // window; the details pane holds the whole text either way. + for i, cap := range [4]int{40, 52, 34, 24} { + w[i] = min(w[i], cap) + } + return w +} + // rowWidget is one line of the list. -func (p *planView) rowWidget(i int, r model.Row) *gtk.ListBoxRow { +func (p *planView) rowWidget(i int, r model.Row, w [4]int) *gtk.ListBoxRow { box := gtk.NewBox(gtk.OrientationHorizontal, 8) box.SetMarginStart(6) box.SetMarginEnd(6) @@ -373,11 +395,14 @@ func (p *planView) rowWidget(i int, r model.Row) *gtk.ListBoxRow { }) box.Append(check) - box.Append(column(escape(r.Rel), 24, true)) - box.Append(column(escape(rowLabel(r, p.dirRoot())), 30, false)) - box.Append(column(escape(r.Rule), 12, false)) + // The name and what-would-happen columns share whatever space is left + // and shrink first; the rule keeps its width, so it is never the column + // cut to an ellipsis. + box.Append(column(escape(r.Rel), w[0], true)) + box.Append(columnMin(escape(rowLabel(r, p.dirRoot())), w[1], 18, true)) + box.Append(column(escape(r.Rule), w[2], false)) if r.Outcome != "" { - box.Append(column(escape(r.Outcome), 12, false)) + box.Append(column(escape(r.Outcome), w[3], false)) } row := gtk.NewListBoxRow() row.SetChild(box) @@ -395,17 +420,34 @@ func (p *planView) dirRoot() string { // column is one cell of a row: left-aligned, and ellipsized to chars so // that a long name or warning cannot widen the window past the screen. The // whole text is in the details pane beside the list. +// +// The expanding column - the file's name - may shrink to yieldChars when +// the window is too narrow for every column, so it is the one that gives +// way and the columns beside it (what would happen, and which rule decided) +// stay readable. A fixed column keeps its width and the list scrolls. func column(text string, chars int, expand bool) *gtk.Label { + return columnMin(text, chars, yieldChars, expand) +} + +// columnMin is column with the width it may shrink to given explicitly. +func columnMin(text string, chars, floor int, expand bool) *gtk.Label { l := gtk.NewLabel(text) l.SetXAlign(0) l.SetEllipsize(pango.EllipsizeEnd) - l.SetWidthChars(chars) + if expand { + l.SetWidthChars(min(chars, floor)) + } else { + l.SetWidthChars(chars) + } l.SetMaxWidthChars(chars) l.SetHExpand(expand) l.SetTooltipText(text) return l } +// yieldChars is how narrow an expanding column may become. +const yieldChars = 14 + // showDetails writes the selected file's steps and warnings into the pane. func (p *planView) showDetails(i int) { if p.tab == nil || i < 0 || i >= len(p.tab.Rows) { |
