From 71a702f375750829c634b552217c9925d549828b Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Wed, 23 Sep 2026 10:58:17 +0200 Subject: tui: context menu on → / ^O; no F-keys, ^N or ^Enter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit → (where no caret uses it) and ^O (everywhere, also in table cells) open "Åtgärder": a box at the right edge with every action of the view and its key. Screens declare their actions with tui_set_actions(); choosing one feeds its key to the widget, so the menu and accelerators share one path. Function keys, Ctrl+N (now n), Ctrl+Enter and Ctrl+F are gone and no extended keyboard protocol is enabled, so the TUI works in a browser terminal; save/post/validate/preview/template/attach are menu actions. make check rejects F-keys, ^N and ^Enter in clients/. Co-Authored-By: Claude Opus 5.5 --- tests/test_tui.c | 99 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 24 deletions(-) (limited to 'tests') diff --git a/tests/test_tui.c b/tests/test_tui.c index d01a546..c0c5f0d 100644 --- a/tests/test_tui.c +++ b/tests/test_tui.c @@ -329,9 +329,13 @@ static void test_nav(void) nav_init(&v, 0); CHECK(tui_nav_key(&v, KEY_DOWN, 0, 0, 0, 0) == TUI_NAV_NONE); - CHECK(tui_nav_key(&v, KEY_F(5), 0, 0, 0, 0) == -2); - CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 1, 0, 0, 0) == -4); - CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 0, 0, 0, 0) == TUI_NAV_NONE); + CHECK(tui_nav_key(&v, 'r', 0, 0, 0, 0) == -2); + CHECK(tui_nav_key(&v, TUI_KEY_REFRESH, 0, 0, 0, 0) == -2); + CHECK(tui_nav_key(&v, 'n', 1, 0, 0, 0) == -4); + CHECK(tui_nav_key(&v, 'n', 0, 0, 0, 0) == TUI_NAV_NONE); + /* no F-keys and no ^N: browsers own them */ + CHECK(tui_nav_key(&v, KEY_F(5), 0, 0, 0, 0) == TUI_NAV_NONE); + CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 1, 0, 0, 0) == TUI_NAV_NONE); CHECK(tui_nav_key(&v, 'q', 0, 0, 0, 0) == -1); CHECK(tui_nav_key(&v, 27, 0, 0, 0, 0) == -1); @@ -350,9 +354,13 @@ static void test_nav(void) CHECK(tui_nav_key(&v, '9', 0, 0, 0, 0) == TUI_NAV_NONE); /* out of range */ CHECK(tui_nav_key(&v, '\n', 0, 0, 0, 0) == v.sel); - CHECK(tui_nav_key(&v, KEY_F(5), 0, 0, 0, 0) == -2); - CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 1, 0, 0, 0) == -4); - CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 0, 0, 0, 0) == TUI_NAV_NONE); + CHECK(tui_nav_key(&v, 'r', 0, 0, 0, 0) == -2); + CHECK(tui_nav_key(&v, TUI_KEY_REFRESH, 0, 0, 0, 0) == -2); + CHECK(tui_nav_key(&v, 'n', 1, 0, 0, 0) == -4); + CHECK(tui_nav_key(&v, 'n', 0, 0, 0, 0) == TUI_NAV_NONE); + /* no F-keys and no ^N: browsers own them */ + CHECK(tui_nav_key(&v, KEY_F(5), 0, 0, 0, 0) == TUI_NAV_NONE); + CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 1, 0, 0, 0) == TUI_NAV_NONE); CHECK(tui_nav_key(&v, 'd', 0, 1, 0, 0) == -6); CHECK(tui_nav_key(&v, 'd', 0, 0, 0, 0) == TUI_NAV_NONE); CHECK(tui_nav_key(&v, 1, 0, 0, 1, 0) == -7); /* ^A */ @@ -590,9 +598,11 @@ static void test_form_nav(void) CHECK(tui_form_next(0, 3, KEY_UP) == TUI_NAV_NONE); CHECK(tui_form_next(0, 3, KEY_DOWN) == 1); CHECK(tui_form_next(2, 3, KEY_DOWN) == TUI_NAV_NONE); - CHECK(tui_form_next(1, 3, KEY_F(5)) == TUI_FORM_REFRESH); - CHECK(tui_form_next(1, 3, TUI_KEY_CTRL_ENTER) == TUI_FORM_SUBMIT); - CHECK(tui_form_next(1, 3, KEY_F(9)) == TUI_FORM_SUBMIT); + CHECK(tui_form_next(1, 3, TUI_KEY_REFRESH) == TUI_FORM_REFRESH); + CHECK(tui_form_next(1, 3, TUI_KEY_SUBMIT) == TUI_FORM_SUBMIT); + CHECK(tui_form_next(1, 3, KEY_F(5)) == TUI_NAV_NONE); + CHECK(tui_form_next(1, 3, KEY_F(9)) == TUI_NAV_NONE); + CHECK(tui_form_next(1, 3, TUI_KEY_CTRL_ENTER) == TUI_NAV_NONE); CHECK(tui_form_next(1, 3, 27) == TUI_FORM_BACK); CHECK(tui_form_next(1, 3, 'q') == TUI_FORM_BACK); CHECK(tui_form_next(1, 3, 'x') == TUI_NAV_NONE); @@ -662,8 +672,9 @@ static void test_form_actions(void) CHECK(tui_form_act_page(4, nf, ACTS, 6, 1) == 8); CHECK(tui_form_act_page(4, nf, ACTS, 6, -1) == 0); CHECK(tui_form_act_page(0, 0, NULL, 0, 1) == -1); - CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_F(5)) == TUI_FORM_REFRESH); - CHECK(tui_form_act_key(5, nf, ACTS, 6, TUI_KEY_CTRL_ENTER) == + CHECK(tui_form_act_key(0, nf, ACTS, 6, TUI_KEY_REFRESH) == + TUI_FORM_REFRESH); + CHECK(tui_form_act_key(5, nf, ACTS, 6, TUI_KEY_SUBMIT) == TUI_FORM_SUBMIT); CHECK(tui_form_act_key(5, nf, ACTS, 6, 27) == TUI_FORM_BACK); CHECK(tui_form_act_key(5, nf, ACTS, 6, 'x') == TUI_NAV_NONE); @@ -936,10 +947,11 @@ static void test_actions(void) { char buf[256]; struct tui_action acts[] = { - { "save", "Spara", KEY_F(9), 1, NULL }, + { "save", "Spara", TUI_KEY_SUBMIT, 1, NULL }, { "del", "Radera utkast", 0, 0, "inget utkast" }, { "sec", "Sektion", 0, -1, NULL }, - { "arch", "Arkivera", KEY_F(2), 1, NULL }, + { "arch", "Arkivera", 'd', 1, NULL }, + { "att", "Bifoga fil", 'a', 1, NULL }, }; tui_action_label(&acts[0], buf, sizeof buf); @@ -947,18 +959,57 @@ static void test_actions(void) tui_action_label(&acts[1], buf, sizeof buf); CHECK(strcmp(buf, "Radera utkast (inget utkast)") == 0); - tui_action_hint(acts, 4, 1, buf, sizeof buf); - CHECK(strstr(buf, "F9 = Spara") != NULL); - CHECK(strstr(buf, "F2 = fler") != NULL); - CHECK(strstr(buf, "Arkivera") == NULL); - - tui_action_hint(acts, 4, 0, buf, sizeof buf); - CHECK(strstr(buf, "F9 = Spara") != NULL); - CHECK(strstr(buf, "F2 = Arkivera F2 = fler") != NULL); - CHECK(strstr(buf, "Radera utkast") == NULL); - + /* virtual keys are menu-only: not in the footer */ + tui_action_hint(acts, 5, 1, buf, sizeof buf); + CHECK(strcmp(buf, "d = Arkivera → = åtgärder") == 0); + tui_action_hint(acts, 5, 0, buf, sizeof buf); + CHECK(strcmp(buf, "d = Arkivera a = Bifoga fil → = åtgärder") == 0); tui_action_hint(NULL, 0, 1, buf, sizeof buf); - CHECK(strcmp(buf, "F2 = fler") == 0); + CHECK(strcmp(buf, "→ = åtgärder") == 0); + + /* key names: web-safe keys only, virtual keys have none */ + tui_key_name('\n', buf, sizeof buf); + CHECK(strcmp(buf, "Enter") == 0); + tui_key_name(27, buf, sizeof buf); + CHECK(strcmp(buf, "Esc") == 0); + tui_key_name(TUI_KEY_CTRL_X, buf, sizeof buf); + CHECK(strcmp(buf, "^X") == 0); + tui_key_name('n', buf, sizeof buf); + CHECK(strcmp(buf, "n") == 0); + tui_key_name(KEY_UP, buf, sizeof buf); + CHECK(strcmp(buf, "↑") == 0); + tui_key_name(TUI_KEY_SUBMIT, buf, sizeof buf); + CHECK(buf[0] == '\0'); + tui_key_name(TUI_KEY_ACTION(3), buf, sizeof buf); + CHECK(buf[0] == '\0'); + + /* the context menu: screen actions, a separator, then the widget's own + actions whose key the screen did not take */ + struct tui_action scr[] = { + { "v.post", "Bokför", TUI_KEY_SUBMIT, 1, NULL }, + { "v.tpl", "Hämta mall…", TUI_KEY_ACTION(0), 1, NULL }, + }; + struct tui_action own[] = { + { "rt.refresh", "Validera", TUI_KEY_REFRESH, 1, NULL }, + { "rt.submit", "Spara", TUI_KEY_SUBMIT, 1, NULL }, + { "rt.back", "Avbryt", 27, 1, NULL }, + }; + struct tui_action out[8]; + int n = tui_ctx_merge(scr, 2, own, 3, out, 8); + CHECK(n == 5); + CHECK(strcmp(out[0].label, "Bokför") == 0); + CHECK(strcmp(out[1].label, "Hämta mall…") == 0); + CHECK(out[2].enabled == -1 && out[2].label[0] == '\0'); + CHECK(strcmp(out[3].label, "Validera") == 0); + CHECK(strcmp(out[4].label, "Avbryt") == 0); /* Spara: key taken */ + /* no screen actions: no separator */ + n = tui_ctx_merge(NULL, 0, own, 3, out, 8); + CHECK(n == 3 && strcmp(out[0].label, "Validera") == 0); + /* capacity is respected */ + n = tui_ctx_merge(scr, 2, own, 3, out, 3); /* no room: no separator */ + CHECK(n == 3 && strcmp(out[2].label, "Validera") == 0); + n = tui_ctx_merge(scr, 2, own, 3, out, 4); + CHECK(n == 4 && strcmp(out[3].label, "Validera") == 0); } static void test_drafts(void) -- cgit v1.3