2 * Copyright Neil Brown ©2022-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * lib-menu: support drop-down and pop-up menus.
7 * A menu is created by called attach-menu with x,y being location
8 * in either the pane or (if str contains 'D') the dispay.
9 * Entries are added by calling "menu-add" with str being the value to
10 * be displayed (the name) and optionally str2 being a different value
11 * to be reported (the action).
13 * A popup will be created which takes the focus. up/down moves the selection
14 * and enter selects, as can the mouse.
16 * The selection is sent to the original focus with a callback specified in
17 * str2 when the menu is attached.
30 m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
31 call("doc:set-ref", ci->focus, 0, m);
32 call("doc:list-add", ci->focus, 0, m);
33 call("doc:set-attr", ci->focus, 0, m, "name", 0, NULL,
35 call("doc:set-attr", ci->focus, 0, m, "action", 0, NULL,
38 call("doc:set-attr", ci->focus, 0, m, "disabled",
47 struct mark *m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
49 call("doc:set-ref", ci->home, 1, m);
50 while (call("doc:list-del", ci->home, 0, m) > 0)
57 if (ci->str && strcmp(ci->str, "FG") == 0) {
58 char *s = call_ret(str, "doc:get-attr", ci->home,
59 0, ci->mark, "disabled");
60 char *v = (s && *s) ? "fg:white-40" : "fg:black";
61 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
66 if (ci->str && strcmp(ci->str, "fg") == 0) {
67 char *s = call_ret(str, "doc:get-attr", ci->home,
68 0, ci->mark, "disabled");
69 char *v = (s && *s) ? "fg:blue+60" : "fg:blue";
70 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
75 if (ci->str && strcmp(ci->str, "shortcut") == 0) {
76 char *s = call_ret(str, "doc:get-attr", ci->home,
77 0, ci->mark, "action");
78 /* a leading space on 'action' suppresses listing as a shortcut */
79 char *v = (s && *s != ' ') ? s : "";
80 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
88 DEF_CMD(menu_reposition)
92 struct pane *p = call_ret(pane, "ThisPopup", ci->focus);
94 if (!p || lines <= 0 || cols <= 0)
96 if (lines > p->parent->h - p->y)
97 lines = p->parent->h - p->y;
98 if (cols > p->parent->w - p->x)
99 cols = p->parent->w - p->x;
100 /* Add 1 to cols so that if menu gets wider we will see that and resize */
101 pane_resize(p, p->x, p->y, cols+1, lines);
107 call("Abort", ci->focus);
113 struct mark *m = ci->mark;
117 m = call_ret(mark, "doc:point", ci->focus);
120 val = pane_mark_attr(ci->focus, m, "action");
121 call("popup:close", ci->focus, 0, m, val);
125 static struct map *menu_map;
126 DEF_LOOKUP_CMD(menu_handle, menu_map);
130 /* ->str gives the "mode"
131 * D means per-display menu, not per-pane
132 * V means show value in menu as well as name
133 * F means to use the focus as the doc, and its
134 * parent as the focus.
135 * ->str2 gives command to call on completion, else
136 * "menu-done" is used.
137 * ->x,y are co-ordinated relative to ->focus where menu
139 * ->comm2 returns the created pane.
141 struct pane *docp, *p, *p2;
142 /* Multi-line temporary popup with x,y location provided. */
143 const char *mode = "Mtx";
144 const char *mmode = ci->str ?: "";
145 struct pane *focus = ci->focus;
147 if (strchr(mmode, 'D'))
148 /* per-display, not per-pane */
151 if (strchr(mmode, 'F')) {
153 focus = focus->parent;
155 docp = call_ret(pane, "attach-doc-list", ci->focus);
158 call("doc:set:autoclose", docp, 1);
159 attr_set_str(&docp->attrs, "render-simple", "format");
160 attr_set_str(&docp->attrs, "heading", "");
161 if (strchr(mmode, 'V'))
162 /* show the 'action' - presumably a key name */
163 attr_set_str(&docp->attrs, "line-format",
164 "<%FG><action-activate:menu-select>%name <rtab><%fg>%shortcut</></></>");
166 attr_set_str(&docp->attrs, "line-format",
167 "<%FG><action-activate:menu-select>%name</></>");
168 attr_set_str(&docp->attrs, "done-key", ci->str2 ?: "menu-done");
169 /* No borders, just a shaded background to make menu stand out */
170 attr_set_str(&docp->attrs, "borders", "");
171 attr_set_str(&docp->attrs, "background", "color:white-80");
173 p = call_ret(pane, "PopupTile", focus, 0, NULL, mode,
174 0, NULL, NULL, ci->x, ci->y);
177 p2 = home_call_ret(pane, docp, "doc:attach-view", p,
183 p2 = pane_register(p2, 0, &menu_handle.c);
184 /* Don't allow any shift - we size the menu to fit */
187 attr_set_int(&p2->attrs, "render-wrap", 0);
188 call("Mouse-grab", p2);
189 return comm_call(ci->comm2, "cb:attach", p2);
192 static void menu_init_map(void)
194 menu_map = key_alloc();
196 key_add(menu_map, "render:reposition", &menu_reposition);
198 key_add(menu_map, "menu-add", &menu_add);
199 key_add(menu_map, "menu-clear", &menu_clear);
200 key_add(menu_map, "Cancel", &menu_abort);
201 key_add(menu_map, "K:Enter", &menu_done);
202 key_add(menu_map, "menu-select", &menu_done);
203 key_add(menu_map, "doc:get-attr", &menu_attr);
206 void edlib_init(struct pane *ed safe)
209 call_comm("global-set-command", ed, &menu_attach,
210 0, NULL, "attach-menu");
211 call_comm("global-set-command", ed, &menu_add,
212 0, NULL, "menu:add");
213 call_comm("global-set-command", ed, &menu_clear,
214 0, NULL, "menu:clear");