summaryrefslogtreecommitdiff
path: root/clients/tui.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-19 22:49:06 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-19 22:49:06 +0200
commit730d7159e3b9b3adfba2c3779893fdb4e107647a (patch)
tree04a08f99adf7b5519d5bf65111453e4124e69720 /clients/tui.c
parente352cd34d30acc1955c62027c779ac98004ac201 (diff)
downloadbokf-730d7159e3b9b3adfba2c3779893fdb4e107647a.tar.gz
bokf-730d7159e3b9b3adfba2c3779893fdb4e107647a.zip
tui: form fields on the row-table screen; home/end/pgup/pgdn navigationv0.1.39
Diffstat (limited to 'clients/tui.c')
-rw-r--r--clients/tui.c284
1 files changed, 237 insertions, 47 deletions
diff --git a/clients/tui.c b/clients/tui.c
index 164b47b..de9b9f9 100644
--- a/clients/tui.c
+++ b/clients/tui.c
@@ -44,6 +44,13 @@ void tui_keys_setup(void)
/* ^Enter has no control code; enable xterm modifyOtherKeys and bind the
sequence it produces. Terminals without it ignore the request. */
define_key("\033[27;5;13~", TUI_KEY_CTRL_ENTER);
+ /* Home/End arrive as CSI or SS3 depending on the terminal */
+ define_key("\033[1~", KEY_HOME);
+ define_key("\033OH", KEY_HOME);
+ define_key("\033[H", KEY_HOME);
+ define_key("\033[4~", KEY_END);
+ define_key("\033OF", KEY_END);
+ define_key("\033[F", KEY_END);
putp("\033[>4;1m");
fflush(stdout);
}
@@ -1081,6 +1088,18 @@ int tui_form_next(int sel, int n, int ch)
return sel > 0 ? sel - 1 : TUI_NAV_NONE;
if (ch == KEY_DOWN)
return sel < n - 1 ? sel + 1 : TUI_NAV_NONE;
+ if (ch == KEY_HOME)
+ return 0;
+ if (ch == KEY_END)
+ return n - 1;
+ if (ch == KEY_NPAGE) {
+ int p = sel + TUI_FORM_PAGE;
+ return p > n - 1 ? n - 1 : p;
+ }
+ if (ch == KEY_PPAGE) {
+ int p = sel - TUI_FORM_PAGE;
+ return p < 0 ? 0 : p;
+ }
if (ch == KEY_F(5))
return TUI_FORM_REFRESH;
if (ch == TUI_KEY_CTRL_ENTER || ch == KEY_F(9))
@@ -1229,6 +1248,7 @@ void tui_rt_init(struct tui_rt *t, int nrows, int maxrows, int ncols,
t->ud = ud;
t->fresh = 1;
t->pos = (size_t)-1;
+ t->focus = -1;
}
void tui_rt_set_key(struct tui_rt *t, int (*key)(void *ud, int ch))
@@ -1242,6 +1262,70 @@ void tui_rt_set_footer(struct tui_rt *t,
t->footer = footer;
}
+void tui_rt_set_fields(struct tui_rt *t, struct tui_form_field *fields, int n)
+{
+ t->fields = fields;
+ t->nfields = n > 0 ? n : 0;
+ t->focus = t->nfields > 0 ? 0 : -1;
+}
+
+int tui_rt_focus_step(int focus, int nf, int *row, int *col, int nrows,
+ int neditable, int dir)
+{
+ int ncells = nrows > 0 && neditable > 0 ? nrows * neditable : 0;
+ int n = nf + ncells;
+ if (n <= 0) {
+ if (row)
+ *row = 0;
+ if (col)
+ *col = 0;
+ return -1;
+ }
+ int pos;
+ if (focus >= 0 && focus < nf) {
+ pos = focus;
+ } else {
+ int r = row ? *row : 0;
+ int c = col ? *col : 0;
+ if (r < 0)
+ r = 0;
+ if (r >= nrows)
+ r = nrows - 1;
+ if (c < 0)
+ c = 0;
+ if (c >= neditable)
+ c = neditable - 1;
+ pos = nf + r * neditable + c;
+ }
+ if (pos < 0 || pos >= n)
+ pos = 0;
+ pos = (pos + (dir >= 0 ? 1 : -1) + n) % n;
+ if (pos < nf)
+ return pos;
+ if (ncells > 0) {
+ int ci = pos - nf;
+ if (row)
+ *row = ci / neditable;
+ if (col)
+ *col = ci % neditable;
+ return -1;
+ }
+ return 0;
+}
+
+int tui_rt_footer_row(int lines)
+{
+ int y = lines - 2;
+ return y < 2 ? 2 : y;
+}
+
+void tui_rt_footer_flatten(char *s)
+{
+ for (; s && *s; s++)
+ if (*s == '\n' || *s == '\r')
+ *s = ' ';
+}
+
/* 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)
@@ -1344,6 +1428,86 @@ int tui_rt_clear(struct tui_rt *t)
return 1;
}
+static void rt_field_value(const struct tui_form_field *f, char *val,
+ size_t n)
+{
+ if (f->kind == TUI_F_ACTION) {
+ snprintf(val, n, "%s", f->value ? f->value : "(lista)");
+ } else if (f->kind == TUI_F_AMOUNT && f->ore) {
+ tui_kr_format(*f->ore, val, n);
+ } else if (f->kind == TUI_F_CHOICE && f->choice && f->choices) {
+ int c = *f->choice;
+ snprintf(val, n, "%s",
+ c >= 0 && c < f->nchoices ? f->choices[c] : "");
+ } else if (f->mask && f->kind == TUI_F_TEXT) {
+ size_t vl = f->value ? strlen(f->value) : 0;
+ if (vl >= n)
+ vl = n - 1;
+ memset(val, '*', vl);
+ val[vl] = '\0';
+ } else {
+ snprintf(val, n, "%s", f->value ? f->value : "");
+ }
+}
+
+static void rt_draw_fields(const struct tui_rt *t)
+{
+ if (t->nfields <= 0)
+ return;
+ attron(A_BOLD);
+ mvaddstr(3, 2, "Uppgift");
+ mvaddstr(3, 34, "Värde");
+ attroff(A_BOLD);
+ for (int i = 0; i < t->nfields; i++) {
+ char lbl[160], val[640], line[832];
+ snprintf(lbl, sizeof lbl, "%s", t->fields[i].label);
+ tui_pad_field(lbl, sizeof lbl, 31);
+ rt_field_value(&t->fields[i], val, sizeof val);
+ snprintf(line, sizeof line, "%s %s", lbl, val);
+ if (i == t->focus)
+ attron(A_REVERSE);
+ mvaddnstr(5 + i, 2, line, COLS - 4);
+ if (i == t->focus)
+ attroff(A_REVERSE);
+ }
+}
+
+/* Opens the editor for header field i; on success focus advances to the
+ next field (or the table). Esc cancels and keeps the focus. */
+static void rt_edit_field(struct tui_rt *t, int i)
+{
+ if (i < 0 || i >= t->nfields)
+ return;
+ struct tui_form_field *fl = &t->fields[i];
+ char label[192];
+ snprintf(label, sizeof label, "%s: ", fl->label);
+ int ok = 0;
+ if (fl->kind == TUI_F_TEXT) {
+ if (fl->value && tui_prompt_into(fl->value, fl->cap, label, fl->value,
+ fl->mask))
+ ok = 1;
+ } else if (fl->kind == TUI_F_DATE) {
+ if (fl->value && tui_date_prompt_into(fl->value, fl->cap, label,
+ fl->value))
+ ok = 1;
+ } else if (fl->kind == TUI_F_AMOUNT) {
+ if (fl->ore && tui_amount_prompt_into(fl->ore, label, *fl->ore))
+ ok = 1;
+ } else if (fl->kind == TUI_F_CHOICE && fl->choice) {
+ int c = tui_choice_prompt(label, fl->choices, fl->nchoices,
+ *fl->choice);
+ if (c >= 0) {
+ *fl->choice = c;
+ ok = 1;
+ }
+ }
+ if (!ok)
+ return;
+ tui_rt_normalize(t);
+ t->focus = tui_rt_focus_step(i, t->nfields, &t->row, &t->col, t->nrows,
+ t->neditable, 1);
+}
+
/* Returns -1 back, -2 refresh, -3 submit; otherwise an edited key was
consumed. */
int tui_rt_run(const char *title, struct tui_rt *t, const char *hint)
@@ -1353,39 +1517,42 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint)
for (;;) {
if (g_frame)
g_frame(title);
- 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;
+ int y = t->y;
+ if (t->nfields > 0 && y < t->nfields + 6)
+ y = t->nfields + 6; /* fields at 5.., column header, then rows */
+ int view = LINES - 2 - y;
if (view < 1)
view = 1;
if (t->row < top)
top = t->row;
if (t->row >= top + view)
top = t->row - view + 1;
+ if (top < 0)
+ top = 0;
+
+ rt_draw_fields(t);
+
attron(A_BOLD);
for (int i = 0; i < t->ncols; i++) {
char hdr[64];
snprintf(hdr, sizeof hdr, "%s", t->cols[i].header);
tui_pad_field(hdr, sizeof hdr, t->cols[i].width);
- mvaddstr(t->y - 1, t->cols[i].x, hdr);
+ mvaddstr(y - 1, t->cols[i].x, hdr);
}
attroff(A_BOLD);
int caret_y = -1, caret_x = -1;
+ if (t->focus >= 0 && t->focus < t->nfields) {
+ char val[640];
+ rt_field_value(&t->fields[t->focus], val, sizeof val);
+ caret_y = 5 + t->focus;
+ caret_x = 34 + (int)tui_disp_width(val);
+ if (caret_x > COLS - 1)
+ caret_x = COLS - 1;
+ }
int ei = 0;
for (int i = 0; i < t->ncols; i++) {
- int focused_col = (t->cols[i].kind != TUI_RT_INFO &&
- ei == t->col);
+ int focused_col = (t->focus < 0 &&
+ t->cols[i].kind != TUI_RT_INFO && ei == t->col);
if (t->cols[i].kind != TUI_RT_INFO)
ei++;
if (t->cols[i].kind == TUI_RT_INFO) {
@@ -1399,10 +1566,10 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint)
char padded[512];
snprintf(padded, sizeof padded, "%s", buf ? buf : "");
tui_pad_field(padded, sizeof padded, t->cols[i].width);
- int y = t->y + v;
+ int vy = y + v;
if (focused_col && row == t->row) {
attron(A_REVERSE);
- caret_y = y;
+ caret_y = vy;
size_t slen = strlen(buf ? buf : "");
size_t cp = (t->fresh || t->pos == (size_t)-1 ||
t->pos > slen)
@@ -1412,7 +1579,7 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint)
caret_x = t->cols[i].x +
(cw > t->cols[i].width ? t->cols[i].width : cw);
}
- mvaddstr(y, t->cols[i].x, padded);
+ mvaddstr(vy, t->cols[i].x, padded);
if (focused_col && row == t->row)
attroff(A_REVERSE);
}
@@ -1426,29 +1593,23 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint)
t->info(t->ud, top + v, ibuf, sizeof ibuf);
tui_pad_field(ibuf, sizeof ibuf, t->cols[i].width);
attron(A_DIM);
- mvaddstr(t->y + v, t->cols[i].x, ibuf);
+ mvaddstr(y + v, t->cols[i].x, ibuf);
attroff(A_DIM);
}
}
}
- 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);
- }
+ char foot[2048];
+ foot[0] = '\0';
+ if (t->footer)
+ t->footer(t->ud, foot, sizeof foot);
+ tui_rt_footer_flatten(foot);
+ int fy = tui_rt_footer_row(LINES);
+ move(fy, 2);
+ clrtoeol();
+ if (foot[0]) {
+ attron(A_DIM);
+ addnstr(foot, COLS - 4);
+ attroff(A_DIM);
}
if (g_hints)
g_hints(hint);
@@ -1485,19 +1646,40 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint)
if (h == TUI_HOOK_STAY)
continue;
}
- if (ch == '\t') {
- tui_rt_tab(t, 1);
+ if (ch == '\t' || ch == KEY_BTAB) {
+ tui_rt_normalize(t);
+ t->focus = tui_rt_focus_step(t->focus, t->nfields, &t->row,
+ &t->col, t->nrows, t->neditable,
+ ch == '\t' ? 1 : -1);
+ t->fresh = 1;
+ t->pos = (size_t)-1;
continue;
}
- if (ch == KEY_BTAB) {
- tui_rt_tab(t, -1);
+ if (ch == KEY_UP || ch == KEY_DOWN) {
+ if (t->focus >= 0) {
+ if (ch == KEY_UP && t->focus > 0)
+ t->focus--;
+ else if (ch == KEY_DOWN && t->focus + 1 < t->nfields)
+ t->focus++;
+ } else {
+ tui_rt_move(t, ch == KEY_UP ? -1 : 1, 0);
+ }
continue;
}
- if (ch == KEY_UP) {
- tui_rt_move(t, -1, 0);
- continue;
+ if (t->focus >= 0) {
+ if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) {
+ rt_edit_field(t, t->focus);
+ continue;
+ }
+ if (ch < 32 || ch >= 256)
+ continue;
+ /* typing is meant for the rows: hand the focus to the table */
+ t->focus = -1;
+ t->col = 0;
+ t->fresh = 1;
+ t->pos = (size_t)-1;
}
- if (ch == KEY_DOWN) {
+ if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) {
tui_rt_move(t, 1, 0);
continue;
}
@@ -1505,6 +1687,14 @@ int tui_rt_run(const char *title, struct tui_rt *t, const char *hint)
tui_rt_clear(t);
continue;
}
+ if (t->row < 0)
+ t->row = 0;
+ if (t->row >= t->nrows)
+ t->row = t->nrows - 1;
+ if (t->col < 0)
+ t->col = 0;
+ if (t->col >= t->neditable)
+ t->col = t->neditable - 1;
int ci = tui_rt_col_of(t, t->col);
char *buf = NULL;
size_t cap = 0;