aboutsummaryrefslogtreecommitdiff
path: root/clients
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-19 22:03:06 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-19 22:03:06 +0200
commit84c4fdcf138ad6ef4f7edfea76f2318b7a7a5e3e (patch)
treedbae3b9fe87503297dc551393add7f0b97a0a543 /clients
parent4329ffb98d7eeffe2b3874d2b31cb0857d33d944 (diff)
downloadbokf-84c4fdcf138ad6ef4f7edfea76f2318b7a7a5e3e.tar.gz
bokf-84c4fdcf138ad6ef4f7edfea76f2318b7a7a5e3e.zip
tui: row table widget (dynamic rows, trailing blank row) + testsv0.1.37
Diffstat (limited to 'clients')
-rw-r--r--clients/tui.c218
-rw-r--r--clients/tui.h33
2 files changed, 251 insertions, 0 deletions
diff --git a/clients/tui.c b/clients/tui.c
index e8508db..d19bd17 100644
--- a/clients/tui.c
+++ b/clients/tui.c
@@ -1126,3 +1126,221 @@ int tui_form_run(const char *title, struct tui_form_field *f, int n,
}
}
}
+
+/* ------------------------------------------------------------------ */
+/* row table (dynamic multi-column editor with a trailing blank row) */
+/* ------------------------------------------------------------------ */
+
+void tui_rt_init(struct tui_rt *t, int nrows, int maxrows, int ncols,
+ const struct tui_rt_col *cols, int y,
+ void (*cell)(void *, int, int, char **, size_t *),
+ void (*info)(void *, int, char *, size_t), void *ud)
+{
+ memset(t, 0, sizeof *t);
+ t->nrows = nrows > 0 ? nrows : 1;
+ t->maxrows = maxrows;
+ t->ncols = ncols;
+ /* only the editable columns take part in Tab/field navigation */
+ for (int i = 0; i < ncols; i++)
+ if (cols[i].kind != TUI_RT_INFO)
+ t->neditable++;
+ t->cols = cols;
+ t->y = y;
+ t->cell = cell;
+ t->info = info;
+ t->ud = ud;
+ t->fresh = 1;
+ t->pos = (size_t)-1;
+}
+
+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);
+}
+
+int tui_rt_blank_row(struct tui_rt *t, int row)
+{
+ for (int i = 0; i < t->ncols; i++) {
+ if (t->cols[i].kind == TUI_RT_INFO)
+ continue;
+ char *buf = NULL;
+ size_t cap = 0;
+ rt_get(t, row, i, &buf, &cap);
+ if (buf && buf[0])
+ return 0;
+ }
+ return 1;
+}
+
+int tui_rt_normalize(struct tui_rt *t)
+{
+ if (t->nrows < 1)
+ t->nrows = 1;
+ while (t->nrows > 1 && tui_rt_blank_row(t, t->nrows - 1) &&
+ tui_rt_blank_row(t, t->nrows - 2))
+ t->nrows--;
+ if (!tui_rt_blank_row(t, t->nrows - 1) && t->nrows < t->maxrows)
+ t->nrows++;
+ if (t->row >= t->nrows)
+ t->row = t->nrows - 1;
+ if (t->col >= t->neditable)
+ t->col = t->neditable - 1;
+ if (t->col < 0)
+ t->col = 0;
+ return t->nrows;
+}
+
+int tui_rt_move(struct tui_rt *t, int dr, int dc)
+{
+ int moved = 0;
+ if (dr < 0 && t->row > 0) {
+ t->row--;
+ moved = 1;
+ } else if (dr > 0 && t->row + 1 < t->nrows) {
+ t->row++;
+ moved = 1;
+ }
+ if (dc && t->neditable > 0) {
+ t->col = (t->col + dc + t->neditable) % t->neditable;
+ moved = 1;
+ }
+ t->fresh = 1;
+ return moved;
+}
+
+int tui_rt_clear(struct tui_rt *t)
+{
+ if (t->row < 0 || t->row >= t->nrows)
+ return 0;
+ for (int i = 0; i < t->ncols; i++) {
+ if (t->cols[i].kind == TUI_RT_INFO)
+ continue;
+ char *buf = NULL;
+ size_t cap = 0;
+ rt_get(t, t->row, i, &buf, &cap);
+ if (buf && cap)
+ buf[0] = '\0';
+ }
+ tui_rt_normalize(t);
+ t->fresh = 1;
+ t->pos = (size_t)-1;
+ return 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)
+{
+ int top = 0;
+ for (;;) {
+ if (g_frame)
+ g_frame(title);
+ int view = LINES - 4 - t->y;
+ if (view < 1)
+ view = 1;
+ if (t->row < top)
+ top = t->row;
+ if (t->row >= top + view)
+ top = t->row - view + 1;
+ 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);
+ }
+ attroff(A_BOLD);
+ int caret_y = -1, caret_x = -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);
+ if (t->cols[i].kind != TUI_RT_INFO)
+ ei++;
+ if (t->cols[i].kind == TUI_RT_INFO) {
+ continue;
+ }
+ for (int v = 0; v < view && top + v < t->nrows; v++) {
+ int row = top + v;
+ char *buf = NULL;
+ size_t cap = 0;
+ rt_get(t, row, i, &buf, &cap);
+ 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;
+ if (focused_col && row == t->row) {
+ attron(A_REVERSE);
+ caret_y = y;
+ size_t slen = strlen(buf ? buf : "");
+ size_t cp = (t->fresh || t->pos == (size_t)-1 ||
+ t->pos > slen)
+ ? slen
+ : t->pos;
+ int cw = tui_disp_width_n(buf ? buf : "", cp);
+ caret_x = t->cols[i].x +
+ (cw > t->cols[i].width ? t->cols[i].width : cw);
+ }
+ mvaddstr(y, t->cols[i].x, padded);
+ if (focused_col && row == t->row)
+ attroff(A_REVERSE);
+ }
+ }
+ if (t->info) {
+ for (int v = 0; v < view && top + v < t->nrows; v++) {
+ char ibuf[512];
+ for (int i = 0; i < t->ncols; i++) {
+ if (t->cols[i].kind != TUI_RT_INFO)
+ continue;
+ 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);
+ attroff(A_DIM);
+ }
+ }
+ }
+ 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')
+ return -1;
+ if (ch == KEY_F(5))
+ return -2;
+ if (ch == TUI_KEY_CTRL_ENTER || ch == KEY_F(9))
+ return -3;
+ if (ch == '\t') {
+ tui_rt_move(t, 0, 1);
+ continue;
+ }
+ if (ch == KEY_BTAB) {
+ tui_rt_move(t, 0, -1);
+ continue;
+ }
+ if (ch == KEY_UP) {
+ tui_rt_move(t, -1, 0);
+ continue;
+ }
+ if (ch == KEY_DOWN) {
+ tui_rt_move(t, 1, 0);
+ continue;
+ }
+ if (ch == TUI_KEY_CTRL_X) {
+ tui_rt_clear(t);
+ continue;
+ }
+ char *buf = NULL;
+ size_t cap = 0;
+ rt_get(t, t->row, t->col, &buf, &cap);
+ if (!buf)
+ continue;
+ if (t->cols[t->col].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);
+ tui_rt_normalize(t);
+ }
+}
diff --git a/clients/tui.h b/clients/tui.h
index 0bda359..d41d58c 100644
--- a/clients/tui.h
+++ b/clients/tui.h
@@ -12,6 +12,7 @@
/* Universal keys that have no control code or need a stable name. */
#define TUI_KEY_CTRL_N 0x0e
+#define TUI_KEY_CTRL_X 0x18
#define TUI_KEY_CTRL_ENTER (KEY_MAX + 1)
/* The application supplies input, frame/hints drawing and a quit hook so
@@ -128,4 +129,36 @@ 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);
+/* --- row table --- */
+enum { TUI_RT_EDIT = 0, TUI_RT_INFO, TUI_RT_DATE };
+
+struct tui_rt_col {
+ const char *header;
+ int x;
+ int width;
+ int kind;
+};
+
+struct tui_rt {
+ int nrows, maxrows, ncols, neditable;
+ const struct tui_rt_col *cols;
+ int y;
+ int row, col;
+ int fresh;
+ size_t pos;
+ void (*cell)(void *, int, int, char **, size_t *);
+ void (*info)(void *, int, char *, size_t);
+ void *ud;
+};
+
+void tui_rt_init(struct tui_rt *t, int nrows, int maxrows, int ncols,
+ const struct tui_rt_col *cols, int y,
+ void (*cell)(void *, int, int, char **, size_t *),
+ void (*info)(void *, int, char *, size_t), void *ud);
+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);
+int tui_rt_clear(struct tui_rt *t);
+int tui_rt_run(const char *title, struct tui_rt *t, const char *hint);
+
#endif