aboutsummaryrefslogtreecommitdiff
path: root/clients
diff options
context:
space:
mode:
Diffstat (limited to 'clients')
-rw-r--r--clients/bokftui.c91
-rw-r--r--clients/tui.c284
-rw-r--r--clients/tui.h19
3 files changed, 282 insertions, 112 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