]> git.neil.brown.name Git - edlib.git/commitdiff
Initial menu support.
authorNeilBrown <neil@brown.name>
Fri, 7 Jul 2023 04:18:29 +0000 (14:18 +1000)
committerNeilBrown <neil@brown.name>
Wed, 12 Jul 2023 22:17:52 +0000 (08:17 +1000)
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>
DOC/TODO.md
Makefile
lib-menu.c [new file with mode: 0644]
mode-emacs.c
modules.ini
python/lib-abbrev.py
tests.d/00-basic

index a347b6e31ff5a0c14b7266ff5098f0eed145c37b..ddbcee1681f6bd85b30797e4169f9a40c6c91cb2 100644 (file)
@@ -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
 
index 68d30c7b957993b1c405195a28e4be8fabd8e530..49102495a3ff9995f1708c2834b7a845a3c14c33 100644 (file)
--- 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 (file)
index 0000000..db98bf0
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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");
+}
index cac83d70ab5529fa7ed60df2d1299a06c5e7435b..34f24f498036ef809e74228aba3d9b805e25c753 100644 (file)
@@ -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);
index eee2beca92e54dec21e91b9835a8305f5b31f8db..6a14178282e8ec05e61c4f2d033bb4cf6cd885ff 100644 (file)
@@ -154,3 +154,4 @@ lib-glibevents = attach-glibevents
 lib-shellcmd = attach-shellcmd
 
 lib-unicode-names = Unicode-names
+lib-menu = attach-menu
index 0e7b87e10d27939d55e8d40a117bff300179afbb..f5a3ceedfc7a9f7c5df15d6a36903dc076627977 100644 (file)
@@ -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
index 53839a4946651b287a8b74e30a056702f8e8a12e..97c93c418494555ee6d9a053d64d444b2b668cd3 100644 (file)
@@ -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