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