From e352cd34d30acc1955c62027c779ac98004ac201 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sat, 19 Sep 2026 22:33:33 +0200 Subject: tui: migrate the remaining screens to the widget layer --- clients/tui.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 193 insertions(+), 19 deletions(-) (limited to 'clients/tui.c') diff --git a/clients/tui.c b/clients/tui.c index d19bd17..164b47b 100644 --- a/clients/tui.c +++ b/clients/tui.c @@ -689,8 +689,16 @@ int tui_choice_prompt(const char *label, const char *const *opts, int n, int tui_nav_key(struct tui_list_nav *v, int ch, int allow_new, int allow_remove, int allow_toggle, int menu_mode) { - if (v->n <= 0) - return -3; + if (v->n <= 0) { + /* an empty list is never a dead end: refresh, add and back work */ + if (ch == KEY_F(5)) + return -2; + if (ch == TUI_KEY_CTRL_N && allow_new) + return -4; + if (ch == 27 || ch == 'q') + return -1; + return TUI_NAV_NONE; + } if (v->goto_active) { if (ch >= '0' && ch <= '9' && strlen(v->gotobuf) < 9) { size_t gl = strlen(v->gotobuf); @@ -806,6 +814,16 @@ static void list_view_sync(struct tui_list_nav *v) int tui_select_list(const char *title, char **items, int n, int start, int allow_refresh, int *cursor, int allow_new, const char *archive_action, int allow_toggle) +{ + return tui_select_list_hook(title, items, n, start, allow_refresh, cursor, + allow_new, archive_action, allow_toggle, NULL, + NULL); +} + +int tui_select_list_hook(const char *title, char **items, int n, int start, + int allow_refresh, int *cursor, int allow_new, + const char *archive_action, int allow_toggle, + int (*key)(void *ud, int ch), void *ud) { struct tui_list_nav v; memset(&v, 0, sizeof v); @@ -833,11 +851,21 @@ int tui_select_list(const char *title, char **items, int n, int start, allow_new ? " ^N = ny" : "", dbuf, allow_toggle ? " ^A = öppna/stäng" : ""); list_draw(title, items, n, &v, hint); - int r = tui_nav_key(&v, in_key(), allow_new, - archive_action != NULL, allow_toggle, 0); + int ch = in_key(); + int r = tui_nav_key(&v, ch, allow_new, archive_action != NULL, + allow_toggle, 0); + if (r == TUI_NAV_NONE && key) { + int h = key(ud, ch); + if (h == TUI_HOOK_BACK) + return -1; + if (h == TUI_HOOK_REFRESH) + return -2; + if (h != TUI_HOOK_NONE) + continue; + } if (r == -2 && !allow_refresh) continue; - if (r != -3) + if (r != TUI_NAV_NONE) return r; } } @@ -926,6 +954,12 @@ void tui_frame(const char *title) attroff(A_DIM); } +/* Flush a frame drawn by the application outside a widget loop. */ +void tui_redraw(void) +{ + refresh(); +} + void tui_hints(const char *s) { char line[512]; @@ -941,6 +975,14 @@ void tui_hints(const char *s) /* ------------------------------------------------------------------ */ int tui_pager(const char *title, const char *text, const char *action_hint) +{ + return tui_pager_hook(title, text, action_hint, + action_hint ? TUI_PAGER_SAVE : 0, NULL, NULL); +} + +int tui_pager_hook(const char *title, const char *text, + const char *extra_hint, unsigned flags, + int (*key)(void *ud, int ch), void *ud) { int ret = 0; int nlines = 0; @@ -970,12 +1012,12 @@ int tui_pager(const char *title, const char *text, const char *action_hint) if (top + i < n) mvaddnstr(2 + i, 2, lines[top + i], COLS - 4); } - char hint[256]; - if (action_hint) + char hint[512]; + if (extra_hint && *extra_hint) snprintf(hint, sizeof hint, "piltangenter/PgUp/PgDn rullar F5 = uppdatera %s " "q = tillbaka", - action_hint); + extra_hint); else snprintf(hint, sizeof hint, "piltangenter/PgUp/PgDn rullar F5 = uppdatera " @@ -989,10 +1031,21 @@ int tui_pager(const char *title, const char *text, const char *action_hint) ret = 1; break; } - if (ch == 's' && action_hint) { + if ((flags & TUI_PAGER_SAVE) && ch == 's') { ret = 2; break; } + if (key) { + int h = key(ud, ch); + if (h == TUI_HOOK_BACK) + break; + if (h == TUI_HOOK_REFRESH) { + ret = 1; + break; + } + if (h == TUI_HOOK_STAY) + continue; + } if (ch == KEY_DOWN && top + view < n) top++; else if (ch == KEY_UP && top > 0) @@ -1039,6 +1092,12 @@ int tui_form_next(int sel, int n, int ch) int tui_form_run(const char *title, struct tui_form_field *f, int n, int can_edit) +{ + return tui_form_run_hook(title, f, n, can_edit, 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) { int sel = 0; for (;;) { @@ -1056,7 +1115,8 @@ int tui_form_run(const char *title, struct tui_form_field *f, int n, snprintf(lbl, sizeof lbl, "%s", f[i].label); tui_pad_field(lbl, sizeof lbl, 31); if (f[i].kind == TUI_F_ACTION) { - snprintf(val, sizeof val, "(lista)"); + snprintf(val, sizeof val, "%s", + f[i].value ? f[i].value : "(lista)"); } else if (f[i].kind == TUI_F_AMOUNT && f[i].ore) { tui_kr_format(*f[i].ore, val, sizeof val); } else if (f[i].kind == TUI_F_CHOICE && f[i].choice && @@ -1064,6 +1124,12 @@ int tui_form_run(const char *title, struct tui_form_field *f, int n, int c = *f[i].choice; snprintf(val, sizeof val, "%s", c >= 0 && c < f[i].nchoices ? f[i].choices[c] : ""); + } else if (f[i].mask && f[i].kind == TUI_F_TEXT) { + size_t vl = f[i].value ? strlen(f[i].value) : 0; + if (vl >= sizeof val) + vl = sizeof val - 1; + memset(val, '*', vl); + val[vl] = '\0'; } else { snprintf(val, sizeof val, "%s", f[i].value ? f[i].value : ""); @@ -1094,6 +1160,17 @@ int tui_form_run(const char *title, struct tui_form_field *f, int n, if (nxt == TUI_FORM_REFRESH || nxt == TUI_FORM_SUBMIT || nxt == TUI_FORM_BACK) return nxt; + if (key) { + int h = key(ud, ch); + if (h == TUI_HOOK_BACK) + return TUI_FORM_BACK; + if (h == TUI_HOOK_REFRESH) + return TUI_FORM_REFRESH; + if (h == TUI_HOOK_SUBMIT) + return TUI_FORM_SUBMIT; + if (h != TUI_HOOK_NONE) + continue; + } if (ch != '\n' && ch != '\r' && ch != KEY_ENTER) continue; if (!can_edit) { @@ -1107,7 +1184,8 @@ int tui_form_run(const char *title, struct tui_form_field *f, int n, snprintf(label, sizeof label, "%s: ", fl->label); if (fl->kind == TUI_F_TEXT) { if (fl->value && - tui_prompt_into(fl->value, fl->cap, label, fl->value, 0)) + tui_prompt_into(fl->value, fl->cap, label, fl->value, + fl->mask)) return sel; } else if (fl->kind == TUI_F_DATE) { if (fl->value && tui_date_prompt_into(fl->value, fl->cap, label, @@ -1153,6 +1231,32 @@ void tui_rt_init(struct tui_rt *t, int nrows, int maxrows, int ncols, t->pos = (size_t)-1; } +void tui_rt_set_key(struct tui_rt *t, int (*key)(void *ud, int ch)) +{ + t->key = key; +} + +void tui_rt_set_footer(struct tui_rt *t, + void (*footer)(void *ud, char *buf, size_t n)) +{ + t->footer = footer; +} + +/* Actual column index of the ecol-th editable column. Cell callbacks always + receive the actual column index, also when INFO columns are interleaved. */ +int tui_rt_col_of(const struct tui_rt *t, int ecol) +{ + int ei = 0; + for (int i = 0; i < t->ncols; i++) { + if (t->cols[i].kind == TUI_RT_INFO) + continue; + if (ei == ecol) + return i; + ei++; + } + return 0; +} + static void rt_get(struct tui_rt *t, int row, int col, char **buf, size_t *cap) { t->cell(t->ud, row, col, buf, cap); @@ -1208,6 +1312,19 @@ int tui_rt_move(struct tui_rt *t, int dr, int dc) return moved; } +/* Tab/Shift-Tab: next/previous editable cell, wrapping to the next row. */ +int tui_rt_tab(struct tui_rt *t, int dir) +{ + if (t->neditable <= 0) + return 0; + int moved = tui_rt_move(t, 0, dir); + if (dir > 0 && t->col == 0) + moved |= tui_rt_move(t, 1, 0); + else if (dir < 0 && t->col == t->neditable - 1) + moved |= tui_rt_move(t, -1, 0); + return moved; +} + int tui_rt_clear(struct tui_rt *t) { if (t->row < 0 || t->row >= t->nrows) @@ -1232,10 +1349,24 @@ int tui_rt_clear(struct tui_rt *t) int tui_rt_run(const char *title, struct tui_rt *t, const char *hint) { int top = 0; + int prev_cursor = curs_set(1); for (;;) { if (g_frame) g_frame(title); - int view = LINES - 4 - t->y; + char foot[2048]; + int nfoot = 0; + foot[0] = '\0'; + if (t->footer) { + t->footer(t->ud, foot, sizeof foot); + for (char *p = foot; *p;) { + nfoot++; + char *nl = strchr(p, '\n'); + if (!nl) + break; + p = nl + 1; + } + } + int view = LINES - 4 - t->y - nfoot; if (view < 1) view = 1; if (t->row < top) @@ -1300,24 +1431,66 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint) } } } + if (nfoot > 0) { + int fy = t->y + (t->nrows - top < view ? t->nrows - top : view); + if (fy + nfoot > LINES - 2) + fy = LINES - 2 - nfoot; + if (fy < t->y) + fy = t->y; + char *p = foot; + for (int i = 0; i < nfoot && fy + i < LINES - 2; i++) { + char *nl = strchr(p, '\n'); + if (nl) + *nl = '\0'; + move(fy + i, 2); + clrtoeol(); + attron(A_DIM); + addnstr(p, COLS - 4); + attroff(A_DIM); + p = nl ? nl + 1 : p + strlen(p); + } + } if (g_hints) g_hints(hint); if (caret_y >= 0) move(caret_y, caret_x); refresh(); int ch = in_key(); - if (ch == 27 || ch == 'q') + if (ch == 27 || ch == 'q') { + curs_set(prev_cursor == ERR ? 0 : prev_cursor); return -1; - if (ch == KEY_F(5)) + } + if (ch == KEY_F(5)) { + curs_set(prev_cursor == ERR ? 0 : prev_cursor); return -2; - if (ch == TUI_KEY_CTRL_ENTER || ch == KEY_F(9)) + } + if (ch == TUI_KEY_CTRL_ENTER || ch == KEY_F(9)) { + curs_set(prev_cursor == ERR ? 0 : prev_cursor); return -3; + } + if (t->key) { + int h = t->key(t->ud, ch); + if (h == TUI_HOOK_BACK) { + curs_set(prev_cursor == ERR ? 0 : prev_cursor); + return -1; + } + if (h == TUI_HOOK_REFRESH) { + curs_set(prev_cursor == ERR ? 0 : prev_cursor); + return -2; + } + if (h == TUI_HOOK_SUBMIT) { + curs_set(prev_cursor == ERR ? 0 : prev_cursor); + return -3; + } + if (h == TUI_HOOK_STAY) + continue; + } if (ch == '\t') { - tui_rt_move(t, 0, 1); + tui_rt_tab(t, 1); continue; } if (ch == KEY_BTAB) { - tui_rt_move(t, 0, -1); + tui_rt_tab(t, -1); continue; } if (ch == KEY_UP) { @@ -1332,12 +1505,13 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint) tui_rt_clear(t); continue; } + int ci = tui_rt_col_of(t, t->col); char *buf = NULL; size_t cap = 0; - rt_get(t, t->row, t->col, &buf, &cap); + rt_get(t, t->row, ci, &buf, &cap); if (!buf) continue; - if (t->cols[t->col].kind == TUI_RT_DATE) + if (t->cols[ci].kind == TUI_RT_DATE) tui_date_field_edit(buf, cap, &t->pos, ch, &t->fresh); else tui_field_edit(buf, cap, &t->pos, ch, &t->fresh); -- cgit v1.3