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 <neil@brown.name>
### dynamic completion
-- [ ] provide a drop-down menu with options
+- [X] provide a drop-down menu with options
- [ ] unify UI with spell
### spell-checker
### 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
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
--- /dev/null
+/*
+ * Copyright Neil Brown ©2022-2023 <neil@brown.name>
+ * 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", "<active-tag:menu-select>%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");
+}
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);
lib-shellcmd = attach-shellcmd
lib-unicode-names = Unicode-names
+lib-menu = attach-menu
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")
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()
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)
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
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"
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"
Display 80,30 84F4B35BF26F9D5EEBB67D572ADD0BBF 1,0
Key ":C-C"
Display 80,30 D2E6DB1DE1E62B191C28D1B2CFF7F1C0 1,0
-Close 1550
+Close 1574