]> git.neil.brown.name Git - edlib.git/blob - lib-popup.c
Discard edlib_do_free()
[edlib.git] / lib-popup.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * popup
6  *
7  * A 'popup' dialogue pane can be used to enter a file name or
8  * probably lots of other things.
9  * It gets a high 'z' value so it obscures whatever is behind.
10  *
11  * As well a interacting with its own buffer, a popup can pass events
12  * on to other panes, and it can disappear.
13  * For now these are combined - the <ENTER> key will make the window
14  * disappear and will pass a message with the content of the text
15  * as a string.
16  * The target pane must not disappear while the popup is active.
17  * I need to find a way to control that.
18  *
19  * A popup is created by "PopupTile"
20  * A prefix to be displayed can be added by setting "prefix" on
21  * the popup pane.
22  * A default value can be given with attr "default" which is displated
23  * after prefix
24  * The event sent when the popup is closed can be set by setting
25  * attribute "done-key"
26  * otherwise "PopupDone" is used.
27  *
28  * The "Style" of a popup is a string of characters:
29  * D - parent is whole display (window) rather than single pane
30  * P - position w.r.t another popup - Currently always 'under'
31  * M - multiple lines of text - default is one line
32  * 1 - 1/4 width of parent
33  * 2 - 1/2 width of parent (default)
34  * 3 - 3/4 width of parent
35  * 4 - full width
36  * T - at top of parent (default is centred)
37  * B - at bottom of parent
38  * L - at left of parent (default is centred)
39  * R - at right of parent
40  * x - x,y passed with PopupTile set location of top-left.
41  * s - border at bottom to show document status
42  * a - allow recursive popups
43  * r - permit this popup even inside non-recursive popups
44  * t - temporary - auto-close when focus leaves
45  */
46 #define _GNU_SOURCE /*  for asprintf */
47 #include <unistd.h>
48 #include <stdlib.h>
49 #include <stdio.h>
50 #include <memory.h>
51
52 #include "core.h"
53
54 static struct map *popup_map;
55 DEF_LOOKUP_CMD(popup_handle, popup_map);
56
57 struct popup_info {
58         struct pane     *target safe;
59         struct pane     *parent_popup;
60         char            *style safe;
61         struct command  *done;
62 };
63
64 static int line_height(struct pane *p safe, int scale)
65 {
66         struct call_return cr =
67                 call_ret(all, "Draw:text-size", p, -1, NULL, "x",
68                          scale, NULL, "");
69         return cr.y;
70 }
71
72 static void popup_resize(struct pane *p safe, const char *style safe,
73                          short cix, short ciy)
74 {
75         struct popup_info *ppi = p->data;
76         int x,y,w,h;
77         int lh, bh = 0, bw = 0;
78         char *bhs, *bws;
79         struct xy xyscale = pane_scale(p);
80
81         /* First find the size */
82         lh = line_height(p, xyscale.x);
83         bhs = pane_attr_get(pane_leaf(p), "border-height");
84         if (bhs)
85                 bh = atoi(bhs);
86         if (bh <= 0)
87                 bh = line_height(p, 0); /* border height */
88         bws = pane_attr_get(pane_leaf(p), "border-width");
89         if (bws)
90                 bw = atoi(bhs);
91         if (bw <= 0)
92                 bw = bh;
93         if (strchr(style, 'M')) {
94                 h = p->parent->h/2 + bh;
95                 attr_set_str(&p->attrs, "render-one-line", "no");
96         } else {
97                 h = bh + lh + bh;
98                 attr_set_str(&p->attrs, "render-one-line", "yes");
99         }
100         if (ppi->parent_popup) {
101                 w = ppi->parent_popup->w;
102                 h = ppi->parent_popup->h;
103                 x = ppi->parent_popup->x;
104                 y = ppi->parent_popup->y + ppi->parent_popup->h;
105         } else {
106                 w = p->parent->w - 2*bw;
107                 if (strchr(style, '1'))
108                         w = w / 4;
109                 else if (strchr(style, '3'))
110                         w = 3 * w / 4;
111                 else if (strchr(style, '4'))
112                         w = w;
113                 else
114                         w = w / 2;
115
116                 x = p->parent->w/2 - w/2 - bw;
117                 y = p->parent->h/2 - h/2 - bh;
118                 if (strchr(style, 'T')) { y = 0; h -= bh; }
119                 if (strchr(style, 'B')) { h -= bh; y = p->parent->h - h; }
120                 if (strchr(style, 'L')) x = 0;
121                 if (strchr(style, 'R')) x = p->parent->w - w;
122                 if (strchr(style, 'x')) {
123                         x = cix; y = ciy;
124                         if (p->w > 0)
125                                 w = p->w;
126                         if (p->h > 0)
127                                 h = p->h;
128                 }
129         }
130         pane_resize(p, x, y, w, h);
131 }
132
133 DEF_CMD(popup_close)
134 {
135         struct popup_info *ppi = ci->home->data;
136
137         if (ci->num)
138                 /* Pane had focus, so give to target */
139                 pane_focus(ppi->target);
140         command_put(ppi->done);
141         ppi->done = NULL;
142         return 1;
143 }
144
145 DEF_CMD(popup_free)
146 {
147         struct popup_info *ppi = ci->home->data;
148
149         free(ppi->style);
150         unalloc(ppi, pane);
151         return 1;
152 }
153
154 DEF_CMD(popup_notify_close)
155 {
156         struct popup_info *ppi = ci->home->data;
157
158         if (ci->focus == ppi->target) {
159                 /* target is closing, so we close too */
160                 ppi->target = safe_cast NULL;
161                 pane_close(ci->home);
162         }
163         return 1;
164 }
165
166 static void popup_finished(struct pane *focus safe, struct pane *home safe,
167                            const char *result)
168 {
169         struct popup_info *ppi = home->data;
170         struct pane *target = ppi->target;
171         const char *key;
172         const char *aux;
173         struct command *done = ppi->done;
174
175         pane_focus(target);
176         key = pane_attr_get(focus, "done-key");
177         if (!key)
178                 key = "PopupDone";
179         aux = pane_attr_get(focus, "popup-aux");
180
181         ppi->done = NULL;
182         pane_close(home);
183         /* home is now closed, so ppi cannot be touched */
184         if (done)
185                 comm_call(done, key, target, 1, NULL, result, 0, NULL, aux);
186         else
187                 call(key, target, 1, NULL, result, 0, NULL, aux);
188 }
189
190 DEF_CMD(popup_abort)
191 {
192         /* A NULL 'result' signals the abort */
193         popup_finished(ci->focus, ci->home, NULL);
194         return 1;
195 }
196
197 static bool popup_set_style(struct pane *p safe)
198 {
199         struct popup_info *ppi = p->data;
200         char *orig_border = attr_find(p->attrs, "borders");
201         bool changed = False;
202
203         if (ppi->parent_popup) {
204                 char *border = pane_attr_get(ppi->parent_popup, "borders");
205                 attr_set_str(&p->attrs, "borders", border);
206         } else {
207                 char border[6];
208                 int i, j;
209
210                 for (i = 0, j = 0; i < 4; i++) {
211                         if (strchr(ppi->style, "TLBR"[i]) == NULL)
212                                 border[j++] = "TLBR"[i];
213                 }
214                 if (strchr(ppi->style, 's'))
215                         /* Force a status line */
216                         border[j++] = 's';
217                 border[j] = 0;
218                 if (!orig_border || strcmp(orig_border, border) != 0) {
219                         attr_set_str(&p->attrs, "borders", border);
220                         changed = True;
221                 }
222         }
223
224         if (strchr(ppi->style, 'a'))
225                 /* allow recursion */
226                 attr_set_str(&p->attrs, "Popup", "ignore");
227         else
228                 attr_set_str(&p->attrs, "Popup", "true");
229         return changed;
230 }
231
232 DEF_CMD(popup_style)
233 {
234         struct popup_info *ppi = ci->home->data;
235
236         if (!ci->str)
237                 return Enoarg;
238
239         free(ppi->style);
240         ppi->style = strdup(ci->str);
241         if (popup_set_style(ci->home))
242                 call("view:changed", ci->focus);
243         popup_resize(ci->home, ppi->style, ci->home->x, ci->home->y);
244         return 1;
245 }
246
247 DEF_CMD(popup_notify_refresh_size)
248 {
249         pane_damaged(ci->home, DAMAGED_SIZE);
250         return 1;
251 }
252
253 DEF_CMD(popup_refresh_size)
254 {
255         struct popup_info *ppi = ci->home->data;
256         char *prompt, *dflt, *prefix;
257         struct pane *focus = pane_leaf(ci->home);
258
259         prefix = pane_attr_get(focus, "prefix");
260         prompt = pane_attr_get(focus, "prompt");
261         if (!prefix && prompt) {
262                 char *t = NULL;
263                 dflt = pane_attr_get(focus, "default");
264                 if (!prompt)
265                         prompt = "";
266                 if (dflt)
267                         asprintf(&t, "%s(%s): ", prompt, dflt);
268                 else
269                         asprintf(&t, "%s: ", prompt);
270                 attr_set_str(&focus->attrs, "prefix", t);
271                 free(t);
272         }
273
274         popup_set_style(ci->home);
275         popup_resize(ci->home, ppi->style, ci->home->x, ci->home->y);
276         return 0;
277 }
278
279 DEF_CMD(popup_get_target)
280 {
281         struct popup_info *ppi = ci->home->data;
282         return comm_call(ci->comm2, "callback:get-target", ppi->target);
283 }
284
285 DEF_CMD(popup_ignore)
286 {
287         return 1;
288 }
289
290 DEF_CMD(popup_close_others)
291 {
292         /* For some popups, like search or find-file, it doesn't make sense
293          * to maximize the popup.  For others line email-compose it does.
294          * For now, allow it on multi-line popups.
295          */
296         struct popup_info *ppi = ci->home->data;
297         struct pane *p;
298
299         if (strchr(ppi->style, 'M') == NULL)
300                 return 1;
301         p = call_ret(pane, "OtherPane", ci->focus);
302         if (p) {
303                 home_call(ci->home->focus, "doc:attach-view", p);
304                 pane_focus(p);
305         }
306         return 1;
307 }
308
309 DEF_CMD(popup_split)
310 {
311         /* Rather than 'split', this moves the popup to an 'other' pane.
312          * For some popups, like search or find-file, it doesn't make sense
313          * to allow this.  For others line email-compose it does.
314          * For now, allow it on multi-line popups.
315          */
316         struct popup_info *ppi = ci->home->data;
317         struct pane *p;
318
319         if (strchr(ppi->style, 'M') == NULL)
320                 return 1;
321         p = call_ret(pane, "OtherPane", ci->focus);
322         if (p)
323                 p = call_ret(pane, "OtherPane", p);
324         if (p) {
325                 home_call(ci->home->focus, "doc:attach-view", p);
326                 pane_focus(p);
327         }
328         return 1;
329 }
330
331 DEF_CMD(popup_set_callback)
332 {
333         struct popup_info *ppi = ci->home->data;
334
335         if (ppi->done)
336                 command_put(ppi->done);
337         ppi->done = NULL;
338         if (ci->comm2)
339                 ppi->done = command_get(ci->comm2);
340         return 1;
341 }
342
343 DEF_CMD(popup_delayed_close)
344 {
345         /* nothing should be using this pane any more */
346         pane_close(ci->focus);
347         return 1;
348 }
349
350 DEF_CMD(popup_defocus)
351 {
352         struct popup_info *ppi = ci->home->data;
353
354         if (strchr(ppi->style, 't') == NULL) {
355                 /* Not interested, target might be though */
356                 home_call(ppi->target, "pane:defocus", ci->focus);
357                 return Efallthrough;
358         }
359
360         if (pane_has_focus(ci->home))
361                 /* We are still on the focal-path from display
362                  * Maybe we focussed in to a sub-popup
363                  */
364                 return Efallthrough;
365         if (call_ret(pane, "ThisPopup", ci->focus))
366                 /* New focus is a popup, so stay for now */
367                 return Efallthrough;
368
369         call_comm("event:on-idle", ci->home, &popup_delayed_close, 1);
370
371         return Efallthrough;
372 }
373
374 DEF_CMD(popup_this)
375 {
376         struct popup_info *ppi = ci->home->data;
377
378         if (strchr(ppi->style, 'a') == NULL &&
379             strcmp(ci->key, "ThisPopup") != 0)
380                 return Efallthrough;
381         return comm_call(ci->comm2, "callback:pane", ci->home,
382                          0, NULL, "Popup");
383 }
384
385 DEF_CMD(popup_other)
386 {
387         /* If a popup is asked for 'Other', return the 'This'
388          * of the target
389          */
390         struct popup_info *ppi = ci->home->data;
391
392         return home_call(ppi->target, "ThisPane", ci->focus,
393                          ci->num, ci->mark, ci->str,
394                          ci->num2, ci->mark2, ci->str2,
395                          ci->x, ci->y,
396                          ci->comm2);
397 }
398
399 DEF_CMD(popup_child_notify)
400 {
401         /* Anything that reponds to ThisPane needs to discard
402          * any children when new are registered.
403          * If none are left, we need to go ourselves.
404          */
405         struct pane *p = ci->home;
406         struct pane *c = ci->focus;
407         struct pane *old;
408
409         if (c->z != 0)
410                 return 1;
411         if (ci->num == -2)
412                 /* When a pane is moved away, not closed, we assume someone will
413                  * move something better in.
414                  */
415                 return 1;
416 restart:
417         list_for_each_entry(old, &p->children, siblings) {
418                 if (c->z != 0)
419                         /* Ignore */
420                         continue;
421                 if (old == c)
422                         /* This pane is under control... */
423                         continue;
424                 if (old->damaged & DAMAGED_CLOSED)
425                         continue;
426                 if (ci->num > 0) {
427                         /* Not the pane we just added, so close it */
428                         pane_close(old);
429                         goto restart;
430                 }
431                 if (ci->num < 0)
432                         /* Not the pane we removed, so not empty yet,
433                          * so nothing to do
434                          */
435                         return 1;
436         }
437         if (ci->num >= 0)
438                 p->focus = c;
439         else
440                 /* Completely empty, so close */
441                 pane_close(p);
442         return 1;
443 }
444
445 DEF_CMD(popup_do_close)
446 {
447         const char *str;
448
449         str = ci->str;
450         if (!str || !str[0])
451                 str = pane_attr_get(ci->focus, "default");
452         if (!str)
453                 str = "";
454         popup_finished(ci->focus, ci->home, str);
455         return 1;
456 }
457
458 DEF_CMD(popup_attach)
459 {
460         /* attach a popup.  It can be attach to the view or the display,
461          * can be in a corner, in a side, or central, and be 1 line or
462          * multi line, and can have controlled width.
463          * These are set with individual character in ci->str as follows.
464          * D  - attach to display, otherwise is on focus.
465          * TBLR - 0, 1, or 2 can be given for center, side, or corner
466          * M  - multi line, else one line
467          * 1234 - how many quarters of width to use.(default 2);
468          * r  - allow recursive popup
469          * t  - temp pane, disappears when it loses focus
470          */
471         struct pane *root, *p;
472         struct popup_info *ppi;
473         const char *style = ci->str;
474         char *in_popup;
475         struct xy xy;
476         int z = 1;
477
478         if (!style)
479                 style = "D3";
480
481         if (!strchr(style, 'r') && !strchr(style, 'P') &&
482             (in_popup = pane_attr_get(ci->focus, "Popup")) != NULL &&
483             strcmp(in_popup, "ignore") != 0)
484                 /* No recusive popups without permission */
485                 return Efallthrough;
486
487         if (strchr(style, 'D'))
488                 root = call_ret(pane, "RootPane", ci->focus);
489         else if (strchr(style, 'P'))
490                 root = call_ret(pane, "ThisPopup", ci->focus);
491         else
492                 root = call_ret(pane, "ThisPane", ci->focus);
493
494         if (!root)
495                 return Efallthrough;
496
497         alloc(ppi, pane);
498         ppi->done = NULL;
499         ppi->target = ci->focus;
500
501         /* If focus is already a popup, make this popup higher */
502         p = pane_my_child(root, ci->focus);
503         if (p && p->z > 0)
504                 z = p->z + 1;
505
506         ppi->parent_popup = NULL;
507         if (strchr(style, 'P')) {
508                 ppi->parent_popup = root;
509                 root = root->parent;
510         }
511
512         p = pane_register(root, z + 1, &popup_handle.c, ppi);
513         if (!p)
514                 return Efail;
515         ppi->style = strdup(style);
516         popup_set_style(p);
517         xy = pane_mapxy(ci->focus, root, ci->x, ci->y, True);
518         popup_resize(p, style, xy.x, xy.y);
519         attr_set_str(&p->attrs, "render-wrap", "no");
520
521         pane_add_notify(p, ppi->target, "Notify:Close");
522         if (ppi->parent_popup)
523                 pane_add_notify(p, ppi->parent_popup, "Notify:resize");
524
525         pane_focus(p);
526
527         if (ci->str2) {
528                 struct pane *doc =
529                         call_ret(pane, "doc:from-text", p, 0, NULL,
530                                  "*popup*", 0, NULL, ci->str2);
531                 if (doc &&
532                     (p = home_call_ret(pane, doc, "doc:attach-view",
533                                        p, -1)) != NULL) {
534                         call("doc:file", p, 1);
535                         call("doc:set:autoclose", p, 1);
536                 }
537         }
538
539         if (!p)
540                 return Efail;
541
542         return comm_call(ci->comm2, "callback:attach", p);
543 }
544
545 void edlib_init(struct pane *ed safe)
546 {
547         call_comm("global-set-command", ed, &popup_attach,
548                   0, NULL, "PopupTile");
549
550         popup_map = key_alloc();
551
552         key_add(popup_map, "Close", &popup_close);
553         key_add(popup_map, "Free", &popup_free);
554         key_add(popup_map, "Notify:Close", &popup_notify_close);
555         key_add(popup_map, "Abort", &popup_abort);
556         key_add(popup_map, "popup:style", &popup_style);
557         key_add(popup_map, "Refresh:size", &popup_refresh_size);
558         key_add(popup_map, "view:changed", &popup_refresh_size);
559         key_add(popup_map, "Notify:resize", &popup_notify_refresh_size);
560         key_add(popup_map, "popup:get-target", &popup_get_target);
561         key_add(popup_map, "popup:close", &popup_do_close);
562         key_add(popup_map, "popup:set-callback", &popup_set_callback);
563         key_add(popup_map, "Child-Notify", &popup_child_notify);
564         key_add(popup_map, "ThisPane", &popup_this);
565         key_add(popup_map, "OtherPane", &popup_other);
566         key_add(popup_map, "ThisPopup", &popup_this);
567
568         key_add(popup_map, "Window:bury", &popup_do_close);
569         key_add(popup_map, "Window:close", &popup_abort);
570         key_add(popup_map, "Window:split-x", &popup_split);
571         key_add(popup_map, "Window:split-y", &popup_split);
572         key_add(popup_map, "Window:x+", &popup_ignore);
573         key_add(popup_map, "Window:x-", &popup_ignore);
574         key_add(popup_map, "Window:y+", &popup_ignore);
575         key_add(popup_map, "Window:y-", &popup_ignore);
576         key_add(popup_map, "Window:close-others", &popup_close_others);
577         key_add(popup_map, "pane:defocus", &popup_defocus);
578 }