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 | |
| 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
| -rw-r--r-- | clients/bokftui.c | 91 | ||||
| -rw-r--r-- | clients/tui.c | 284 | ||||
| -rw-r--r-- | clients/tui.h | 19 | ||||
| -rw-r--r-- | docs/TUI-GUIDELINES.md | 18 | ||||
| -rw-r--r-- | tests/test_tui.c | 88 |
5 files changed, 383 insertions, 117 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c index af6e284..e94faeb 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -1427,6 +1427,7 @@ static void vform_template(struct vform *vf) free(tser); vf->rt.row = 0; vf->rt.col = 0; + vf->rt.focus = -1; vf->rt.fresh = 1; vf->rt.pos = (size_t)-1; tui_rt_normalize(&vf->rt); @@ -1579,42 +1580,22 @@ static int64_t vouchers_new(struct app *a) ff[2].value = vf.desc; ff[2].cap = sizeof vf.desc; ff[2].kind = TUI_F_TEXT; - const char *hint = "F4 = mall ^F = bifoga fil Tab = byta fält F5 =" - " validera ^X = rensa rad ^Enter = bokför Esc =" - " avbryt"; - int stage = 0; + tui_rt_set_fields(&vf.rt, ff, 3); + const char *hint = "Enter = ändra fält Tab = byta fält F4 = mall" + " ^F = bifoga fil F5 = validera ^X = rensa rad" + " ^Enter = bokför Esc = avbryt"; for (;;) { - if (stage == 0) { - int r = tui_form_run_hook("Nytt verifikat", ff, 3, 1, vform_key, - &vf); - if (r == TUI_FORM_BACK) - return 0; - if (r == TUI_FORM_REFRESH) { - vform_post(&vf, 1); - continue; - } - if (r == TUI_FORM_SUBMIT) { - int64_t id = vform_post(&vf, 0); - if (id > 0) - return id; - continue; - } - stage = 1; - } else { - int rr = tui_rt_run("Nytt verifikat", &vf.rt, hint); - if (rr == -1) { - stage = 0; - continue; - } - if (rr == -2) { - vform_post(&vf, 1); - continue; - } - if (rr == -3) { - int64_t id = vform_post(&vf, 0); - if (id > 0) - return id; - } + int rr = tui_rt_run("Nytt verifikat", &vf.rt, hint); + if (rr == -1) + return 0; + if (rr == -2) { + vform_post(&vf, 1); + continue; + } + if (rr == -3) { + int64_t id = vform_post(&vf, 0); + if (id > 0) + return id; } } } @@ -3601,38 +3582,20 @@ static int template_form(struct app *a, const char *load_name) ff[2].value = tf.desc; ff[2].cap = sizeof tf.desc; ff[2].kind = TUI_F_TEXT; + tui_rt_set_fields(&tf.rt, ff, 3); const char *title = load_name ? "Redigera mall" : "Ny mall"; - const char *hint = "Tab = byta fält F5 = validera ^X = rensa rad" - " ^Enter = spara Esc = avbryt"; - int stage = 0; + const char *hint = "Enter = ändra fält Tab = byta fält F5 = validera" + " ^X = rensa rad ^Enter = spara Esc = avbryt"; for (;;) { - if (stage == 0) { - int r = tui_form_run(title, ff, 3, 1); - if (r == TUI_FORM_BACK) - return 0; - if (r == TUI_FORM_REFRESH) { - tpl_save(&tf, 1); - continue; - } - if (r == TUI_FORM_SUBMIT) { - if (tpl_save(&tf, 0)) - return 1; - continue; - } - stage = 1; - } else { - int rr = tui_rt_run(title, &tf.rt, hint); - if (rr == -1) { - stage = 0; - continue; - } - if (rr == -2) { - tpl_save(&tf, 1); - continue; - } - if (rr == -3 && tpl_save(&tf, 0)) - return 1; + int rr = tui_rt_run(title, &tf.rt, hint); + if (rr == -1) + return 0; + if (rr == -2) { + tpl_save(&tf, 1); + continue; } + if (rr == -3 && tpl_save(&tf, 0)) + return 1; } } 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; diff --git a/clients/tui.h b/clients/tui.h index 39ea913..061edbf 100644 --- a/clients/tui.h +++ b/clients/tui.h @@ -127,6 +127,7 @@ enum { }; #define TUI_FORM_BACK (-1) +#define TUI_FORM_PAGE 10 #define TUI_FORM_REFRESH (-2) #define TUI_FORM_SUBMIT (-8) @@ -169,8 +170,12 @@ struct tui_rt { void (*info)(void *, int, char *, size_t); void *ud; int (*key)(void *, int); - /* Writes the dim status lines shown under the table ('\n' separated). */ + /* Writes the dim status line shown under the table ('\n' -> ' '). */ void (*footer)(void *, char *, size_t); + /* Optional header fields drawn above the table; focus starts there. */ + struct tui_form_field *fields; + int nfields; + int focus; /* header field index, or -1 for the table */ }; void tui_rt_init(struct tui_rt *t, int nrows, int maxrows, int ncols, @@ -180,6 +185,7 @@ void tui_rt_init(struct tui_rt *t, int nrows, int maxrows, int ncols, void tui_rt_set_key(struct tui_rt *t, int (*key)(void *ud, int ch)); void tui_rt_set_footer(struct tui_rt *t, void (*footer)(void *ud, char *buf, size_t n)); +void tui_rt_set_fields(struct tui_rt *t, struct tui_form_field *fields, int n); int tui_rt_blank_row(struct tui_rt *t, int row); int tui_rt_normalize(struct tui_rt *t); int tui_rt_move(struct tui_rt *t, int dr, int dc); @@ -188,4 +194,15 @@ int tui_rt_clear(struct tui_rt *t); int tui_rt_col_of(const struct tui_rt *t, int ecol); int tui_rt_run(const char *title, struct tui_rt *t, const char *hint); +/* Focus order for a row table with header fields: fields first, then the + editable cells row by row, wrapping back to the first field. focus -1 is + the table, 0..nf-1 the fields; dir 1 = Tab, -1 = Shift-Tab. When the new + focus is in the table, updates the row and col pointers. */ +int tui_rt_focus_step(int focus, int nf, int *row, int *col, int nrows, + int neditable, int dir); + +/* The footer line (above the hints) and its flattening. */ +int tui_rt_footer_row(int lines); +void tui_rt_footer_flatten(char *s); + #endif diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md index 276cfd9..e02fade 100644 --- a/docs/TUI-GUIDELINES.md +++ b/docs/TUI-GUIDELINES.md @@ -73,13 +73,21 @@ written compactly as `^N`, `^A`, `^C`, `^R` to save width. - Row tables: always exactly one empty trailing row; entering data appends a new empty row; an empty row followed by another empty row collapses. `Ctrl+X` clears the selected row. `Tab` walks the editable cells and wraps - to the next row's first cell (`Shift-Tab` back). A dim footer callback may - show derived lines (balance, attachment list) directly under the table. + to the next row's first cell (`Shift-Tab` back). A dim footer callback + shows one derived line (balance, attachment list) on its own row above the + hints (`LINES-2`), never over a table row; `\n` in the text becomes a + space. - Fields with `mask` set are shown as `*` (`tui_form_field.mask`); used for passwords. ACTION fields show their `value` when set, else `(lista)`. -- Header fields plus rows: the header is a `tui_form_run`, the rows a - `tui_rt_run`. Editing a header field hands over to the row table; `Esc` in - the table goes back to the header. `F5`/`Ctrl+Enter` work in both stages. +- Header fields plus rows: one `tui_rt_run` with `tui_rt_set_fields` draws + the fields above the table on the same screen. Focus starts on the first + field; `Tab`/`Shift-Tab` walk fields, then the cells row by row, and wrap + back to the first field. `Up`/`Down` move between fields (in the table + they change row). `Enter` opens the field's editor (text/date/amount/ + choice) and advances to the next field; `Esc` cancels the editor without + changing anything. Typing a printable character hands the focus to the + first table cell and inserts it there, so rows can be entered without + touching the header. `F5`/`Ctrl+Enter`/`Esc` work the same from both. - Validation: `F5` validates without writing and reports exactly what is wrong (field, row number). Server `dry_run` is used where available. - Saving: `Ctrl+Enter` writes, shows a confirmation message, and returns to diff --git a/tests/test_tui.c b/tests/test_tui.c index 98a1ac2..9c47c43 100644 --- a/tests/test_tui.c +++ b/tests/test_tui.c @@ -203,6 +203,13 @@ static void test_form_nav(void) { CHECK(TUI_NAV_NONE != TUI_FORM_SUBMIT); CHECK(tui_form_next(1, 3, 'x') == TUI_NAV_NONE); + CHECK(tui_form_next(2, 5, KEY_HOME) == 0); + CHECK(tui_form_next(2, 5, KEY_END) == 4); + CHECK(tui_form_next(0, 5, KEY_NPAGE) == 4); + CHECK(tui_form_next(0, 25, KEY_NPAGE) == 10); + CHECK(tui_form_next(12, 25, KEY_PPAGE) == 2); + CHECK(tui_form_next(3, 25, KEY_PPAGE) == 0); + CHECK(tui_form_next(3, 25, KEY_HOME) == 0); CHECK(tui_form_next(0, 3, KEY_UP) == TUI_NAV_NONE); CHECK(tui_form_next(0, 3, KEY_DOWN) == 1); CHECK(tui_form_next(2, 3, KEY_DOWN) == TUI_NAV_NONE); @@ -306,6 +313,84 @@ static void test_rowtable(void) CHECK(t.nrows == 1); /* never below one row */ } +static void test_rt_fields(void) +{ + struct tui_rt t; + memset(rt_cells, 0, sizeof rt_cells); + tui_rt_init(&t, 1, 8, 3, RT_COLS, 6, rt_cb, rt_info_cb, NULL); + CHECK(t.focus == -1); + struct tui_form_field f[2]; + memset(f, 0, sizeof f); + f[0].label = "Datum"; + f[1].label = "Text"; + tui_rt_set_fields(&t, f, 2); + CHECK(t.fields == f && t.nfields == 2 && t.focus == 0); + tui_rt_set_fields(&t, NULL, 0); + CHECK(t.fields == NULL && t.nfields == 0 && t.focus == -1); +} + +static void test_rt_focus(void) +{ + int row, col; + + /* fields 0..2, then a 2-row table with 3 editable cells each */ + row = 0; + col = 0; + CHECK(tui_rt_focus_step(0, 3, &row, &col, 2, 3, 1) == 1); + CHECK(tui_rt_focus_step(1, 3, &row, &col, 2, 3, 1) == 2); + CHECK(tui_rt_focus_step(2, 3, &row, &col, 2, 3, 1) == -1); + CHECK(row == 0 && col == 0); + CHECK(tui_rt_focus_step(-1, 3, &row, &col, 2, 3, 1) == -1); + CHECK(row == 0 && col == 1); + CHECK(tui_rt_focus_step(-1, 3, &row, &col, 2, 3, 1) == -1); + CHECK(row == 0 && col == 2); + CHECK(tui_rt_focus_step(-1, 3, &row, &col, 2, 3, 1) == -1); + CHECK(row == 1 && col == 0); + /* after the last cell Tab wraps back to the first field */ + row = 1; + col = 2; + CHECK(tui_rt_focus_step(-1, 3, &row, &col, 2, 3, 1) == 0); + /* Shift-Tab from the first field lands on the last cell */ + CHECK(tui_rt_focus_step(0, 3, &row, &col, 2, 3, -1) == -1); + CHECK(row == 1 && col == 2); + CHECK(tui_rt_focus_step(-1, 3, &row, &col, 2, 3, -1) == -1); + CHECK(row == 1 && col == 1); + /* Shift-Tab from the first cell wraps back to the last field */ + row = 0; + col = 0; + CHECK(tui_rt_focus_step(-1, 3, &row, &col, 2, 3, -1) == 2); + /* without fields the table still cycles through its cells */ + row = 0; + col = 0; + CHECK(tui_rt_focus_step(-1, 0, &row, &col, 2, 3, -1) == -1); + CHECK(row == 1 && col == 2); + CHECK(tui_rt_focus_step(-1, 0, &row, &col, 2, 3, 1) == -1); + CHECK(row == 0 && col == 0); + /* an empty table cycles only through the fields */ + row = 0; + col = 0; + CHECK(tui_rt_focus_step(2, 3, &row, &col, 0, 0, 1) == 0); + CHECK(tui_rt_focus_step(0, 3, &row, &col, 0, 0, -1) == 2); +} + +static void test_rt_footer(void) +{ + CHECK(tui_rt_footer_row(24) == 22); + CHECK(tui_rt_footer_row(4) == 2); + CHECK(tui_rt_footer_row(3) == 2); + CHECK(tui_rt_footer_row(2) == 2); + char f[64]; + snprintf(f, sizeof f, "Bilagor: a\nSumma 0,00 I BALANS"); + tui_rt_footer_flatten(f); + CHECK(strcmp(f, "Bilagor: a Summa 0,00 I BALANS") == 0); + snprintf(f, sizeof f, "en enda rad"); + tui_rt_footer_flatten(f); + CHECK(strcmp(f, "en enda rad") == 0); + f[0] = '\0'; + tui_rt_footer_flatten(f); + CHECK(f[0] == '\0'); +} + int main(void) { test_disp_width(); @@ -316,6 +401,9 @@ int main(void) test_nav(); test_form_nav(); test_rowtable(); + test_rt_fields(); + test_rt_focus(); + test_rt_footer(); printf("test_tui: %d checks, %d failures\n", checks, failures); return failures ? 1 : 0; } |
