From: NeilBrown Date: Sun, 3 Sep 2023 12:11:51 +0000 (+1000) Subject: Add lib-menubar X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=aa801540314a74d9d491de4a1656f13ff5af455b;p=edlib.git Add lib-menubar We now have a menu bar. It can be disabled by config, and this is done for testing. There are still things to do to make it good, but we have a good start. Signed-off-by: NeilBrown --- diff --git a/DOC/TODO.md b/DOC/TODO.md index b5a5faff..0e6df9d5 100644 --- a/DOC/TODO.md +++ b/DOC/TODO.md @@ -18,7 +18,7 @@ the file. - [X] Disable if cursor is in the hidden region. - [X] fill mode to handle all punctuation at start of this line - [X] Enable lib-menu to show short-cut keys -- [ ] Add menu-bar to lib-menu. Pop it up on F10 with simple commands +- [X] Add menu-bar to lib-menu. Pop it up on F10 with simple commands - [ ] attach an extensible menu to the selection copy, paste-in, QR, git-view @@ -35,7 +35,7 @@ the file. - [ ] image-display pane - [ ] git-mode - [ ] render-markdown.py -- [ ] lib-menu +- [X] lib-menu - [ ] Remote display Requirements for a v1.0 release @@ -161,6 +161,13 @@ Core features Module features --------------- +### lib-menubar + +- enable activation via F10. Cancel makes it disappear if configed + not too. How to keep?? +- allow left/right arrows to move between menus +- emacs to disable 'save' when cannot be saved. + ### lib-qrcode - [ ] pop up window to show selection as QR code @@ -567,7 +574,7 @@ Module features ### Notmuch message display -- [ ] notmuch addresses in From: list to have menu to add address to +- [X] notmuch addresses in From: list to have menu to add address to any from-* query - [ ] "%d quoted lines" still not quite right. Moving 'down' past it jumps to end of line. @@ -805,7 +812,7 @@ Module features ### lib-menu - [X] Enable lib-menu to show short-cut keys -- [ ] menu-bar to which we can add menus from which commands are sent +- [X] menu-bar to which we can add menus from which commands are sent - [ ] track movement so entry under cursor can be highlighted - [ ] support positioning above the target is no space below. - [ ] support single-click to open modal menu.?? @@ -970,7 +977,7 @@ Possibly some of these will end up being features in other modules. - [ ] tags handling - and easy tag-search without tags. e.g. git-search. alt-S looks for TAGS or .git and either does a tags-search or a grep-l and check every match. So maybe this is part of the 'make' module -- [ ] menus +- [X] menus This might support a menu-bar, or drop-downs for spelling or dynamic completion. - [ ] hex edit block device - mmap document type diff --git a/Makefile b/Makefile index 537b013b..2b676f18 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ SHOBJ = O/doc-text.o O/doc-dir.o O/doc-docs.o \ O/lib-x11selection-xcb.o O/display-x11-xcb.o \ O/lib-linefilter.o O/lib-wiggle.o O/lib-aspell.o O/lib-calc.o \ O/lib-menu.o O/lib-unicode-names.o O/lib-askpass.o \ - O/lib-test-markup.o \ + O/lib-test-markup.o O/lib-menubar.o \ O/lang-python.o \ O/mode-emacs.o O/emacs-search.o \ O/display-ncurses.o diff --git a/data/edlib.ini b/data/edlib.ini index 0a3a39b3..fcbddd82 100644 --- a/data/edlib.ini +++ b/data/edlib.ini @@ -5,7 +5,12 @@ include = modules.ini editor-initial-panes = input DISPLAY " x11selection messageline" - " global-keymap mode-emacs tile" + " global-keymap" + " menubar" + " mode-emacs" + " tile" + +TESTING menubar-visible = no [file:COMMIT_EDITMSG*] APPEND view-default = ,textfill,whitespace,autospell fill-width = 72 diff --git a/data/modules.ini b/data/modules.ini index 5bb1ab85..6f7d7346 100644 --- a/data/modules.ini +++ b/data/modules.ini @@ -163,3 +163,5 @@ lib-test-markup = attach-test-markup interactive-cmd-test-markup +lib-menubar = + attach-menubar diff --git a/lib-menubar.c b/lib-menubar.c new file mode 100644 index 00000000..ad72d55f --- /dev/null +++ b/lib-menubar.c @@ -0,0 +1,468 @@ +/* + * Copyright Neil Brown ©2023 + * May be distributed under terms of GPLv2 - see file:COPYING + * + * trim a line off the top line of a pane and place a menu + * bar. Actions are sent to the focus. + * + * We place a renderline at the top and construct a string + * to give to it as needed. + * We create menu documents as children of the main pane, + * and display them as needed. + * + * Menus are added either to LHS or RHS and must be added in order + * So FILE EDIT VIEW must be in order, and HELP on right before + * any other right-side menus. + * Before displayed a menu, the pane which requested it is given + * a chance to update the content via a "menu:refresh" notification. + * + * Menus are created and populated with "menubar-add" which acts + * like menu-add, The name is "X/Y" where is the name of the menu + * and Y is the name in the menu. If X doesn't exist, the menu + * is created. If Y already exists, the other details are updated. + * menubar-delete and menubar-clear can delete individual menus, + * or clear all entries so they can be repopulated. + * + * Menu documents are collected as children of this pane. The focus + * of each document is the pane which requested the window. This + * allows the menu to be discarded when that pane is closed, and to + * be hidden when the pane loses focus. + * + * Child panes have ->z value: + * 0 for child and bar + * 1 or more for active menu + * -1 for menu documents created by in-focus clients + * -2 for menu documents created by not-in-focus clients + */ + +#include +#define PANE_DATA_TYPE struct mbinfo +#include "core.h" + +struct mbinfo { + struct pane *bar safe; + struct pane *child; + struct pane *menu, *open; + bool hidden, wanted; +}; +#include "core-pane.h" + +static struct map *menubar_map; +DEF_LOOKUP_CMD(menubar_handle, menubar_map); + +DEF_CMD(menubar_border) +{ + struct mbinfo *mbi = ci->home->data; + + mbi->hidden = !mbi->wanted || ci->num <= 0; + pane_damaged(ci->home, DAMAGED_SIZE); + return Efallthrough; +} + +DEF_CMD(menubar_refresh_size) +{ + struct mbinfo *mbi = ci->home->data; + struct pane *p = mbi->bar; + + if (mbi->hidden) { + /* Put bar below window - out of sight */ + pane_resize(p, 0, ci->home->h, + p->w, p->h); + if (mbi->child) + pane_resize(mbi->child, 0, 0, + ci->home->w, ci->home->h); + } else { + pane_resize(p, 0, 0, ci->home->w, ci->home->h/3); + call("render-line:measure", p, -1); + if (mbi->child && ci->home->h > p->h) + pane_resize(mbi->child, 0, p->h, + ci->home->w, ci->home->h - p->h); + } + pane_damaged(ci->home, DAMAGED_VIEW); + return 1; +} + +DEF_CMD(menubar_child_notify) +{ + struct mbinfo *mbi = ci->home->data; + + if (ci->focus->z) + /* Ignore */ + return 1; + if (ci->num < 0) { + if (ci->home->focus == ci->focus) + ci->home->focus = NULL; + mbi->child = NULL; + } else { + if (mbi->child) + pane_close(mbi->child); + mbi->child = ci->focus; + ci->home->focus = ci->focus; + } + return 1; +} + +DEF_CMD(menubar_refresh) +{ + struct buf b; + struct pane *home = ci->home; + struct mbinfo *mbi = home->data; + struct pane *p; + struct pane *bar = mbi->bar; + int h; + + if (mbi->hidden) + return 1; + if (!mbi->child) + return 1; + buf_init(&b); + buf_concat(&b, ACK SOH "tab:20" STX); + + list_for_each_entry(p, &home->children, siblings) { + char *n, *c; + if (p->z >= 0) + continue; + if (!p->focus) + /* Strange - every doc should have a focus... */ + continue; + p->x = -1; + p->z = -2; + if (!pane_has_focus(p->focus, mbi->child)) + /* Owner of this menu not in focus */ + continue; + n = pane_attr_get(p, "doc-name"); + if (!n || !*n) + continue; + for (c = n ; *c; c++) + if (*c == ',' || (*c > 0 && *c < ' ')) + *c = '_'; + if (mbi->menu && mbi->open == p) + buf_concat(&b, SOH "fg:black,bg:white-80," + "menu-name:"); + else + buf_concat(&b, SOH "fg:blue,underline," + "menu-name:"); + buf_concat(&b, n); + buf_concat(&b, STX); + buf_concat(&b, n); + buf_concat(&b, ETX " "); + p->x = b.len; + p->z = -1; + } + buf_concat(&b, ETX); + h = bar->h; + call("render-line:set", bar, -1, NULL, buf_final(&b), + 0, NULL, "bg:#ffa500+50"); + pane_resize(bar, 0, 0, bar->w, home->h/3); + call("render-line:measure", bar, -1); + if (bar->h != h) + pane_damaged(home, DAMAGED_SIZE); + free(buf_final(&b)); + return 1; +} + +enum create_where { + C_NOWHERE, + C_LEFT, + C_RIGHT, +}; +static struct pane *menubar_find(struct pane *home safe, + struct pane *owner, + const char *name safe, + enum create_where create) +{ + struct pane *p, *m, *d; + char *a; + struct pane *last_left = NULL; + + list_for_each_entry(p, &home->children, siblings) { + if (p->z >= 0) + continue; + if (!p->focus) + /* Strange - every doc should have a focus... */ + continue; + /* If no owner, then we only want currently visible docs */ + if (!owner && p->z != -1) + continue; + a = pane_attr_get(p, "doc-name"); + if (owner && p->focus != owner) + continue; + if (!a || strcmp(name, a) != 0) + continue; + return p; + } + if (create == C_NOWHERE || !owner) + return NULL; + m = call_ret(pane, "attach-menu", home, 0, NULL, "DV", 0, NULL, + "menubar-done"); + if (!m) + return NULL; + d = call_ret(pane, "doc:get-doc", m); + if (d) + call("doc:set:autoclose", d, 0); + call("popup:close", m); + if (!d) + return NULL; + call("doc:set-name", d, 0, NULL, name); + call("doc:set:menubar-side", d, 0, NULL, + create == C_LEFT ? "left" : "right"); + /* Find insertion point */ + list_for_each_entry(p, &home->children, siblings) { + if (p->z >= 0) + continue; + if (!p->focus) + /* Strange - every doc should have a focus... */ + continue; + a = pane_attr_get(p, "menubar-side"); + if (a && strcmp(a, "left") == 0) + last_left = p; + } + d->z = -1; + pane_reparent(d, home); + d->focus = owner; + pane_add_notify(home, owner, "Notify:Close"); + if (last_left) + list_move(&d->siblings, &last_left->siblings); + else if (create == C_RIGHT) + list_move_tail(&d->siblings, &home->children); + pane_damaged(home, DAMAGED_VIEW); + return d; +} + +DEF_CMD(menubar_add) +{ + const char *val; + char *menu; + struct pane *d; + + if (!ci->str || !ci->str2) + return Enoarg; + + val = strchr(ci->str, '/'); + if (!val) + return Enoarg; + menu = strndup(ci->str, val - ci->str); + val += 1; + d = menubar_find(ci->home, ci->focus, menu, + ci->num & 2 ? C_RIGHT : C_LEFT); + if (!d) { + free(menu); + return Efail; + } + call("menu:add", d, 0, NULL, val, 0, NULL, ci->str2); + return 1; +} + +DEF_CMD(menubar_delete) +{ + struct pane *d; + + if (!ci->str) + return Enoarg; + d = menubar_find(ci->home, ci->focus, ci->str, C_NOWHERE); + if (!d) + return Efail; + pane_close(d); + return 1; +} + +DEF_CMD(menubar_clear) +{ + struct pane *d; + + if (!ci->str) + return Enoarg; + d = menubar_find(ci->home, ci->focus, ci->str, C_NOWHERE); + if (!d) + return Efail; + call("menu:clear", d); + return 1; +} + +DEF_CMD(menubar_done) +{ + struct pane *home = ci->home; + struct mbinfo *mbi = home->data; + char *dash; + const char *c, *e; + + if (mbi->child) + pane_focus(mbi->child); + if (!ci->str) + /* Abort ?? */ + return 1; + c = ci->str; + while ((e = strchr(c, ' ')) != NULL) { + int ret; + + dash = ""; + if (*c != ':' || e == c+1) + dash = "-"; + ret = call("Keystroke", home, 0, NULL, + strconcat(home, dash, + strnsave(home, c, e - c))); + if (ret < 0) + return Efail; + c = e+1; + } + dash = ""; + if (*c != ':' || c[1] == '\0') + dash = "-"; + return call("Keystroke", home, 0, NULL, + strconcat(home, dash, c)); +} + +DEF_CMD(menubar_root) +{ + /* Provide a pane for popup to attach to */ + comm_call(ci->comm2, "cb", ci->home); + return 1; +} + +DEF_CMD(menubar_view_changed) +{ + //struct mbinfo *mbi = ci->home->data; + + //ci->home->focus = mbi->child; + return 1; +} + +DEF_CMD(menubar_press) +{ + struct mbinfo *mbi = ci->home->data; + struct call_return cr; + struct xy cih; + struct pane *p; + int x, y; + + if (ci->focus != mbi->bar) + return Efallthrough; + if (mbi->menu) { + call("popup:close", mbi->menu); + mbi->menu = NULL; + pane_damaged(ci->home, DAMAGED_VIEW); + } + cih = pane_mapxy(mbi->bar, ci->home, + ci->x == INT_MAX ? ci->focus->cx : ci->x, + ci->y == INT_MAX ? ci->focus->cy : ci->y, + False); + cr = pane_call_ret(all, mbi->bar, "render-line:findxy", + mbi->bar, -1, NULL, NULL, + 0, NULL, NULL, + cih.x, cih.y); + if (cr.ret <= 0) + return 1; + if (cr.s && sscanf(cr.s, "%dx%d,", &x, &y) == 2) { + cih.x = x; + cih.y = y; + } + list_for_each_entry(p, &ci->home->children, siblings) { + if (p->z != -1) + continue; + if (!p->focus) + continue; + if (p->x < cr.ret - 1) + continue; + /* clicked on 'p' */ + mbi->menu = call_ret(pane, "attach-menu", p, 0, NULL, "DVF", + 0, NULL, NULL, + cih.x, mbi->bar->h); + if (mbi->menu) { + pane_add_notify(ci->home, mbi->menu, "Notify:Close"); + mbi->open = p; + } + pane_damaged(ci->home, DAMAGED_VIEW); + return 1; + } + return 1; +} + +DEF_CMD(menubar_release) +{ + struct mbinfo *mbi = ci->home->data; + struct pane *c = pane_my_child(ci->home, ci->focus); + + if (c == mbi->child) + return Efallthrough; + + /* any button maps to -3 for menu action */ + return home_call(ci->home->parent, "M:Release-3", ci->focus, + ci->num, ci->mark, ci->str, + ci->num2, ci->mark2, ci->str2, + ci->x, ci->y, ci->comm2); +} + +DEF_CMD(menubar_close_notify) +{ + struct mbinfo *mbi = ci->home->data; + struct pane *p; + + if (ci->focus == mbi->menu) { + mbi->menu = NULL; + pane_damaged(ci->home, DAMAGED_VIEW); + return 1; + } + if (ci->focus == mbi->child) { + mbi->child = NULL; + return 1; + } + if (ci->focus == mbi->bar) { + // FIXME + return 1; + } + list_for_each_entry(p, &ci->home->children, siblings) { + if (p->z >= 0) + continue; + if (p->focus == ci->focus) { + p->focus = NULL; + pane_close(p); + return 1; + } + } + return 1; +} + +DEF_CMD(menubar_attach) +{ + struct pane *ret, *mbp; + struct mbinfo *mbi; + char *v = pane_attr_get(ci->focus, "menubar-visible"); + + ret = pane_register(ci->focus, 0, &menubar_handle.c); + if (!ret) + return Efail; + mbi = ret->data; + mbi->wanted = True; + if (v && strcmp(v, "no") == 0) + mbi->wanted = False; + mbi->hidden = ! mbi->wanted; + mbp = call_ret(pane, "attach-renderline", ret, 1); + if (!mbp) { + pane_close(ret); + return Efail; + } + mbi->bar = mbp; + pane_damaged(ret, DAMAGED_VIEW); + return comm_call(ci->comm2, "callback:attach", ret); +} + +void edlib_init(struct pane *ed safe) +{ + call_comm("global-set-command", ed, &menubar_attach, + 0, NULL, "attach-menubar"); + + menubar_map = key_alloc(); + key_add(menubar_map, "Display:border", &menubar_border); + key_add(menubar_map, "Refresh:size", &menubar_refresh_size); + key_add(menubar_map, "Child-Notify", &menubar_child_notify); + key_add(menubar_map, "Refresh:view", &menubar_refresh); + key_add(menubar_map, "menubar-add", &menubar_add); + key_add(menubar_map, "menubar-delete", &menubar_delete); + key_add(menubar_map, "menubar-clear", &menubar_clear); + key_add(menubar_map, "menubar-done", &menubar_done); + key_add(menubar_map, "RootPane", &menubar_root); + key_add(menubar_map, "Notify:Close", &menubar_close_notify); + key_add(menubar_map, "view:changed", &menubar_view_changed); + key_add_prefix(menubar_map, "M:Press-", &menubar_press); + key_add_prefix(menubar_map, "M:Release-", &menubar_release); +} diff --git a/mode-emacs.c b/mode-emacs.c index a8c22924..82476411 100644 --- a/mode-emacs.c +++ b/mode-emacs.c @@ -3477,9 +3477,24 @@ static void emacs_init(void) DEF_LOOKUP_CMD(mode_emacs, emacs_map); +static char *menus[][3] = { + { "Help/Recent", ":F1 l", "R" }, + { "File/Open", ":C-X :C-F", "L"}, + { "File/Save", ":C-X :C-S", "L"}, + { "File/Exit", ":C-X :C-C", "L"}, + { "Edit/Copy", ":A-w", "L"}, +}; + DEF_CMD(attach_mode_emacs) { - return call_comm("global-set-keymap", ci->focus, &mode_emacs.c); + unsigned int i; + call_comm("global-set-keymap", ci->focus, &mode_emacs.c); + for (i = 0; i < ARRAY_SIZE(menus); i++) + call("menubar-add", ci->focus, + menus[i][2][0] == 'R' ? 2 : 0, + NULL, menus[i][0], + 0, NULL, menus[i][1]); + return 1; } DEF_CMD(attach_file_entry)