aboutsummaryrefslogtreecommitdiff
path: root/clients/tui.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-19 22:33:33 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-19 22:33:33 +0200
commite352cd34d30acc1955c62027c779ac98004ac201 (patch)
tree8a94163b837fdc16e334c159d91a9f622d42763d /clients/tui.c
parent84c4fdcf138ad6ef4f7edfea76f2318b7a7a5e3e (diff)
downloadbokf-e352cd34d30acc1955c62027c779ac98004ac201.tar.gz
bokf-e352cd34d30acc1955c62027c779ac98004ac201.zip
tui: migrate the remaining screens to the widget layerv0.1.38
Diffstat (limited to 'clients/tui.c')
-rw-r--r--clients/tui.c212
1 files changed, 193 insertions, 19 deletions
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);
@@ -807,6 +815,16 @@ 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);
v.n = n;
@@ -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];
@@ -942,6 +976,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;
for (const char *p = text; *p; p++)
@@ -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)
@@ -1040,6 +1093,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 (;;) {
if (sel >= n)
@@ -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);