]> git.neil.brown.name Git - edlib.git/blob - lib-menu.c
menu: allow short-cut keys to be shown.
[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 reported and optionally str2 being the name to display.
11  *
12  * A popup will be created which takes the focus. up/down moves the selection
13  * and enter selects, as can the mouse.
14  *
15  * The selection is sent to the original focus with a callback specified in
16  * str2 when the menu is attached.
17  *
18  */
19
20 #include "core.h"
21 #include "misc.h"
22
23 DEF_CMD(menu_add)
24 {
25         struct mark *m;
26
27         if (!ci->str)
28                 return Enoarg;
29         m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
30         call("doc:set-ref", ci->focus, 0, m);
31         call("doc:list-add", ci->focus, 0, m);
32         call("doc:set-attr", ci->focus, 0, m, "name", 0, NULL,
33              ci->str2 ?: ci->str);
34         call("doc:set-attr", ci->focus, 0, m, "value", 0, NULL,
35              ci->str);
36         if (ci->num & 1)
37                 call("doc:set-attr", ci->focus, 0, m, "disabled",
38                      0, NULL, "1");
39
40         mark_free(m);
41         return 1;
42 }
43
44 DEF_CMD(menu_attr)
45 {
46         if (ci->str && strcmp(ci->str, "BG") == 0) {
47                 char *s = call_ret(str, "doc:get-attr", ci->home,
48                                    0, ci->mark, "disabled");
49                 char *v = (s && *s) ? "fg:white-40" : "fg:black";
50                 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
51                           v, 0, NULL, ci->str);
52                 free(s);
53                 return 1;
54         }
55         if (ci->str && strcmp(ci->str, "shortcut") == 0) {
56                 char *s = call_ret(str, "doc:get-attr", ci->home,
57                                    0, ci->mark, "value");
58                 /* a leading space on 'value' suppresses listing as a shortcut */
59                 char *v = (s && *s != ' ') ? s : "";
60                 comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
61                           v, 0, NULL, ci->str);
62                 free(s);
63                 return 1;
64         }
65         return Efallthrough;
66 }
67
68 DEF_CMD(menu_reposition)
69 {
70         int lines = ci->num;
71         int cols = ci->num2;
72         struct pane *p = call_ret(pane, "ThisPopup", ci->focus);
73
74         if (!p || lines <= 0 || cols <= 0)
75                 return Efallthrough;
76         if (lines > p->parent->h - p->y)
77                 lines = p->parent->h - p->y;
78         if (cols > p->parent->w - p->x)
79                 cols = p->parent->w - p->x;
80         /* Add 1 to cols so that if menu gets wider we will see that and resize */
81         pane_resize(p, p->x, p->y, cols+1, lines);
82         return Efallthrough;
83 }
84
85 DEF_CMD(menu_abort)
86 {
87         call("Abort", ci->focus);
88         return 1;
89 }
90
91 DEF_CMD(menu_done)
92 {
93         struct mark *m = ci->mark;
94         const char *val;
95
96         if (!m)
97                 m = call_ret(mark, "doc:point", ci->focus);
98         if (!m)
99                 return Enoarg;
100         val = pane_mark_attr(ci->focus, m, "value");
101         call("popup:close", ci->focus, 0, m, val);
102         return 1;
103 }
104
105 static struct map *menu_map;
106 DEF_LOOKUP_CMD(menu_handle, menu_map);
107
108 DEF_CMD(menu_attach)
109 {
110         struct pane *docp, *p, *p2;
111         /* Multi-line temporary popup with x,y location provided. */
112         const char *mode = "Mtx";
113         const char *mmode = ci->str ?: "";
114
115         if (strchr(mmode, 'D'))
116                 /* per-display, not per-pane */
117                 mode = "DMtx";
118
119         docp = call_ret(pane, "attach-doc-list", ci->focus);
120         if (!docp)
121                 return Efail;
122         call("doc:set:autoclose", docp, 1);
123         attr_set_str(&docp->attrs, "render-simple", "format");
124         attr_set_int(&docp->attrs, "render-wrap", 0);
125         attr_set_str(&docp->attrs, "heading", "");
126         if (ci->num & 1)
127                 /* show the 'value' - presumably a key name */
128                 attr_set_str(&docp->attrs, "line-format", "<%BG><action-activate:menu-select>%name<rtab>%shortcut</></>");
129         else
130                 attr_set_str(&docp->attrs, "line-format", "<%BG><action-activate:menu-select>%name</></>");
131         attr_set_str(&docp->attrs, "done-key", ci->str2 ?: "menu-done");
132         /* No borders, just a shaded background to make menu stand out */
133         attr_set_str(&docp->attrs, "borders", "");
134         attr_set_str(&docp->attrs, "background", "color:white-80");
135         p = call_ret(pane, "PopupTile", ci->focus, 0, NULL, mode,
136                      0, NULL, NULL, ci->x, ci->y);
137         if (!p)
138                 return Efail;
139         p2 = home_call_ret(pane, docp, "doc:attach-view", p,
140                            0, NULL, "simple");
141         if (!p2) {
142                 pane_close(p);
143                 return Efail;
144         }
145         p2 = pane_register(p2, 0, &menu_handle.c);
146         if (!p2)
147                 return Efail;
148         call("Mouse-grab", p2);
149         return comm_call(ci->comm2, "cb:attach", p2);
150 }
151
152 static void menu_init_map(void)
153 {
154         menu_map = key_alloc();
155
156         key_add(menu_map, "render:reposition", &menu_reposition);
157
158         key_add(menu_map, "menu-add", &menu_add);
159         key_add(menu_map, "Cancel", &menu_abort);
160         key_add(menu_map, "K:Enter", &menu_done);
161         key_add(menu_map, "menu-select", &menu_done);
162         key_add(menu_map, "doc:get-attr", &menu_attr);
163 }
164
165 void edlib_init(struct pane *ed safe)
166 {
167         menu_init_map();
168         call_comm("global-set-command", ed, &menu_attach,
169                   0, NULL, "attach-menu");
170 }