From 1f2d7a8190876e3d1c541251e079a11a2cc637f2 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sat, 19 Sep 2026 23:53:09 +0200 Subject: tui: bokslut hub with year fields, reports and the posting action --- clients/tui.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 167 insertions(+), 25 deletions(-) (limited to 'clients/tui.c') diff --git a/clients/tui.c b/clients/tui.c index a964f65..a5b0cd2 100644 --- a/clients/tui.c +++ b/clients/tui.c @@ -1341,27 +1341,122 @@ int tui_form_next(int sel, int n, int ch) return TUI_NAV_NONE; } -int tui_form_run(const char *title, struct tui_form_field *f, int n, - int can_edit) +static int act_selectable(const struct tui_form_action *acts, int i, int na) { - return tui_form_run_hook(title, f, n, can_edit, NULL, NULL); + return acts && i >= 0 && i < na && acts[i].enabled != -1; } -int tui_form_run_hook(const char *title, struct tui_form_field *f, int n, - int can_edit, int (*key)(void *ud, int ch), void *ud) +int tui_form_act_step(int focus, int nf, const struct tui_form_action *acts, + int na, int dir) +{ + if (nf < 0) + nf = 0; + if (na < 0) + na = 0; + int n = nf + na; + if (n <= 0) + return -1; + int pos = focus >= 0 && focus < n ? focus : 0; + for (int i = 0; i < n; i++) { + pos = (pos + (dir >= 0 ? 1 : -1) + n) % n; + if (pos < nf || act_selectable(acts, pos - nf, na)) + return pos; + } + return focus; +} + +int tui_form_act_first(int nf, const struct tui_form_action *acts, int na) +{ + if (nf > 0) + return 0; + for (int i = 0; i < na; i++) + if (act_selectable(acts, i, na)) + return i; + return -1; +} + +int tui_form_act_key(int sel, int nf, const struct tui_form_action *acts, + int na, int ch) +{ + int n = (nf > 0 ? nf : 0) + (na > 0 ? na : 0); + if (ch == KEY_UP || ch == KEY_DOWN || ch == '\t' || ch == KEY_BTAB) { + int dir = ch == KEY_UP || ch == KEY_BTAB ? -1 : 1; + return tui_form_act_step(sel, nf, acts, na, dir); + } + if (ch == KEY_HOME) + return tui_form_act_first(nf, acts, na); + if (ch == KEY_END) { + for (int i = n - 1; i >= 0; i--) { + if (i < nf || act_selectable(acts, i - nf, na)) + return i; + } + return tui_form_act_first(nf, acts, na); + } + if (ch == KEY_NPAGE || ch == KEY_PPAGE) { + int dir = ch == KEY_NPAGE ? 1 : -1; + int pos = sel; + for (int i = 0; i < TUI_FORM_PAGE; i++) + pos = tui_form_act_step(pos, nf, acts, na, dir); + return pos; + } + return tui_form_next(sel, nf, ch); +} + +void tui_form_act_label(const struct tui_form_action *a, char *buf, size_t n) +{ + const char *label = a && a->label ? a->label : ""; + if (a && a->enabled == 0) { + const char *reason = a->disabled_reason && *a->disabled_reason + ? a->disabled_reason + : "otillgänglig"; + snprintf(buf, n, "%s (%s)", label, reason); + } else { + snprintf(buf, n, "%s", label); + } +} + +/* Action rows sit under the fields, one blank line below them. */ +static void form_draw_actions(const struct tui_form_action *acts, int na, + int nf, int sel) +{ + int y = 5 + nf + 1; + for (int i = 0; i < na; i++) { + if (y + i >= LINES - 2) + break; + char line[832]; + tui_form_act_label(&acts[i], line, sizeof line); + int dim = acts[i].enabled != 1; + if (dim) + attron(tui_style_attrs(TUI_DIM, g_colours, 0)); + if (sel == nf + i) + attron(A_REVERSE); + mvaddnstr(y + i, 2, line, COLS - 4); + if (sel == nf + i) + attroff(A_REVERSE); + if (dim) + attroff(tui_style_attrs(TUI_DIM, g_colours, 0)); + } +} + +static int form_run(const char *title, struct tui_form_field *f, int nf, + int can_edit, const struct tui_form_action *acts, int na, + const char *hint, int (*key)(void *ud, int ch), void *ud) { - int sel = 0; + int sel = nf > 0 ? 0 : tui_form_act_first(0, acts, na); for (;;) { - if (sel >= n) - sel = n - 1; + int ntot = nf + (na > 0 ? na : 0); + if (ntot <= 0) + return TUI_FORM_BACK; if (sel < 0) - sel = 0; + sel = tui_form_act_first(nf, acts, na); + if (sel >= ntot) + sel = ntot - 1; tui_frame(title); tui_style(TUI_HEADING); mvaddstr(3, 2, "Uppgift"); mvaddstr(3, 34, "Värde"); tui_style_reset(); - for (int i = 0; i < n; i++) { + for (int i = 0; i < nf; i++) { char lbl[160], val[640], line[832]; snprintf(lbl, sizeof lbl, "%s", f[i].label); tui_pad_field(lbl, sizeof lbl, 31); @@ -1396,19 +1491,32 @@ int tui_form_run_hook(const char *title, struct tui_form_field *f, int n, else if (!can_edit) attroff(tui_style_attrs(TUI_DIM, g_colours, 0)); } - tui_hints(g_form_hint - ? g_form_hint - : (can_edit - ? "upp/ned/Home/End Enter = ändra" - " F5 = uppdatera ^Enter = spara" - " Esc/q = tillbaka ^C = avsluta" - : "upp/ned/Home/End F5 = uppdatera" - " Esc/q = tillbaka ^C = avsluta" - " (endast behöriga kan ändra)")); + if (na > 0) + form_draw_actions(acts, na, nf, sel); + const char *h = g_form_hint ? g_form_hint : hint; + if (!h) + h = na > 0 + ? (can_edit + ? "upp/ned/Tab = flytta Enter = ändra/utför" + " F5 = uppdatera ^Enter = spara" + " Esc/q = tillbaka ^C = avsluta" + : "upp/ned/Tab = flytta Enter = utför" + " F5 = uppdatera Esc/q = tillbaka" + " ^C = avsluta" + " (endast behöriga kan ändra)") + : (can_edit + ? "upp/ned/Home/End Enter = ändra" + " F5 = uppdatera ^Enter = spara" + " Esc/q = tillbaka ^C = avsluta" + : "upp/ned/Home/End F5 = uppdatera" + " Esc/q = tillbaka ^C = avsluta" + " (endast behöriga kan ändra)"); + tui_hints(h); g_form_hint = NULL; refresh(); int ch = in_key(); - int nxt = tui_form_next(sel, n, ch); + int nxt = na > 0 ? tui_form_act_key(sel, nf, acts, na, ch) + : tui_form_next(sel, nf, ch); if (nxt >= 0) { sel = nxt; continue; @@ -1417,18 +1525,33 @@ int tui_form_run_hook(const char *title, struct tui_form_field *f, int n, nxt == TUI_FORM_BACK) return nxt; if (key) { - int h = key(ud, ch); - if (h == TUI_HOOK_BACK) + int hk = key(ud, ch); + if (hk == TUI_HOOK_BACK) return TUI_FORM_BACK; - if (h == TUI_HOOK_REFRESH) + if (hk == TUI_HOOK_REFRESH) return TUI_FORM_REFRESH; - if (h == TUI_HOOK_SUBMIT) + if (hk == TUI_HOOK_SUBMIT) return TUI_FORM_SUBMIT; - if (h != TUI_HOOK_NONE) + if (hk != TUI_HOOK_NONE) continue; } if (ch != '\n' && ch != '\r' && ch != KEY_ENTER) continue; + if (sel < 0) + continue; /* no focusable row: only the return keys work */ + if (sel >= nf) { + const struct tui_form_action *act = &acts[sel - nf]; + if (act->enabled == 0) { + tui_message(title, "%s", + act->disabled_reason && *act->disabled_reason + ? act->disabled_reason + : "Otillgänglig."); + continue; + } + if (act->enabled == -1) + continue; + return TUI_FORM_ACTION + (sel - nf); + } if (!can_edit) { tui_message(title, "Endast behöriga kan ändra."); continue; @@ -1461,6 +1584,25 @@ int tui_form_run_hook(const char *title, struct tui_form_field *f, int n, } } +int tui_form_run(const char *title, struct tui_form_field *f, int n, + int can_edit) +{ + return form_run(title, f, n, can_edit, NULL, 0, NULL, NULL, NULL); +} + +int tui_form_run_hook(const char *title, struct tui_form_field *f, int n, + int can_edit, int (*key)(void *ud, int ch), void *ud) +{ + return form_run(title, f, n, can_edit, NULL, 0, NULL, key, ud); +} + +int tui_form_run_actions(const char *title, struct tui_form_field *f, int nf, + int can_edit, const struct tui_form_action *acts, + int na, const char *hint) +{ + return form_run(title, f, nf, can_edit, acts, na, hint, NULL, NULL); +} + /* ------------------------------------------------------------------ */ /* row table (dynamic multi-column editor with a trailing blank row) */ /* ------------------------------------------------------------------ */ -- cgit v1.3