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.
21 #define PANE_DATA_VOID
31 m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
32 call("doc:set-ref", ci->focus, 0, m);
33 call("doc:list-add", ci->focus, 0, m);
34 call("doc:set-attr", ci->focus, 0, m, "name", 0, NULL,
36 call("doc:set-attr", ci->focus, 0, m, "action", 0, NULL,
39 call("doc:set-attr", ci->focus, 0, m, "disabled",
48 struct mark *m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
50 call("doc:set-ref", ci->focus, 1, m);
51 while (call("doc:list-del", ci->focus, 0, m) > 0)
58 if (ci->str && strcmp(ci->str, "FG") == 0) {
59 char *s = call_ret(str, "doc:get-attr", ci->home,
60 0, ci->mark, "disabled");
61 char *v = (s && *s) ? "fg:white-40" : "fg:black";
62 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
67 if (ci->str && strcmp(ci->str, "fg") == 0) {
68 char *s = call_ret(str, "doc:get-attr", ci->home,
69 0, ci->mark, "disabled");
70 char *v = (s && *s) ? "fg:blue+60" : "fg:blue";
71 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
76 if (ci->str && strcmp(ci->str, "shortcut") == 0) {
77 char *s = call_ret(str, "doc:get-attr", ci->home,
78 0, ci->mark, "action");
79 /* a leading space on 'action' suppresses listing as a shortcut */
80 char *v = (s && *s != ' ') ? s : "";
81 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
89 DEF_CMD(menu_reposition)
93 struct pane *p = call_ret(pane, "ThisPopup", ci->focus);
95 if (!p || lines <= 0 || cols <= 0)
97 if (lines > p->parent->h - p->y)
98 lines = p->parent->h - p->y;
99 if (cols > p->parent->w - p->x)
100 cols = p->parent->w - p->x;
101 /* Add 1 to cols so that if menu gets wider we will see that and resize */
102 pane_resize(p, p->x, p->y, cols+1, lines);
108 call("Abort", ci->focus);
114 struct mark *m = ci->mark;
118 m = call_ret(mark, "doc:point", ci->focus);
121 val = pane_mark_attr(ci->focus, m, "action");
122 call("popup:close", ci->focus, 0, m, val);
126 static struct map *menu_map;
127 DEF_LOOKUP_CMD(menu_handle, menu_map);
131 /* ->str gives the "mode"
132 * D means per-display menu, not per-pane
133 * V means show value in menu as well as name
134 * F means to use the focus as the doc, and its
135 * parent as the focus.
136 * ->str2 gives command to call on completion, else
137 * "menu-done" is used.
138 * ->x,y are co-ordinated relative to ->focus where menu
140 * ->comm2 returns the created pane.
142 struct pane *docp, *p, *p2;
143 /* Multi-line temporary popup with x,y location provided. */
144 const char *mode = "Mtx";
145 const char *mmode = ci->str ?: "";
146 struct pane *focus = ci->focus;
148 if (strchr(mmode, 'D'))
149 /* per-display, not per-pane */
152 if (strchr(mmode, 'F')) {
154 focus = focus->parent;
156 docp = call_ret(pane, "attach-doc-list", ci->focus);
159 call("doc:set:autoclose", docp, 1);
160 attr_set_str(&docp->attrs, "render-simple", "format");
161 attr_set_str(&docp->attrs, "heading", "");
162 if (strchr(mmode, 'V'))
163 /* show the 'action' - presumably a key name */
164 attr_set_str(&docp->attrs, "line-format",
165 "<%FG><action-activate:menu-select>%name <rtab><%fg>%shortcut</></></>");
167 attr_set_str(&docp->attrs, "line-format",
168 "<%FG><action-activate:menu-select>%name</></>");
169 attr_set_str(&docp->attrs, "done-key", ci->str2 ?: "menu-done");
170 /* No borders, just a shaded background to make menu stand out */
171 attr_set_str(&docp->attrs, "borders", "");
172 attr_set_str(&docp->attrs, "background", "color:white-80");
174 p = call_ret(pane, "PopupTile", focus, 0, NULL, mode,
175 0, NULL, NULL, ci->x, ci->y);
178 p2 = home_call_ret(pane, docp, "doc:attach-view", p,
184 call("doc:file", p2, -1);
185 p2 = pane_register(p2, 0, &menu_handle.c);
186 /* Don't allow any shift - we size the menu to fit */
189 attr_set_int(&p2->attrs, "render-wrap", 0);
190 call("Mouse-grab", p2);
191 return comm_call(ci->comm2, "cb:attach", p2);
194 static void menu_init_map(void)
196 menu_map = key_alloc();
198 key_add(menu_map, "render:reposition", &menu_reposition);
200 key_add(menu_map, "menu-add", &menu_add);
201 key_add(menu_map, "menu-clear", &menu_clear);
202 key_add(menu_map, "Cancel", &menu_abort);
203 key_add(menu_map, "K:Enter", &menu_done);
204 key_add(menu_map, "menu-select", &menu_done);
205 key_add(menu_map, "doc:get-attr", &menu_attr);
208 void edlib_init(struct pane *ed safe)
211 call_comm("global-set-command", ed, &menu_attach,
212 0, NULL, "attach-menu");
213 call_comm("global-set-command", ed, &menu_add,
214 0, NULL, "menu:add");
215 call_comm("global-set-command", ed, &menu_clear,
216 0, NULL, "menu:clear");