From 07f5b140922d13519ad7a0d1dad67a9b403be9f1 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Tue, 22 Sep 2026 12:43:52 +0200 Subject: tui: kundutkast, , explicit Spara och F2-åtgärder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clients/tui.c | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) (limited to 'clients/tui.c') diff --git a/clients/tui.c b/clients/tui.c index db5599d..13da593 100644 --- a/clients/tui.c +++ b/clients/tui.c @@ -17,6 +17,12 @@ static int (*g_in)(void) = getch; static void (*g_frame)(const char *title); static void (*g_hints)(const char *hints); static void (*g_quit)(void); +static const char *g_list_hint_extra; + +void tui_list_hint_extra(const char *extra) +{ + g_list_hint_extra = extra; +} void tui_set_input(int (*fn)(void)) { @@ -1082,6 +1088,10 @@ int tui_select_list_hook(const char *title, char **items, int n, int start, allow_refresh ? " F5 = uppdatera" : "", allow_new ? " ^N = ny" : "", dbuf, allow_toggle ? " ^A = öppna/stäng" : ""); + if (g_list_hint_extra && *g_list_hint_extra) { + size_t hl = strlen(hint); + snprintf(hint + hl, sizeof hint - hl, " %s", g_list_hint_extra); + } list_draw(title, items, n, &v, hint); int ch = in_key(); int r = tui_nav_key(&v, ch, allow_new, archive_action != NULL, @@ -1190,6 +1200,7 @@ int tui_menu(const char *title, const char *const *items, int n, /* ------------------------------------------------------------------ */ static const char *g_form_hint; +static const char *g_form_hint_extra; static char g_status[512]; static char g_right[160]; @@ -1198,6 +1209,11 @@ void tui_form_hint(const char *hint) g_form_hint = hint; } +void tui_form_hint_extra(const char *extra) +{ + g_form_hint_extra = extra; +} + void tui_set_status(const char *status, const char *right) { snprintf(g_status, sizeof g_status, "%s", status ? status : ""); @@ -1531,6 +1547,174 @@ void tui_form_act_label(const struct tui_form_action *a, char *buf, size_t n) } } +static void action_key_name(int key, char *buf, size_t n) +{ + if (key >= KEY_F(1) && key <= KEY_F(12)) + snprintf(buf, n, "F%d", key - KEY_F(0)); + else if (key == TUI_KEY_CTRL_N) + snprintf(buf, n, "^N"); + else if (key == TUI_KEY_CTRL_X) + snprintf(buf, n, "^X"); + else if (key == 27) + snprintf(buf, n, "Esc"); + else if (key == '\n' || key == '\r' || key == KEY_ENTER) + snprintf(buf, n, "Enter"); + else if (key > 0 && key < 27) + snprintf(buf, n, "^%c", 'A' + key - 1); + else + snprintf(buf, n, "?"); +} + +void tui_action_label(const struct tui_action *a, char *buf, size_t n) +{ + const char *label = a && a->label ? a->label : ""; + + if (a && a->enabled == 0) { + const char *reason = + a->reason && *a->reason ? a->reason : "otillgänglig"; + snprintf(buf, n, "%s (%s)", label, reason); + } else { + snprintf(buf, n, "%s", label); + } +} + +void tui_action_hint(const struct tui_action *acts, int n, int max, char *buf, + size_t cap) +{ + size_t o = 0; + int shown = 0; + + if (!buf || cap == 0) + return; + buf[0] = '\0'; + for (int i = 0; i < n && (max <= 0 || shown < max); i++) { + char key[16], item[200]; + size_t len; + + if (acts[i].enabled != 1 || !acts[i].key) + continue; + action_key_name(acts[i].key, key, sizeof key); + snprintf(item, sizeof item, "%s%s = %s", shown ? " " : "", key, + acts[i].label ? acts[i].label : ""); + len = strlen(item); + if (o + len + 1 > cap) + break; + memcpy(buf + o, item, len + 1); + o += len; + shown++; + } + const char *more = "F2 = fler"; + size_t ml = strlen(more); + if (o + ml + (o ? 3 : 0) + 1 <= cap) + snprintf(buf + o, cap - o, "%s%s", o ? " " : "", more); +} + +int tui_action_menu(const char *title, const struct tui_action *acts, int n, + int *focus) +{ + struct tui_list_nav v; + unsigned char *ok; + char **items; + int ret = -1; + + if (!acts || n <= 0) + return -1; + memset(&v, 0, sizeof v); + v.n = n; + ok = xcalloc((size_t)n, 1); + items = xcalloc((size_t)n, sizeof(char *)); + for (int i = 0; i < n; i++) { + char line[192]; + + tui_action_label(&acts[i], line, sizeof line); + items[i] = xstrdup(line); + ok[i] = acts[i].enabled != -1; + } + v.selectable = ok; + v.sel = focus && *focus >= 0 && *focus < n ? *focus : 0; + if (!ok[v.sel]) + v.sel = nav_snap(&v, v.sel, 1); + v.numw = tui_num_width(n); + if (v.numw < 2) + v.numw = 2; + v.view = LINES - 4; + snprintf(v.gotolabel, sizeof v.gotolabel, "Gå till nummer: "); + for (;;) { + int num = 0; + + if (v.sel < 0 || v.sel >= n) { + v.sel = nav_snap(&v, 0, 1); + if (v.sel < 0) + break; + } + if (focus) + *focus = v.sel; + list_view_sync(&v); + if (g_frame) + g_frame(title); + for (int i = 0; i < v.top && i < n; i++) + if (ok[i]) + num++; + for (int idx = v.top; idx < n && idx < v.top + v.view; idx++) { + char line[832]; + if (!ok[idx]) { + snprintf(line, sizeof line, "%*s %s", v.numw, "", + items[idx]); + attron(tui_style_attrs(TUI_DIM, g_colours, 0)); + mvaddnstr(3 + (idx - v.top), 4, line, COLS - 6); + attroff(tui_style_attrs(TUI_DIM, g_colours, 0)); + continue; + } + num++; + snprintf(line, sizeof line, "%*d. %s", v.numw, num, items[idx]); + if (acts[idx].enabled != 1) + attron(tui_style_attrs(TUI_DIM, g_colours, 0)); + if (idx == v.sel) + attron(A_REVERSE); + mvaddnstr(3 + (idx - v.top), 4, line, COLS - 6); + if (idx == v.sel) + attroff(A_REVERSE); + if (acts[idx].enabled != 1) + attroff(tui_style_attrs(TUI_DIM, g_colours, 0)); + } + if (v.goto_active) { + move(LINES - 2, 2); + tui_style(TUI_ACCENT); + printw("%s%s", v.gotolabel, v.gotobuf); + tui_style_reset(); + clrtoeol(); + } else { + move(LINES - 2, 2); + clrtoeol(); + } + if (g_hints) + g_hints("upp/ned, 1-9 = hoppa, g = gå till, PgUp/PgDn, Home/End," + " Enter = utför Esc/q = avbryt"); + refresh(); + int ch = in_key(); + if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { + if (!ok[v.sel]) + continue; + if (acts[v.sel].enabled != 1) { + tui_message(title, "%s", + acts[v.sel].reason && *acts[v.sel].reason + ? acts[v.sel].reason + : "Otillgänglig."); + continue; + } + ret = v.sel; + break; + } + if (tui_nav_key(&v, ch, 0, 0, 0, 0) == -1) + break; + } + for (int i = 0; i < n; i++) + free(items[i]); + free(items); + free(ok); + return ret; +} + /* 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) @@ -1628,6 +1812,11 @@ static int form_run(const char *title, struct tui_form_field *f, int nf, : "upp/ned/Home/End F5 = uppdatera" " Esc/q = tillbaka ^C = avsluta" " (endast behöriga kan ändra)"); + char hbuf[640]; + if (g_form_hint_extra && *g_form_hint_extra && + snprintf(hbuf, sizeof hbuf, "%s %s", h, g_form_hint_extra) < + (int)sizeof hbuf) + h = hbuf; tui_hints(h); g_form_hint = NULL; refresh(); -- cgit v1.3