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 */
54 static struct map *popup_map;
55 DEF_LOOKUP_CMD(popup_handle, popup_map);
58 struct pane *target safe;
59 struct pane *parent_popup;
64 static int line_height(struct pane *p safe, int scale)
66 struct call_return cr =
67 call_ret(all, "Draw:text-size", p, -1, NULL, "x",
72 static void popup_resize(struct pane *p safe, const char *style safe,
75 struct popup_info *ppi = p->data;
77 int lh, bh = 0, bw = 0;
79 struct xy xyscale = pane_scale(p);
81 /* First find the size */
82 lh = line_height(p, xyscale.x);
83 bhs = pane_attr_get(pane_leaf(p), "border-height");
87 bh = line_height(p, 0); /* border height */
88 bws = pane_attr_get(pane_leaf(p), "border-width");
93 if (strchr(style, 'M')) {
94 h = p->parent->h/2 + bh;
95 attr_set_str(&p->attrs, "render-one-line", "no");
98 attr_set_str(&p->attrs, "render-one-line", "yes");
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;
106 w = p->parent->w - 2*bw;
107 if (strchr(style, '1'))
109 else if (strchr(style, '3'))
111 else if (strchr(style, '4'))
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')) {
130 pane_resize(p, x, y, w, h);
135 struct popup_info *ppi = ci->home->data;
138 /* Pane had focus, so give to target */
139 pane_focus(ppi->target);
140 command_put(ppi->done);
147 struct popup_info *ppi = ci->home->data;
154 DEF_CMD(popup_notify_close)
156 struct popup_info *ppi = ci->home->data;
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);
166 static void popup_finished(struct pane *focus safe, struct pane *home safe,
169 struct popup_info *ppi = home->data;
170 struct pane *target = ppi->target;
173 struct command *done = ppi->done;
176 key = pane_attr_get(focus, "done-key");
179 aux = pane_attr_get(focus, "popup-aux");
183 /* home is now closed, so ppi cannot be touched */
185 comm_call(done, key, target, 1, NULL, result, 0, NULL, aux);
187 call(key, target, 1, NULL, result, 0, NULL, aux);
192 /* A NULL 'result' signals the abort */
193 popup_finished(ci->focus, ci->home, NULL);
197 static bool popup_set_style(struct pane *p safe)
199 struct popup_info *ppi = p->data;
200 char *orig_border = attr_find(p->attrs, "borders");
201 bool changed = False;
203 if (ppi->parent_popup) {
204 char *border = pane_attr_get(ppi->parent_popup, "borders");
205 attr_set_str(&p->attrs, "borders", border);
210 for (i = 0, j = 0; i < 4; i++) {
211 if (strchr(ppi->style, "TLBR"[i]) == NULL)
212 border[j++] = "TLBR"[i];
214 if (strchr(ppi->style, 's'))
215 /* Force a status line */
218 if (!orig_border || strcmp(orig_border, border) != 0) {
219 attr_set_str(&p->attrs, "borders", border);
224 if (strchr(ppi->style, 'a'))
225 /* allow recursion */
226 attr_set_str(&p->attrs, "Popup", "ignore");
228 attr_set_str(&p->attrs, "Popup", "true");
234 struct popup_info *ppi = ci->home->data;
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);
247 DEF_CMD(popup_notify_refresh_size)
249 pane_damaged(ci->home, DAMAGED_SIZE);
253 DEF_CMD(popup_refresh_size)
255 struct popup_info *ppi = ci->home->data;
256 char *prompt, *dflt, *prefix;
257 struct pane *focus = pane_leaf(ci->home);
259 prefix = pane_attr_get(focus, "prefix");
260 prompt = pane_attr_get(focus, "prompt");
261 if (!prefix && prompt) {
263 dflt = pane_attr_get(focus, "default");
267 asprintf(&t, "%s(%s): ", prompt, dflt);
269 asprintf(&t, "%s: ", prompt);
270 attr_set_str(&focus->attrs, "prefix", t);
274 popup_set_style(ci->home);
275 popup_resize(ci->home, ppi->style, ci->home->x, ci->home->y);
279 DEF_CMD(popup_get_target)
281 struct popup_info *ppi = ci->home->data;
282 return comm_call(ci->comm2, "callback:get-target", ppi->target);
285 DEF_CMD(popup_ignore)
290 DEF_CMD(popup_close_others)
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.
296 struct popup_info *ppi = ci->home->data;
299 if (strchr(ppi->style, 'M') == NULL)
301 p = call_ret(pane, "OtherPane", ci->focus);
303 home_call(ci->home->focus, "doc:attach-view", p);
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.
316 struct popup_info *ppi = ci->home->data;
319 if (strchr(ppi->style, 'M') == NULL)
321 p = call_ret(pane, "OtherPane", ci->focus);
323 p = call_ret(pane, "OtherPane", p);
325 home_call(ci->home->focus, "doc:attach-view", p);
331 DEF_CMD(popup_set_callback)
333 struct popup_info *ppi = ci->home->data;
336 command_put(ppi->done);
339 ppi->done = command_get(ci->comm2);
343 DEF_CMD(popup_delayed_close)
345 /* nothing should be using this pane any more */
346 pane_close(ci->focus);
350 DEF_CMD(popup_defocus)
352 struct popup_info *ppi = ci->home->data;
354 if (strchr(ppi->style, 't') == NULL) {
355 /* Not interested, target might be though */
356 home_call(ppi->target, "pane:defocus", ci->focus);
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
365 if (call_ret(pane, "ThisPopup", ci->focus))
366 /* New focus is a popup, so stay for now */
369 call_comm("event:on-idle", ci->home, &popup_delayed_close, 1);
376 struct popup_info *ppi = ci->home->data;
378 if (strchr(ppi->style, 'a') == NULL &&
379 strcmp(ci->key, "ThisPopup") != 0)
381 return comm_call(ci->comm2, "callback:pane", ci->home,
387 /* If a popup is asked for 'Other', return the 'This'
390 struct popup_info *ppi = ci->home->data;
392 return home_call(ppi->target, "ThisPane", ci->focus,
393 ci->num, ci->mark, ci->str,
394 ci->num2, ci->mark2, ci->str2,
399 DEF_CMD(popup_child_notify)
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.
405 struct pane *p = ci->home;
406 struct pane *c = ci->focus;
412 /* When a pane is moved away, not closed, we assume someone will
413 * move something better in.
417 list_for_each_entry(old, &p->children, siblings) {
422 /* This pane is under control... */
424 if (old->damaged & DAMAGED_CLOSED)
427 /* Not the pane we just added, so close it */
432 /* Not the pane we removed, so not empty yet,
440 /* Completely empty, so close */
445 DEF_CMD(popup_do_close)
451 str = pane_attr_get(ci->focus, "default");
454 popup_finished(ci->focus, ci->home, str);
458 DEF_CMD(popup_attach)
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
471 struct pane *root, *p;
472 struct popup_info *ppi;
473 const char *style = ci->str;
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 */
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);
492 root = call_ret(pane, "ThisPane", ci->focus);
499 ppi->target = ci->focus;
501 /* If focus is already a popup, make this popup higher */
502 p = pane_my_child(root, ci->focus);
506 ppi->parent_popup = NULL;
507 if (strchr(style, 'P')) {
508 ppi->parent_popup = root;
512 p = pane_register(root, z + 1, &popup_handle.c, ppi);
515 ppi->style = strdup(style);
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");
521 pane_add_notify(p, ppi->target, "Notify:Close");
522 if (ppi->parent_popup)
523 pane_add_notify(p, ppi->parent_popup, "Notify:resize");
529 call_ret(pane, "doc:from-text", p, 0, NULL,
530 "*popup*", 0, NULL, ci->str2);
532 (p = home_call_ret(pane, doc, "doc:attach-view",
534 call("doc:file", p, 1);
535 call("doc:set:autoclose", p, 1);
542 return comm_call(ci->comm2, "callback:attach", p);
545 void edlib_init(struct pane *ed safe)
547 call_comm("global-set-command", ed, &popup_attach,
548 0, NULL, "PopupTile");
550 popup_map = key_alloc();
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);
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);