]> git.neil.brown.name Git - edlib.git/blob - lib-menubar.c
TODO: clean out done items.
[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                 pane_damaged(p, DAMAGED_REFRESH);
78                 call("render-line:measure", p, -1);
79                 if (mbi->child && ci->home->h > p->h)
80                         pane_resize(mbi->child, 0, p->h,
81                                     ci->home->w, ci->home->h - p->h);
82         }
83         pane_damaged(ci->home, DAMAGED_VIEW);
84         return 1;
85 }
86
87 DEF_CMD(menubar_child_notify)
88 {
89         struct mbinfo *mbi = ci->home->data;
90
91         if (ci->focus->z)
92                 /* Ignore */
93                 return 1;
94         if (ci->num < 0) {
95                 if (ci->home->focus == ci->focus)
96                         ci->home->focus = NULL;
97                 mbi->child = NULL;
98         } else {
99                 if (mbi->child)
100                         pane_close(mbi->child);
101                 mbi->child = ci->focus;
102                 ci->home->focus = ci->focus;
103         }
104         return 1;
105 }
106
107 DEF_CMD(menubar_refresh)
108 {
109         struct buf b;
110         struct pane *home = ci->home;
111         struct mbinfo *mbi = home->data;
112         struct pane *p;
113         struct pane *bar = mbi->bar;
114         int h;
115
116         if (mbi->hidden || !bar)
117                 return 1;
118         if (!mbi->child)
119                 return 1;
120         buf_init(&b);
121         buf_concat(&b, ACK SOH "tab:20" STX);
122
123         list_for_each_entry(p, &home->children, siblings) {
124                 char *n, *c;
125                 if (p->z >= 0)
126                         continue;
127                 if (!p->focus)
128                         /* Strange - every doc should have a focus... */
129                         continue;
130                 p->x = -1;
131                 p->z = -2;
132                 if (!pane_has_focus(p->focus, mbi->child))
133                         /* Owner of this menu not in focus */
134                         continue;
135                 n = pane_attr_get(p, "doc-name");
136                 if (!n || !*n)
137                         continue;
138                 for (c = n ; *c; c++)
139                         if (*c == ',' || (*c > 0 && *c < ' '))
140                                 *c = '_';
141                 if (mbi->menu && mbi->open == p)
142                         buf_concat(&b, SOH "fg:black,bg:white-80,"
143                                    "menu-name:");
144                 else
145                         buf_concat(&b, SOH "fg:blue,underline,"
146                                    "menu-name:");
147                 buf_concat(&b, n);
148                 buf_concat(&b, STX);
149                 buf_concat(&b, n);
150                 buf_concat(&b, ETX " ");
151                 p->x = b.len;
152                 p->z = -1;
153         }
154         buf_concat(&b, ETX);
155         h = bar->h;
156         call("render-line:set", bar, -1, NULL, buf_final(&b),
157              0, NULL, "bg:#ffa500+50");
158         pane_resize(bar, 0, 0, bar->w, home->h/3);
159         call("render-line:measure", bar, -1);
160         if (bar->h != h)
161                 pane_damaged(home, DAMAGED_SIZE);
162         free(buf_final(&b));
163         return 1;
164 }
165
166 enum create_where {
167         C_NOWHERE,
168         C_LEFT,
169         C_RIGHT,
170 };
171 static struct pane *menubar_find(struct pane *home safe,
172                                  struct pane *owner,
173                                  const char *name safe,
174                                  enum create_where create)
175 {
176         struct pane *p, *m, *d;
177         char *a;
178         struct pane *last_left = NULL;
179
180         list_for_each_entry(p, &home->children, siblings) {
181                 if (p->z >= 0)
182                         continue;
183                 if (!p->focus)
184                         /* Strange - every doc should have a focus... */
185                         continue;
186                 /* If no owner, then we only want currently visible docs */
187                 if (!owner && p->z != -1)
188                         continue;
189                 a = pane_attr_get(p, "doc-name");
190                 if (owner && p->focus != owner)
191                         continue;
192                 if (!a || strcmp(name, a) != 0)
193                         continue;
194                 return p;
195         }
196         if (create == C_NOWHERE || !owner)
197                 return NULL;
198         m = call_ret(pane, "attach-menu", home, 0, NULL, "DV", 0, NULL,
199                      "menubar-done");
200         if (!m)
201                 return NULL;
202         d = call_ret(pane, "doc:get-doc", m);
203         if (d)
204                 call("doc:set:autoclose", d, 0);
205         call("popup:close", m);
206         if (!d)
207                 return NULL;
208         call("doc:set-name", d, 0, NULL, name);
209         call("doc:set:menubar-side", d, 0, NULL,
210              create == C_LEFT ? "left" : "right");
211         /* Find insertion point */
212         list_for_each_entry(p, &home->children, siblings) {
213                 if (p->z >= 0)
214                         continue;
215                 if (!p->focus)
216                         /* Strange - every doc should have a focus... */
217                         continue;
218                 a = pane_attr_get(p, "menubar-side");
219                 if (a && strcmp(a, "left") == 0)
220                         last_left = p;
221         }
222         d->z = -1;
223         pane_reparent(d, home);
224         d->focus = owner;
225         pane_add_notify(home, owner, "Notify:Close");
226         if (last_left)
227                 list_move(&d->siblings, &last_left->siblings);
228         pane_damaged(home, DAMAGED_VIEW);
229         return d;
230 }
231
232 DEF_CMD(menubar_add)
233 {
234         const char *val;
235         char *menu;
236         struct pane *d;
237
238         if (!ci->str || !ci->str2)
239                 return Enoarg;
240
241         val = strchr(ci->str, '/');
242         if (!val)
243                 return Enoarg;
244         menu = strndup(ci->str, val - ci->str);
245         val += 1;
246         d = menubar_find(ci->home, ci->focus, menu,
247                          ci->num & 2 ? C_RIGHT : C_LEFT);
248         if (!d) {
249                 free(menu);
250                 return Efail;
251         }
252         call("menu:add", d, 0, NULL, val, 0, NULL, ci->str2);
253         return 1;
254 }
255
256 DEF_CMD(menubar_delete)
257 {
258         struct pane *d;
259
260         if (!ci->str)
261                 return Enoarg;
262         d = menubar_find(ci->home, ci->focus, ci->str, C_NOWHERE);
263         if (!d)
264                 return Efail;
265         pane_close(d);
266         return 1;
267 }
268
269 DEF_CMD(menubar_clear)
270 {
271         struct pane *d;
272
273         if (!ci->str)
274                 return Enoarg;
275         d = menubar_find(ci->home, ci->focus, ci->str, C_NOWHERE);
276         if (!d)
277                 return Efail;
278         call("menu:clear", d);
279         return 1;
280 }
281
282 DEF_CMD(menubar_done)
283 {
284         struct pane *home = ci->home;
285         struct mbinfo *mbi = home->data;
286
287         if (mbi->child)
288                 pane_take_focus(mbi->child);
289         if (!ci->str || !ci->str[0])
290                 return 1;
291         if (ci->str[0] == ' ')
292                 call(ci->str+1, pane_focus(home));
293         else
294                 call("Keystroke-sequence", home, 0, NULL, ci->str);
295         return 1;
296 }
297
298 DEF_CMD(menubar_root)
299 {
300         /* Provide a pane for popup to attach to */
301         comm_call(ci->comm2, "cb", ci->home);
302         return 1;
303 }
304
305 DEF_CMD(menubar_view_changed)
306 {
307         //struct mbinfo *mbi = ci->home->data;
308
309         //ci->home->focus = mbi->child;
310         return 1;
311 }
312
313 DEF_CMD(menubar_press)
314 {
315         struct mbinfo *mbi = ci->home->data;
316         struct call_return cr;
317         struct xy cih;
318         struct pane *p;
319         int x, y;
320
321         if (ci->focus != mbi->bar || !mbi->bar)
322                 return Efallthrough;
323         if (mbi->menu) {
324                 call("popup:close", mbi->menu);
325                 mbi->menu = NULL;
326                 pane_damaged(ci->home, DAMAGED_VIEW);
327         }
328         cih = pane_mapxy(mbi->bar, ci->home,
329                          ci->x == INT_MAX ? ci->focus->cx : ci->x,
330                          ci->y == INT_MAX ? ci->focus->cy : ci->y,
331                          False);
332         cr = pane_call_ret(all, mbi->bar, "render-line:findxy",
333                            mbi->bar, -1, NULL, NULL,
334                            0, NULL, NULL,
335                            cih.x, cih.y);
336         if (cr.ret <= 0)
337                 return 1;
338         if (cr.s && sscanf(cr.s, "%dx%d,", &x, &y) == 2) {
339                 cih.x = x;
340                 cih.y = y;
341         }
342         list_for_each_entry(p, &ci->home->children, siblings) {
343                 if (p->z != -1)
344                         continue;
345                 if (!p->focus)
346                         continue;
347                 if (p->x < cr.ret - 1)
348                         continue;
349                 /* clicked on 'p' */
350                 /* FIXME this should be pane_call, but emacs mode is
351                  * a bit confusing.
352                  */
353                 home_call(p->focus, "menu:refresh", p);
354                 mbi->menu = call_ret(pane, "attach-menu", p, 0, NULL, "DVF",
355                                      0, NULL, NULL,
356                                      cih.x, mbi->bar->h);
357                 if (mbi->menu) {
358                         pane_add_notify(ci->home, mbi->menu, "Notify:Close");
359                         mbi->open = p;
360                 }
361                 pane_damaged(ci->home, DAMAGED_VIEW);
362                 return 1;
363         }
364         return 1;
365 }
366
367 DEF_CMD(menubar_release)
368 {
369         struct mbinfo *mbi = ci->home->data;
370         struct pane *c = pane_my_child(ci->home, ci->focus);
371
372         if (c == mbi->child)
373                 return Efallthrough;
374
375         /* any button maps to -3 for menu action */
376         return home_call(ci->home->parent, "M:Release-3", ci->focus,
377                          ci->num, ci->mark, ci->str,
378                          ci->num2, ci->mark2, ci->str2,
379                          ci->x, ci->y, ci->comm2);
380 }
381
382 DEF_CMD(menubar_close_notify)
383 {
384         struct mbinfo *mbi = ci->home->data;
385         struct pane *p;
386
387         if (ci->focus == mbi->menu) {
388                 mbi->menu = NULL;
389                 pane_damaged(ci->home, DAMAGED_VIEW);
390                 return 1;
391         }
392         if (ci->focus == mbi->child) {
393                 mbi->child = NULL;
394                 return 1;
395         }
396         if (ci->focus == mbi->bar) {
397                 mbi->bar = NULL;
398                 return 1;
399         }
400         list_for_each_entry(p, &ci->home->children, siblings) {
401                 if (p->z >= 0)
402                         continue;
403                 if (p->focus == ci->focus) {
404                         p->focus = NULL;
405                         pane_close(p);
406                         return 1;
407                 }
408         }
409         return 1;
410 }
411
412 DEF_CMD(menubar_attach)
413 {
414         struct pane *ret, *mbp;
415         struct mbinfo *mbi;
416         char *v = pane_attr_get(ci->focus, "menubar-visible");
417
418         ret = pane_register(ci->focus, 0, &menubar_handle.c);
419         if (!ret)
420                 return Efail;
421         mbi = ret->data;
422         mbi->wanted = True;
423         if (v && strcmp(v, "no") == 0)
424                 mbi->wanted = False;
425         mbi->hidden = ! mbi->wanted;
426         mbp = call_ret(pane, "attach-renderline", ret, 1);
427         if (!mbp) {
428                 pane_close(ret);
429                 return Efail;
430         }
431         mbi->bar = mbp;
432         pane_add_notify(ret, mbp, "Notify:Close");
433         pane_damaged(ret, DAMAGED_VIEW);
434         comm_call(ci->comm2, "callback:attach", ret);
435         /* Allow pane close to root to register */
436         call("menubar:ready", ret);
437         return 1;
438 }
439
440 void edlib_init(struct pane *ed safe)
441 {
442         call_comm("global-set-command", ed, &menubar_attach,
443                   0, NULL, "attach-menubar");
444
445         menubar_map = key_alloc();
446         key_add(menubar_map, "Window:border", &menubar_border);
447         key_add(menubar_map, "Refresh:size", &menubar_refresh_size);
448         key_add(menubar_map, "Child-Notify", &menubar_child_notify);
449         key_add(menubar_map, "Refresh:view", &menubar_refresh);
450         key_add(menubar_map, "menubar-add", &menubar_add);
451         key_add(menubar_map, "menubar-delete", &menubar_delete);
452         key_add(menubar_map, "menubar-clear", &menubar_clear);
453         key_add(menubar_map, "menubar-done", &menubar_done);
454         key_add(menubar_map, "RootPane", &menubar_root);
455         key_add(menubar_map, "Notify:Close", &menubar_close_notify);
456         key_add(menubar_map, "view:changed", &menubar_view_changed);
457         key_add_prefix(menubar_map, "M:Press-", &menubar_press);
458         key_add_prefix(menubar_map, "M:Release-", &menubar_release);
459 }