2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
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.
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
16 * The target pane must not disappear while the popup is active.
17 * I need to find a way to control that.
19 * A popup is created by "PopupTile"
20 * A prefix to be displayed can be added by setting "prefix" on
22 * A default value can be given with attr "default" which is displated
24 * The event sent when the popup is closed can be set by setting
25 * attribute "done-key"
26 * otherwise "PopupDone" is used.
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
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
46 #define _GNU_SOURCE /* for asprintf */
52 #define PANE_DATA_TYPE struct popup_info
56 struct pane *target safe;
57 struct pane *parent_popup;
61 #include "core-pane.h"
63 static struct map *popup_map;
64 DEF_LOOKUP_CMD(popup_handle, popup_map);
66 static int line_height(struct pane *p safe, int scale)
68 struct call_return cr =
69 call_ret(all, "Draw:text-size", p, -1, NULL, "x",
74 static void popup_resize(struct pane *p safe, const char *style safe,
77 struct popup_info *ppi = p->data;
79 int lh, bh = 0, bw = 0;
81 struct xy xyscale = pane_scale(p);
83 /* First find the size */
84 lh = line_height(p, xyscale.x);
85 bhs = pane_attr_get(pane_focus(p), "border-height");
89 bh = line_height(p, 0); /* border height */
90 bws = pane_attr_get(pane_focus(p), "border-width");
95 if (strchr(style, 'M')) {
96 h = p->parent->h/2 + bh;
97 attr_set_str(&p->attrs, "render-one-line", "no");
100 attr_set_str(&p->attrs, "render-one-line", "yes");
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;
108 w = p->parent->w - 2*bw;
109 if (strchr(style, '1'))
111 else if (strchr(style, '3'))
113 else if (strchr(style, '4')) {
115 h = p->parent->h - 2 * bh;
119 x = p->parent->w/2 - w/2;
120 y = p->parent->h/2 - h/2;
121 if (strchr(style, 'T')) { y = 0; h -= bh; }
122 if (strchr(style, 'B')) { h -= bh; y = p->parent->h - h; }
123 if (strchr(style, 'L')) x = 0;
124 if (strchr(style, 'R')) x = p->parent->w - w;
125 if (strchr(style, 'x')) {
133 pane_resize(p, x, y, w, h);
136 DEF_CMD_CLOSED(popup_close)
138 struct popup_info *ppi = ci->home->data;
141 /* Pane had focus, so give to target */
142 pane_take_focus(ppi->target);
143 command_put(ppi->done);
150 DEF_CMD(popup_notify_close)
152 struct popup_info *ppi = ci->home->data;
154 if (ci->focus == ppi->target) {
155 /* target is closing, so we close too */
156 ppi->target = safe_cast NULL;
157 pane_close(ci->home);
162 static void popup_finished(struct pane *focus safe, struct pane *home safe,
165 struct popup_info *ppi = home->data;
166 struct pane *target = ppi->target;
169 struct command *done = ppi->done;
171 pane_take_focus(target);
172 key = pane_attr_get(focus, "done-key");
175 aux = pane_attr_get(focus, "popup-aux");
179 /* home is now closed, so ppi cannot be touched */
181 comm_call(done, key, target, 1, NULL, result, 0, NULL, aux);
183 call(key, target, 1, NULL, result, 0, NULL, aux);
188 /* A NULL 'result' signals the abort */
189 popup_finished(ci->focus, ci->home, NULL);
193 static bool popup_set_style(struct pane *p safe)
195 struct popup_info *ppi = p->data;
196 char *orig_border = attr_find(p->attrs, "borders");
197 bool changed = False;
199 if (ppi->parent_popup) {
200 char *border = pane_attr_get(ppi->parent_popup, "borders");
201 attr_set_str(&p->attrs, "borders", border);
206 for (i = 0, j = 0; i < 4; i++) {
207 if (strchr(ppi->style, "TLBR"[i]) == NULL)
208 border[j++] = "TLBR"[i];
210 if (strchr(ppi->style, 's'))
211 /* Force a status line */
214 if (!orig_border || strcmp(orig_border, border) != 0) {
215 attr_set_str(&p->attrs, "borders", border);
220 if (strchr(ppi->style, 'a'))
221 /* allow recursion */
222 attr_set_str(&p->attrs, "Popup", "ignore");
224 attr_set_str(&p->attrs, "Popup", "true");
230 struct popup_info *ppi = ci->home->data;
236 ppi->style = strdup(ci->str);
237 if (popup_set_style(ci->home))
238 call("view:changed", ci->focus);
239 popup_resize(ci->home, ppi->style, ci->home->x, ci->home->y);
243 DEF_CMD(popup_notify_refresh_size)
245 pane_damaged(ci->home, DAMAGED_SIZE);
249 DEF_CMD(popup_refresh_size)
251 struct popup_info *ppi = ci->home->data;
252 char *prompt, *dflt, *prefix;
253 struct pane *focus = pane_focus(ci->home);
255 prefix = pane_attr_get(focus, "prefix");
256 prompt = pane_attr_get(focus, "prompt");
257 if (!prefix && prompt) {
259 dflt = pane_attr_get(focus, "default");
263 asprintf(&t, "%s(%s): ", prompt, dflt);
265 asprintf(&t, "%s: ", prompt);
266 attr_set_str(&focus->attrs, "prefix", t);
270 popup_set_style(ci->home);
271 popup_resize(ci->home, ppi->style, ci->home->x, ci->home->y);
275 DEF_CMD(popup_get_target)
277 struct popup_info *ppi = ci->home->data;
278 return comm_call(ci->comm2, "callback:get-target", ppi->target);
281 DEF_CMD(popup_ignore)
286 DEF_CMD(popup_close_others)
288 /* For some popups, like search or find-file, it doesn't make sense
289 * to maximize the popup. For others line email-compose it does.
290 * For now, allow it on multi-line popups.
292 struct popup_info *ppi = ci->home->data;
295 if (strchr(ppi->style, 'M') == NULL)
297 p = call_ret(pane, "OtherPane", ci->focus);
299 home_call(ci->home->focus, "doc:attach-view", p);
307 /* Rather than 'split', this moves the popup to an 'other' pane.
308 * For some popups, like search or find-file, it doesn't make sense
309 * to allow this. For others line email-compose it does.
310 * For now, allow it on multi-line popups.
312 struct popup_info *ppi = ci->home->data;
315 if (strchr(ppi->style, 'M') == NULL)
317 p = call_ret(pane, "OtherPane", ci->focus);
319 p = call_ret(pane, "OtherPane", p);
321 home_call(ci->home->focus, "doc:attach-view", p);
327 DEF_CMD(popup_set_callback)
329 struct popup_info *ppi = ci->home->data;
332 command_put(ppi->done);
335 ppi->done = command_get(ci->comm2);
339 DEF_CMD(popup_delayed_close)
341 /* nothing should be using this pane any more */
342 pane_close(ci->focus);
346 DEF_CMD(popup_defocus)
348 struct popup_info *ppi = ci->home->data;
350 if (strchr(ppi->style, 't') == NULL) {
351 /* Not interested, target might be though */
352 home_call(ppi->target, "pane:defocus", ci->focus);
356 if (pane_has_focus(ci->home))
357 /* We are still on the focal-path from display
358 * Maybe we focussed in to a sub-popup
361 if (call_ret(pane, "ThisPopup", ci->focus))
362 /* New focus is a popup, so stay for now */
365 call_comm("event:on-idle", ci->home, &popup_delayed_close, 1);
372 struct popup_info *ppi = ci->home->data;
374 if (strchr(ppi->style, 'a') == NULL &&
375 strcmp(ci->key, "ThisPopup") != 0)
377 return comm_call(ci->comm2, "callback:pane", ci->home,
383 /* If a popup is asked for 'Other', return the 'This'
386 struct popup_info *ppi = ci->home->data;
388 return home_call(ppi->target, "ThisPane", ci->focus,
389 ci->num, ci->mark, ci->str,
390 ci->num2, ci->mark2, ci->str2,
395 DEF_CMD(popup_child_notify)
397 /* Anything that reponds to ThisPane needs to discard
398 * any children when new are registered.
399 * If none are left, we need to go ourselves.
401 struct pane *p = ci->home;
402 struct pane *c = ci->focus;
408 /* When a pane is moved away, not closed, we assume someone will
409 * move something better in.
413 list_for_each_entry(old, &p->children, siblings) {
418 /* This pane is under control... */
420 if (old->damaged & DAMAGED_CLOSED)
423 /* Not the pane we just added, so close it */
428 /* Not the pane we removed, so not empty yet,
436 /* Completely empty, so close */
441 DEF_CMD(popup_do_close)
447 str = pane_attr_get(ci->focus, "default");
450 popup_finished(ci->focus, ci->home, str);
454 DEF_CMD(popup_attach)
456 /* attach a popup. It can be attach to the view or the display,
457 * can be in a corner, in a side, or central, and be 1 line or
458 * multi line, and can have controlled width.
459 * These are set with individual character in ci->str as follows.
460 * D - attach to display, otherwise is on focus.
461 * TBLR - 0, 1, or 2 can be given for center, side, or corner
462 * M - multi line, else one line
463 * 1234 - how many quarters of width to use.(default 2);
464 * r - allow recursive popup
465 * t - temp pane, disappears when it loses focus
467 struct pane *root, *p, *parent;
468 struct popup_info *ppi;
469 const char *style = ci->str;
477 if (!strchr(style, 'r') && !strchr(style, 'P') &&
478 (in_popup = pane_attr_get(ci->focus, "Popup")) != NULL &&
479 strcmp(in_popup, "ignore") != 0)
480 /* No recusive popups without permission */
483 if (strchr(style, 'D'))
484 root = call_ret(pane, "RootPane", ci->focus);
485 else if (strchr(style, 'P'))
486 root = call_ret(pane, "ThisPopup", ci->focus);
488 root = call_ret(pane, "ThisPane", ci->focus);
493 /* If focus is already a popup, make this popup higher */
494 p = pane_my_child(root, ci->focus);
499 if (strchr(style, 'P')) {
504 p = pane_register(root, z + 1, &popup_handle.c);
509 ppi->target = ci->focus;
511 ppi->parent_popup = parent;
513 ppi->style = strdup(style);
515 xy = pane_mapxy(ci->focus, root, ci->x, ci->y, True);
516 popup_resize(p, style, xy.x, xy.y);
517 attr_set_str(&p->attrs, "render-wrap", "no");
519 pane_add_notify(p, ppi->target, "Notify:Close");
520 if (ppi->parent_popup)
521 pane_add_notify(p, ppi->parent_popup, "Notify:resize");
527 call_ret(pane, "doc:from-text", p, 0, NULL,
528 "*popup*", 0, NULL, ci->str2);
530 (p = home_call_ret(pane, doc, "doc:attach-view",
532 call("doc:file", p, 1);
533 call("doc:set:autoclose", p, 1);
540 return comm_call(ci->comm2, "callback:attach", p);
543 void edlib_init(struct pane *ed safe)
545 call_comm("global-set-command", ed, &popup_attach,
546 0, NULL, "PopupTile");
548 popup_map = key_alloc();
550 key_add(popup_map, "Close", &popup_close);
551 key_add(popup_map, "Notify:Close", &popup_notify_close);
552 key_add(popup_map, "Abort", &popup_abort);
553 key_add(popup_map, "popup:style", &popup_style);
554 key_add(popup_map, "Refresh:size", &popup_refresh_size);
555 key_add(popup_map, "view:changed", &popup_refresh_size);
556 key_add(popup_map, "Notify:resize", &popup_notify_refresh_size);
557 key_add(popup_map, "popup:get-target", &popup_get_target);
558 key_add(popup_map, "popup:close", &popup_do_close);
559 key_add(popup_map, "popup:set-callback", &popup_set_callback);
560 key_add(popup_map, "Child-Notify", &popup_child_notify);
561 key_add(popup_map, "ThisPane", &popup_this);
562 key_add(popup_map, "OtherPane", &popup_other);
563 key_add(popup_map, "ThisPopup", &popup_this);
565 key_add(popup_map, "Window:bury", &popup_do_close);
566 key_add(popup_map, "Window:close", &popup_abort);
567 key_add(popup_map, "Window:split-x", &popup_split);
568 key_add(popup_map, "Window:split-y", &popup_split);
569 key_add(popup_map, "Window:x+", &popup_ignore);
570 key_add(popup_map, "Window:x-", &popup_ignore);
571 key_add(popup_map, "Window:y+", &popup_ignore);
572 key_add(popup_map, "Window:y-", &popup_ignore);
573 key_add(popup_map, "Window:close-others", &popup_close_others);
574 key_add(popup_map, "pane:defocus", &popup_defocus);