diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-19 22:49:06 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-19 22:49:06 +0200 |
| commit | 730d7159e3b9b3adfba2c3779893fdb4e107647a (patch) | |
| tree | 04a08f99adf7b5519d5bf65111453e4124e69720 /clients/tui.c | |
| parent | e352cd34d30acc1955c62027c779ac98004ac201 (diff) | |
| download | bokf-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.c | 284 |
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; |
