From d49fa9862ac8080c4d85ff45d19677cb851be66e Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Fri, 7 Jul 2023 14:18:29 +1000 Subject: [PATCH] Initial menu support. We can now place a menu and receive a selection. This is demonstrated with lib-abbrev which pops up a menu showing the options to choose between. Signed-off-by: NeilBrown --- DOC/TODO.md | 4 +- Makefile | 2 +- lib-menu.c | 131 +++++++++++++++++++++++++++++++++++++++++++ mode-emacs.c | 5 +- modules.ini | 1 + python/lib-abbrev.py | 63 ++++++++++++++++++++- tests.d/00-basic | 26 ++++----- 7 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 lib-menu.c diff --git a/DOC/TODO.md b/DOC/TODO.md index a347b6e3..ddbcee16 100644 --- a/DOC/TODO.md +++ b/DOC/TODO.md @@ -776,7 +776,7 @@ Module features ### dynamic completion -- [ ] provide a drop-down menu with options +- [X] provide a drop-down menu with options - [ ] unify UI with spell ### spell-checker @@ -800,7 +800,7 @@ Module features ### lib-menu -- [ ] popup menu to which we can add entries, pop it at a location, get +- [X] popup menu to which we can add entries, pop it at a location, get a selection - [ ] menu-bar to which we can add menus from which commands are sent diff --git a/Makefile b/Makefile index 68d30c7b..49102495 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ SHOBJ = O/doc-text.o O/doc-dir.o O/doc-docs.o \ O/lib-renderline.o O/lib-x11selection-gtk.o O/lib-autosave.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-unicode-names.o \ + O/lib-menu.o O/lib-unicode-names.o \ O/lang-python.o \ O/mode-emacs.o O/emacs-search.o \ O/display-ncurses.o diff --git a/lib-menu.c b/lib-menu.c new file mode 100644 index 00000000..db98bf09 --- /dev/null +++ b/lib-menu.c @@ -0,0 +1,131 @@ +/* + * Copyright Neil Brown ©2022-2023 + * May be distributed under terms of GPLv2 - see file:COPYING + * + * lib-menu: support drop-down and pop-up menus. + * + * A menu is created by called attach-menu with x,y being location + * in either the pane or (if str contains 'D') the dispay. + * Entries are added by calling "menu-add" with str being the value to + * be reported and optionally str2 being the name to display. + * + * A popup will be created which takes the focus. up/down moves the selection + * and enter selects, as can the mouse. + * + * The selection is sent to the original focus with a callback specified in + * str2 when the menu is attached. + * + */ + +#include "core.h" +#include "misc.h" + +DEF_CMD(menu_add) +{ + struct mark *m; + + if (!ci->str) + return Enoarg; + m = vmark_new(ci->focus, MARK_UNGROUPED, NULL); + call("doc:set-ref", ci->focus, 0, m); + call("doc:list-add", ci->focus, 0, m); + call("doc:set-attr", ci->focus, 0, m, "name", 0, NULL, + ci->str2 ?: ci->str); + call("doc:set-attr", ci->focus, 0, m, "value", 0, NULL, + ci->str); + + mark_free(m); + return 1; +} + +DEF_CMD(menu_reposition) +{ + int lines = ci->num; + int cols = ci->num2; + struct pane *p = call_ret(pane, "ThisPopup", ci->focus); + + if (p && lines != 0 && cols != 0 && + (lines <= p->h && cols <= p->w)) + pane_resize(p, p->x, p->y, cols, lines); + return Efallthrough; +} + +DEF_CMD(menu_abort) +{ + call("Abort", ci->focus); + return 1; +} + +DEF_CMD(menu_done) +{ + struct mark *m = ci->mark; + const char *val; + + if (!m) + m = call_ret(mark, "doc:point", ci->focus); + if (!m) + return Enoarg; + val = pane_mark_attr(ci->focus, m, "value"); + call("popup:close", ci->focus, 0, m, val); + return 1; +} + +static struct map *menu_map; +DEF_LOOKUP_CMD(menu_handle, menu_map); + +DEF_CMD(menu_attach) +{ + struct pane *docp, *p, *p2; + /* Multi-line popup with x,y location provided. */ + const char *mode = "Mx"; + const char *mmode = ci->str ?: ""; + + if (strchr(mmode, 'D')) + /* per-display, not per-pane */ + mode = "DMx"; + + docp = call_ret(pane, "attach-doc-list", ci->focus); + if (!docp) + return Efail; + call("doc:set:autoclose", docp, 1); + attr_set_str(&docp->attrs, "render-simple", "format"); + attr_set_str(&docp->attrs, "heading", ""); + attr_set_str(&docp->attrs, "line-format", "%name"); + attr_set_str(&docp->attrs, "done-key", ci->str2 ?: "menu-done"); + /* No borders, just a shaded background to make menu stand out */ + attr_set_str(&docp->attrs, "borders", ""); + attr_set_str(&docp->attrs, "background", "color:white-80"); + p = call_ret(pane, "PopupTile", ci->focus, 0, NULL, mode, + 0, NULL, NULL, ci->x, ci->y); + if (!p) + return Efail; + p2 = home_call_ret(pane, docp, "doc:attach-view", p, + 0, NULL, "simple"); + if (!p2) { + pane_close(p); + return Efail; + } + p2 = pane_register(p2, 0, &menu_handle.c); + if (!p2) + return Efail; + return comm_call(ci->comm2, "cb:attach", p2); +} + +static void menu_init_map(void) +{ + menu_map = key_alloc(); + + key_add(menu_map, "render:reposition", &menu_reposition); + + key_add(menu_map, "menu-add", &menu_add); + key_add(menu_map, "K:ESC", &menu_abort); + key_add(menu_map, "K:Enter", &menu_done); + key_add(menu_map, "Activate:menu-select", &menu_done); +} + +void edlib_init(struct pane *ed safe) +{ + menu_init_map(); + call_comm("global-set-command", ed, &menu_attach, + 0, NULL, "attach-menu"); +} diff --git a/mode-emacs.c b/mode-emacs.c index cac83d70..34f24f49 100644 --- a/mode-emacs.c +++ b/mode-emacs.c @@ -2515,7 +2515,10 @@ DEF_CMD(emacs_release) call("Move-CursorXY", ci->focus, 2, m, NULL, moved, NULL, NULL, ci->x, ci->y); - if (moved) { + /* That action might have closed a pane. Better check... */ + if (ci->focus->damaged & DAMAGED_CLOSED) { + /* Do nothing */ + } else if (moved) { /* Moved the mouse, so new location is point */ call("Move-to", ci->focus, 0, m); update_sel(ci->focus, p, m2, NULL); diff --git a/modules.ini b/modules.ini index eee2beca..6a141782 100644 --- a/modules.ini +++ b/modules.ini @@ -154,3 +154,4 @@ lib-glibevents = attach-glibevents lib-shellcmd = attach-shellcmd lib-unicode-names = Unicode-names +lib-menu = attach-menu diff --git a/python/lib-abbrev.py b/python/lib-abbrev.py index 0e7b87e1..f5a3ceed 100644 --- a/python/lib-abbrev.py +++ b/python/lib-abbrev.py @@ -19,6 +19,8 @@ import edlib class AbbrevPane(edlib.Pane): def __init__(self, focus): edlib.Pane.__init__(self, focus) + self.menu = None + self.opening_menu = False self.call("doc:request:doc:replaced") self.call("doc:request:mark:moving") @@ -44,7 +46,43 @@ class AbbrevPane(edlib.Pane): self.prefix_end.step(0) self.completions = [] self.current = -1 - self.next_completion(1) + #self.next_completion(1) + self.get_completions() + self.complete_len = 0 + + if not self.completions: + return 1 + self.opening_menu = True + mp = self.call("attach-menu", (self.parent.cx, self.parent.cy), ret='pane') + for c in self.completions: + mp.call("menu-add", self.prefix+c) + mp.call("doc:file", -1) + self.menu = mp + self.add_notify(mp, "Notify:Close") + self.opening_menu = False + + def handle_close(self, key, focus, **a): + "handle:Notify:Close" + if focus == self.menu: + self.menu = None + return 1 + + def menu_done(self, key, focus, str, **a): + "handle:menu-done" + if not self.prefix_start: + return + edlib.LOG(key, str) + if not str: + # Menu aborted + self.call("view:changed", self.prefix_start, self.prefix_end) + self.prefix_start = None + self.prefix_end = None + self.call("Message", "") + return 1 + self.call("doc:replace", str, self.prefix_end, self.prefix_start) + self.complete_len = len(str) + self.call("view:changed", self.prefix_start, self.prefix_end) + return 1 def get_completions(self): m = self.prefix_start.dup() @@ -156,12 +194,25 @@ class AbbrevPane(edlib.Pane): self.complete_len = len(c) self.call("view:changed", self.prefix_start, self.prefix_end) - def handle_highlight(self, key, focus, mark, str1, str2, comm2, **a): + def handle_draw(self, key, focus, str2, xy, **a): + "handle:Draw:text" + if not self.menu or not str2 or ",menu_here" not in str2: + return edlib.Efallthrough + + p = self.menu.call("ThisPopup", ret='pane') + if p: + xy = p.mapxy(focus, xy[0], focus.h) + p.x = xy[0] + p.y = xy[1] + return edlib.Efallthrough + + def handle_highlight(self, key, focus, mark, str1, str2, xy, comm2, **a): "handle:map-attr" if not comm2 or not self.prefix_start: return if str1 == "render:abbrev" and str2 == 'prefix' and mark == self.prefix_start: comm2("cb", focus, mark, "bg:yellow", self.prefix_len, 250) + comm2("cb", focus, mark, "menu_here", 1, 250) return 1 if str1 == "render:abbrev" and str2 == 'completion' and mark == self.prefix_end: comm2("cb", focus, mark, "bg:cyan", self.complete_len, 250) @@ -231,6 +282,14 @@ class AbbrevPane(edlib.Pane): if mark.seq != point.seq: return edlib.Efallthrough + if key == "pane:defocus": + if self.opening_menu: + # Focus moved to menu - ignore + return edlib.Efallthrough + if self.has_focus(): + # Maybe the menu lost focus ... I wonder why I would care + return edlib.Efallthrough + if not self.active and self.prefix_start: self.call("view:changed", self.prefix_start, self.prefix_end) self.prefix_start = None diff --git a/tests.d/00-basic b/tests.d/00-basic index 53839a49..97c93c41 100644 --- a/tests.d/00-basic +++ b/tests.d/00-basic @@ -540,17 +540,13 @@ Display 80,30 2C925032938071DE257DE77D7EFF9A0B 8,2 Key "-p" Display 80,30 57B0121C30A300F9ED93171BD0D569BD 9,2 Key ":A-/" -Display 80,30 E8776A04265368929F66064FD60C07AE 19,2 +Display 80,30 BA988BD1C0AF7CB5B05ED2D2D20CD77F 5,3 Key ":Down" -Display 80,30 372F59E84810A205C6775BAE496873D6 14,2 +Display 80,30 B6BCCFE18ADD334C6C9027B52CE9CBE1 5,4 Key ":Down" -Display 80,30 3D507104BA841F564D779AEB225C58B0 12,2 -Key ":Down" -Display 80,30 482240D38480504A48054D7BFFDFC846 13,2 -Key ":Down" -Display 80,30 BFC98002CB6713F1C7D37AF154CC36C3 19,2 -Key ":Down" -Display 80,30 372F59E84810A205C6775BAE496873D6 14,2 +Display 80,30 B6BCCFE18ADD334C6C9027B52CE9CBE1 5,5 +Key ":Enter" +Display 80,30 C33B543761C5D34F322D063006A9099F 12,2 Key ":A:Backspace" Display 80,30 E7979E21C8A8DE26C622B842D72ADA50 5,2 Key "-d" @@ -568,15 +564,15 @@ Display 80,30 2C925032938071DE257DE77D7EFF9A0B 9,2 Key ":Backspace" Display 80,30 2C925032938071DE257DE77D7EFF9A0B 8,2 Key ":A-/" -Display 80,30 2AA09FEFEDF600B1DF2DD2ED9DD14534 16,2 +Display 80,30 5C4CE8698100A7263286F46B34C24BD1 5,3 Key ":Left" -Display 80,30 6CD98BAF00C0148D9C5A8403AB6A0F53 19,2 +Display 80,30 9173D165F92FAC77DB0E1FCC7F1BAC5B 5,3 Key ":Enter" -Display 80,30 D1DD103C84497C3E548CEE8A9232A552 1,3 +Display 80,30 2BB95054BA3FEFEA2C5E84CB264B727B 16,2 Key ":C-U" -Display 80,30 4B1C47234051213271FED14393477B35 1,3 +Display 80,30 D942F9B2848CB8DDABAD73FB638B3F5A 16,2 Key ":C-X" -Display 80,30 F96972F072D843A031AD49641130AF4E 1,3 +Display 80,30 312A12F758B4F74265EFFEB48978F7A6 16,2 Key "-k" Display 80,30 CDBBFFABF0534E6F4508C0F00F8F75E0 1,8 Key ":C-X" @@ -605,4 +601,4 @@ Key ":C-X" Display 80,30 84F4B35BF26F9D5EEBB67D572ADD0BBF 1,0 Key ":C-C" Display 80,30 D2E6DB1DE1E62B191C28D1B2CFF7F1C0 1,0 -Close 1550 +Close 1574 -- 2.39.5