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'))
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')) {
132 pane_resize(p, x, y, w, h);
135 DEF_CMD_CLOSED(popup_close)
137 struct popup_info *ppi = ci->home->data;
140 /* Pane had focus, so give to target */
141 pane_take_focus(ppi->target);
142 command_put(ppi->done);
149 DEF_CMD(popup_notify_close)
151 struct popup_info *ppi = ci->home->data;
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);
161 static void popup_finished(struct pane *focus safe, struct pane *home safe,
164 struct popup_info *ppi = home->data;
165 struct pane *target = ppi->target;
168 struct command *done = ppi->done;
170 pane_take_focus(target);
171 key = pane_attr_get(focus, "done-key");
174 aux = pane_attr_get(focus, "popup-aux");
178 /* home is now closed, so ppi cannot be touched */
180 comm_call(done, key, target, 1, NULL, result, 0, NULL, aux);
182 call(key, target, 1, NULL, result, 0, NULL, aux);
187 /* A NULL 'result' signals the abort */
188 popup_finished(ci->focus, ci->home, NULL);
192 static bool popup_set_style(struct pane *p safe)
194 struct popup_info *ppi = p->data;
195 char *orig_border = attr_find(p->attrs, "borders");
196 bool changed = False;
198 if (ppi->parent_popup) {
199 char *border = pane_attr_get(ppi->parent_popup, "borders");
200 attr_set_str(&p->attrs, "borders", border);
205 for (i = 0, j = 0; i < 4; i++) {
206 if (strchr(ppi->style, "TLBR"[i]) == NULL)
207 border[j++] = "TLBR"[i];
209 if (strchr(ppi->style, 's'))
210 /* Force a status line */
213 if (!orig_border || strcmp(orig_border, border) != 0) {
214 attr_set_str(&p->attrs, "borders", border);
219 if (strchr(ppi->style, 'a'))
220 /* allow recursion */
221 attr_set_str(&p->attrs, "Popup", "ignore");
223 attr_set_str(&p->attrs, "Popup", "true");
229 struct popup_info *ppi = ci->home->data;
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);
242 DEF_CMD(popup_notify_refresh_size)
244 pane_damaged(ci->home, DAMAGED_SIZE);
248 DEF_CMD(popup_refresh_size)
250 struct popup_info *ppi = ci->home->data;
251 char *prompt, *dflt, *prefix;
252 struct pane *focus = pane_focus(ci->home);
254 prefix = pane_attr_get(focus, "prefix");
255 prompt = pane_attr_get(focus, "prompt");
256 if (!prefix && prompt) {
258 dflt = pane_attr_get(focus, "default");
262 asprintf(&t, "%s(%s): ", prompt, dflt);
264 asprintf(&t, "%s: ", prompt);
265 attr_set_str(&focus->attrs, "prefix", t);
269 popup_set_style(ci->home);
270 popup_resize(ci->home, ppi->style, ci->home->x, ci->home->y);
274 DEF_CMD(popup_get_target)
276 struct popup_info *ppi = ci->home->data;
277 return comm_call(ci->comm2, "callback:get-target", ppi->target);
280 DEF_CMD(popup_ignore)
285 DEF_CMD(popup_close_others)
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.
291 struct popup_info *ppi = ci->home->data;
294 if (strchr(ppi->style, 'M') == NULL)
296 p = call_ret(pane, "OtherPane", ci->focus);
298 home_call(ci->home->focus, "doc:attach-view", p);
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.
311 struct popup_info *ppi = ci->home->data;
314 if (strchr(ppi->style, 'M') == NULL)
316 p = call_ret(pane, "OtherPane", ci->focus);
318 p = call_ret(pane, "OtherPane", p);
320 home_call(ci->home->focus, "doc:attach-view", p);
326 DEF_CMD(popup_set_callback)
328 struct popup_info *ppi = ci->home->data;
331 command_put(ppi->done);
334 ppi->done = command_get(ci->comm2);
338 DEF_CMD(popup_delayed_close)
340 /* nothing should be using this pane any more */
341 pane_close(ci->focus);
345 DEF_CMD(popup_defocus)
347 struct popup_info *ppi = ci->home->data;
349 if (strchr(ppi->style, 't') == NULL) {
350 /* Not interested, target might be though */
351 home_call(ppi->target, "pane:defocus", ci->focus);
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
360 if (call_ret(pane, "ThisPopup", ci->focus))
361 /* New focus is a popup, so stay for now */
364 call_comm("event:on-idle", ci->home, &popup_delayed_close, 1);
371 struct popup_info *ppi = ci->home->data;
373 if (strchr(ppi->style, 'a') == NULL &&
374 strcmp(ci->key, "ThisPopup") != 0)
376 return comm_call(ci->comm2, "callback:pane", ci->home,
382 /* If a popup is asked for 'Other', return the 'This'
385 struct popup_info *ppi = ci->home->data;
387 return home_call(ppi->target, "ThisPane", ci->focus,
388 ci->num, ci->mark, ci->str,
389 ci->num2, ci->mark2, ci->str2,
394 DEF_CMD(popup_child_notify)
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.
400 struct pane *p = ci->home;
401 struct pane *c = ci->focus;
407 /* When a pane is moved away, not closed, we assume someone will
408 * move something better in.
412 list_for_each_entry(old, &p->children, siblings) {
417 /* This pane is under control... */
419 if (old->damaged & DAMAGED_CLOSED)
422 /* Not the pane we just added, so close it */
427 /* Not the pane we removed, so not empty yet,
435 /* Completely empty, so close */
440 DEF_CMD(popup_do_close)
446 str = pane_attr_get(ci->focus, "default");
449 popup_finished(ci->focus, ci->home, str);
453 DEF_CMD(popup_attach)
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
466 struct pane *root, *p, *parent;
467 struct popup_info *ppi;
468 const char *style = ci->str;
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 */
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);
487 root = call_ret(pane, "ThisPane", ci->focus);
492 /* If focus is already a popup, make this popup higher */
493 p = pane_my_child(root, ci->focus);
498 if (strchr(style, 'P')) {
503 p = pane_register(root, z + 1, &popup_handle.c);
508 ppi->target = ci->focus;
510 ppi->parent_popup = parent;
512 ppi->style = strdup(style);
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");
518 pane_add_notify(p, ppi->target, "Notify:Close");
519 if (ppi->parent_popup)
520 pane_add_notify(p, ppi->parent_popup, "Notify:resize");
526 call_ret(pane, "doc:from-text", p, 0, NULL,
527 "*popup*", 0, NULL, ci->str2);
529 (p = home_call_ret(pane, doc, "doc:attach-view",
531 call("doc:file", p, 1);
532 call("doc:set:autoclose", p, 1);
539 return comm_call(ci->comm2, "callback:attach", p);
542 void edlib_init(struct pane *ed safe)
544 call_comm("global-set-command", ed, &popup_attach,
545 0, NULL, "PopupTile");
547 popup_map = key_alloc();
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);
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);