diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-19 21:58:58 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-19 21:58:58 +0200 |
| commit | 4329ffb98d7eeffe2b3874d2b31cb0857d33d944 (patch) | |
| tree | 16e777dc90fa334ce3f436a3b5d3a7a9efdbd8b9 /clients/bokftui.c | |
| parent | 34fc2b1dce788fbb093e70c2124630167e7980ef (diff) | |
| download | bokf-0.1.36.tar.gz bokf-0.1.36.zip | |
tui: shared widget layer (fields, prompts, lists, forms, pager) + testsv0.1.36
Diffstat (limited to 'clients/bokftui.c')
| -rw-r--r-- | clients/bokftui.c | 1918 |
1 files changed, 444 insertions, 1474 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c index 9ee5859..0967f51 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -13,6 +13,7 @@ #include <unistd.h> #include "client.h" +#include "tui.h" #include "formula.h" #include "util.h" #include "version.h" @@ -46,6 +47,11 @@ static int ui_getch(void) return ch; } +static void request_quit(void) +{ + g_quit = 1; +} + struct app { struct client_conn conn; char socket[256]; @@ -139,10 +145,8 @@ static int jbool_val(const char *resp, const char *path, int def) /* ui primitives */ /* ------------------------------------------------------------------ */ -static char g_status[512]; static char g_server_version[32]; -static int disp_width(const char *s); static void tui_log(const char *fmt, ...) __attribute__((format(printf, 1, 2))); @@ -151,759 +155,12 @@ static void bokslut_screen(struct app *a); static void arsredovisning_screen(struct app *a); static char *rpc_dry(struct app *a, const char *cmd, const char *args); static int64_t vouchers_new(struct app *a); -static void acct_cache_free(void); -static void acct_cache_load(struct app *a); -static const char *acct_name(struct app *a, const char *number); -static int acct_exists(struct app *a, const char *number); - -/* ---- shared line editor used by every form and prompt ---- */ -struct ledit { - char *buf; - size_t cap; - size_t len; - size_t pos; /* caret position in bytes */ -}; - -static size_t disp_width_n(const char *s, size_t n) -{ - size_t w = 0; - for (size_t i = 0; i < n && s[i]; i++) - if (((unsigned char)s[i] & 0xC0) != 0x80) - w++; - return w; -} - -static void le_init(struct ledit *e, char *buf, size_t cap) -{ - e->buf = buf; - e->cap = cap; - e->len = strlen(buf); - e->pos = e->len; -} - -static void le_clear(struct ledit *e) -{ - e->len = 0; - e->pos = 0; - e->buf[0] = '\0'; -} - -static void le_insert(struct ledit *e, unsigned char ch) -{ - if (e->len + 1 >= e->cap) - return; - memmove(e->buf + e->pos + 1, e->buf + e->pos, e->len - e->pos + 1); - e->buf[e->pos] = (char)ch; - e->pos++; - e->len++; -} - -static void le_backspace(struct ledit *e) -{ - if (e->pos == 0) - return; - memmove(e->buf + e->pos - 1, e->buf + e->pos, e->len - e->pos + 1); - e->pos--; - e->len--; -} - -static void le_delete(struct ledit *e) -{ - if (e->pos >= e->len) - return; - memmove(e->buf + e->pos, e->buf + e->pos + 1, e->len - e->pos); - e->len--; -} - -/* Consumes the key when it is an editing key; returns 1 then. */ -static int le_key(struct ledit *e, int ch) -{ - if (ch == KEY_LEFT) { - if (e->pos > 0) { - e->pos--; - while (e->pos > 0 && ((unsigned char)e->buf[e->pos] & 0xC0) == 0x80) - e->pos--; - } - return 1; - } - if (ch == KEY_RIGHT) { - if (e->pos < e->len) { - e->pos++; - while (e->pos < e->len && - ((unsigned char)e->buf[e->pos] & 0xC0) == 0x80) - e->pos++; - } - return 1; - } - if (ch == KEY_HOME || ch == 1) { /* Ctrl+A */ - e->pos = 0; - return 1; - } - if (ch == KEY_END || ch == 5) { /* Ctrl+E */ - e->pos = e->len; - return 1; - } - if (ch == KEY_DC) { - le_delete(e); - return 1; - } - if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - le_backspace(e); - return 1; - } - if (ch == 21) { /* ^U */ - le_clear(e); - return 1; - } - if (ch >= 32 && ch < 256) { - le_insert(e, (unsigned char)ch); - return 1; - } - return 0; -} - -/* Date field: only digits are accepted, dashes are inserted automatically - ("20260215" -> "2026-02-15"). The caret is tracked in digit space and - reported back as a byte offset so forms render it like any field. */ -static int date_field_edit(char *buf, size_t cap, size_t *pos, int ch, - int *fresh) -{ - char d[9]; - size_t n = 0; - for (const char *q = buf; *q && n < 8; q++) - if (*q >= '0' && *q <= '9') - d[n++] = *q; - size_t ci = n; /* caret in digit space; byte pos -> digit index */ - if (*pos != (size_t)-1) { - size_t seen = 0; - for (size_t i = 0; i < *pos && buf[i]; i++) - if (buf[i] >= '0' && buf[i] <= '9') - seen++; - ci = seen; - } - - if (*fresh) { - int editing = ch == KEY_BACKSPACE || ch == 127 || ch == 8 || - ch == KEY_DC || (ch >= '0' && ch <= '9'); - int moving = ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || - ch == KEY_END || ch == 1 || ch == 5; - if (editing || moving) { - if (editing) { - n = 0; - ci = 0; - } - *fresh = 0; /* movement keeps the date and moves the caret */ - } - } - - if (ch >= '0' && ch <= '9') { - if (ci > n) - ci = n; - if (n < 8) { - for (size_t i = n; i > ci; i--) - d[i] = d[i - 1]; - d[ci] = (char)ch; - n++; - } else { - /* full field: overwrite the digit at the caret */ - if (ci > 7) - ci = 7; - d[ci] = (char)ch; - } - ci++; - } else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - if (ci > 0) { - for (size_t i = ci - 1; i + 1 < n; i++) - d[i] = d[i + 1]; - n--; - ci--; - } - } else if (ch == KEY_DC) { - if (ci < n) { - for (size_t i = ci; i + 1 < n; i++) - d[i] = d[i + 1]; - n--; - } - } else if (ch == KEY_LEFT) { - if (ci > 0) - ci--; - } else if (ch == KEY_RIGHT) { - if (ci < n) - ci++; - } else if (ch == KEY_HOME || ch == 1) { - ci = 0; - } else if (ch == KEY_END || ch == 5) { - ci = n; - } else if (ch == 21) { - n = 0; - ci = 0; - *fresh = 0; - } else { - return 0; /* not a date editing key */ - } - - /* rebuild "YYYY-MM-DD" (partial while typing) */ - char out[16]; - size_t o = 0; - for (size_t i = 0; i < n; i++) { - if (i == 4 || i == 6) - out[o++] = '-'; - out[o++] = d[i]; - } - out[o] = '\0'; - snprintf(buf, cap, "%s", out); - - size_t byte = ci; - if (n > 4 && ci >= 4) - byte++; - if (n > 6 && ci >= 6) - byte++; - if (byte > strlen(buf)) - byte = strlen(buf); - *pos = byte; - return 1; -} - -/* Prompt for a date with the date editor. Returns a static buffer or NULL - on Esc. */ -static char *date_prompt(const char *label, const char *def) -{ - static char buf[16]; - snprintf(buf, sizeof buf, "%s", def ? def : ""); - int y = LINES - 3; - move(y, 2); - clrtoeol(); - attron(A_BOLD); - mvaddstr(y, 2, label); - attroff(A_BOLD); - size_t pos = (size_t)-1; - int fresh = 1; - int prev = curs_set(1); - for (;;) { - move(y, (int)strlen(label) + 3); - clrtoeol(); - addnstr(buf, (int)sizeof buf); - clrtoeol(); - size_t slen = strlen(buf); - size_t cp = pos == (size_t)-1 || pos > slen ? slen : pos; - move(y, (int)strlen(label) + 3 + (int)disp_width_n(buf, cp)); - refresh(); - int ch = ui_getch(); - if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { - curs_set(prev == ERR ? 0 : prev); - return buf; - } - if (ch == 27) { - curs_set(prev == ERR ? 0 : prev); - return NULL; - } - date_field_edit(buf, sizeof buf, &pos, ch, &fresh); - } -} - -/* Applies a key to a form field. *pos is the caret, (size_t)-1 = at the - end. When *fresh is set, the first editing key replaces the content. */ -static int field_edit(char *buf, size_t cap, size_t *pos, int ch, int *fresh) -{ - struct ledit e; - le_init(&e, buf, cap); - if (*pos != (size_t)-1 && *pos <= e.len) - e.pos = *pos; - if (*fresh) { - int editing = ch == KEY_BACKSPACE || ch == 127 || ch == 8 || - ch == KEY_DC || (ch >= 32 && ch < 256); - int moving = ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || - ch == KEY_END || ch == 1 || ch == 5; - if (editing || moving) { - if (editing) - le_clear(&e); - *fresh = 0; /* movement keeps the content and moves the caret */ - } - } - int handled = le_key(&e, ch); - *pos = e.pos; - return handled; -} - -static void frame(const char *title) -{ - erase(); - attron(A_BOLD); - box(stdscr, 0, 0); - mvaddstr(0, 2, title); - attroff(A_BOLD); - if (g_status[0]) { - attron(A_DIM); - mvaddnstr(1, 2, g_status, COLS - 4); - char ver[96]; - if (g_server_version[0]) - snprintf(ver, sizeof ver, "tui %s srv %s", BOKF_VERSION, - g_server_version); - else - snprintf(ver, sizeof ver, "tui %s", BOKF_VERSION); - int x = COLS - 3 - disp_width(ver); - if (x > disp_width(g_status) + 6) - mvaddstr(1, x, ver); - attroff(A_DIM); - } -} - -static void hints(const char *s) -{ - char line[512]; - snprintf(line, sizeof line, "%s ^R = ladda om", s); - attron(A_DIM); - mvaddstr(LINES - 1, 2, line); - attroff(A_DIM); - clrtoeol(); -} - -static void message(const char *title, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); - -static void message(const char *title, const char *fmt, ...) -{ - char text[1024]; - va_list ap; - va_start(ap, fmt); - vsnprintf(text, sizeof text, fmt, ap); - va_end(ap); - int h = 5, w = (int)strlen(text) + 4; - if (w > COLS - 4) - w = COLS - 4; - if (w < 30) - w = 30; - WINDOW *win = newwin(h, w, (LINES - h) / 2, (COLS - w) / 2); - box(win, 0, 0); - wattron(win, A_BOLD); - mvwaddnstr(win, 0, 2, title, w - 4); - wattroff(win, A_BOLD); - mvwaddnstr(win, 2, 2, text, w - 4); - mvwaddstr(win, h - 1, w - 12, " tryck Enter"); - wrefresh(win); - nodelay(stdscr, FALSE); - timeout(-1); - for (;;) { - int ch = wgetch(win); - if (ch == 3) { - g_quit = 1; - break; - } - if (ch == '\n' || ch == '\r' || ch == ' ' || ch == 27 || ch == KEY_ENTER) - break; - } - delwin(win); - touchwin(stdscr); - refresh(); -} - -/* Line editor inside the current window at (y,x) with fixed label already - drawn. Returns 1 on Enter, 2 on Tab, 3 on Shift-Tab, 0 on Esc. */ -static int edit_field(int y, int x, char *buf, size_t cap, int mask) -{ - size_t pos = (size_t)-1; - int fresh = 1; - for (;;) { - move(y, x); - if (mask) { - size_t len = strlen(buf); - for (size_t i = 0; i < len; i++) - addch('*'); - } else { - addnstr(buf, (int)cap); - } - clrtoeol(); - size_t len = strlen(buf); - size_t cp = pos == (size_t)-1 || pos > len ? len : pos; - move(y, x + (mask ? (int)cp : (int)disp_width_n(buf, cp))); - refresh(); - int ch = ui_getch(); - if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) - return 1; - if (ch == 27) - return 0; - if (ch == '\t') - return 2; - if (ch == KEY_BTAB) - return 3; - field_edit(buf, cap, &pos, ch, &fresh); - } -} - -static char *prompt(const char *label, const char *def, int mask) -{ - static char buf[512]; - snprintf(buf, sizeof buf, "%s", def ? def : ""); - int y = LINES - 3; - move(y, 2); - clrtoeol(); - attron(A_BOLD); - mvaddstr(y, 2, label); - attroff(A_BOLD); - refresh(); - int prev = curs_set(1); - int rc = edit_field(y, (int)strlen(label) + 3, buf, sizeof buf, mask); - curs_set(prev == ERR ? 0 : prev); - if (rc == 0) - return NULL; - return buf; -} - -static int text_view(const char *title, const char *text, - const char *action_hint) -{ - int ret = 0; - int nlines = 0; - for (const char *p = text; *p; p++) - if (*p == '\n') - nlines++; - nlines += 1; - char **lines = xcalloc(nlines ? nlines : 1, sizeof(char *)); - int n = 0; - char *copy = xstrdup(text); - for (char *p = copy;;) { - char *nl = strchr(p, '\n'); - if (nl) - *nl = '\0'; - lines[n++] = p; - if (!nl) - break; - p = nl + 1; - } - int top = 0; - int view = LINES - 4; - for (;;) { - frame(title); - for (int i = 0; i < view; i++) { - move(2 + i, 2); - clrtoeol(); - if (top + i < n) - mvaddnstr(2 + i, 2, lines[top + i], COLS - 4); - } - char hint[256]; - if (action_hint) - snprintf(hint, sizeof hint, - "piltangenter/PgUp/PgDn rullar F5 = uppdatera %s " - "q = tillbaka", - action_hint); - else - snprintf(hint, sizeof hint, - "piltangenter/PgUp/PgDn rullar F5 = uppdatera " - "q = tillbaka"); - hints(hint); - refresh(); - int ch = ui_getch(); - if (ch == 'q' || ch == 27) - break; - if (ch == KEY_F(5)) { - ret = 1; - break; - } - if (ch == 's' && action_hint) { - ret = 2; - break; - } - if (ch == KEY_DOWN && top + view < n) - top++; - else if (ch == KEY_UP && top > 0) - top--; - else if (ch == KEY_NPAGE) - top += view; - else if (ch == KEY_PPAGE) - top -= view; - else if (ch == KEY_HOME) - top = 0; - else if (ch == KEY_END) - top = n > view ? n - view : 0; - if (top + view > n) - top = n > view ? n - view : 0; - if (top < 0) - top = 0; - } - free(copy); - free(lines); - return ret; -} - -static int menu(const char *title, const char *const *items, int n, - int allow_new, int *cursor) -{ - int sel = (cursor && *cursor >= 0 && *cursor < n) ? *cursor : 0; - int top = 0; - char gotobuf[12] = ""; - int goto_active = 0; - for (;;) { - if (cursor) - *cursor = sel; - int view = (LINES - 4) / 2; - if (view < 1) - view = 1; - if (sel < top) - top = sel; - if (sel >= top + view) - top = sel - view + 1; - frame(title); - for (int i = 0; i < view && top + i < n; i++) { - int idx = top + i; - char line[512]; - snprintf(line, sizeof line, "%2d. %s", idx + 1, items[idx]); - if (idx == sel) - attron(A_REVERSE); - mvaddnstr(3 + i * 2, 4, line, COLS - 6); - if (idx == sel) - attroff(A_REVERSE); - } - if (goto_active) { - move(LINES - 2, 2); - attron(A_BOLD); - printw("Gå till nummer: %s", gotobuf); - attroff(A_BOLD); - clrtoeol(); - } else { - move(LINES - 2, 2); - clrtoeol(); - } - hints(allow_new - ? "upp/ned, 1-9 = snabbval, g = gå till, PgUp/PgDn," - " Home/End, Enter ^N = ny Esc/q = tillbaka" - " ^C = avsluta" - : "upp/ned, 1-9 = snabbval, g = gå till, PgUp/PgDn," - " Home/End, Enter Esc/q = tillbaka ^C = avsluta"); - refresh(); - int ch = ui_getch(); - if (goto_active) { - if (ch >= '0' && ch <= '9' && strlen(gotobuf) < 9) { - size_t gl = strlen(gotobuf); - gotobuf[gl] = (char)ch; - gotobuf[gl + 1] = '\0'; - int v = atoi(gotobuf); - if (v >= 1 && v <= n) - sel = v - 1; - } else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - size_t gl = strlen(gotobuf); - if (gl) - gotobuf[gl - 1] = '\0'; - if (gotobuf[0]) { - int v = atoi(gotobuf); - if (v >= 1 && v <= n) - sel = v - 1; - } - } else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER || - ch == 27 || ch == 'g' || ch == 'G') { - goto_active = 0; - gotobuf[0] = '\0'; - } - continue; - } - if (ch == KEY_UP && sel > 0) - sel--; - else if (ch == KEY_DOWN && sel < n - 1) - sel++; - else if (ch == KEY_NPAGE) - sel = sel + view < n ? sel + view : n - 1; - else if (ch == KEY_PPAGE) - sel = sel - view > 0 ? sel - view : 0; - else if (ch == KEY_HOME) - sel = 0; - else if (ch == KEY_END) - sel = n - 1; - else if (ch >= '1' && ch <= '9' && ch - '1' < n) { - sel = ch - '1'; - if (cursor) - *cursor = sel; - return sel; - } - else if (ch == 'g' || ch == 'G') { - goto_active = 1; - gotobuf[0] = '\0'; - } else if (ch == KEY_CTRL_N && allow_new) - return -4; - else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) - return sel; - else if (ch == 27 || ch == 'q') - return g_quit ? -5 : -1; - } -} - -static int select_list(const char *title, char **items, int n, int start, - int allow_refresh, int *cursor, int allow_new, - const char *archive_action, int allow_toggle) -{ - int sel = start; - if (sel < 0) - sel = 0; - if (n > 0 && sel >= n) - sel = n - 1; - int top = 0; - int view = LINES - 4; - char gotobuf[12] = ""; - int goto_active = 0; - for (;;) { - if (cursor) - *cursor = sel; - frame(title); - if (sel < top) - top = sel; - if (sel >= top + view) - top = sel - view + 1; - int numw = 1; - for (int tmp = n; tmp >= 10; tmp /= 10) - numw++; - if (numw > 9) - numw = 9; - for (int i = 0; i < view && top + i < n; i++) { - char line[1024]; - snprintf(line, sizeof line, "%*d. %s", numw, top + i + 1, - items[top + i]); - if (top + i == sel) - attron(A_REVERSE); - mvaddnstr(2 + i, 2, line, COLS - 4); - if (top + i == sel) - attroff(A_REVERSE); - } - if (goto_active) { - move(LINES - 2, 2); - attron(A_BOLD); - printw("Gå till rad: %s", gotobuf); - attroff(A_BOLD); - clrtoeol(); - } else { - move(LINES - 2, 2); - clrtoeol(); - } - { - char hint[256], dbuf[48] = ""; - if (archive_action) - snprintf(dbuf, sizeof dbuf, " d = %s", archive_action); - snprintf(hint, sizeof hint, - "upp/ned, 1-9 = hoppa, g = gå till, PgUp/PgDn, Home/End," - " Enter%s%s%s%s Esc/q = tillbaka", - allow_refresh ? " F5 = uppdatera" : "", - allow_new ? " ^N = ny" : "", dbuf, - allow_toggle ? " ^A = öppna/stäng" : ""); - snprintf(hint + strlen(hint), sizeof hint - strlen(hint), - " ^C = avsluta"); - hints(hint); - } - refresh(); - int ch = ui_getch(); - if (goto_active) { - if (ch >= '0' && ch <= '9' && strlen(gotobuf) < 9) { - size_t gl = strlen(gotobuf); - gotobuf[gl] = (char)ch; - gotobuf[gl + 1] = '\0'; - int v = atoi(gotobuf); - if (v >= 1 && v <= n) - sel = v - 1; - } else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - size_t gl = strlen(gotobuf); - if (gl) - gotobuf[gl - 1] = '\0'; - if (gotobuf[0]) { - int v = atoi(gotobuf); - if (v >= 1 && v <= n) - sel = v - 1; - } - } else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER || - ch == 27 || ch == 'g' || ch == 'G') { - goto_active = 0; - gotobuf[0] = '\0'; - } - continue; - } - if (ch == KEY_UP && sel > 0) - sel--; - else if (ch == KEY_DOWN && sel < n - 1) - sel++; - else if (ch == KEY_NPAGE) - sel = sel + view < n ? sel + view : n - 1; - else if (ch == KEY_PPAGE) - sel = sel - view > 0 ? sel - view : 0; - else if (ch == KEY_HOME) - sel = 0; - else if (ch == KEY_END) - sel = n - 1; - else if (ch >= '1' && ch <= '9') { - int idx = ch - '1'; - if (idx < n) - sel = idx; /* goto row, Enter opens */ - } else if (ch == 'g' || ch == 'G') { - goto_active = 1; - gotobuf[0] = '\0'; - } else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) - return sel; - else if (allow_refresh && ch == KEY_F(5)) - return -2; - else if (allow_new && ch == KEY_CTRL_N) - return -4; - else if (archive_action && (ch == 'd' || ch == 'D') && n > 0) - return -6; - else if (allow_toggle && ch == 1 && n > 0) - return -7; - else if (ch == 27 || ch == 'q') - return g_quit ? -5 : -1; - } -} - -static int parse_kr(const char *s, int64_t *out) -{ - if (!s || !*s) { - *out = 0; - return 0; - } - int64_t whole = 0, frac = 0; - int fd = 0, seen_dot = 0; - for (const char *p = s; *p; p++) { - if (*p == ' ' || *p == '\t') - continue; - if (*p == '.' || *p == ',') { - if (seen_dot) - return -1; - seen_dot = 1; - continue; - } - if (*p < '0' || *p > '9') - return -1; - if (!seen_dot) { - whole = whole * 10 + (*p - '0'); - } else { - if (fd >= 2) - return -1; - frac = frac * 10 + (*p - '0'); - fd++; - } - } - if (fd == 1) - frac *= 10; - *out = whole * 100 + frac; - return 0; -} - -static void kr_format(int64_t ore, char *buf, size_t n) -{ - long long v = (long long)ore; - int neg = v < 0; - if (neg) - v = -v; - char digits[32]; - snprintf(digits, sizeof digits, "%lld", v / 100); - char out[28]; - size_t o = 0; - size_t len = strlen(digits); - for (size_t i = 0; i < len && o + 2 < sizeof out; i++) { - if (i > 0 && (len - i) % 3 == 0) - out[o++] = ' '; - out[o++] = digits[i]; - } - out[o] = '\0'; - snprintf(buf, n, "%s%s,%02lld", neg ? "-" : "", out, v % 100); -} /* Whole kronor with space thousands, öre truncated like the blankett. */ static void kr_whole(int64_t ore, char *buf, size_t n) { char tmp[48]; - kr_format((ore / 100) * 100, tmp, sizeof tmp); + tui_kr_format((ore / 100) * 100, tmp, sizeof tmp); char *comma = strchr(tmp, ','); if (comma) *comma = '\0'; @@ -951,40 +208,7 @@ static void replace_x_into(const char *src, double x, char *dst, size_t cap) /* Display width in terminal columns, counting UTF-8 lead bytes. Good enough for Swedish text; combining marks are ignored. */ -static int disp_width(const char *s) -{ - int w = 0; - for (const unsigned char *p = (const unsigned char *)s; *p; p++) - if ((*p & 0xC0) != 0x80) - w++; - return w; -} -static void pad_field(char *buf, size_t cap, int width) -{ - size_t slen = strlen(buf); - int w = 0; - size_t i = 0; - while (i < slen && buf[i]) { - unsigned char c = (unsigned char)buf[i]; - size_t len = 1; - if ((c & 0xE0) == 0xC0) - len = 2; - else if ((c & 0xF0) == 0xE0) - len = 3; - else if ((c & 0xF8) == 0xF0) - len = 4; - if (i + len > slen || w + 1 > width) - break; - w++; - i += len; - } - buf[i] = '\0'; - while (w < width && strlen(buf) + 1 < cap) { - strcat(buf, " "); - w++; - } -} /* ------------------------------------------------------------------ */ /* app helpers */ @@ -1075,8 +299,15 @@ static void app_refresh_context(struct app *a) static void update_status(struct app *a) { - snprintf(g_status, sizeof g_status, "%s | %s - %s | %s | %s", - a->org_name, a->fy_start, a->fy_end, a->role, a->username); + char st[512], right[160]; + snprintf(st, sizeof st, "%s | %s - %s | %s | %s", a->org_name, + a->fy_start, a->fy_end, a->role, a->username); + if (g_server_version[0]) + snprintf(right, sizeof right, "tui %s srv %s", BOKF_VERSION, + g_server_version); + else + snprintf(right, sizeof right, "tui %s", BOKF_VERSION); + tui_set_status(st, right); } @@ -1120,107 +351,63 @@ static int64_t fy_new_form(struct app *a) else snprintf(label, sizeof label, "%d/%d", sy, ey); - int field = 0; - int field_fresh = 1; - size_t field_pos = (size_t)-1; - curs_set(1); + struct tui_form_field ff[3]; for (;;) { - frame("Nytt räkenskapsår"); - int caret_y = -1, caret_x = -1; - attron(A_BOLD); - mvaddstr(4, 4, "Etikett"); - mvaddstr(6, 4, "Startdatum"); - mvaddstr(8, 4, "Slutdatum"); - attroff(A_BOLD); - { - const char *vals[3] = { label, start, end }; - int ys[3] = { 4, 6, 8 }; - for (int k = 0; k < 3; k++) { - char padded[64]; - snprintf(padded, sizeof padded, "%-24s", vals[k]); - if (field == k) { - attron(A_REVERSE); - caret_y = ys[k]; - size_t slen = strlen(vals[k]); - size_t cp = (field_fresh || field_pos == (size_t)-1 || - field_pos > slen) - ? slen - : field_pos; - caret_x = 16 + (int)disp_width_n(vals[k], cp); - } - mvaddstr(ys[k], 16, padded); - if (field == k) - attroff(A_REVERSE); - } - } - hints("Tab = byta fält F5 = validera ^Enter = skapa Esc = avbryt"); - if (caret_y >= 0) - move(caret_y, caret_x); - refresh(); - - int ch = ui_getch(); - if (ch == 27) { - curs_set(0); + memset(ff, 0, sizeof ff); + ff[0].label = "Etikett"; + ff[0].value = label; + ff[0].cap = sizeof label; + ff[0].kind = TUI_F_TEXT; + ff[1].label = "Startdatum"; + ff[1].value = start; + ff[1].cap = sizeof start; + ff[1].kind = TUI_F_DATE; + ff[2].label = "Slutdatum"; + ff[2].value = end; + ff[2].cap = sizeof end; + ff[2].kind = TUI_F_DATE; + int r = tui_form_run("Nytt räkenskapsår", ff, 3, 1); + if (r == TUI_FORM_BACK) return 0; - } - if (ch == '\t') { - field = (field + 1) % 3; - field_fresh = 1; + if (r != TUI_FORM_REFRESH && r != TUI_FORM_SUBMIT) + continue; /* a field was edited; keep the form up */ + if (!label[0]) { + tui_message("Räkenskapsår", "Etiketten får inte vara tom."); continue; } - if (ch == KEY_BTAB) { - field = (field + 2) % 3; - field_fresh = 1; + if (!util_parse_iso_date(start) || !util_parse_iso_date(end)) { + tui_message("Räkenskapsår", + "Start- och slutdatum måste vara ÅÅÅÅ-MM-DD."); continue; } - if (ch == KEY_F(5) || ch == KEY_CTRL_ENTER || ch == KEY_F(9)) { - if (!label[0]) { - message("Räkenskapsår", "Etiketten får inte vara tom."); - continue; - } - if (!util_parse_iso_date(start) || !util_parse_iso_date(end)) { - message("Räkenskapsår", - "Start- och slutdatum måste vara ÅÅÅÅ-MM-DD."); - continue; - } - if (strcmp(start, end) >= 0) { - message("Räkenskapsår", + if (strcmp(start, end) >= 0) { + tui_message("Räkenskapsår", "Startdatum måste ligga före slutdatum."); - continue; - } - if (ch == KEY_F(5)) { - message("Validering OK", "%s %s - %s", label, start, end); - continue; - } - yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); - yyjson_mut_val *o = yyjson_mut_obj(d); - yyjson_mut_doc_set_root(d, o); - yyjson_mut_obj_add_strcpy(d, o, "label", label); - yyjson_mut_obj_add_strcpy(d, o, "start_date", start); - yyjson_mut_obj_add_strcpy(d, o, "end_date", end); - char *args = yyjson_mut_write(d, 0, NULL); - yyjson_mut_doc_free(d); - resp = client_rpc(&a->conn, "fiscal_year.open", a->session, a->org, - args); - free(args); - if (resp && client_ok(resp)) { - int64_t id = jint_val(resp, "result.id", 0); - message("Räkenskapsår", "%s skapat.", label); - free(resp); - curs_set(0); - return id; - } - show_error("Kunde inte skapa räkenskapsåret", resp); - free(resp); continue; } - char *buf = field == 0 ? label : (field == 1 ? start : end); - size_t cap = field == 0 ? sizeof label - : (field == 1 ? sizeof start : sizeof end); - if (field == 0) - field_edit(buf, cap, &field_pos, ch, &field_fresh); - else - date_field_edit(buf, cap, &field_pos, ch, &field_fresh); + if (r == TUI_FORM_REFRESH) { + tui_message("Validering OK", "%s %s - %s", label, start, end); + continue; + } + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_strcpy(d, o, "label", label); + yyjson_mut_obj_add_strcpy(d, o, "start_date", start); + yyjson_mut_obj_add_strcpy(d, o, "end_date", end); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *oresp = client_rpc(&a->conn, "fiscal_year.open", a->session, + a->org, args); + free(args); + if (oresp && client_ok(oresp)) { + int64_t id = jint_val(oresp, "result.id", 0); + tui_message("Räkenskapsår", "%s skapat.", label); + free(oresp); + return id; + } + show_error("Kunde inte skapa räkenskapsåret", oresp); + free(oresp); } } @@ -1273,7 +460,7 @@ static void select_fiscal_year(struct app *a) closed[i] = strcmp(status ? status : "", "closed") == 0; char line[256], stbuf[16]; snprintf(stbuf, sizeof stbuf, "%s", closed[i] ? "stängd" : "öppen"); - pad_field(stbuf, sizeof stbuf, 6); + tui_pad_field(stbuf, sizeof stbuf, 6); snprintf(line, sizeof line, "%-25s %s %s", ranges[i], stbuf, labels[i]); lines[i] = xstrdup(line); @@ -1287,7 +474,7 @@ static void select_fiscal_year(struct app *a) int start = cursor >= 0 ? cursor : sel_default; if (start >= (int)n) start = (int)n - 1; - int sel = select_list("Räkenskapsår", lines, (int)n, start, 0, + int sel = tui_select_list("Räkenskapsår", lines, (int)n, start, 0, &cursor, 1, NULL, 1); for (size_t i = 0; i < n; i++) free(lines[i]); @@ -1309,7 +496,7 @@ static void select_fiscal_year(struct app *a) snprintf(label, sizeof label, "%s räkenskapsåret %s? (j/n): ", closed[cursor] ? "Återöppna" : "Avsluta", labels[cursor]); - char *ans = prompt(label, "n", 0); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, label, "n", 0) ? ansbuf : NULL; if (ans && (ans[0] == 'j' || ans[0] == 'J')) { char args[64]; snprintf(args, sizeof args, @@ -1321,7 +508,7 @@ static void select_fiscal_year(struct app *a) : "fiscal_year.close", a->session, a->org, args); if (r && client_ok(r)) - message("Räkenskapsår", "Räkenskapsåret %s är %s.", + tui_message("Räkenskapsår", "Räkenskapsåret %s är %s.", labels[cursor], closed[cursor] ? "återöppnat" : "avslutat"); else @@ -1362,11 +549,11 @@ static void show_error(const char *title, const char *resp) char *code = jstr_dup(resp, "error.code"); char *msg = jstr_dup(resp, "error.message"); if ((!code || !*code) && (!msg || !*msg)) { - message(title, "%s", resp && *resp + tui_message(title, "%s", resp && *resp ? resp : "Inget svar från servern (kör daemonen?)"); } else { - message(title, "%s: %s", code && *code ? code : "fel", + tui_message(title, "%s: %s", code && *code ? code : "fel", msg && *msg ? msg : "okänt fel"); } free(code); @@ -1442,7 +629,7 @@ static char *file_browser(struct app *a, const char *start_dir) for (;;) { DIR *d = opendir(dir); if (!d) { - message("Filväljare", "Kan inte öppna %s", dir); + tui_message("Filväljare", "Kan inte öppna %s", dir); return NULL; } struct fentry *ents = NULL; @@ -1481,7 +668,7 @@ static char *file_browser(struct app *a, const char *start_dir) } char title[1200]; snprintf(title, sizeof title, "Välj fil — %.200s", dir); - int sel = select_list(title, items, (int)n + 1, 0, 1, NULL, 0, NULL, 0); + int sel = tui_select_list(title, items, (int)n + 1, 0, 1, NULL, 0, NULL, 0); for (size_t i = 0; i <= n; i++) free(items[i]); free(items); @@ -1561,7 +748,7 @@ static void attachment_download(struct app *a, int64_t att_id) unsigned char *data = NULL; size_t n = 0; if (!b64 || util_b64_decode(b64, strlen(b64), &data, &n) != 0) { - message("Underlag", "Kunde inte avkoda innehållet."); + tui_message("Underlag", "Kunde inte avkoda innehållet."); goto done; } const char *home = getenv("HOME"); @@ -1571,7 +758,7 @@ static void attachment_download(struct app *a, int64_t att_id) if (stat(dl, &sb) != 0 || !S_ISDIR(sb.st_mode)) snprintf(dl, sizeof dl, "%s", home && *home ? home : "."); snprintf(def, sizeof def, "%s/%s", dl, fn && *fn ? fn : "underlag"); - char *path = prompt("Spara underlag: ", def, 0); + char pathbuf[512]; char *path = tui_prompt_into(pathbuf, sizeof pathbuf, "Spara underlag: ", def, 0) ? pathbuf : NULL; if (!path || !*path) goto done; char full[600]; @@ -1580,13 +767,13 @@ static void attachment_download(struct app *a, int64_t att_id) else snprintf(full, sizeof full, "%s", path); if (stat(full, &sb) == 0) { - char *ans = prompt("Filen finns, skriv över? (j/n): ", "n", 0); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, "Filen finns, skriv över? (j/n): ", "n", 0) ? ansbuf : NULL; if (!ans || (ans[0] != 'j' && ans[0] != 'J')) goto done; } FILE *f = fopen(full, "wb"); if (!f || fwrite(data, 1, n, f) != n) { - message("Underlag", "Kunde inte skriva %s: %s", full, strerror(errno)); + tui_message("Underlag", "Kunde inte skriva %s: %s", full, strerror(errno)); if (f) fclose(f); goto done; @@ -1597,17 +784,17 @@ static void attachment_download(struct app *a, int64_t att_id) util_sha256(data, n, raw); util_hex(raw, sizeof raw, hex); if (sha && *sha && strcmp(hex, sha) != 0) - message("Underlag", "VARNING: kontrollsumman stämmer inte.\nSparat: %s", + tui_message("Underlag", "VARNING: kontrollsumman stämmer inte.\nSparat: %s", full); else - message("Underlag", "Sparat: %s\n(%zu byte)", full, n); + tui_message("Underlag", "Sparat: %s\n(%zu byte)", full, n); int is_text = (mime && strncmp(mime, "text/", 5) == 0) || (n > 0 && bytes_look_text(data, n)); if (is_text && n <= 256 * 1024) { char *copy = xmalloc(n + 1); memcpy(copy, data, n); copy[n] = '\0'; - text_view("Underlag", copy, NULL); + tui_pager("Underlag", copy, NULL); free(copy); } done: @@ -1657,13 +844,13 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) snprintf(path, sizeof path, "result.rows.%zu.credit_ore", i); int64_t credit = jint_val(resp, path, 0); char d[32], c[32]; - kr_format(debit, d, sizeof d); - kr_format(credit, c, sizeof c); + tui_kr_format(debit, d, sizeof d); + tui_kr_format(credit, c, sizeof c); char acol[32], ncol[256]; snprintf(acol, sizeof acol, "%s", acc ? acc : ""); - pad_field(acol, sizeof acol, 6); + tui_pad_field(acol, sizeof acol, 6); snprintf(ncol, sizeof ncol, "%s", name ? name : ""); - pad_field(ncol, sizeof ncol, 34); + tui_pad_field(ncol, sizeof ncol, 34); snprintf(line, sizeof line, " %s %s D %12s K %12s\n", acol, ncol, d, c); buf_append(&text, line, strlen(line)); @@ -1720,10 +907,10 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) "upp/ned F5 = uppdatera ^N = nytt verifikat c =" " rätta ^F = bifoga%s Esc/q = tillbaka", natts ? " f = underlag" : ""); - frame("Verifikat"); + tui_frame("Verifikat"); for (int i = 0; i < view && top + i < n; i++) mvaddnstr(2 + i, 2, lines[top + i], COLS - 4); - hints(hint); + tui_hints(hint); refresh(); int ch = ui_getch(); if (ch == 'q' || ch == 27) { @@ -1739,7 +926,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) stop = 1; } } else if (ch == 'c') { - char *reason = prompt("Beskriv rättelsen: ", "", 0); + char reasonbuf[512]; char *reason = tui_prompt_into(reasonbuf, sizeof reasonbuf, "Beskriv rättelsen: ", "", 0) ? reasonbuf : NULL; if (reason && *reason) { yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); yyjson_mut_val *o = yyjson_mut_obj(d); @@ -1754,7 +941,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) a->org, cargs); if (r && client_ok(r)) { int64_t new_id = jint_val(r, "result.id", 0); - message("Rättat", "Ändringsverifikat %lld skapat", + tui_message("Rättat", "Ändringsverifikat %lld skapat", (long long)new_id); corrected = 1; } else { @@ -1771,14 +958,14 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) struct stat sb; if (stat(path, &sb) == 0 && (long)sb.st_size > a->max_attachment_bytes) { - message("Bilaga", "Filen är %.1f MB, max %ld MB.", + tui_message("Bilaga", "Filen är %.1f MB, max %ld MB.", (double)sb.st_size / (1024 * 1024), a->max_attachment_bytes / (1024 * 1024)); free(path); } else { char *b64 = read_file_b64(path); if (!b64) { - message("Fel", "Kunde inte läsa %s", path); + tui_message("Fel", "Kunde inte läsa %s", path); free(path); } else { const char *base = strrchr(path, '/'); @@ -1797,7 +984,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) char *r = client_rpc(&a->conn, "attachment.put", a->session, a->org, fargs); if (r && client_ok(r)) - message("Underlag", "Bifogat."); + tui_message("Underlag", "Bifogat."); else show_error("Kunde inte bifoga", r); free(r); @@ -1822,7 +1009,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) free(fn); } int acursor = 0; - int asel = select_list("Underlag", alines, (int)natts, 0, 0, + int asel = tui_select_list("Underlag", alines, (int)natts, 0, 0, &acursor, 0, "ta bort", 0); if (asel >= 0) attachment_download(a, aids[asel]); @@ -1832,7 +1019,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) snprintf(q, sizeof q, "Ta bort '%s' från verifikatet? (j/n): ", alines[acursor]); - char *ans = prompt(q, "n", 0); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, q, "n", 0) ? ansbuf : NULL; if (ans && (ans[0] == 'j' || ans[0] == 'J')) { char uargs[64]; snprintf(uargs, sizeof uargs, @@ -1841,7 +1028,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) char *r = client_rpc(&a->conn, "attachment.unlink", a->session, a->org, uargs); if (r && client_ok(r)) { - message("Underlag", "Länken borttagen."); + tui_message("Underlag", "Länken borttagen."); want_refresh = 1; stop = 1; } else { @@ -2033,7 +1220,7 @@ static int64_t vouchers_new(struct app *a) text_w = 8; for (;;) { - frame("Nytt verifikat"); + tui_frame("Nytt verifikat"); int caret_y = -1, caret_x = -1; attron(A_BOLD); mvaddstr(2, 2, "Datum"); @@ -2050,7 +1237,7 @@ static int64_t vouchers_new(struct app *a) int w = hw[k] > 1 ? hw[k] : 1; char padded[512]; snprintf(padded, sizeof padded, "%s", hvals[k]); - pad_field(padded, sizeof padded, w); + tui_pad_field(padded, sizeof padded, w); if (field == k) { attron(A_REVERSE); caret_y = hy[k]; @@ -2059,7 +1246,7 @@ static int64_t vouchers_new(struct app *a) field_pos > slen) ? slen : field_pos; - int cw = (int)disp_width_n(hvals[k], cp); + int cw = (int)tui_disp_width_n(hvals[k], cp); caret_x = hx[k] + (cw > w ? w : cw); } mvaddstr(hy[k], hx[k], padded); @@ -2079,9 +1266,9 @@ static int64_t vouchers_new(struct app *a) for (int i = 0; i < nrows; i++) { int y = 7 + i; int64_t v; - if (parse_kr(rows[i].debit, &v) == 0) + if (tui_parse_kr(rows[i].debit, &v) == 0) sum_d += v; - if (parse_kr(rows[i].credit, &v) == 0) + if (tui_parse_kr(rows[i].credit, &v) == 0) sum_c += v; int sel_ci = -1; if (field >= 3) { @@ -2097,7 +1284,7 @@ static int64_t vouchers_new(struct app *a) int w = ws[k] > 1 ? ws[k] : 1; char padded[512]; snprintf(padded, sizeof padded, "%s", vals[k]); - pad_field(padded, sizeof padded, w); + tui_pad_field(padded, sizeof padded, w); if (k == sel_ci) { attron(A_REVERSE); caret_y = y; @@ -2106,7 +1293,7 @@ static int64_t vouchers_new(struct app *a) field_pos > slen) ? slen : field_pos; - int cw = (int)disp_width_n(vals[k], cp); + int cw = (int)tui_disp_width_n(vals[k], cp); caret_x = xs[k] + (cw > w ? w : cw); } mvaddstr(y, xs[k], padded); @@ -2117,16 +1304,16 @@ static int64_t vouchers_new(struct app *a) char nbuf[512]; const char *nm = acct_name(a, rows[i].account); snprintf(nbuf, sizeof nbuf, "%s", nm ? nm : ""); - pad_field(nbuf, sizeof nbuf, name_w); + tui_pad_field(nbuf, sizeof nbuf, name_w); attron(A_DIM); mvaddstr(y, x_name, nbuf); attroff(A_DIM); } } char d1[32], c1[32], diff[32]; - kr_format(sum_d, d1, sizeof d1); - kr_format(sum_c, c1, sizeof c1); - kr_format(sum_d - sum_c, diff, sizeof diff); + tui_kr_format(sum_d, d1, sizeof d1); + tui_kr_format(sum_c, c1, sizeof c1); + tui_kr_format(sum_d - sum_c, diff, sizeof diff); int balanced = sum_d == sum_c; move(7 + nrows, 2); clrtoeol(); @@ -2149,7 +1336,7 @@ static int64_t vouchers_new(struct app *a) attroff(A_BOLD); if (!balanced) mvprintw(7 + nrows + 2, 2, "Differens: %s", diff); - hints("F4 = mall ^F = bifoga fil Tab = byta fält F5 = validera" + tui_hints("F4 = mall ^F = bifoga fil Tab = byta fält F5 = validera" " ^X = rensa rad ^Enter = bokför Esc = avbryt"); if (caret_y >= 0) move(caret_y, caret_x); @@ -2203,7 +1390,7 @@ static int64_t vouchers_new(struct app *a) } size_t ln = jarr_size(lresp, "result.items"); if (ln == 0) { - message("Mallar", + tui_message("Mallar", "Inga mallar ännu. Skapa en under Mallar först."); free(lresp); continue; @@ -2223,7 +1410,7 @@ static int64_t vouchers_new(struct app *a) char *ds = jstr_dup(lresp, path); char nmbuf[128]; snprintf(nmbuf, sizeof nmbuf, "%s", nm ? nm : ""); - pad_field(nmbuf, sizeof nmbuf, 24); + tui_pad_field(nmbuf, sizeof nmbuf, 24); snprintf(line, sizeof line, "%s %-3s %2lld rader %s", nmbuf, ser ? ser : "", (long long)rc, ds ? ds : ""); names[i] = xstrdup(nm ? nm : ""); @@ -2232,7 +1419,7 @@ static int64_t vouchers_new(struct app *a) free(ser); free(ds); } - int tsel = select_list("Välj mall", lines, (int)ln, 0, 1, NULL, 0, NULL, 0); + int tsel = tui_select_list("Välj mall", lines, (int)ln, 0, 1, NULL, 0, NULL, 0); if (tsel >= 0) snprintf(tname, sizeof tname, "%s", names[tsel]); for (size_t i = 0; i < ln; i++) { @@ -2260,10 +1447,10 @@ static int64_t vouchers_new(struct app *a) show_error("Mall", resp); free(resp); } else { - char *xs = prompt("Belopp (x, kr): ", "", 0); + char xsbuf[512]; char *xs = tui_prompt_into(xsbuf, sizeof xsbuf, "Belopp (x, kr): ", "", 0) ? xsbuf : NULL; double xv = 0; if (xs && *xs && !parse_x_double(xs, &xv)) { - message("Fel", "Ogiltigt belopp."); + tui_message("Fel", "Ogiltigt belopp."); free(resp); continue; } @@ -2294,7 +1481,7 @@ static int64_t vouchers_new(struct app *a) char ferr[256] = ""; if (formula_resolve_rows(in, n, xv, out, &on, ferr, sizeof ferr) != 0) { - message("Mall", "%s", + tui_message("Mall", "%s", ferr[0] ? ferr : "kunde inte lösa mallen"); } else { memset(rows, 0, sizeof rows); @@ -2304,11 +1491,11 @@ static int64_t vouchers_new(struct app *a) "%s", out[i].account); char tmp[32]; if (out[i].debit_ore) { - kr_format(out[i].debit_ore, tmp, sizeof tmp); + tui_kr_format(out[i].debit_ore, tmp, sizeof tmp); snprintf(rows[i].debit, sizeof rows[i].debit, "%s", tmp); } else { - kr_format(out[i].credit_ore, tmp, + tui_kr_format(out[i].credit_ore, tmp, sizeof tmp); snprintf(rows[i].credit, sizeof rows[i].credit, "%s", tmp); @@ -2352,7 +1539,7 @@ static int64_t vouchers_new(struct app *a) struct stat sb; if (stat(path, &sb) == 0 && (long)sb.st_size > a->max_attachment_bytes) { - message("Bilaga", "Filen är %.1f MB, max %ld MB.", + tui_message("Bilaga", "Filen är %.1f MB, max %ld MB.", (double)sb.st_size / (1024 * 1024), a->max_attachment_bytes / (1024 * 1024)); free(path); @@ -2360,9 +1547,9 @@ static int64_t vouchers_new(struct app *a) } char *b64 = read_file_b64(path); if (!b64) { - message("Bilaga", "Kunde inte läsa %s", path); + tui_message("Bilaga", "Kunde inte läsa %s", path); } else if (natt >= 32) { - message("Bilaga", "Max 32 bilagor per verifikat."); + tui_message("Bilaga", "Max 32 bilagor per verifikat."); } else { const char *base = strrchr(path, '/'); base = base ? base + 1 : path; @@ -2429,9 +1616,9 @@ static int64_t vouchers_new(struct app *a) if (!rows[i].account[0]) continue; int64_t dv = 0, cv = 0; - if (parse_kr(rows[i].debit, &dv) != 0 || - parse_kr(rows[i].credit, &cv) != 0) { - message("Fel", "Ogiltigt belopp på rad %d", i + 1); + if (tui_parse_kr(rows[i].debit, &dv) != 0 || + tui_parse_kr(rows[i].credit, &cv) != 0) { + tui_message("Fel", "Ogiltigt belopp på rad %d", i + 1); yyjson_mut_doc_free(d); goto next_key; } @@ -2463,14 +1650,14 @@ static int64_t vouchers_new(struct app *a) char *r = client_read_line(&a->conn); if (r && client_ok(r)) { int64_t num = jint_val(r, "result.number", 0); - message("Validering OK", + tui_message("Validering OK", "Nummer %lld skulle tilldelas.", (long long)num); } else { show_error("Validering misslyckades", r); } free(r); } else { - message("Fel", "Kunde inte nå servern"); + tui_message("Fel", "Kunde inte nå servern"); } free(raw); } else { @@ -2481,7 +1668,7 @@ static int64_t vouchers_new(struct app *a) int64_t num = jint_val(r, "result.number", 0); char *ser = jstr_dup(r, "result.series"); int replayed = jbool_val(r, "result.replayed", 0); - message("Bokfört", "%s%lld (id %lld)%s", ser ? ser : "", + tui_message("Bokfört", "%s%lld (id %lld)%s", ser ? ser : "", (long long)num, (long long)id, replayed ? " — redan bokfört (idempotent)" : ""); free(ser); @@ -2538,9 +1725,9 @@ static int64_t vouchers_new(struct app *a) /* first keystroke in a freshly focused field replaces its content (so a prefilled date can be typed over directly) */ if (field == 0) - date_field_edit(buf, cap, &field_pos, ch, &field_fresh); + tui_date_field_edit(buf, cap, &field_pos, ch, &field_fresh); else - field_edit(buf, cap, &field_pos, ch, &field_fresh); + tui_field_edit(buf, cap, &field_pos, ch, &field_fresh); normalize_rows(rows, &nrows, &field); nfields = 3 + 4 * nrows; } @@ -2633,7 +1820,7 @@ static void vouchers_screen(struct app *a) } } int cur = start; - int sel = select_list("Verifikat", items, (int)total, start, 1, &cur, + int sel = tui_select_list("Verifikat", items, (int)total, start, 1, &cur, 1, NULL, 0); if (cur >= 0 && (size_t)cur < n) a->voucher_sel = ids[cur]; @@ -2800,9 +1987,9 @@ static void accounts_report(struct app *a) char vatbuf[32]; char tibuf[32]; snprintf(ncol, sizeof ncol, "%s", name ? name : ""); - pad_field(ncol, sizeof ncol, name_w); + tui_pad_field(ncol, sizeof ncol, name_w); snprintf(tibuf, sizeof tibuf, "%s", type_sv(type)); - pad_field(tibuf, sizeof tibuf, 12); + tui_pad_field(tibuf, sizeof tibuf, 12); snprintf(line, sizeof line, "%-6s %s %s %-5s %-6s %-8s\n", number ? number : "", ncol, tibuf, active ? "ja" : "nej", sru ? sru : "", @@ -2820,7 +2007,7 @@ static void accounts_report(struct app *a) (long long)n, (long long)active_count); buf_append(&text, line, strlen(line)); buf_append(&text, "\0", 1); - int again = text_view("Kontolista", (char *)text.p, NULL); + int again = tui_pager("Kontolista", (char *)text.p, NULL); buf_free(&text); free(resp); if (!again) @@ -2895,7 +2082,7 @@ static int acct_in(const char *number, int lo, int hi) /* Pad to `width` display columns without truncating (labels may be longer). */ static void pad_min(char *buf, size_t n, int width) { - int w = disp_width(buf); + int w = tui_disp_width(buf); size_t len = strlen(buf); while (w < width && len + 1 < n) { buf[len++] = ' '; @@ -2905,25 +2092,6 @@ static void pad_min(char *buf, size_t n, int width) } /* Right-align `text` in `width` display columns (headers with å/ä/ö). */ -static void pad_hdr(char *buf, size_t n, int width, const char *text) -{ - int w = disp_width(text); - int pad = width > w ? width - w : 0; - snprintf(buf, n, "%*s%s", pad, "", text); -} - -static void pad_label(char *buf, size_t n, const char *text, int width) -{ - snprintf(buf, n, "%s", text); - pad_field(buf, n, width); -} - -static void amt_col(char *buf, size_t n, int width, int64_t ore) -{ - char a[48]; - kr_format(ore, a, sizeof a); - snprintf(buf, n, "%*s", width, a); -} /* ---------------- balansräkning ---------------- */ @@ -2958,11 +2126,11 @@ static int bs_rows(struct buf *t, yyjson_val *res, int lo, int hi, int64_t per = ub - ib; if (t) { char nm[128], a1[48], a2[48], a3[48], a4[48]; - pad_label(nm, sizeof nm, vstr(row, "name"), 48); - amt_col(a1, sizeof a1, 14, ib); - amt_col(a2, sizeof a2, 14, ib); - amt_col(a3, sizeof a3, 14, per); - amt_col(a4, sizeof a4, 14, ub); + tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48); + tui_amt_col(a1, sizeof a1, 14, ib); + tui_amt_col(a2, sizeof a2, 14, ib); + tui_amt_col(a3, sizeof a3, 14, per); + tui_amt_col(a4, sizeof a4, 14, ub); buf_line(t, " %5s %s %s %s %s %s\n", acc, nm, a1, a2, a3, a4); } sum->ib += ib; @@ -2978,11 +2146,11 @@ static void bs_sum_line(struct buf *t, const char *label, const struct bs_sum *s) { char lbl[64], a1[48], a2[48], a3[48], a4[48]; - pad_label(lbl, sizeof lbl, label, 48); - amt_col(a1, sizeof a1, 14, s->ib); - amt_col(a2, sizeof a2, 14, s->ib); - amt_col(a3, sizeof a3, 14, s->per); - amt_col(a4, sizeof a4, 14, s->ub); + tui_pad_label(lbl, sizeof lbl, label, 48); + tui_amt_col(a1, sizeof a1, 14, s->ib); + tui_amt_col(a2, sizeof a2, 14, s->ib); + tui_amt_col(a3, sizeof a3, 14, s->per); + tui_amt_col(a4, sizeof a4, 14, s->ub); buf_line(t, " %5s %s %s %s %s %s\n", "", lbl, a1, a2, a3, a4); } @@ -3066,9 +2234,9 @@ static void fmt_balans(struct buf *t, const struct app *a, yyjson_val *res) yyjson_val *eq = yyjson_obj_get(res, "equity"); int64_t result = eq ? vint(eq, "result_ore") : 0; char lbl[64], zero[48], a1[48]; - pad_label(lbl, sizeof lbl, "Beräknat resultat", 48); - amt_col(zero, sizeof zero, 14, 0); - amt_col(a1, sizeof a1, 14, result); + tui_pad_label(lbl, sizeof lbl, "Beräknat resultat", 48); + tui_amt_col(zero, sizeof zero, 14, 0); + tui_amt_col(a1, sizeof a1, 14, result); buf_line(t, "\n"); buf_rule(t, BS_RULE_W); buf_line(t, " %5s %s %s %s %s %s\n", "", lbl, zero, zero, a1, a1); @@ -3137,9 +2305,9 @@ static void is_summary(struct buf *t, const char *label, int64_t cur, char lbl[64], a1[48], a2[48]; if (blank) buf_line(t, "\n"); - pad_label(lbl, sizeof lbl, label, 48); - amt_col(a1, sizeof a1, 15, cur); - amt_col(a2, sizeof a2, 15, prev); + tui_pad_label(lbl, sizeof lbl, label, 48); + tui_amt_col(a1, sizeof a1, 15, cur); + tui_amt_col(a2, sizeof a2, 15, prev); buf_line(t, " %5s %s %s %s %s\n", "", lbl, a1, a1, a2); } @@ -3161,9 +2329,9 @@ static void is_print_group(struct buf *t, yyjson_val *res, yyjson_val *prev, : -vint(row, "amount_ore"); int64_t p = is_prev_lookup(prev, acc, !gr->rev); char nm[128], a1[48], a2[48]; - pad_label(nm, sizeof nm, vstr(row, "name"), 48); - amt_col(a1, sizeof a1, 15, c); - amt_col(a2, sizeof a2, 15, p); + tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48); + tui_amt_col(a1, sizeof a1, 15, c); + tui_amt_col(a2, sizeof a2, 15, p); buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2); sc += c; sp += p; @@ -3205,9 +2373,9 @@ static void is_range_rows(struct buf *t, yyjson_val *res, yyjson_val *prev, : -vint(row, "amount_ore"); int64_t p = is_prev_lookup(prev, acc, k != 0); char nm[128], a1[48], a2[48]; - pad_label(nm, sizeof nm, vstr(row, "name"), 48); - amt_col(a1, sizeof a1, 15, c); - amt_col(a2, sizeof a2, 15, p); + tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48); + tui_amt_col(a1, sizeof a1, 15, c); + tui_amt_col(a2, sizeof a2, 15, p); buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2); *sum += c; *sum_pre += p; @@ -3233,10 +2401,10 @@ static void fmt_resultat(struct buf *t, const struct app *a, yyjson_val *res, buf_line(t, "%s\n", gr->section); } else { char lbl[80], h1[32], h2[32], h3[32]; - pad_label(lbl, sizeof lbl, gr->section, 48); - pad_hdr(h1, sizeof h1, 15, "Period"); - pad_hdr(h2, sizeof h2, 15, "Ackumulerat"); - pad_hdr(h3, sizeof h3, 15, "Föreg. per."); + tui_pad_label(lbl, sizeof lbl, gr->section, 48); + tui_pad_hdr(h1, sizeof h1, 15, "Period"); + tui_pad_hdr(h2, sizeof h2, 15, "Ackumulerat"); + tui_pad_hdr(h3, sizeof h3, 15, "Föreg. per."); buf_line(t, " %5s %s %s %s %s\n", "", lbl, h1, h2, h3); buf_rule(t, IS_RULE_W); buf_line(t, "\n"); @@ -3313,11 +2481,11 @@ static void fmt_saldobalans(struct buf *t, const struct app *a, { fmt_head(t, a, "Saldobalans", res); char nmh[64], h1[32], h2[32], h3[32], h4[32]; - pad_label(nmh, sizeof nmh, "Benämning", 48); - pad_hdr(h1, sizeof h1, 14, "Ingående"); - pad_hdr(h2, sizeof h2, 14, "Debet"); - pad_hdr(h3, sizeof h3, 14, "Kredit"); - pad_hdr(h4, sizeof h4, 14, "Utgående"); + tui_pad_label(nmh, sizeof nmh, "Benämning", 48); + tui_pad_hdr(h1, sizeof h1, 14, "Ingående"); + tui_pad_hdr(h2, sizeof h2, 14, "Debet"); + tui_pad_hdr(h3, sizeof h3, 14, "Kredit"); + tui_pad_hdr(h4, sizeof h4, 14, "Utgående"); buf_line(t, " %5s %s %s %s %s %s\n", "Konto", nmh, h1, h2, h3, h4); buf_rule(t, BS_RULE_W); yyjson_val *arr = yyjson_obj_get(res, "accounts"); @@ -3325,21 +2493,21 @@ static void fmt_saldobalans(struct buf *t, const struct app *a, for (size_t i = 0; i < n; i++) { yyjson_val *row = yyjson_arr_get(arr, i); char nm[128], a1[48], a2[48], a3[48], a4[48]; - pad_label(nm, sizeof nm, vstr(row, "name"), 48); - amt_col(a1, sizeof a1, 14, vint(row, "ib_ore")); - amt_col(a2, sizeof a2, 14, vint(row, "debit_ore")); - amt_col(a3, sizeof a3, 14, vint(row, "credit_ore")); - amt_col(a4, sizeof a4, 14, vint(row, "ub_ore")); + tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48); + tui_amt_col(a1, sizeof a1, 14, vint(row, "ib_ore")); + tui_amt_col(a2, sizeof a2, 14, vint(row, "debit_ore")); + tui_amt_col(a3, sizeof a3, 14, vint(row, "credit_ore")); + tui_amt_col(a4, sizeof a4, 14, vint(row, "ub_ore")); buf_line(t, " %5s %s %s %s %s %s\n", vstr(row, "account"), nm, a1, a2, a3, a4); } yyjson_val *tot = yyjson_obj_get(res, "totals"); char lbl[64], a1[48], a2[48], a3[48], a4[48]; - pad_label(lbl, sizeof lbl, "Summa", 48); - amt_col(a1, sizeof a1, 14, vint(tot, "ib_ore")); - amt_col(a2, sizeof a2, 14, vint(tot, "debit_ore")); - amt_col(a3, sizeof a3, 14, vint(tot, "credit_ore")); - amt_col(a4, sizeof a4, 14, vint(tot, "ub_ore")); + tui_pad_label(lbl, sizeof lbl, "Summa", 48); + tui_amt_col(a1, sizeof a1, 14, vint(tot, "ib_ore")); + tui_amt_col(a2, sizeof a2, 14, vint(tot, "debit_ore")); + tui_amt_col(a3, sizeof a3, 14, vint(tot, "credit_ore")); + tui_amt_col(a4, sizeof a4, 14, vint(tot, "ub_ore")); buf_line(t, "\n"); buf_rule(t, BS_RULE_W); buf_line(t, " %5s %s %s %s %s %s\n", "", lbl, a1, a2, a3, a4); @@ -3508,11 +2676,11 @@ static void fmt_huvudbok(struct buf *t, const struct app *a, yyjson_val *res) int64_t c = vint(ao, "credit_ore"); buf_line(t, "\n%s %s\n\n", vstr(ao, "account"), vstr(ao, "name")); char a1[48]; - amt_col(a1, sizeof a1, 16, ib); + tui_amt_col(a1, sizeof a1, 16, ib); char lbl[96]; - pad_label(lbl, sizeof lbl, "Ingående balans:", 59); + tui_pad_label(lbl, sizeof lbl, "Ingående balans:", 59); buf_line(t, " %s %14s %14s %16s\n", lbl, "", "", a1); - pad_label(lbl, sizeof lbl, "Ingående saldo:", 59); + tui_pad_label(lbl, sizeof lbl, "Ingående saldo:", 59); buf_line(t, " %s %14s %14s %16s\n", lbl, "", "", a1); buf_line(t, "\n"); yyjson_val *rows = yyjson_obj_get(ao, "rows"); @@ -3525,23 +2693,23 @@ static void fmt_huvudbok(struct buf *t, const struct app *a, yyjson_val *res) const char *txt = vstr(ro, "row_description"); if (!*txt) txt = vstr(ro, "description"); - pad_label(nm, sizeof nm, txt, 40); - amt_col(dd, sizeof dd, 14, vint(ro, "debit_ore")); - amt_col(cc, sizeof cc, 14, vint(ro, "credit_ore")); + tui_pad_label(nm, sizeof nm, txt, 40); + tui_amt_col(dd, sizeof dd, 14, vint(ro, "debit_ore")); + tui_amt_col(cc, sizeof cc, 14, vint(ro, "credit_ore")); snprintf(deb, sizeof deb, "%s", vint(ro, "debit_ore") ? dd : ""); snprintf(kre, sizeof kre, "%s", vint(ro, "credit_ore") ? cc : ""); - amt_col(sal, sizeof sal, 16, vint(ro, "saldo_ore")); + tui_amt_col(sal, sizeof sal, 16, vint(ro, "saldo_ore")); buf_line(t, " %-7s %-10s %s %14s %14s %16s\n", ver, vstr(ro, "date"), nm, deb, kre, sal); } char s1[48], s2[48], s3[48]; - amt_col(s1, sizeof s1, 14, d); - amt_col(s2, sizeof s2, 14, c); - amt_col(s3, sizeof s3, 16, d - c); - pad_label(lbl, sizeof lbl, "Omslutning:", 59); + tui_amt_col(s1, sizeof s1, 14, d); + tui_amt_col(s2, sizeof s2, 14, c); + tui_amt_col(s3, sizeof s3, 16, d - c); + tui_pad_label(lbl, sizeof lbl, "Omslutning:", 59); buf_line(t, " %s %14s %14s %16s\n", lbl, s1, s2, s3); - amt_col(s3, sizeof s3, 16, vint(ao, "ub_ore")); - pad_label(lbl, sizeof lbl, "Utgående saldo:", 59); + tui_amt_col(s3, sizeof s3, 16, vint(ao, "ub_ore")); + tui_pad_label(lbl, sizeof lbl, "Utgående saldo:", 59); buf_line(t, " %s %14s %14s %16s\n", lbl, s1, s2, s3); } } @@ -3557,7 +2725,7 @@ static void fmt_verifikationslista(struct buf *t, const struct app *a, buf_line(t, "Senaste ver.: %s%lld\n\n", vstr(lv, "series"), (long long)vint(lv, "number")); char nmh[64]; - pad_label(nmh, sizeof nmh, "Benämning", 48); + tui_pad_label(nmh, sizeof nmh, "Benämning", 48); buf_line(t, "%-8s %-11s %s %14s %14s\n", "Ver.", "Datum/Konto", nmh, "Debet", "Kredit"); buf_rule(t, 98); @@ -3575,9 +2743,9 @@ static void fmt_verifikationslista(struct buf *t, const struct app *a, for (size_t k = 0; k < nr; k++) { yyjson_val *ro = yyjson_arr_get(rows, k); char nm[256], dd[48], cc[48], deb[48], kre[48]; - pad_label(nm, sizeof nm, vstr(ro, "name"), 48); - amt_col(dd, sizeof dd, 14, vint(ro, "debit_ore")); - amt_col(cc, sizeof cc, 14, vint(ro, "credit_ore")); + tui_pad_label(nm, sizeof nm, vstr(ro, "name"), 48); + tui_amt_col(dd, sizeof dd, 14, vint(ro, "debit_ore")); + tui_amt_col(cc, sizeof cc, 14, vint(ro, "credit_ore")); snprintf(deb, sizeof deb, "%s", vint(ro, "debit_ore") ? dd : ""); snprintf(kre, sizeof kre, "%s", vint(ro, "credit_ore") ? cc : ""); buf_line(t, " %-10s %s %14s %14s\n", vstr(ro, "account"), @@ -3586,8 +2754,8 @@ static void fmt_verifikationslista(struct buf *t, const struct app *a, } yyjson_val *tot = yyjson_obj_get(res, "totals"); char s1[48], s2[48]; - amt_col(s1, sizeof s1, 14, vint(tot, "debit_ore")); - amt_col(s2, sizeof s2, 14, vint(tot, "credit_ore")); + tui_amt_col(s1, sizeof s1, 14, vint(tot, "debit_ore")); + tui_amt_col(s2, sizeof s2, 14, vint(tot, "credit_ore")); buf_line(t, "\n%-69s %14s %14s\n", "Omslutning:", s1, s2); } @@ -3598,7 +2766,7 @@ static void eskd_save(struct app *a, const char *from, const char *to) snprintf(period, sizeof period, "%.4s%.2s", to, to + 5); snprintf(def, sizeof def, "%s/eskd_%s.xml", home && *home ? home : ".", period); - char *path = prompt("Spara eSKD-fil: ", def, 0); + char pathbuf[512]; char *path = tui_prompt_into(pathbuf, sizeof pathbuf, "Spara eSKD-fil: ", def, 0) ? pathbuf : NULL; if (!path || !*path) return; if (path[0] == '~' && (path[1] == '/' || path[1] == '\0')) @@ -3618,16 +2786,16 @@ static void eskd_save(struct app *a, const char *from, const char *to) unsigned char *data = NULL; size_t n = 0; if (!b64 || util_b64_decode(b64, strlen(b64), &data, &n) != 0) { - message("eSKD", "Kunde inte avkoda filen från servern."); + tui_message("eSKD", "Kunde inte avkoda filen från servern."); } else { FILE *f = fopen(full, "wb"); if (!f || fwrite(data, 1, n, f) != n) { - message("eSKD", "Kunde inte skriva %s: %s", full, strerror(errno)); + tui_message("eSKD", "Kunde inte skriva %s: %s", full, strerror(errno)); if (f) fclose(f); } else { fclose(f); - message("eSKD", "Sparat: %s\n(%zu byte, ISO-8859-1)", full, n); + tui_message("eSKD", "Sparat: %s\n(%zu byte, ISO-8859-1)", full, n); } } free(data); @@ -3813,8 +2981,8 @@ static void sru_rows(struct buf *t, yyjson_val *arr) for (size_t i = 0; i < n; i++) { yyjson_val *row = yyjson_arr_get(arr, i); char lbl[256], amt[48]; - pad_label(lbl, sizeof lbl, sru_label(vstr(row, "code")), 66); - kr_format(vint(row, "amount") * 100, amt, sizeof amt); + tui_pad_label(lbl, sizeof lbl, sru_label(vstr(row, "code")), 66); + tui_kr_format(vint(row, "amount") * 100, amt, sizeof amt); buf_line(t, "%s %14s\n", lbl, amt); } } @@ -3841,7 +3009,7 @@ static void fmt_ink2(struct buf *t, const struct app *a, yyjson_val *res) for (size_t i = 0; i < nu; i++) { yyjson_val *row = yyjson_arr_get(un, i); char amt[48]; - kr_format(vint(row, "amount_ore"), amt, sizeof amt); + tui_kr_format(vint(row, "amount_ore"), amt, sizeof amt); buf_line(t, " %s %s\n", vstr(row, "account"), amt); } } @@ -3855,7 +3023,7 @@ static void sru_save(struct app *a) struct stat sb; if (stat(def, &sb) != 0 || !S_ISDIR(sb.st_mode)) snprintf(def, sizeof def, "%s", home && *home ? home : "."); - char *dir = prompt("Spara SRU-filer i: ", def, 0); + char dirbuf[512]; char *dir = tui_prompt_into(dirbuf, sizeof dirbuf, "Spara SRU-filer i: ", def, 0) ? dirbuf : NULL; if (!dir || !*dir) return; char full[512]; @@ -3892,7 +3060,7 @@ static void sru_save(struct app *a) } else { if (f) fclose(f); - message("SRU", "Kunde inte skriva %s: %s", saved[i], + tui_message("SRU", "Kunde inte skriva %s: %s", saved[i], strerror(errno)); } free(data); @@ -3900,7 +3068,7 @@ static void sru_save(struct app *a) } free(resp); if (ok == 2) - message("SRU", "Sparat:\n%s\n%s\n\nLadda upp båda i Skatteverkets" + tui_message("SRU", "Sparat:\n%s\n%s\n\nLadda upp båda i Skatteverkets" " filöverföring.", saved[0], saved[1]); } @@ -3922,7 +3090,7 @@ static void reports_screen(struct app *a) for (;;) { if (g_quit) return; - int sel = menu("Rapporter", report_items, 9, 0, &last_sel); + int sel = tui_menu("Rapporter", report_items, 9, 0, &last_sel); if (sel < 0) return; if (sel == 6) { @@ -3947,7 +3115,7 @@ static void reports_screen(struct app *a) else buf_line(&out, "%s", resp); buf_append(&out, "", 1); - int sagain = text_view("Rapport", (const char *)out.p, + int sagain = tui_pager("Rapport", (const char *)out.p, "s = spara SRU"); buf_free(&out); yyjson_doc_free(sd); @@ -3999,12 +3167,12 @@ static void reports_screen(struct app *a) break; case 5: { cmd = "report.vat"; - char *from = date_prompt("Från (YYYY-MM-DD): ", a->fy_start); + char frombuf[16]; char *from = tui_date_prompt_into(frombuf, sizeof frombuf, "Från (YYYY-MM-DD): ", a->fy_start) ? frombuf : NULL; if (!from) continue; char f[16]; snprintf(f, sizeof f, "%s", from); - char *to = date_prompt("Till (YYYY-MM-DD): ", a->fy_end); + char tobuf[16]; char *to = tui_date_prompt_into(tobuf, sizeof tobuf, "Till (YYYY-MM-DD): ", a->fy_end) ? tobuf : NULL; if (!to) continue; snprintf(vat_from, sizeof vat_from, "%s", f); @@ -4060,7 +3228,7 @@ static void reports_screen(struct app *a) fmt_moms(&out, a, res); } buf_append(&out, "", 1); - int again = text_view("Rapport", (const char *)out.p, + int again = tui_pager("Rapport", (const char *)out.p, sel == 5 ? "s = spara eSKD" : NULL); buf_free(&out); yyjson_doc_free(d); @@ -4090,7 +3258,7 @@ static int64_t pick_voucher(struct app *a) } size_t n = jarr_size(resp, "result.items"); if (n == 0) { - message("Verifikat", "Inga verifikat i räkenskapsåret."); + tui_message("Verifikat", "Inga verifikat i räkenskapsåret."); free(resp); return 0; } @@ -4117,7 +3285,7 @@ static int64_t pick_voucher(struct app *a) free(date); free(desc); } - int sel = select_list("Välj verifikat", items, (int)n, 0, 1, NULL, 0, + int sel = tui_select_list("Välj verifikat", items, (int)n, 0, 1, NULL, 0, NULL, 0); int64_t out = sel >= 0 ? ids[sel] : 0; for (size_t i = 0; i < n; i++) @@ -4154,7 +3322,7 @@ static void inbox_screen(struct app *a) int64_t size = jint_val(resp, path, 0); char fnbuf[256]; snprintf(fnbuf, sizeof fnbuf, "%s", fn ? fn : ""); - pad_field(fnbuf, sizeof fnbuf, 40); + tui_pad_field(fnbuf, sizeof fnbuf, 40); snprintf(line, sizeof line, "%s %8lld byte", fnbuf, (long long)size); items[i] = xstrdup(line); @@ -4173,7 +3341,7 @@ static void inbox_screen(struct app *a) top = (int)n - view; if (top < 0) top = 0; - frame("Underlag (inkorg)"); + tui_frame("Underlag (inkorg)"); for (size_t i = (size_t)top; i < n && (int)(i - (size_t)top) < view; i++) { int y = 2 + (int)(i - (size_t)top); @@ -4185,7 +3353,7 @@ static void inbox_screen(struct app *a) } if (n == 0) mvaddstr(2, 2, "Inga obokförda underlag."); - hints("Enter = hämta k = koppla till verifikat a/^N = lägg till" + tui_hints("Enter = hämta k = koppla till verifikat a/^N = lägg till" " fil PgUp/PgDn, Home/End rullar F5 = uppdatera Esc/q =" " tillbaka"); refresh(); @@ -4222,7 +3390,7 @@ static void inbox_screen(struct app *a) char *r = client_rpc(&a->conn, "attachment.link", a->session, a->org, kargs); if (r && client_ok(r)) - message("Underlag", "Kopplat till verifikatet."); + tui_message("Underlag", "Kopplat till verifikatet."); else show_error("Kunde inte koppla", r); free(r); @@ -4234,7 +3402,7 @@ static void inbox_screen(struct app *a) struct stat sb; if (stat(path, &sb) == 0 && (long)sb.st_size > a->max_attachment_bytes) { - message("Bilaga", "Filen är %.1f MB, max %ld MB.", + tui_message("Bilaga", "Filen är %.1f MB, max %ld MB.", (double)sb.st_size / (1024 * 1024), a->max_attachment_bytes / (1024 * 1024)); free(path); @@ -4242,7 +3410,7 @@ static void inbox_screen(struct app *a) } char *b64 = read_file_b64(path); if (!b64) { - message("Fel", "Kunde inte läsa %s", path); + tui_message("Fel", "Kunde inte läsa %s", path); continue; } const char *base = strrchr(path, '/'); @@ -4267,7 +3435,7 @@ static void inbox_screen(struct app *a) r = client_read_line(&a->conn); free(raw); if (r && client_ok(r)) - message("Underlag", "Sparat."); + tui_message("Underlag", "Sparat."); else show_error("Kunde inte spara", r); free(r); @@ -4283,7 +3451,7 @@ static void audit_screen(struct app *a) for (;;) { if (g_quit) return; - int sel = menu("Revision", audit_items, 2, 0, &last_sel); + int sel = tui_menu("Revision", audit_items, 2, 0, &last_sel); if (sel < 0) return; if (sel == 0) { @@ -4296,7 +3464,7 @@ static void audit_screen(struct app *a) } int64_t checked = jint_val(resp, "result.checked", 0); int is_ok = jbool_val(resp, "result.ok", 0); - message("Hashkedja", "%s\n%d händelser kontrollerade.", + tui_message("Hashkedja", "%s\n%d händelser kontrollerade.", is_ok ? "Kedjan är intakt." : "KEDJAN ÄR BRUTEN!", (int)checked); free(resp); @@ -4333,7 +3501,7 @@ static void audit_screen(struct app *a) free(code); } buf_append(&text, "\0", 1); - int again = text_view("Behandlingshistorik", (char *)text.p, NULL); + int again = tui_pager("Behandlingshistorik", (char *)text.p, NULL); buf_free(&text); free(resp); if (!again) @@ -4464,7 +3632,7 @@ static int template_form(struct app *a, const char *load_name) text_w = 8; for (;;) { - frame(load_name ? "Redigera mall" : "Ny mall"); + tui_frame(load_name ? "Redigera mall" : "Ny mall"); int caret_y = -1, caret_x = -1; attron(A_BOLD); mvaddstr(2, 2, "Namn"); @@ -4480,7 +3648,7 @@ static int template_form(struct app *a, const char *load_name) int w = hw[k] > 1 ? hw[k] : 1; char padded[512]; snprintf(padded, sizeof padded, "%s", hvals[k]); - pad_field(padded, sizeof padded, w); + tui_pad_field(padded, sizeof padded, w); if (field == k) { attron(A_REVERSE); caret_y = hy[k]; @@ -4489,7 +3657,7 @@ static int template_form(struct app *a, const char *load_name) field_pos > slen) ? slen : field_pos; - int cw = (int)disp_width_n(hvals[k], cp); + int cw = (int)tui_disp_width_n(hvals[k], cp); caret_x = hx[k] + (cw > w ? w : cw); } mvaddstr(hy[k], hx[k], padded); @@ -4520,7 +3688,7 @@ static int template_form(struct app *a, const char *load_name) int w = ws[k] > 1 ? ws[k] : 1; char padded[512]; snprintf(padded, sizeof padded, "%s", vals[k]); - pad_field(padded, sizeof padded, w); + tui_pad_field(padded, sizeof padded, w); if (k == sel_ci) { attron(A_REVERSE); caret_y = y; @@ -4529,7 +3697,7 @@ static int template_form(struct app *a, const char *load_name) field_pos > slen) ? slen : field_pos; - int cw = (int)disp_width_n(vals[k], cp); + int cw = (int)tui_disp_width_n(vals[k], cp); caret_x = xs[k] + (cw > w ? w : cw); } mvaddstr(y, xs[k], padded); @@ -4540,7 +3708,7 @@ static int template_form(struct app *a, const char *load_name) char nbuf[512]; const char *nm = acct_name(a, rows[i].account); snprintf(nbuf, sizeof nbuf, "%s", nm ? nm : ""); - pad_field(nbuf, sizeof nbuf, name_w); + tui_pad_field(nbuf, sizeof nbuf, name_w); attron(A_DIM); mvaddstr(y, x_name, nbuf); attroff(A_DIM); @@ -4553,7 +3721,7 @@ static int template_form(struct app *a, const char *load_name) "x = belopp i kronor. Positivt blir debet, negativt kredit.", COLS - 4); attroff(A_DIM); - hints("Tab = byta fält F5 = validera ^X = rensa rad ^Enter = spara Esc = avbryt"); + tui_hints("Tab = byta fält F5 = validera ^X = rensa rad ^Enter = spara Esc = avbryt"); if (caret_y >= 0) move(caret_y, caret_x); refresh(); @@ -4638,7 +3806,7 @@ static int template_form(struct app *a, const char *load_name) if (!problem && used == 0) problem = "Mallen behöver minst en rad."; if (problem) { - message("Mall", "%s", problem); + tui_message("Mall", "%s", problem); continue; } @@ -4672,10 +3840,10 @@ static int template_form(struct app *a, const char *load_name) free(args); if (resp && client_ok(resp)) { if (ch == KEY_F(5)) { - message("Validering OK", + tui_message("Validering OK", "Mallen är korrekt (%d rader).", used); } else { - message("Mall", "Mallen '%s' sparad.", tname); + tui_message("Mall", "Mallen '%s' sparad.", tname); free(resp); curs_set(0); return 1; @@ -4719,7 +3887,7 @@ static int template_form(struct app *a, const char *load_name) } if (!buf) continue; - field_edit(buf, cap, &field_pos, ch, &field_fresh); + tui_field_edit(buf, cap, &field_pos, ch, &field_fresh); trow_normalize(rows, &nrows, &field); nfields = 3 + 3 * nrows; } @@ -4729,7 +3897,7 @@ static void template_archive(struct app *a, const char *name) { char label[256]; snprintf(label, sizeof label, "Arkivera '%s'? (j/n): ", name); - char *ans = prompt(label, "n", 0); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, label, "n", 0) ? ansbuf : NULL; if (!ans || (ans[0] != 'j' && ans[0] != 'J')) return; yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); @@ -4742,7 +3910,7 @@ static void template_archive(struct app *a, const char *name) client_rpc(&a->conn, "template.archive", a->session, a->org, args); free(args); if (resp && client_ok(resp)) - message("Mall", "Mallen '%s' arkiverad.", name); + tui_message("Mall", "Mallen '%s' arkiverad.", name); else show_error("Kunde inte arkivera", resp); free(resp); @@ -4849,14 +4017,14 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) { static struct tui_ibrow rows[64]; if (nold > 63) { - message("Ingående balans", + tui_message("Ingående balans", "För många konton (%d) för att redigera här.", nold); return 0; } memset(rows, 0, sizeof rows); for (int i = 0; i < nold && i < 63; i++) { snprintf(rows[i].account, sizeof rows[i].account, "%s", old_acc[i]); - kr_format(old_amt[i], rows[i].amount, sizeof rows[i].amount); + tui_kr_format(old_amt[i], rows[i].amount, sizeof rows[i].amount); } int nrows = nold > 0 ? nold : 1; int field = 0; @@ -4875,7 +4043,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) curs_set(1); for (;;) { - frame("Ingående balans"); + tui_frame("Ingående balans"); int caret_y = -1, caret_x = -1; attron(A_BOLD); mvaddstr(2, 2, @@ -4904,7 +4072,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) int w = ws[k] > 1 ? ws[k] : 1; char padded[512]; snprintf(padded, sizeof padded, "%s", vals[k]); - pad_field(padded, sizeof padded, w); + tui_pad_field(padded, sizeof padded, w); if (k == sel_ci) { attron(A_REVERSE); caret_y = y; @@ -4913,7 +4081,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) field_pos > slen) ? slen : field_pos; - int cw = (int)disp_width_n(vals[k], cp); + int cw = (int)tui_disp_width_n(vals[k], cp); caret_x = xs[k] + (cw > w ? w : cw); } mvaddstr(y, xs[k], padded); @@ -4924,13 +4092,13 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) char nbuf[512]; const char *nm = acct_name(a, rows[i].account); snprintf(nbuf, sizeof nbuf, "%s", nm ? nm : ""); - pad_field(nbuf, sizeof nbuf, name_w); + tui_pad_field(nbuf, sizeof nbuf, name_w); attron(A_DIM); mvaddstr(y, x_name, nbuf); attroff(A_DIM); } } - hints("Tab = byta fält F5 = validera ^X = rensa rad ^Enter = spara Esc = avbryt"); + tui_hints("Tab = byta fält F5 = validera ^X = rensa rad ^Enter = spara Esc = avbryt"); if (caret_y >= 0) move(caret_y, caret_x); refresh(); @@ -5012,12 +4180,12 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) problem = msg; } if (problem) { - message("Ingående balans", "%s", problem); + tui_message("Ingående balans", "%s", problem); continue; } if (ch == KEY_F(5)) { - message("Validering OK", "%d konton, summan är noll.", used); + tui_message("Validering OK", "%d konton, summan är noll.", used); continue; } @@ -5079,7 +4247,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) } if (posted == 0) { yyjson_mut_doc_free(d); - message("Ingående balans", "Ingen ändring."); + tui_message("Ingående balans", "Ingen ändring."); continue; } yyjson_mut_obj_add_val(d, o, "rows", arr); @@ -5090,7 +4258,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) free(args); if (resp && client_ok(resp)) { int64_t num = jint_val(resp, "result.number", 0); - message("Ingående balans", + tui_message("Ingående balans", "Sparat som IB %lld (%d justerade rader).", (long long)num, posted); free(resp); @@ -5123,7 +4291,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) } if (!buf) continue; - field_edit(buf, cap, &field_pos, ch, &field_fresh); + tui_field_edit(buf, cap, &field_pos, ch, &field_fresh); ibrow_normalize(rows, &nrows, &field); nfields = 3 * nrows; } @@ -5157,7 +4325,7 @@ static void ib_screen(struct app *a) top = n - view; if (top < 0) top = 0; - frame("Ingående balans"); + tui_frame("Ingående balans"); attron(A_BOLD); mvprintw(2, 2, "Räkenskapsår %s ingående balans (effektiv)", a->fy_label); @@ -5174,19 +4342,19 @@ static void ib_screen(struct app *a) for (int i = 0; i < view && top + i < n; i++) { int idx = top + i; char amount[32]; - kr_format(amts[idx], amount, sizeof amount); + tui_kr_format(amts[idx], amount, sizeof amount); const char *nm = acct_name(a, accs[idx]); char nbuf[128]; snprintf(nbuf, sizeof nbuf, "%s", nm ? nm : ""); - pad_field(nbuf, sizeof nbuf, 50); + tui_pad_field(nbuf, sizeof nbuf, 50); mvprintw(6 + i, 2, "%-6s %s %14s", accs[idx], nbuf, amount); } char sumbuf[32]; - kr_format(sum, sumbuf, sizeof sumbuf); + tui_kr_format(sum, sumbuf, sizeof sumbuf); attron(A_BOLD); mvprintw(LINES - 4, 2, "Summa: %s", sumbuf); attroff(A_BOLD); - hints("e/^N = redigera PgUp/PgDn, Home/End rullar F5 =" + tui_hints("e/^N = redigera PgUp/PgDn, Home/End rullar F5 =" " uppdatera Esc/q = tillbaka"); refresh(); int ch = ui_getch(); @@ -5295,13 +4463,13 @@ static void board_screen(struct app *a) } if (owner) lines[n] = xstrdup("+ Ny ledamot (^N)"); - int s = select_list("Styrelseledamöter", lines, rows, cursor, 1, + int s = tui_select_list("Styrelseledamöter", lines, rows, cursor, 1, &cursor, owner ? 1 : 0, owner && n > 0 ? "ta bort" : NULL, 0); if (s == -6 && cursor >= 0 && (size_t)cursor < n) { char q[320]; snprintf(q, sizeof q, "Ta bort '%s'? (j/n): ", lines[cursor]); - char *ans = prompt(q, "n", 0); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, q, "n", 0) ? ansbuf : NULL; if (ans && (ans[0] == 'j' || ans[0] == 'J')) { char args[64]; snprintf(args, sizeof args, "{\"id\":%lld}", @@ -5309,64 +4477,66 @@ static void board_screen(struct app *a) char *r = client_rpc(&a->conn, "board.remove", a->session, a->org, args); if (r && client_ok(r)) { - message("Styrelseledamöter", "Borttagen."); + tui_message("Styrelseledamöter", "Borttagen."); } else { show_error("Kunde inte ta bort", r); } free(r); } } else if (s == -4 || (s >= 0 && owner && (size_t)s == n)) { - char namebuf[256] = ""; - char *name = prompt("Namn: ", "", 0); - if (name && *name) - snprintf(namebuf, sizeof namebuf, "%s", name); - char *title = namebuf[0] - ? prompt("Titel: ", "Styrelseledamot", 0) - : NULL; - if (namebuf[0] && title && *title) { + char namebuf[512] = "", titlebuf[512] = ""; + int ok_name = tui_prompt_into(namebuf, sizeof namebuf, "Namn: ", + "", 0); + int ok_title = ok_name && namebuf[0] + ? tui_prompt_into(titlebuf, sizeof titlebuf, + "Titel: ", + "Styrelseledamot", 0) + : 0; + if (ok_name && namebuf[0] && ok_title && titlebuf[0]) { yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); yyjson_mut_val *o = yyjson_mut_obj(d); yyjson_mut_doc_set_root(d, o); yyjson_mut_obj_add_strcpy(d, o, "name", namebuf); - yyjson_mut_obj_add_strcpy(d, o, "title", title); + yyjson_mut_obj_add_strcpy(d, o, "title", titlebuf); char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); char *r = client_rpc(&a->conn, "board.add", a->session, a->org, args); free(args); if (r && client_ok(r)) - message("Styrelseledamöter", "Tillagd."); + tui_message("Styrelseledamöter", "Tillagd."); else show_error("Kunde inte lägga till", r); free(r); } } else if (s >= 0 && (size_t)s < n) { if (!owner) { - message("Styrelseledamöter", + tui_message("Styrelseledamöter", "Endast ägare kan ändra styrelseledamöterna."); } else { - char namebuf[256] = ""; - char *name = prompt("Namn: ", names[s] ? names[s] : "", 0); - if (name && *name) - snprintf(namebuf, sizeof namebuf, "%s", name); - char *title = namebuf[0] - ? prompt("Titel: ", - titles[s] ? titles[s] : "", 0) - : NULL; - if (namebuf[0] && title && *title) { + char namebuf[512] = "", titlebuf[512] = ""; + int ok_name = tui_prompt_into(namebuf, sizeof namebuf, + "Namn: ", + names[s] ? names[s] : "", 0); + int ok_title = ok_name && namebuf[0] + ? tui_prompt_into( + titlebuf, sizeof titlebuf, "Titel: ", + titles[s] ? titles[s] : "", 0) + : 0; + if (ok_name && namebuf[0] && ok_title && titlebuf[0]) { yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); yyjson_mut_val *o = yyjson_mut_obj(d); yyjson_mut_doc_set_root(d, o); yyjson_mut_obj_add_int(d, o, "id", ids[s]); yyjson_mut_obj_add_strcpy(d, o, "name", namebuf); - yyjson_mut_obj_add_strcpy(d, o, "title", title); + yyjson_mut_obj_add_strcpy(d, o, "title", titlebuf); char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); char *r = client_rpc(&a->conn, "board.update", a->session, a->org, args); free(args); if (r && client_ok(r)) - message("Styrelseledamöter", "Sparad."); + tui_message("Styrelseledamöter", "Sparad."); else show_error("Kunde inte spara", r); free(r); @@ -5421,107 +4591,52 @@ static void yearinfo_screen(struct app *a) return; } char *vals[6]; - vals[0] = jstr_dup(resp, "result.events"); - vals[1] = jstr_dup(resp, "result.agm_date"); - char divbuf[48]; - kr_format(jint_val(resp, "result.dividend_ore", 0), divbuf, - sizeof divbuf); - vals[2] = xstrdup(divbuf); - vals[3] = jstr_dup(resp, "result.dividend_date"); - vals[4] = jstr_dup(resp, "result.employees"); - vals[5] = jstr_dup(resp, "result.notes"); - for (int i = 0; i < nf; i++) - if (!vals[i]) - vals[i] = xstrdup(""); - free(resp); - - int sel = 0; - int back = 0; - for (;;) { - frame("Information om året"); - attron(A_BOLD); - mvaddstr(3, 2, "Uppgift"); - mvaddstr(3, 34, "Värde"); - attroff(A_BOLD); - for (int i = 0; i < nf; i++) { - char lbl[128], line[640]; - snprintf(lbl, sizeof lbl, "%s", labels[i]); - pad_field(lbl, sizeof lbl, 31); - snprintf(line, sizeof line, "%s %s", lbl, vals[i]); - if (i == sel) { - attron(A_REVERSE); - mvaddnstr(5 + i, 2, line, COLS - 4); - attroff(A_REVERSE); - } else { - if (!can_edit) - attron(A_DIM); - mvaddnstr(5 + i, 2, line, COLS - 4); - if (!can_edit) - attroff(A_DIM); - } - } - hints(can_edit - ? "upp/ned Enter = ändra F5 = uppdatera" - " Esc/q = tillbaka ^C = avsluta" - : "upp/ned F5 = uppdatera Esc/q = tillbaka" - " ^C = avsluta (endast ägare/redovisare)"); - refresh(); - int ch = ui_getch(); - if (ch == KEY_UP && sel > 0) - sel--; - else if (ch == KEY_DOWN && sel < nf - 1) - sel++; - else if (ch == KEY_F(5)) - break; /* reload */ - else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { - if (!can_edit) { - message("Information om året", - "Endast ägare eller redovisare kan ändra."); - continue; - } - char label[160]; - snprintf(label, sizeof label, "%s: ", labels[sel]); - char *v = prompt(label, vals[sel], 0); - if (!v) - continue; - yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); - yyjson_mut_val *o = yyjson_mut_obj(d); - yyjson_mut_doc_set_root(d, o); - yyjson_mut_obj_add_int(d, o, "id", a->fy); - if (sel == 2) { - int64_t ore = 0; - if (parse_kr(v, &ore) != 0) { - message("Ogiltigt värde", "%s", labels[sel]); - yyjson_mut_doc_free(d); - continue; - } - yyjson_mut_obj_add_int(d, o, keys[sel], ore); - } else { - if ((sel == 1 || sel == 3) && *v && - !util_parse_iso_date(v)) { - message("Ogiltigt datum", "%s", v); - yyjson_mut_doc_free(d); - continue; - } - yyjson_mut_obj_add_strcpy(d, o, keys[sel], v); - } - char *uargs = yyjson_mut_write(d, 0, NULL); - yyjson_mut_doc_free(d); - char *r = client_rpc(&a->conn, "fiscal_year.update", - a->session, a->org, uargs); - free(uargs); - if (!r || !client_ok(r)) - show_error("Kunde inte spara", r); - free(r); - break; /* reload from the server */ - } else if (ch == 27 || ch == 'q') { - back = 1; - break; + struct tui_form_field ff[6]; + int64_t div_ore = jint_val(resp, "result.dividend_ore", 0); + memset(ff, 0, sizeof ff); + for (int i = 0; i < nf; i++) { + vals[i] = xcalloc(512, 1); + if (i != 2) { + char path[64], *v; + snprintf(path, sizeof path, "result.%s", keys[i]); + v = jstr_dup(resp, path); + snprintf(vals[i], 512, "%s", v ? v : ""); + free(v); } + ff[i].label = labels[i]; + ff[i].value = vals[i]; + ff[i].cap = 512; + } + free(resp); + ff[0].kind = TUI_F_TEXT; + ff[1].kind = TUI_F_DATE; + ff[2].kind = TUI_F_AMOUNT; + ff[2].ore = &div_ore; + ff[3].kind = TUI_F_DATE; + ff[4].kind = TUI_F_TEXT; + ff[5].kind = TUI_F_TEXT; + int r = tui_form_run("Information om året", ff, nf, can_edit); + if (r >= 0) { + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_int(d, o, "id", a->fy); + if (r == 2) + yyjson_mut_obj_add_int(d, o, "dividend_ore", div_ore); + else + yyjson_mut_obj_add_strcpy(d, o, keys[r], vals[r]); + char *uargs = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *rr = client_rpc(&a->conn, "fiscal_year.update", a->session, + a->org, uargs); + free(uargs); + if (!rr || !client_ok(rr)) + show_error("Kunde inte spara", rr); + free(rr); } for (int i = 0; i < nf; i++) free(vals[i]); - if (back) + if (r == TUI_FORM_BACK) return; } } @@ -5539,120 +4654,58 @@ static void company_screen(struct app *a) free(resp); return; } - char **vals = xcalloc(nf, sizeof(char *)); + char *vals[16]; + struct tui_form_field ff[16]; + memset(ff, 0, sizeof ff); for (int i = 0; i < nf; i++) { char path[64]; + vals[i] = xcalloc(512, 1); snprintf(path, sizeof path, "result.%s", COMPANY_FIELDS[i].key); if (COMPANY_FIELDS[i].numeric) { - char tmp[32]; - snprintf(tmp, sizeof tmp, "%lld", + snprintf(vals[i], 512, "%lld", (long long)jint_val(resp, path, 0)); - vals[i] = xstrdup(tmp); } else { char *v = jstr_dup(resp, path); - vals[i] = v ? v : xstrdup(""); + snprintf(vals[i], 512, "%s", v ? v : ""); + free(v); } + ff[i].label = COMPANY_FIELDS[i].label; + ff[i].value = vals[i]; + ff[i].cap = 512; + ff[i].kind = TUI_F_TEXT; } free(resp); - - int sel = 0; - int back = 0; - for (;;) { - frame("Företagsuppgifter"); - attron(A_BOLD); - mvaddstr(3, 2, "Uppgift"); - mvaddstr(3, 34, "Värde"); - attroff(A_BOLD); - for (int i = 0; i <= nf; i++) { - char lbl[128], line[512]; - if (i < nf) { - snprintf(lbl, sizeof lbl, "%s", COMPANY_FIELDS[i].label); - pad_field(lbl, sizeof lbl, 31); - snprintf(line, sizeof line, "%s %s", lbl, vals[i]); - } else { - snprintf(lbl, sizeof lbl, "Styrelseledamöter"); - pad_field(lbl, sizeof lbl, 31); - snprintf(line, sizeof line, "%s (lista)", lbl); - } - if (i == sel) { - attron(A_REVERSE); - mvaddnstr(5 + i, 2, line, COLS - 4); - attroff(A_REVERSE); - } else { - if (!owner && i < nf) - attron(A_DIM); - mvaddnstr(5 + i, 2, line, COLS - 4); - if (!owner && i < nf) - attroff(A_DIM); - } - } - hints(owner - ? "upp/ned Enter = ändra F5 = uppdatera" - " Esc/q = tillbaka ^C = avsluta" - : "upp/ned F5 = uppdatera Esc/q = tillbaka" - " ^C = avsluta (endast ägare kan ändra)"); - refresh(); - int ch = ui_getch(); - if (ch == KEY_UP && sel > 0) - sel--; - else if (ch == KEY_DOWN && sel < nf) - sel++; - else if (ch == KEY_F(5)) - break; /* reload */ - else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { - if (sel == nf) { - if (!owner) { - message("Företagsuppgifter", - "Endast ägare kan ändra" - " styrelseledamöterna."); - continue; - } - board_screen(a); - break; /* reload */ - } - if (!owner) { - message("Företagsuppgifter", - "Endast ägare kan ändra företagsuppgifterna."); - continue; - } - char label[160]; - snprintf(label, sizeof label, "%s: ", - COMPANY_FIELDS[sel].label); - char *val = prompt(label, vals[sel], 0); - if (!val) - continue; - if (!company_field_valid(&COMPANY_FIELDS[sel], val)) { - message("Ogiltigt värde", "%s", - COMPANY_FIELDS[sel].label); - continue; - } + ff[nf].label = "Styrelseledamöter"; + ff[nf].kind = TUI_F_ACTION; + int r = tui_form_run("Företagsuppgifter", ff, nf + 1, owner); + if (r >= 0 && r < nf) { + if (!company_field_valid(&COMPANY_FIELDS[r], vals[r])) { + tui_message("Ogiltigt värde", "%s", COMPANY_FIELDS[r].label); + } else { yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); yyjson_mut_val *o = yyjson_mut_obj(d); yyjson_mut_doc_set_root(d, o); - if (COMPANY_FIELDS[sel].numeric) - yyjson_mut_obj_add_int(d, o, COMPANY_FIELDS[sel].key, - strtoll(val, NULL, 10)); + if (COMPANY_FIELDS[r].numeric) + yyjson_mut_obj_add_int(d, o, COMPANY_FIELDS[r].key, + strtoll(vals[r], NULL, 10)); else - yyjson_mut_obj_add_strcpy(d, o, COMPANY_FIELDS[sel].key, - val); + yyjson_mut_obj_add_strcpy(d, o, COMPANY_FIELDS[r].key, + vals[r]); char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); - char *r = client_rpc(&a->conn, "org.update", a->session, - a->org, args); + char *rr = client_rpc(&a->conn, "org.update", a->session, + a->org, args); free(args); - if (!r || !client_ok(r)) - show_error("Kunde inte spara", r); - free(r); - break; /* reload from the server */ - } else if (ch == 27 || ch == 'q') { - back = 1; - break; + if (!rr || !client_ok(rr)) + show_error("Kunde inte spara", rr); + free(rr); } } + if (r == nf) + board_screen(a); for (int i = 0; i < nf; i++) free(vals[i]); - free(vals); - if (back || g_quit) + if (r == TUI_FORM_BACK) return; } } @@ -5675,87 +4728,54 @@ static void settings_screen(struct app *a) free(resp); return; } - char **vals = xcalloc(nset, sizeof(char *)); + char *vals[8]; + struct tui_form_field ff[8]; + memset(ff, 0, sizeof ff); for (int i = 0; i < nset; i++) { - char path[64]; + char path[64], *v; + vals[i] = xcalloc(512, 1); snprintf(path, sizeof path, "result.%s", SETTING_KEYS[i]); - char *v = jstr_dup(resp, path); - vals[i] = v ? v : xstrdup(""); + v = jstr_dup(resp, path); + snprintf(vals[i], 512, "%s", v ? v : ""); + free(v); + ff[i].label = SETTING_LABELS[i]; + ff[i].value = vals[i]; + ff[i].cap = 512; + ff[i].kind = TUI_F_TEXT; } free(resp); - - int sel = 0; - for (;;) { - frame("Inställningar"); - attron(A_BOLD); - mvaddstr(4, 2, "Inställning"); - mvaddstr(4, 29, "Värde"); - attroff(A_BOLD); - for (int i = 0; i < nset; i++) { - char lbl[128], line[512]; - snprintf(lbl, sizeof lbl, "%s", SETTING_LABELS[i]); - pad_field(lbl, sizeof lbl, 26); - snprintf(line, sizeof line, "%s %s", lbl, vals[i]); - if (i == sel) - attron(A_REVERSE); - mvaddnstr(6 + i, 2, line, COLS - 4); - if (i == sel) - attroff(A_REVERSE); - } - hints("upp/ned Enter = ändra F5 = uppdatera Esc/q = tillbaka" - " ^C = avsluta"); - refresh(); - int ch = ui_getch(); - if (ch == KEY_UP && sel > 0) - sel--; - else if (ch == KEY_DOWN && sel < nset - 1) - sel++; - else if (ch == KEY_F(5)) - break; /* reload */ - else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { - char label[128]; - snprintf(label, sizeof label, "%s: ", SETTING_LABELS[sel]); - char *val = prompt(label, vals[sel], 0); - if (val) { - yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); - yyjson_mut_val *o = yyjson_mut_obj(d); - yyjson_mut_doc_set_root(d, o); - yyjson_mut_obj_add_strcpy(d, o, "key", SETTING_KEYS[sel]); - yyjson_mut_obj_add_strcpy(d, o, "value", val); - char *args = yyjson_mut_write(d, 0, NULL); - yyjson_mut_doc_free(d); - char *r = client_rpc(&a->conn, "settings.set", a->session, - a->org, args); - free(args); - if (r && client_ok(r)) { - char *nv = jstr_dup(r, "result.value"); - if (nv) { - free(vals[sel]); - vals[sel] = nv; - if (strcmp(SETTING_KEYS[sel], - "default_series") == 0) - snprintf(a->default_series, - sizeof a->default_series, "%s", nv); - else if (strcmp(SETTING_KEYS[sel], - "attachment_dir") == 0) - snprintf(a->attachment_dir, - sizeof a->attachment_dir, "%s", nv); - } - } else { - show_error("Kunde inte spara inställningen", r); - } - free(r); + int r = tui_form_run("Inställningar", ff, nset, 1); + if (r >= 0) { + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_strcpy(d, o, "key", SETTING_KEYS[r]); + yyjson_mut_obj_add_strcpy(d, o, "value", vals[r]); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *rr = client_rpc(&a->conn, "settings.set", a->session, + a->org, args); + free(args); + if (rr && client_ok(rr)) { + char *nv = jstr_dup(rr, "result.value"); + if (nv) { + if (strcmp(SETTING_KEYS[r], "default_series") == 0) + snprintf(a->default_series, sizeof a->default_series, + "%s", nv); + else if (strcmp(SETTING_KEYS[r], "attachment_dir") == 0) + snprintf(a->attachment_dir, + sizeof a->attachment_dir, "%s", nv); + free(nv); } - } else if (ch == 27 || ch == 'q') { - for (int i = 0; i < nset; i++) - free(vals[i]); - free(vals); - return; + } else { + show_error("Kunde inte spara inställningen", rr); } + free(rr); } for (int i = 0; i < nset; i++) free(vals[i]); - free(vals); + if (r == TUI_FORM_BACK) + return; } } @@ -5787,7 +4807,7 @@ static void templates_screen(struct app *a) char *ds = jstr_dup(resp, path); char nmbuf[128]; snprintf(nmbuf, sizeof nmbuf, "%s", nm ? nm : ""); - pad_field(nmbuf, sizeof nmbuf, 24); + tui_pad_field(nmbuf, sizeof nmbuf, 24); snprintf(line, sizeof line, "%s %-3s %2lld rader %s", nmbuf, ser ? ser : "", (long long)rc, ds ? ds : ""); names[i] = xstrdup(nm ? nm : ""); @@ -5798,7 +4818,7 @@ static void templates_screen(struct app *a) } names[n] = xstrdup(""); lines[n] = xstrdup("+ Ny mall (^N)"); - int s = select_list("Mallar", lines, (int)n + 1, cursor, 1, &cursor, 1, + int s = tui_select_list("Mallar", lines, (int)n + 1, cursor, 1, &cursor, 1, n > 0 ? "arkivera" : NULL, 0); if (s == -1) { for (size_t i = 0; i <= n; i++) { @@ -5883,8 +4903,8 @@ static void fmt_bokslut_plan(struct buf *t, yyjson_val *res) for (size_t k = 0; k < nr; k++) { yyjson_val *r = yyjson_arr_get(rows, k); char a1[48], a2[48]; - amt_col(a1, sizeof a1, 15, vint(r, "debit_ore")); - amt_col(a2, sizeof a2, 15, vint(r, "credit_ore")); + tui_amt_col(a1, sizeof a1, 15, vint(r, "debit_ore")); + tui_amt_col(a2, sizeof a2, 15, vint(r, "credit_ore")); buf_line(t, " %-5s %s %s\n", vstr(r, "account"), a1, a2); } } @@ -5894,12 +4914,12 @@ static void bokslut_plan(struct app *a, const char *fond, const char *rate, int do_post) { int64_t fond_ore = 0, rate_ore = 0; - if (parse_kr(fond, &fond_ore) != 0) { - message("Bokslut", "Ogiltigt fondbelopp: %s", fond); + if (tui_parse_kr(fond, &fond_ore) != 0) { + tui_message("Bokslut", "Ogiltigt fondbelopp: %s", fond); return; } - if (parse_kr(rate, &rate_ore) != 0 || rate_ore < 0 || rate_ore > 10000) { - message("Bokslut", "Ogiltig skattesats: %s", rate); + if (tui_parse_kr(rate, &rate_ore) != 0 || rate_ore < 0 || rate_ore > 10000) { + tui_message("Bokslut", "Ogiltig skattesats: %s", rate); return; } char args[160]; @@ -5923,13 +4943,13 @@ static void bokslut_plan(struct app *a, const char *fond, const char *rate, else buf_line(&t, "%s", resp); buf_append(&t, "", 1); - text_view("Bokslut - plan (torrkörning)", (const char *)t.p, NULL); + tui_pager("Bokslut - plan (torrkörning)", (const char *)t.p, NULL); buf_free(&t); yyjson_doc_free(d); free(resp); if (!do_post) return; - char *ans = prompt("Bokför planen? (j/n): ", "n", 0); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, "Bokför planen? (j/n): ", "n", 0) ? ansbuf : NULL; if (!ans || !(ans[0] == 'j' || ans[0] == 'J')) return; char *presp = client_rpc(&a->conn, "bokslut.post", a->session, a->org, @@ -5952,7 +4972,7 @@ static void bokslut_plan(struct app *a, const char *fond, const char *rate, buf_line(&m, "\n %s%lld %s", vstr(pv, "series"), (long long)vint(pv, "number"), vstr(v, "description")); } - message("Bokslut", "%s", (const char *)m.p); + tui_message("Bokslut", "%s", (const char *)m.p); buf_free(&m); yyjson_doc_free(pd); free(presp); @@ -5960,66 +4980,27 @@ static void bokslut_plan(struct app *a, const char *fond, const char *rate, static void bokslut_screen(struct app *a) { - char fond[32] = "0,00"; - char rate[16] = "20,6"; - int sel = 0; + char fond[64] = "0,00", rate[16] = "20,6"; + struct tui_form_field ff[2]; for (;;) { if (g_quit) return; - frame("Bokslut"); - attron(A_BOLD); - mvaddstr(4, 2, "Fält"); - mvaddstr(4, 34, "Värde"); - attroff(A_BOLD); - static const char *const labels[2] = { - "Periodiseringsfond (kr)", "Skattesats (%)" - }; - const char *vals[2] = { fond, rate }; - for (int i = 0; i < 2; i++) { - char lbl[64], line[128]; - snprintf(lbl, sizeof lbl, "%s", labels[i]); - pad_field(lbl, sizeof lbl, 31); - snprintf(line, sizeof line, "%s %s", lbl, vals[i]); - if (i == sel) - attron(A_REVERSE); - mvaddnstr(6 + i, 2, line, COLS - 4); - if (i == sel) - attroff(A_REVERSE); - } - mvaddstr(9, 2, "Fondbelopp i kr, tomt = ingen avsättning."); - mvaddstr(10, 2, "Skattesatsen tillämpas på resultat efter" - " bokslutsdispositioner."); - hints("upp/ned Enter = ändra F5 = visa plan ^Enter = bokför" - " Esc/q = tillbaka ^C = avsluta"); - refresh(); - int ch = ui_getch(); - if (ch == KEY_UP && sel > 0) - sel--; - else if (ch == KEY_DOWN && sel < 1) - sel++; - else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { - char label[80]; - snprintf(label, sizeof label, "%s: ", labels[sel]); - char *v = prompt(label, vals[sel], 0); - if (!v) - continue; - int64_t ore = 0; - if (parse_kr(v, &ore) != 0 || - (sel == 1 && (ore < 0 || ore > 10000))) { - message("Bokslut", "Ogiltigt värde: %s", v); - continue; - } - if (sel == 0) - snprintf(fond, sizeof fond, "%s", v); - else - snprintf(rate, sizeof rate, "%s", v); - } else if (ch == KEY_F(5)) { + memset(ff, 0, sizeof ff); + ff[0].label = "Periodiseringsfond (kr)"; + ff[0].value = fond; + ff[0].cap = sizeof fond; + ff[0].kind = TUI_F_TEXT; + ff[1].label = "Skattesats (%)"; + ff[1].value = rate; + ff[1].cap = sizeof rate; + ff[1].kind = TUI_F_TEXT; + int r = tui_form_run("Bokslut", ff, 2, 1); + if (r == TUI_FORM_REFRESH) bokslut_plan(a, fond, rate, 0); - } else if (ch == KEY_CTRL_ENTER || ch == KEY_F(9)) { + else if (r == TUI_FORM_SUBMIT) bokslut_plan(a, fond, rate, 1); - } else if (ch == 27 || ch == 'q') { + else if (r == TUI_FORM_BACK) return; - } } } @@ -6028,24 +5009,6 @@ static void bokslut_screen(struct app *a) /* ------------------------------------------------------------------ */ /* Whole kronor with space thousands; rounded half away from zero. */ -static void kr0_format(int64_t ore, char *buf, size_t n) -{ - long long v = (long long)(ore >= 0 ? (ore + 50) / 100 - : (ore - 50) / 100); - int neg = v < 0; - if (neg) - v = -v; - char digits[32], out[40]; - snprintf(digits, sizeof digits, "%lld", v); - size_t o = 0, len = strlen(digits); - for (size_t i = 0; i < len && o + 2 < sizeof out; i++) { - if (i > 0 && (len - i) % 3 == 0) - out[o++] = ' '; - out[o++] = digits[i]; - } - out[o] = '\0'; - snprintf(buf, n, "%s%s", neg ? "-" : "", out); -} /* Thousands of kronor for the flerårsöversikt. */ static long long ar_tkr(int64_t ore) @@ -6170,7 +5133,15 @@ static void ar_load_year(struct ar_year *y, yyjson_val *gl) static void ar_amt_col(char *buf, size_t n, int width, int64_t ore) { char tmp[40]; - kr0_format(ore, tmp, sizeof tmp); + if (n == 0) + return; + tui_kr0_format(ore, tmp, sizeof tmp); + if ((size_t)width >= n) + width = (int)n - 1; + if (width < 1) + width = 1; + if (strlen(tmp) >= n) + tmp[n - 1] = '\0'; snprintf(buf, n, "%*s", width, tmp); } @@ -6179,7 +5150,7 @@ static void ar_row(struct buf *t, const char *label, int64_t cur, int64_t prev, { char nm[160], padded[200], a1[40], a2[40]; snprintf(nm, sizeof nm, "%s%s", indent ? " " : "", label); - pad_label(padded, sizeof padded, nm, 48); + tui_pad_label(padded, sizeof padded, nm, 48); ar_amt_col(a1, sizeof a1, 16, cur); ar_amt_col(a2, sizeof a2, 16, prev); buf_line(t, "%s %s %s\n", padded, a1, a2); @@ -6191,7 +5162,7 @@ static void ar_eq_row(struct buf *t, const char *label, int64_t ak, { char nm[160], padded[200], c1[32], c2[32], c3[32], c4[32]; snprintf(nm, sizeof nm, "%s%s", indent ? " " : "", label); - pad_label(padded, sizeof padded, nm, 34); + tui_pad_label(padded, sizeof padded, nm, 34); ar_amt_col(c1, sizeof c1, 12, ak); ar_amt_col(c2, sizeof c2, 14, br); ar_amt_col(c3, sizeof c3, 14, ar); @@ -6369,7 +5340,7 @@ static void fmt_arsredovisning(struct buf *t, const struct app *a, " vinstutdelningen\n\n"); if (shares > 0) { char per[48]; - kr_format((dividend + shares / 2) / shares, per, sizeof per); + tui_kr_format((dividend + shares / 2) / shares, per, sizeof per); buf_line(t, "Utdelning per aktie: %s kr. Datum för utdelning är" " %s.\n\n", per, pay_sv); } else { @@ -6534,7 +5505,7 @@ static void arsredovisning_save(struct app *a, const char *text) struct stat sb; if (stat(def, &sb) != 0 || !S_ISDIR(sb.st_mode)) snprintf(def, sizeof def, "%s", home && *home ? home : "."); - char *dir = prompt("Spara årsredovisningen i: ", def, 0); + char dirbuf[512]; char *dir = tui_prompt_into(dirbuf, sizeof dirbuf, "Spara årsredovisningen i: ", def, 0) ? dirbuf : NULL; if (!dir || !*dir) return; char full[640]; @@ -6557,12 +5528,12 @@ static void arsredovisning_save(struct app *a, const char *text) if (!f || fwrite(text, 1, n, f) != n) { if (f) fclose(f); - message("Årsredovisning", "Kunde inte skriva %s: %s", full, + tui_message("Årsredovisning", "Kunde inte skriva %s: %s", full, strerror(errno)); return; } fclose(f); - message("Årsredovisning", "Sparat: %s\n\nFilen är ett utkast i textform" + tui_message("Årsredovisning", "Sparat: %s\n\nFilen är ett utkast i textform" " att redigera vidare (t.ex. i Word).", full); } @@ -6681,7 +5652,7 @@ static void arsredovisning_screen(struct app *a) buf_line(&t, "Kunde inte läsa rapporterna."); buf_append(&t, "", 1); int again = - text_view("Årsredovisning (K2)", (const char *)t.p, "s = spara"); + tui_pager("Årsredovisning (K2)", (const char *)t.p, "s = spara"); if (again == 2) arsredovisning_save(a, (const char *)t.p); buf_free(&t); @@ -6771,7 +5742,7 @@ static void dashboard(struct app *a) for (;;) { if (g_reload) return; - int sel = menu("Bokf", MAIN_ITEMS, 12, 1, &last_sel); + int sel = tui_menu("Bokf", MAIN_ITEMS, 12, 1, &last_sel); snprintf(g_scene, sizeof g_scene, "%s", "dashboard"); if (g_quit || g_reload) return; @@ -6982,7 +5953,7 @@ static int login_screen(struct app *a) int field = 3; /* 0 server, 1 user, 2 password, 3 = Logga in button */ for (;;) { - frame("bokf — inloggning"); + tui_frame("bokf — inloggning"); attron(A_BOLD); mvaddstr(4, 4, "Server"); mvaddstr(6, 4, "Användare"); @@ -7001,7 +5972,7 @@ static int login_screen(struct app *a) } { const char *btn = " Logga in "; - int bw = disp_width(btn); + int bw = tui_disp_width(btn); int bx = (COLS - bw) / 2; if (bx < 2) bx = 2; @@ -7013,7 +5984,7 @@ static int login_screen(struct app *a) if (field == 3) attroff(A_REVERSE | A_BOLD); } - hints("Tab = byta fält/knapp Enter = logga in ^C = avsluta"); + tui_hints("Tab = byta fält/knapp Enter = logga in ^C = avsluta"); refresh(); if (field == 3) { @@ -7040,7 +6011,7 @@ static int login_screen(struct app *a) /* fall through to the login attempt */ } else { int y = 4 + field * 2; - int rc = edit_field(y, 16, field == 0 ? socket_path + int rc = tui_edit_field(y, 16, field == 0 ? socket_path : (field == 1 ? user : pass), field == 0 ? sizeof socket_path : (field == 1 ? sizeof user @@ -7073,7 +6044,7 @@ static int login_screen(struct app *a) char *err = NULL; int rc = try_login(a, user, pass, NULL, &err); if (rc == -2) { - message("Fel", "Kunde inte ansluta till %s: %s", a->socket, + tui_message("Fel", "Kunde inte ansluta till %s: %s", a->socket, err ? err : "okänt fel"); free(err); continue; @@ -7082,12 +6053,12 @@ static int login_screen(struct app *a) if (err && err[0] == '{') { char *code = jstr_dup(err, "error.code"); char *msg = jstr_dup(err, "error.message"); - message("Inloggning misslyckades", "%s: %s", + tui_message("Inloggning misslyckades", "%s: %s", code ? code : "fel", msg ? msg : "okänt fel"); free(code); free(msg); } else { - message("Inloggning misslyckades", "%s", + tui_message("Inloggning misslyckades", "%s", err ? err : "transportfel"); } free(err); @@ -7109,7 +6080,7 @@ static int select_org(struct app *a) } size_t n = jarr_size(resp, "result.items"); if (n == 0) { - message("Organisationer", + tui_message("Organisationer", "Inga organisationer. Skapa en med bokfctl org.create."); free(resp); return -1; @@ -7124,7 +6095,7 @@ static int select_org(struct app *a) char *role = jstr_dup(resp, path); char nbuf[128], line[256]; snprintf(nbuf, sizeof nbuf, "%s", name ? name : ""); - pad_field(nbuf, sizeof nbuf, 30); + tui_pad_field(nbuf, sizeof nbuf, 30); snprintf(line, sizeof line, "%s (%s)", nbuf, role ? role : ""); items[i] = xstrdup(line); snprintf(path, sizeof path, "result.items.%zu.id", i); @@ -7132,7 +6103,7 @@ static int select_org(struct app *a) free(name); free(role); } - int sel = select_list("Välj organisation att representera", items, (int)n, + int sel = tui_select_list("Välj organisation att representera", items, (int)n, 0, 0, NULL, 0, NULL, 0); for (size_t i = 0; i < n; i++) free(items[i]); @@ -7220,11 +6191,10 @@ int main(int argc, char **argv) cbreak(); noecho(); keypad(stdscr, TRUE); - /* ^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~", KEY_CTRL_ENTER); - putp("\033[>4;1m"); - fflush(stdout); + tui_keys_setup(); + tui_set_input(ui_getch); + tui_set_screen(tui_frame, tui_hints); + tui_set_quit(request_quit); atexit(reset_modify_keys); curs_set(1); @@ -7315,12 +6285,12 @@ int main(int argc, char **argv) if (auto_err) { char *code = jstr_dup(auto_err, "error.code"); char *msg = jstr_dup(auto_err, "error.message"); - message("Återanslutning misslyckades", "%s: %s", + tui_message("Återanslutning misslyckades", "%s: %s", code ? code : "fel", msg ? msg : "okänt fel"); free(code); free(msg); } else if (stale_session) { - message("Session", "Sessionen har gått ut, logga in igen."); + tui_message("Session", "Sessionen har gått ut, logga in igen."); } } free(auto_err); |
