]> git.neil.brown.name Git - edlib.git/blob - lib-menu.c
TODO: clean out done items.
[edlib.git] / lib-menu.c
1 /*
2  * Copyright Neil Brown ©2022-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * lib-menu: support drop-down and pop-up menus.
6  *
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).
12  *
13  * A popup will be created which takes the focus. up/down moves the selection
14  * and enter selects, as can the mouse.
15  *
16  * The selection is sent to the original focus with a callback specified in
17  * str2 when the menu is attached.
18  *
19  */
20
21 #define PANE_DATA_VOID
22 #include "core.h"
23 #include "misc.h"
24
25 DEF_CMD(menu_add)
26 {
27         struct mark *m;
28
29         if (!ci->str)
30                 return Enoarg;
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,
35              ci->str);
36         call("doc:set-attr", ci->focus, 0, m, "action", 0, NULL,
37              ci->str2 ?: ci->str);
38         if (ci->num & 1)
39                 call("doc:set-attr", ci->focus, 0, m, "disabled",
40                      0, NULL, "1");
41
42         mark_free(m);
43         return 1;
44 }
45
46 DEF_CMD(menu_clear)
47 {
48         struct mark *m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
49
50         call("doc:set-ref", ci->focus, 1, m);
51         while (call("doc:list-del", ci->focus, 0, m) > 0)
52                 ;
53         return 1;
54 }
55
56 DEF_CMD(menu_attr)
57 {
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,
63                           v, 0, NULL, ci->str);
64                 free(s);
65                 return 1;
66         }
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,
72                           v, 0, NULL, ci->str);
73                 free(s);
74                 return 1;
75         }
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,
82                           v, 0, NULL, ci->str);
83                 free(s);
84                 return 1;
85         }
86         return Efallthrough;
87 }
88
89 DEF_CMD(menu_reposition)
90 {
91         int lines = ci->num;
92         int cols = ci->num2;
93         struct pane *p = call_ret(pane, "ThisPopup", ci->focus);
94
95         if (!p || lines <= 0 || cols <= 0)
96                 return Efallthrough;
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);
103         return Efallthrough;
104 }
105
106 DEF_CMD(menu_abort)
107 {
108         call("Abort", ci->focus);
109         return 1;
110 }
111
112 DEF_CMD(menu_done)
113 {
114         struct mark *m = ci->mark;
115         const char *val;
116
117         if (!m)
118                 m = call_ret(mark, "doc:point", ci->focus);
119         if (!m)
120                 return Enoarg;
121         val = pane_mark_attr(ci->focus, m, "action");
122         call("popup:close", ci->focus, 0, m, val);
123         return 1;
124 }
125
126 static struct map *menu_map;
127 DEF_LOOKUP_CMD(menu_handle, menu_map);
128
129 DEF_CMD(menu_attach)
130 {
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
139          *    (Top-Left) appears
140          * ->comm2 returns the created pane.
141          */
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;
147
148         if (strchr(mmode, 'D'))
149                 /* per-display, not per-pane */
150                 mode = "DMtx";
151
152         if (strchr(mmode, 'F')) {
153                 docp = focus;
154                 focus = focus->parent;
155         } else {
156                 docp = call_ret(pane, "attach-doc-list", ci->focus);
157                 if (!docp)
158                         return Efail;
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</></></>");
166                 else
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");
173         }
174         p = call_ret(pane, "PopupTile", focus, 0, NULL, mode,
175                      0, NULL, NULL, ci->x, ci->y);
176         if (!p)
177                 return Efail;
178         p2 = home_call_ret(pane, docp, "doc:attach-view", p,
179                            0, NULL, "simple");
180         if (!p2) {
181                 pane_close(p);
182                 return Efail;
183         }
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 */
187         if (!p2)
188                 return Efail;
189         attr_set_int(&p2->attrs, "render-wrap", 0);
190         call("Mouse-grab", p2);
191         return comm_call(ci->comm2, "cb:attach", p2);
192 }
193
194 static void menu_init_map(void)
195 {
196         menu_map = key_alloc();
197
198         key_add(menu_map, "render:reposition", &menu_reposition);
199
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);
206 }
207
208 void edlib_init(struct pane *ed safe)
209 {
210         menu_init_map();
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");
217 }