]> git.neil.brown.name Git - edlib.git/blob - lib-menu.c
Send warning message when nested notification is prohibited.
[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 #include "core.h"
22 #include "misc.h"
23
24 DEF_CMD(menu_add)
25 {
26         struct mark *m;
27
28         if (!ci->str)
29                 return Enoarg;
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,
34              ci->str);
35         call("doc:set-attr", ci->focus, 0, m, "action", 0, NULL,
36              ci->str2 ?: ci->str);
37         if (ci->num & 1)
38                 call("doc:set-attr", ci->focus, 0, m, "disabled",
39                      0, NULL, "1");
40
41         mark_free(m);
42         return 1;
43 }
44
45 DEF_CMD(menu_clear)
46 {
47         struct mark *m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
48
49         call("doc:set-ref", ci->home, 1, m);
50         while (call("doc:list-del", ci->home, 0, m) > 0)
51                 ;
52         return 1;
53 }
54
55 DEF_CMD(menu_attr)
56 {
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,
62                           v, 0, NULL, ci->str);
63                 free(s);
64                 return 1;
65         }
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,
71                           v, 0, NULL, ci->str);
72                 free(s);
73                 return 1;
74         }
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,
81                           v, 0, NULL, ci->str);
82                 free(s);
83                 return 1;
84         }
85         return Efallthrough;
86 }
87
88 DEF_CMD(menu_reposition)
89 {
90         int lines = ci->num;
91         int cols = ci->num2;
92         struct pane *p = call_ret(pane, "ThisPopup", ci->focus);
93
94         if (!p || lines <= 0 || cols <= 0)
95                 return Efallthrough;
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);
102         return Efallthrough;
103 }
104
105 DEF_CMD(menu_abort)
106 {
107         call("Abort", ci->focus);
108         return 1;
109 }
110
111 DEF_CMD(menu_done)
112 {
113         struct mark *m = ci->mark;
114         const char *val;
115
116         if (!m)
117                 m = call_ret(mark, "doc:point", ci->focus);
118         if (!m)
119                 return Enoarg;
120         val = pane_mark_attr(ci->focus, m, "action");
121         call("popup:close", ci->focus, 0, m, val);
122         return 1;
123 }
124
125 static struct map *menu_map;
126 DEF_LOOKUP_CMD(menu_handle, menu_map);
127
128 DEF_CMD(menu_attach)
129 {
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
138          *    (Top-Left) appears
139          * ->comm2 returns the created pane.
140          */
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;
146
147         if (strchr(mmode, 'D'))
148                 /* per-display, not per-pane */
149                 mode = "DMtx";
150
151         if (strchr(mmode, 'F')) {
152                 docp = focus;
153                 focus = focus->parent;
154         } else {
155                 docp = call_ret(pane, "attach-doc-list", ci->focus);
156                 if (!docp)
157                         return Efail;
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</></></>");
165                 else
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");
172         }
173         p = call_ret(pane, "PopupTile", focus, 0, NULL, mode,
174                      0, NULL, NULL, ci->x, ci->y);
175         if (!p)
176                 return Efail;
177         p2 = home_call_ret(pane, docp, "doc:attach-view", p,
178                            0, NULL, "simple");
179         if (!p2) {
180                 pane_close(p);
181                 return Efail;
182         }
183         p2 = pane_register(p2, 0, &menu_handle.c);
184         /* Don't allow any shift - we size the menu to fit */
185         if (!p2)
186                 return Efail;
187         attr_set_int(&p2->attrs, "render-wrap", 0);
188         call("Mouse-grab", p2);
189         return comm_call(ci->comm2, "cb:attach", p2);
190 }
191
192 static void menu_init_map(void)
193 {
194         menu_map = key_alloc();
195
196         key_add(menu_map, "render:reposition", &menu_reposition);
197
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);
204 }
205
206 void edlib_init(struct pane *ed safe)
207 {
208         menu_init_map();
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");
215 }