]> git.neil.brown.name Git - edlib.git/commitdiff
Add lib-menubar
authorNeilBrown <neil@brown.name>
Sun, 3 Sep 2023 12:11:51 +0000 (22:11 +1000)
committerNeilBrown <neil@brown.name>
Mon, 4 Sep 2023 23:07:53 +0000 (09:07 +1000)
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 <neil@brown.name>
DOC/TODO.md
Makefile
data/edlib.ini
data/modules.ini
lib-menubar.c [new file with mode: 0644]
mode-emacs.c

index b5a5faff7bd09e00c38ed8fc8fe9dd11dcc08b2c..0e6df9d585b39fee92d426e6007d69bea004ef11 100644 (file)
@@ -18,7 +18,7 @@ the file.
 - [X] Disable <hide> 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
 
index 537b013b3899719b69ca24b4eadf9d5e79731370..2b676f181323a9a1eb47a273495dd38d44a11cff 100644 (file)
--- 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
index 0a3a39b369f4c928d9ca2ac9bad6e4861dc2e0d1..fcbddd828e44823516a122f97be57a74c570a057 100644 (file)
@@ -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
index 5bb1ab85bd524182157cb4b543a2d13f06a8ccd2..6f7d73465c3b34dcd75ee7f4000851e9305c4e72 100644 (file)
@@ -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 (file)
index 0000000..ad72d55
--- /dev/null
@@ -0,0 +1,468 @@
+/*
+ * Copyright Neil Brown ©2023 <neil@brown.name>
+ * 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 <stdio.h>
+#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);
+}
index a8c22924b5aab944742ca448bc94a8a687b397df..82476411a84feaa0bebe0bf7c23d23687b71c1e2 100644 (file)
@@ -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)