]> git.neil.brown.name Git - edlib.git/blob - lib-menubar.c
b4a265505adccbc698908a9ab289f1c07b74322c
[edlib.git] / lib-menubar.c
1 /*
2  * Copyright Neil Brown ©2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * trim a line off the top line of a pane and place a menu
6  * bar.   Actions are sent to the focus.
7  *
8  * We place a renderline at the top and construct a string
9  * to give to it as needed.
10  * We create menu documents as children of the main pane,
11  * and display them as needed.
12  *
13  * Menus are added either to LHS or RHS and must be added in order
14  * So FILE EDIT VIEW must be in order, and HELP on right before
15  * any other right-side menus.
16  * Before displayed a menu, the pane which requested it is given
17  * a chance to update the content via a "menu:refresh" notification.
18  *
19  * Menus are created and populated with "menubar-add" which acts
20  * like menu-add,  The name is "X/Y" where is the name of the menu
21  * and Y is the name in the menu.  If X doesn't exist, the menu
22  * is created.  If Y already exists, the other details are updated.
23  * menubar-delete and menubar-clear can delete individual menus,
24  * or clear all entries so they can be repopulated.
25  *
26  * Menu documents are collected as children of this pane.  The focus
27  * of each document is the pane which requested the window.  This
28  * allows the menu to be discarded when that pane is closed, and to
29  * be hidden when the pane loses focus.
30  *
31  * Child panes have ->z value:
32  * 0 for child and bar
33  * 1 or more for active menu
34  * -1 for menu documents created by in-focus clients
35  * -2 for menu documents created by not-in-focus clients
36  */
37
38 #include <stdio.h>
39 #define PANE_DATA_TYPE struct mbinfo
40 #include "core.h"
41
42 struct mbinfo {
43         struct pane *bar;
44         struct pane *child;
45         struct pane *menu, *open;
46         bool hidden, wanted;
47 };
48 #include "core-pane.h"
49
50 static struct map *menubar_map;
51 DEF_LOOKUP_CMD(menubar_handle, menubar_map);
52
53 DEF_CMD(menubar_border)
54 {
55         struct mbinfo *mbi = ci->home->data;
56
57         mbi->hidden = !mbi->wanted || ci->num <= 0;
58         pane_damaged(ci->home, DAMAGED_SIZE);
59         return Efallthrough;
60 }
61
62 DEF_CMD(menubar_refresh_size)
63 {
64         struct mbinfo *mbi = ci->home->data;
65         struct pane *p = mbi->bar;
66
67         if (!p || mbi->hidden) {
68                 /* Put bar below window - out of sight */
69                 if (p)
70                         pane_resize(p, 0, ci->home->h,
71                                     p->w, p->h);
72                 if (mbi->child)
73                         pane_resize(mbi->child, 0, 0,
74                                     ci->home->w, ci->home->h);
75         } else {
76                 pane_resize(p, 0, 0, ci->home->w, ci->home->h/3);
77                 call("render-line:measure", p, -1);
78                 if (mbi->child && ci->home->h > p->h)
79                         pane_resize(mbi->child, 0, p->h,
80                                     ci->home->w, ci->home->h - p->h);
81         }
82         pane_damaged(ci->home, DAMAGED_VIEW);
83         return 1;
84 }
85
86 DEF_CMD(menubar_child_notify)
87 {
88         struct mbinfo *mbi = ci->home->data;
89
90         if (ci->focus->z)
91                 /* Ignore */
92                 return 1;
93         if (ci->num < 0) {
94                 if (ci->home->focus == ci->focus)
95                         ci->home->focus = NULL;
96                 mbi->child = NULL;
97         } else {
98                 if (mbi->child)
99                         pane_close(mbi->child);
100                 mbi->child = ci->focus;
101                 ci->home->focus = ci->focus;
102         }
103         return 1;
104 }
105
106 DEF_CMD(menubar_refresh)
107 {
108         struct buf b;
109         struct pane *home = ci->home;
110         struct mbinfo *mbi = home->data;
111         struct pane *p;
112         struct pane *bar = mbi->bar;
113         int h;
114
115         if (mbi->hidden || !bar)
116                 return 1;
117         if (!mbi->child)
118                 return 1;
119         buf_init(&b);
120         buf_concat(&b, ACK SOH "tab:20" STX);
121
122         list_for_each_entry(p, &home->children, siblings) {
123                 char *n, *c;
124                 if (p->z >= 0)
125                         continue;
126                 if (!p->focus)
127                         /* Strange - every doc should have a focus... */
128                         continue;
129                 p->x = -1;
130                 p->z = -2;
131                 if (!pane_has_focus(p->focus, mbi->child))
132                         /* Owner of this menu not in focus */
133                         continue;
134                 n = pane_attr_get(p, "doc-name");
135                 if (!n || !*n)
136                         continue;
137                 for (c = n ; *c; c++)
138                         if (*c == ',' || (*c > 0 && *c < ' '))
139                                 *c = '_';
140                 if (mbi->menu && mbi->open == p)
141                         buf_concat(&b, SOH "fg:black,bg:white-80,"
142                                    "menu-name:");
143                 else
144                         buf_concat(&b, SOH "fg:blue,underline,"
145                                    "menu-name:");
146                 buf_concat(&b, n);
147                 buf_concat(&b, STX);
148                 buf_concat(&b, n);
149                 buf_concat(&b, ETX " ");
150                 p->x = b.len;
151                 p->z = -1;
152         }
153         buf_concat(&b, ETX);
154         h = bar->h;
155         call("render-line:set", bar, -1, NULL, buf_final(&b),
156              0, NULL, "bg:#ffa500+50");
157         pane_resize(bar, 0, 0, bar->w, home->h/3);
158         call("render-line:measure", bar, -1);
159         if (bar->h != h)
160                 pane_damaged(home, DAMAGED_SIZE);
161         free(buf_final(&b));
162         return 1;
163 }
164
165 enum create_where {
166         C_NOWHERE,
167         C_LEFT,
168         C_RIGHT,
169 };
170 static struct pane *menubar_find(struct pane *home safe,
171                                  struct pane *owner,
172                                  const char *name safe,
173                                  enum create_where create)
174 {
175         struct pane *p, *m, *d;
176         char *a;
177         struct pane *last_left = NULL;
178
179         list_for_each_entry(p, &home->children, siblings) {
180                 if (p->z >= 0)
181                         continue;
182                 if (!p->focus)
183                         /* Strange - every doc should have a focus... */
184                         continue;
185                 /* If no owner, then we only want currently visible docs */
186                 if (!owner && p->z != -1)
187                         continue;
188                 a = pane_attr_get(p, "doc-name");
189                 if (owner && p->focus != owner)
190                         continue;
191                 if (!a || strcmp(name, a) != 0)
192                         continue;
193                 return p;
194         }
195         if (create == C_NOWHERE || !owner)
196                 return NULL;
197         m = call_ret(pane, "attach-menu", home, 0, NULL, "DV", 0, NULL,
198                      "menubar-done");
199         if (!m)
200                 return NULL;
201         d = call_ret(pane, "doc:get-doc", m);
202         if (d)
203                 call("doc:set:autoclose", d, 0);
204         call("popup:close", m);
205         if (!d)
206                 return NULL;
207         call("doc:set-name", d, 0, NULL, name);
208         call("doc:set:menubar-side", d, 0, NULL,
209              create == C_LEFT ? "left" : "right");
210         /* Find insertion point */
211         list_for_each_entry(p, &home->children, siblings) {
212                 if (p->z >= 0)
213                         continue;
214                 if (!p->focus)
215                         /* Strange - every doc should have a focus... */
216                         continue;
217                 a = pane_attr_get(p, "menubar-side");
218                 if (a && strcmp(a, "left") == 0)
219                         last_left = p;
220         }
221         d->z = -1;
222         pane_reparent(d, home);
223         d->focus = owner;
224         pane_add_notify(home, owner, "Notify:Close");
225         if (last_left)
226                 list_move(&d->siblings, &last_left->siblings);
227         else if (create == C_RIGHT)
228                 list_move_tail(&d->siblings, &home->children);
229         pane_damaged(home, DAMAGED_VIEW);
230         return d;
231 }
232
233 DEF_CMD(menubar_add)
234 {
235         const char *val;
236         char *menu;
237         struct pane *d;
238
239         if (!ci->str || !ci->str2)
240                 return Enoarg;
241
242         val = strchr(ci->str, '/');
243         if (!val)
244                 return Enoarg;
245         menu = strndup(ci->str, val - ci->str);
246         val += 1;
247         d = menubar_find(ci->home, ci->focus, menu,
248                          ci->num & 2 ? C_RIGHT : C_LEFT);
249         if (!d) {
250                 free(menu);
251                 return Efail;
252         }
253         call("menu:add", d, 0, NULL, val, 0, NULL, ci->str2);
254         return 1;
255 }
256
257 DEF_CMD(menubar_delete)
258 {
259         struct pane *d;
260
261         if (!ci->str)
262                 return Enoarg;
263         d = menubar_find(ci->home, ci->focus, ci->str, C_NOWHERE);
264         if (!d)
265                 return Efail;
266         pane_close(d);
267         return 1;
268 }
269
270 DEF_CMD(menubar_clear)
271 {
272         struct pane *d;
273
274         if (!ci->str)
275                 return Enoarg;
276         d = menubar_find(ci->home, ci->focus, ci->str, C_NOWHERE);
277         if (!d)
278                 return Efail;
279         call("menu:clear", d);
280         return 1;
281 }
282
283 DEF_CMD(menubar_done)
284 {
285         struct pane *home = ci->home;
286         struct mbinfo *mbi = home->data;
287
288         if (mbi->child)
289                 pane_take_focus(mbi->child);
290         call("Keystroke-sequence", home, 0, NULL, ci->str);
291         return 1;
292 }
293
294 DEF_CMD(menubar_root)
295 {
296         /* Provide a pane for popup to attach to */
297         comm_call(ci->comm2, "cb", ci->home);
298         return 1;
299 }
300
301 DEF_CMD(menubar_view_changed)
302 {
303         //struct mbinfo *mbi = ci->home->data;
304
305         //ci->home->focus = mbi->child;
306         return 1;
307 }
308
309 DEF_CMD(menubar_press)
310 {
311         struct mbinfo *mbi = ci->home->data;
312         struct call_return cr;
313         struct xy cih;
314         struct pane *p;
315         int x, y;
316
317         if (ci->focus != mbi->bar || !mbi->bar)
318                 return Efallthrough;
319         if (mbi->menu) {
320                 call("popup:close", mbi->menu);
321                 mbi->menu = NULL;
322                 pane_damaged(ci->home, DAMAGED_VIEW);
323         }
324         cih = pane_mapxy(mbi->bar, ci->home,
325                          ci->x == INT_MAX ? ci->focus->cx : ci->x,
326                          ci->y == INT_MAX ? ci->focus->cy : ci->y,
327                          False);
328         cr = pane_call_ret(all, mbi->bar, "render-line:findxy",
329                            mbi->bar, -1, NULL, NULL,
330                            0, NULL, NULL,
331                            cih.x, cih.y);
332         if (cr.ret <= 0)
333                 return 1;
334         if (cr.s && sscanf(cr.s, "%dx%d,", &x, &y) == 2) {
335                 cih.x = x;
336                 cih.y = y;
337         }
338         list_for_each_entry(p, &ci->home->children, siblings) {
339                 if (p->z != -1)
340                         continue;
341                 if (!p->focus)
342                         continue;
343                 if (p->x < cr.ret - 1)
344                         continue;
345                 /* clicked on 'p' */
346                 mbi->menu = call_ret(pane, "attach-menu", p, 0, NULL, "DVF",
347                                      0, NULL, NULL,
348                                      cih.x, mbi->bar->h);
349                 if (mbi->menu) {
350                         pane_add_notify(ci->home, mbi->menu, "Notify:Close");
351                         mbi->open = p;
352                 }
353                 pane_damaged(ci->home, DAMAGED_VIEW);
354                 return 1;
355         }
356         return 1;
357 }
358
359 DEF_CMD(menubar_release)
360 {
361         struct mbinfo *mbi = ci->home->data;
362         struct pane *c = pane_my_child(ci->home, ci->focus);
363
364         if (c == mbi->child)
365                 return Efallthrough;
366
367         /* any button maps to -3 for menu action */
368         return home_call(ci->home->parent, "M:Release-3", ci->focus,
369                          ci->num, ci->mark, ci->str,
370                          ci->num2, ci->mark2, ci->str2,
371                          ci->x, ci->y, ci->comm2);
372 }
373
374 DEF_CMD(menubar_close_notify)
375 {
376         struct mbinfo *mbi = ci->home->data;
377         struct pane *p;
378
379         if (ci->focus == mbi->menu) {
380                 mbi->menu = NULL;
381                 pane_damaged(ci->home, DAMAGED_VIEW);
382                 return 1;
383         }
384         if (ci->focus == mbi->child) {
385                 mbi->child = NULL;
386                 return 1;
387         }
388         if (ci->focus == mbi->bar) {
389                 mbi->bar = NULL;
390                 return 1;
391         }
392         list_for_each_entry(p, &ci->home->children, siblings) {
393                 if (p->z >= 0)
394                         continue;
395                 if (p->focus == ci->focus) {
396                         p->focus = NULL;
397                         pane_close(p);
398                         return 1;
399                 }
400         }
401         return 1;
402 }
403
404 DEF_CMD(menubar_attach)
405 {
406         struct pane *ret, *mbp;
407         struct mbinfo *mbi;
408         char *v = pane_attr_get(ci->focus, "menubar-visible");
409
410         ret = pane_register(ci->focus, 0, &menubar_handle.c);
411         if (!ret)
412                 return Efail;
413         mbi = ret->data;
414         mbi->wanted = True;
415         if (v && strcmp(v, "no") == 0)
416                 mbi->wanted = False;
417         mbi->hidden = ! mbi->wanted;
418         mbp = call_ret(pane, "attach-renderline", ret, 1);
419         if (!mbp) {
420                 pane_close(ret);
421                 return Efail;
422         }
423         mbi->bar = mbp;
424         pane_add_notify(ret, mbp, "Notify:Close");
425         pane_damaged(ret, DAMAGED_VIEW);
426         return comm_call(ci->comm2, "callback:attach", ret);
427 }
428
429 void edlib_init(struct pane *ed safe)
430 {
431         call_comm("global-set-command", ed, &menubar_attach,
432                   0, NULL, "attach-menubar");
433
434         menubar_map = key_alloc();
435         key_add(menubar_map, "window:border", &menubar_border);
436         key_add(menubar_map, "Refresh:size", &menubar_refresh_size);
437         key_add(menubar_map, "Child-Notify", &menubar_child_notify);
438         key_add(menubar_map, "Refresh:view", &menubar_refresh);
439         key_add(menubar_map, "menubar-add", &menubar_add);
440         key_add(menubar_map, "menubar-delete", &menubar_delete);
441         key_add(menubar_map, "menubar-clear", &menubar_clear);
442         key_add(menubar_map, "menubar-done", &menubar_done);
443         key_add(menubar_map, "RootPane", &menubar_root);
444         key_add(menubar_map, "Notify:Close", &menubar_close_notify);
445         key_add(menubar_map, "view:changed", &menubar_view_changed);
446         key_add_prefix(menubar_map, "M:Press-", &menubar_press);
447         key_add_prefix(menubar_map, "M:Release-", &menubar_release);
448 }