]> git.neil.brown.name Git - edlib.git/blob - core-window.c
TODO: clean out done items.
[edlib.git] / core-window.c
1 /*
2  * Copyright Neil Brown ©2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Core per-window functionality.
6  *
7  * Provide a pane that is instantiated between the root and any window
8  * stack, to provide common functionality.  These includes:
9  *
10  * - setting per-window attributes
11  * - registering and forwarding per-window notifications
12  * - Being an intermediary for per-window selections.
13  *
14  * ==============================================================
15  * Allow any pane to "claim ownership" of "the selection", or to
16  * "commit" the selection.  A pane can also "discard" the selection,
17  * but that only works if the pane owns it.
18  *
19  * This can be used for mouse-based copy/paste and interaction with the
20  * X11 "PRIMARY" clipboard.
21  * When a selection is made in any pane it claims "the selection".
22  * When a mouse-based paste request is made, the receiving pane can ask for
23  * the selection to be "commited", and then access the most recent copy-buffer.
24  * The owner of a selection will, if the selection is still valid, call
25  * copy:save to save the selected content.
26  * When a "paste" request is made where the location is based on the "point"
27  * (current cursor) it is unlikely that a selection in the same pane should be
28  * used - if there is one it is more likely to be intended to receive the paste.
29  * So the target pane can first "discard" the selection, then "commit", then call
30  * "copy:get".  If the selection is in this pane, the "discard" will succeed,
31  * the "commit" will be a no-op, and the top copy buf will be used.
32  * If the selection is in another pane (or another app via X11), the "discard"
33  * will fail (wrong owner), the "commit" will succeed and copy the selection,
34  * and the "copy:get" will get it.
35  *
36  * Operations are "selection:claim", "selection:commit" and "selection:discard".
37  * When the selection is claimed, the old owner gets called (not notified)
38  * "Notify:selection:claimed", and when a commit request is made,
39  * "Notify:selection:commit" is sent.
40  *
41  * A client can declare itself to be a fall-back handler by calling
42  * select:claim with num==1.  Then if any other client discards its selection,
43  * the ownership reverse to the fallback.  The fallback typically provides
44  * access to some selection external to edlib, such as the x11 selections.
45  */
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <time.h>
50 #include <stdio.h>
51
52 #define PANE_DATA_TYPE struct window_data
53 #include "core.h"
54 #include "internal.h"
55
56 struct window_data {
57         struct pane     *sel_owner;
58         int             sel_committed;
59         struct pane     *sel_owner_fallback;
60 };
61 #include "core-pane.h"
62
63 DEF_CMD(request_notify)
64 {
65         pane_add_notify(ci->focus, ci->home, ksuffix(ci, "Window:request:"));
66         return 1;
67 }
68
69 DEF_CMD(send_notify)
70 {
71         /* Window:notify:... */
72         return home_pane_notify(ci->home, ksuffix(ci, "Window:notify:"),
73                                 ci->focus,
74                                 ci->num, ci->mark, ci->str,
75                                 ci->num2, ci->mark2, ci->str2, ci->comm2);
76 }
77
78 DEF_CMD(window_set)
79 {
80         const char *val = ksuffix(ci, "Window:set:");
81
82         if (!*val)
83                 val = ci->str2;
84         if (!val)
85                 return Enoarg;
86
87         attr_set_str(&ci->home->attrs, val, ci->str);
88
89         return 1;
90 }
91
92 DEF_CMD(selection_claim)
93 {
94         struct window_data *wd = ci->home->data;
95
96         if (wd->sel_owner && wd->sel_owner != ci->focus) {
97                 call("Notify:selection:claimed", wd->sel_owner);
98                 //pane_drop_notifiers(ci->home, "Notify:Close", wd->sel_owner);
99         }
100         wd->sel_owner = ci->focus;
101         if (ci->num == 1)
102                 wd->sel_owner_fallback = ci->focus;
103         wd->sel_committed = 0;
104         pane_add_notify(ci->home, ci->focus, "Notify:Close");
105         return 1;
106 }
107
108 DEF_CMD(selection_commit)
109 {
110         struct window_data *wd = ci->home->data;
111
112         if (wd->sel_owner && !wd->sel_committed) {
113                 if (call("Notify:selection:commit", wd->sel_owner) != 2)
114                         wd->sel_committed = 1;
115         }
116         return 1;
117 }
118
119 DEF_CMD(selection_discard)
120 {
121         struct window_data *wd = ci->home->data;
122         struct pane *op, *fp;
123
124         if (!wd->sel_owner)
125                 return Efalse;
126         if (wd->sel_owner_fallback == ci->focus)
127                 wd->sel_owner_fallback = NULL;
128         /* Don't require exactly same pane for sel_owner,
129          * but ensure they have the same focus.
130          */
131         op = pane_focus(wd->sel_owner);
132         fp = pane_focus(ci->focus);
133         if (fp != op)
134                 return Efalse;
135
136         wd->sel_owner = wd->sel_owner_fallback;
137         wd->sel_committed = 0;
138         return 1;
139 }
140
141 DEF_CMD(scale_image)
142 {
143         /* This is a helper for Draw:image which interprets str2
144          * with other values and calls comm2 with:
145          * "width" returns image width
146          * "height" returns image height
147          * "scale"  num=new width, num2=new height
148          * "crop" x,y is top-left num,num2 - width,height
149          *          These numbers apply after scaling.
150          * "draw"  num,num2 = offset
151          * "cursor" x,y=pos, num,num2=size
152          *
153          * Inputs are:
154          * 'str2' container 'mode' information.
155          *     By default the image is placed centrally in the pane
156          *     and scaled to use either fully height or fully width.
157          *     Various letters modify this:
158          *     'S' - stretch to use full height *and* full width
159          *     'L' - place on left if full width isn't used
160          *     'R' - place on right if full width isn't used
161          *     'T' - place at top if full height isn't used
162          *     'B' - place at bottom if full height isn't used.
163          *
164          *    Also a suffix ":NNxNN" will be parse and the two numbers used
165          *    to give number of rows and cols to overlay on the image for
166          *    the purpose of cursor positioning.  If these are present and
167          *    p->cx,cy are not negative, draw a cursor at p->cx,cy highlighting
168          *    the relevant cell.
169          *
170          * num,num2, if both positive, override the automatic scaling.
171          *    The image is scaled to this many pixels.
172          * x,y is top-left pixel in the scaled image to start display at.
173          *    Negative values allow a margin between pane edge and this image.
174          */
175         struct pane *p = ci->focus;
176         const char *mode = ci->str2 ?: "";
177         bool stretch = strchr(mode, 'S');
178         int w, h;
179         int x = 0, y = 0;
180         int pw, ph;
181         int xo = 0, yo = 0;
182         int cix, ciy;
183         const char *pxl;
184         short px, py;
185
186         if (!ci->comm2)
187                 return Enoarg;
188
189         pxl = pane_attr_get(p, "Display:pixels");
190         if (sscanf(pxl ?: "1x1", "%hdx%hx", &px, &py) != 2)
191                 px = py = 1;
192
193         w = p->w * px;
194         h = p->h * py;
195         if (ci->num > 0 && ci->num2 > 0) {
196                 w = ci->num;
197                 h = ci->num2;
198         } else if (ci->num > 0) {
199                 int iw = comm_call(ci->comm2, "width", p);
200                 int ih = comm_call(ci->comm2, "height", p);
201
202                 if (iw <= 0 || ih <= 0)
203                         return Efail;
204
205                 w = iw * ci->num / 1024;
206                 h = ih * ci->num / 1024;
207         } else if (!stretch) {
208                 int iw = comm_call(ci->comm2, "width", p);
209                 int ih = comm_call(ci->comm2, "height", p);
210
211                 if (iw <= 0 || ih <= 0)
212                         return Efail;
213
214                 if (iw * h > ih * w) {
215                         /* Image is wider than space, use less height */
216                         ih = ih * w / iw;
217                         if (strchr(mode, 'B'))
218                                 /* bottom */
219                                 y = h - ih;
220                         else if (!strchr(mode, 'T'))
221                                 /* center */
222                                 y = (h - ih) / 2;
223                         /* Round up to pixels-per-cell */
224                         h = ((ih + py - 1) / py) * py;
225                 } else {
226                         /* image is too tall, use less width */
227                         iw = iw * h / ih;
228                         if (strchr(mode, 'R'))
229                                 /* right */
230                                 x = w - iw;
231                         else if (!strchr(mode, 'L'))
232                                 x = (w - iw) / 2;
233                         w = ((iw + px - 1) / px) * px;
234                 }
235         }
236
237         comm_call(ci->comm2, "scale", p, w, NULL, NULL, h);
238         pw = p->w * px;
239         ph = p->h * py;
240         cix = ci->x;
241         ciy = ci->y;
242         if (cix < 0) {
243                 xo -= cix;
244                 pw += cix;
245                 cix = 0;
246         }
247         if (ciy < 0) {
248                 yo -= ciy;
249                 ph += ciy;
250                 ciy = 0;
251         }
252         if (w - cix <= pw)
253                 w -= cix;
254         else
255                 w = pw;
256         if (h - ciy <= ph)
257                 h -= ciy;
258         else
259                 h = ph;
260         comm_call(ci->comm2, "crop", p, w, NULL, NULL, h, NULL, NULL, cix, ciy);
261         comm_call(ci->comm2, "draw", p, x + xo, NULL, NULL, y + yo);
262
263         if (p->cx >= 0) {
264                 int rows, cols;
265                 char *cl = strchr(mode, ':');
266                 if (cl && sscanf(cl, ":%dx%d", &cols, &rows) == 2)
267                         comm_call(ci->comm2, "cursor", p,
268                                   w/cols, NULL, NULL, h/rows, NULL, NULL,
269                                   p->cx + xo, p->cy + yo);
270         }
271         return 1;
272 }
273
274 DEF_CMD(window_activate_display)
275 {
276         /* Given a display attached to the root, integrate it
277          * into a full initial stack of panes.
278          * The display is the focus of this pane.  This doc to
279          * attach there is the focus in the command.
280          */
281         struct pane *disp = ci->home->focus;
282         struct pane *p, *p2;
283         bool display_added = False;
284         char *ip;
285         char *save, *t, *m;
286
287         if (!disp || !list_empty(&disp->children))
288                 return Efail;
289         ip = pane_attr_get(disp, "window-initial-panes");
290         if (!ip)
291                 return Efail;
292         ip = strdup(ip);
293         p = ci->home;
294
295         for (t = strtok_r(ip, " \t\n", &save);
296              t;
297              t = strtok_r(NULL, " \t\n", &save)) {
298                 if (!*t)
299                         continue;
300                 if (strcmp(t, "DISPLAY") == 0) {
301                         if (!display_added) {
302                                 pane_reparent(disp, p);
303                                 p = disp;
304                                 display_added = True;
305                         }
306                 } else {
307                         m = strconcat(NULL, "attach-", t);
308                         p2 = call_ret(pane, m, p);
309                         free(m);
310                         if (p2)
311                                 p = p2;
312                 }
313         }
314         free(ip);
315         if (p && ci->focus != disp)
316                 p = home_call_ret(pane, ci->focus, "doc:attach-view", p, 1);
317         if (p)
318                 comm_call(ci->comm2, "cb", p);
319         return 1;
320 }
321
322 DEF_CMD(close_notify)
323 {
324         struct window_data *wd = ci->home->data;
325
326         if (wd->sel_owner_fallback == ci->focus)
327                 wd->sel_owner_fallback = NULL;
328
329         if (wd->sel_owner == ci->focus)
330                 wd->sel_owner = wd->sel_owner_fallback;
331         return 1;
332 }
333
334 static struct map *window_map;
335 DEF_LOOKUP_CMD(window_handle, window_map);
336
337 DEF_CMD(window_attach)
338 {
339         struct pane *p;
340
341         p = pane_register(pane_root(ci->focus), 0, &window_handle.c);
342         if (!p)
343                 return Efail;
344         comm_call(ci->comm2, "cb", p);
345         return 1;
346 }
347
348 DEF_CMD(window_close)
349 {
350         pane_close(ci->home);
351         return 1;
352 }
353
354 void window_setup(struct pane *ed safe)
355 {
356         window_map = key_alloc();
357
358         key_add_prefix(window_map, "Window:request:", &request_notify);
359         key_add_prefix(window_map, "Window:notify:", &send_notify);
360
361         key_add(window_map, "Window:close", &window_close);
362
363         key_add_prefix(window_map, "Window:set:", &window_set);
364
365         key_add(window_map, "selection:claim", &selection_claim);
366         key_add(window_map, "selection:commit", &selection_commit);
367         key_add(window_map, "selection:discard", &selection_discard);
368         key_add(window_map, "Notify:Close", &close_notify);
369
370         key_add(window_map, "Draw:scale-image", &scale_image);
371         key_add(window_map, "Window:activate-display",
372                 &window_activate_display);
373
374         call_comm("global-set-command", ed, &window_attach, 0, NULL,
375                   "attach-window-core");
376 }