]> git.neil.brown.name Git - edlib.git/blob - lib-tile.c
TODO: clean out done items.
[edlib.git] / lib-tile.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Tile manager for edlib.
6  *
7  * Given a display pane, tile it with other panes which will be
8  * used by some other clients, probably text buffers.
9  * The owner of a pane can:
10  *  - split it: above/below/left/right,
11  *  - destroy it
12  *  - add/remove lines above/below/left/right
13  *
14  * Child panes are grouped either in rows or columns.  Those panes
15  * can then be subdivided further.
16  */
17
18 #include <unistd.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 /* The data can move between panes, so we must use a PTR type */
23 #define PANE_DATA_PTR_TYPE struct tileinfo *
24 struct tileinfo;
25 #include "core.h"
26
27 struct tileinfo {
28         /* If direction is Horiz, this and siblings are stacked
29          * left to right.  Y co-ordinate is zero.
30          * If direction is Vert, siblings are stacked top to bottom.
31          * X co-ordinate is zero.
32          * The root of a tree of panes has direction of Neither.  All
33          * other panes are either Horiz or Vert.
34          *
35          * avail_inline is how much this tile can shrink in the direction
36          * of stacking.  Add these for parent.
37          * avail_perp is how much this tile can shrink perpendicular to direction.
38          * Min of these applies to parent.
39          */
40         enum dir {Neither, Horiz, Vert} direction;
41         short                           avail_inline;
42         short                           avail_perp;
43         short                           leaf;
44         struct list_head                tiles; /* headless ordered list of all tiles
45                                                 * in the tree.  Used for next/prev
46                                                 */
47         struct pane                     *p safe;
48         struct pane                     *content; /* if 'leaf' */
49         char                            *group; /* only allocate for root, other share */
50         char                            *name; /* name in group for this leaf */
51 };
52 #include "core-pane.h"
53
54 static struct map *tile_map safe;
55 static void tile_adjust(struct pane *p safe);
56 static void tile_avail(struct pane *p safe, struct pane *ignore);
57 static int tile_destroy(struct pane *p safe);
58 DEF_LOOKUP_CMD(tile_handle, tile_map);
59
60 static inline bool mine(struct pane *t safe)
61 {
62         return t->z == 0 && t->handle == &tile_handle.c;
63 }
64
65 DEF_CMD_CLOSED(tile_close)
66 {
67         struct tileinfo *ti = ci->home->data;
68
69         tile_destroy(ci->home);
70         free(ti->name);
71         unalloc(ti, pane);
72         return 1;
73 }
74
75 DEF_CMD(tile_refresh_size)
76 {
77         struct pane *p = ci->home;
78         struct tileinfo *ti = p->data;
79
80         if (ti->direction == Neither) {
81                 tile_avail(p, NULL);
82                 tile_adjust(p);
83         }
84         return !ti->leaf;
85 }
86
87 DEF_CMD(tile_clone)
88 {
89         struct pane *parent = ci->focus;
90         struct pane *p2, *child;
91         struct tileinfo *ti, *cti;
92
93         /* Clone a new 'tile' onto the parent, but only
94          * create a single tile, cloned from the focus pane
95          */
96         child = ci->home;
97         cti = child->data;
98         alloc(ti, pane);
99         ti->leaf = 1;
100         ti->direction = Neither;
101         if (cti->group)
102                 ti->group = strdup(cti->group);
103         INIT_LIST_HEAD(&ti->tiles);
104         p2 = pane_register(parent, 0, &tile_handle.c, ti);
105         if (!p2)
106                 return Efail;
107         ti->p = p2;
108         /* Remove borders as our children will provide their own. */
109         call("Tile:border", p2);
110         attr_set_str(&p2->attrs, "borders", "BL");
111         while (!cti->leaf && child->focus) {
112                 child = child->focus;
113                 cti = child->data;
114         }
115         cti = list_next_entry(cti, tiles);
116         while (cti != child->data &&
117                (cti->name == NULL || strcmp(cti->name, "main") != 0))
118                 cti = list_next_entry(cti, tiles);
119         child = cti->p;
120         if (cti->name)
121                 ti->name = strdup(cti->name);
122         pane_clone_children(child, p2);
123         return 1;
124 }
125
126 DEF_CMD(tile_attach)
127 {
128         struct pane *display = ci->focus;
129         struct tileinfo *ti;
130         struct pane *p;
131
132         /* Remove borders as our children will provide their own. */
133         call("Tile:border", display);
134
135         alloc(ti, pane);
136         p = pane_register(display, 0, &tile_handle.c, ti);
137         if (!p)
138                 return Efail;
139         ti->leaf = 1;
140         ti->p = p;
141         ti->direction = Neither;
142         if (ci->str)
143                 ti->group = strdup(ci->str);
144         if (ci->str2)
145                 ti->name = strdup(ci->str2);
146         INIT_LIST_HEAD(&ti->tiles);
147         attr_set_str(&p->attrs, "borders", "BL");
148         return comm_call(ci->comm2, "callback:attach", p);
149 }
150
151 static struct pane *tile_split(struct pane **pp safe, int horiz, int after,
152                                const char *name)
153 {
154         /* Create a new pane near the given one, reducing its size,
155          * and possibly the size of other siblings.
156          * Return a new pane which is a sibling of the old, or NULL
157          * if there is no room for further splits.
158          * This may require creating a new parent and moving 'p' down
159          * in the hierarchy.
160          */
161         int space, new_space;
162         struct pane *p = safe_cast *pp;
163         struct pane *ret;
164         struct tileinfo *ti = p->data;
165         struct tileinfo *ti2;
166         if (horiz)
167                 space = p->w;
168         else
169                 space = p->h;
170
171         /* FIXME ask the leafs */
172         if (space < 8)
173                 return NULL;
174         new_space = space / 2;
175         space -= new_space;
176
177         if (ti->direction != (horiz? Horiz : Vert)) {
178                 /* This tile does not stack in the required direction, need
179                  * to create an extra level.
180                  */
181                 struct pane *p2, *child;
182                 struct list_head *t;
183                 /* ti2 will be tileinfo for p, new pane gets ti */
184                 alloc(ti2, pane);
185                 ti2->leaf = 0;
186                 ti2->direction = ti->direction;
187                 ti2->group = ti->group;
188                 INIT_LIST_HEAD(&ti2->tiles);
189                 p->data = ti2;
190                 ti2->p = p;
191                 p2 = pane_register(p, 0, &tile_handle.c, ti);
192                 if (!p2)
193                         return NULL;
194                 ti->p = p2;
195                 ti->direction = horiz ? Horiz : Vert;
196                 /* All children of p must be moved to p2, except p2 */
197                 list_for_each_entry_safe(child, t, &p->children, siblings)
198                         if (child != p2)
199                                 pane_reparent(child, p2);
200                 p = p2;
201         }
202         alloc(ti2, pane);
203         ti2->group = ti->group;
204         ti2->direction = ti->direction;
205         ti2->leaf = 1;
206         if (name)
207                 ti2->name = strdup(name);
208         /* FIXME if ti wasn't a leaf, this is wrong.  Is that possible? */
209         if (after)
210                 list_add(&ti2->tiles, &ti->tiles);
211         else
212                 list_add_tail(&ti2->tiles, &ti->tiles);
213         ret = pane_register(p->parent, 0, &tile_handle.c, ti2);
214         if (!ret)
215                 return NULL;
216
217         ti2->p = ret;
218         if (after)
219                 pane_move_after(ret, p);
220         else if (p == list_first_entry(&p->parent->children,
221                                        struct pane, siblings))
222                 pane_move_after(ret, NULL);
223         else
224                 pane_move_after(ret, list_prev_entry(p, siblings));
225         switch (!!horiz + 2 * !!after) {
226         case 0: /* vert before */
227                 pane_resize(ret, p->x, p->y, p->w, new_space);
228                 pane_resize(p, p->x, p->y + ret->h, p->w, space);
229                 break;
230         case 1: /* horiz before */
231                 pane_resize(ret, p->x, p->y, new_space, p->h);
232                 pane_resize(p, p->x + ret->w, p->y, space, p->h);
233                 break;
234         case 2: /* vert after */
235                 pane_resize(ret, p->x, p->y + space, p->w, new_space);
236                 pane_resize(p, p->x, p->y, p->w, space);
237                 break;
238         case 3: /* horiz after */
239                 pane_resize(ret, p->x + space, p->y, new_space, p->h);
240                 pane_resize(p, p->x, p->y, space, p->h);
241                 break;
242         }
243         tile_adjust(ret);
244         tile_adjust(p);
245         *pp = p;
246         return ret;
247 }
248
249 static int tile_destroy(struct pane *p safe)
250 {
251         struct tileinfo *ti = p->data;
252         struct pane *prev = NULL, *next = NULL;
253         struct pane *t, *remain = NULL;
254         int pos, prevpos, nextpos;
255         int remaining = 0;
256
257         if (ti->direction == Neither /* Root file a tile-set */
258             || p->parent == p /* subsumbed hust being destroyed */
259             || p->parent->handle != p->handle /* Some messed with parentage */
260         )
261                 return 1;
262
263         if (ti->direction == Vert)
264                 pos = p->y;
265         else
266                 pos = p->x;
267         prevpos = nextpos = -1;
268         list_for_each_entry(t, &p->parent->children, siblings) {
269                 int pos2;
270                 if (t->z)
271                         continue;
272                 if (ti->direction == Vert)
273                         pos2 = t->y;
274                 else
275                         pos2 = t->x;
276                 if (pos2 < pos && (prev == NULL || prevpos < pos2))
277                         prev = t;
278                 if (pos2 > pos && (next == NULL || nextpos > pos2))
279                         next = t;
280
281                 remaining += 1;
282                 remain = t;
283         }
284         /* There is always a sibling of a non-root */
285         //ASSERT(remaining > 0);
286         if (prev == NULL /* FIXME redundant */ && next) {
287                 /* next gets the space and focus*/
288                 if (ti->direction == Horiz)
289                         pane_resize(next, p->x, next->y,
290                                     p->w + next->w, next->h);
291                 else
292                         pane_resize(next, next->x, p->y,
293                                     next->w, p->h + next->h);
294                 tile_adjust(next);
295                 p->parent->focus = next;
296         } else if (next == NULL /* FIXME redundant */ && prev) {
297                 /* prev gets the space and focus */
298                 if (ti->direction == Horiz)
299                         pane_resize(prev, prev->x, prev->y,
300                                     prev->w + p->w, prev->h);
301                 else
302                         pane_resize(prev, prev->x, prev->y,
303                                     prev->w, prev->h + p->h);
304                 tile_adjust(prev);
305                 p->parent->focus = prev;
306         } else if (/*FIXME*/ next && prev) {
307                 /* space to the smallest, else share the space */
308                 /* Focus goes to prev, unless next is small */
309                 p->parent->focus = prev;
310                 if (ti->direction == Horiz) {
311                         int w = p->w / 2;
312                         if (prev->w < next->w*2/3)
313                                 /* prev is much smaller, it gets all */
314                                 w = p->w;
315                         else if (next->w < prev->w*2/3) {
316                                 w = 0;
317                                 p->parent->focus = next;
318                         }
319                         pane_resize(prev, prev->x, prev->y, prev->w + w, prev->h);
320                         w = p->w - w;
321                         pane_resize(next, prev->x + prev->w, next->y,
322                                     next->w + w, next->h);
323                 } else {
324                         int h = p->h / 2;
325                         if (prev->h < next->h*2/3)
326                                 /* prev is much smaller, it gets all */
327                                 h = p->h;
328                         else if (next->h < prev->h*2/3) {
329                                 h = 0;
330                                 p->parent->focus = next;
331                         }
332                         pane_resize(prev, prev->x, prev->y, prev->w, prev->h + h);
333                         h = p->h - h;
334                         pane_resize(next, next->x, prev->y + prev->h,
335                                     next->w , next->h + h);
336                 }
337                 tile_adjust(next);
338                 tile_adjust(prev);
339         }
340         list_del(&ti->tiles);
341         if (remaining == 1 && remain && remain->parent != remain &&
342             remain->handle == p->handle) {
343                 struct tileinfo *ti2;
344                 enum dir tmp;
345                 /* Only one child left, must move it into parent.
346                  * Cannot destroy the parent, so bring child into parent */
347                 p = remain->parent;
348
349                 ti = remain->data;
350                 ti2 = p->data;
351
352                 tmp = ti2->direction;
353                 ti2->direction = ti->direction;
354                 ti->p = p;
355                 ti->direction = tmp;
356                 ti2->p = remain;
357
358                 pane_subsume(remain, p);
359         }
360         return 1;
361 }
362
363 static void tile_avail(struct pane *p safe, struct pane *ignore)
364 {
365         /* How much can we shrink this pane?
366          * If 'ignore' is set, it is a child of 'p', and we only
367          * consider other children.
368          * If stacking direction matches 'horiz' find sum of avail in children.
369          * if stacking direction doesn't match 'horiz', find minimum.
370          * If only one child, assume min of 4.
371          */
372         struct tileinfo *ti = p->data;
373         struct pane *t;
374
375         if (ti->leaf) {
376                 /* one or zero children */
377                 if (ti->direction == Horiz) {
378                         ti->avail_inline = p->w < 4 ? 0 : p->w - 4;
379                         ti->avail_perp = p->h < 4 ? 0 : p->h - 4;
380                 } else {
381                         ti->avail_inline = p->h < 4 ? 0 : p->h - 4;
382                         ti->avail_perp = p->w < 4 ? 0 : p->w - 4;
383                 }
384         } else {
385                 struct tileinfo *ti2;
386                 int sum = 0, min = -1;
387                 list_for_each_entry(t, &p->children, siblings) {
388                         if (t == ignore || !mine(t))
389                                 continue;
390                         tile_avail(t, NULL);
391                         ti2 = t->data;
392                         if (min < 0 || min > ti2->avail_perp)
393                                 min = ti2->avail_perp;
394                         sum += ti2->avail_inline;
395                 }
396                 ti->avail_perp = sum;
397                 ti->avail_inline = min;
398         }
399 }
400
401 static void tile_adjust(struct pane *p safe)
402 {
403         /* Size of pane 'p' has changed and it is time to adjust
404          * the children, both their size and offset.
405          * For the non-stacking direction, offset must be zero and
406          * size is copied from p.
407          * For the stacking direction, we count used size, find the
408          * change required and distribute that.  For shrinking, this
409          * relies on the fact that 'tile_avail' must have been run
410          * and it left avail space calculations around.
411          */
412         struct pane *t;
413         int used = 0;
414         int cnt = 0;
415         int avail_cnt = 0;
416         int pos;
417         int size = 0;
418         struct tileinfo *ti = p->data;
419
420         if (ti->leaf)
421                 /* Children are responsible for themselves. */
422                 return;
423
424         list_for_each_entry(t, &p->children, siblings) {
425
426                 if (!mine(t))
427                         continue;
428                 ti = t->data;
429                 if (ti->direction == Horiz) {
430                         pane_resize(t, t->x, 0, t->w, p->h);
431                         used += t->w;
432                         size = p->w;
433                 } else {
434                         pane_resize(t, 0, t->y, p->w, t->h);
435                         used += t->h;
436                         size = p->h;
437                 }
438                 if (ti->avail_inline)
439                         avail_cnt++;
440                 cnt++;
441         }
442         while (used < size || (used > size && avail_cnt)) {
443                 int change = 0;
444                 int remain = used; /* size of panes still to be resized */
445
446                 if (used > size)
447                         cnt = avail_cnt;
448                 avail_cnt = 0;
449                 list_for_each_entry(t, &p->children, siblings) {
450                         struct tileinfo *ti2 = t->data;
451                         int diff;
452                         int mysize;
453                         if (!mine(t))
454                                 continue;
455                         if (!remain)
456                                 break;
457                         mysize = (ti2->direction == Horiz) ? t->w : t->h;
458
459                         if (used > size) {
460                                 /* shrinking */
461                                 if (ti2->avail_inline == 0) {
462                                         remain -= mysize;
463                                         continue;
464                                 }
465                                 diff = (((used - size) * mysize) +
466                                         (used%remain)) / remain;
467                                 if (diff > ti2->avail_inline)
468                                         diff = ti2->avail_inline;
469                                 ti2->avail_inline -= diff;
470                                 if (ti2->avail_inline)
471                                         /* Still space available if needed */
472                                         avail_cnt++;
473
474                                 diff = -diff;
475                         } else if (used == size)
476                                 break;
477                         else
478                                 diff = (((size - used) * mysize) +
479                                         (used%remain) )/ remain;
480                         remain -= mysize;
481                         if (diff)
482                                 change = 1;
483                         if (ti2->direction == Horiz)
484                                 pane_resize(t, t->x, t->y, t->w + diff, t->h);
485                          else
486                                 pane_resize(t, t->x, t->y, t->w, t->h + diff);
487
488                         used += diff;
489                         cnt--;
490                 }
491                 if (!change)
492                         break;
493         }
494         pos = 0;
495         list_for_each_entry(t, &p->children, siblings) {
496                 struct tileinfo *ti2 = t->data;
497                 if (!mine(t))
498                         continue;
499                 if (ti2->direction == Horiz) {
500                         pane_resize(t, pos, t->y, t->w, t->h);
501                         pos += t->w;
502                 } else {
503                         pane_resize(t, t->x, pos, t->w, t->h);
504                         pos += t->h;
505                 }
506                 tile_adjust(t);
507         }
508 }
509
510 static bool tile_grow(struct pane *p safe, int horiz, int size)
511 {
512         /* Try to grow the pane in given direction, or shrink if
513          * size < 0.
514          * This is only done by shrinking other tiles, not by
515          * resizing the top level.
516          * If this pane isn't stacked in the right direction, or if
517          * neighbors are too small to shrink, the parent is resized.
518          * Then that propagates back down.  Size of this pane is adjusted
519          * first to catch the propagations, then corrected after.
520          */
521         struct tileinfo *ti = p->data;
522         struct tileinfo *tip;
523         int avail;
524
525         if (ti->direction == Neither)
526                 /* Cannot grow/shrink the root */
527                 return False;
528         if (size < 0) {
529                 /* Does this pane have room to shrink */
530                 tile_avail(p, NULL);
531                 if (ti->direction == (horiz? Horiz : Vert))
532                         avail = ti->avail_inline;
533                 else
534                         avail = ti->avail_perp;
535                 if (avail < -size)
536                         return False;
537         }
538         if (ti->direction != (horiz ? Horiz : Vert)) {
539                 /* need to ask parent to do this */
540                 return tile_grow(p->parent, horiz, size);
541         }
542
543         /* OK, this stacks in the right direction. if shrinking we can commit */
544         if (size < 0) {
545                 struct pane *other = NULL;
546                 struct pane *t;
547                 int p_found = 0;
548                 list_for_each_entry(t, &p->parent->children, siblings) {
549                         if (!mine(t))
550                                 continue;
551                         if (t == p)
552                                 p_found = 1;
553                         else
554                                 other = t;
555                         if (other && p_found)
556                                 break;
557                 }
558
559                 if (other == NULL)
560                         /* Strange - there should have been two elements in list */
561                         return True;
562                 if (ti->direction == Horiz) {
563                         pane_resize(p, p->x, p->y, p->w + size, p->h);
564                         pane_resize(other, other->x, other->y,
565                                     other->w - size, other->h);
566                 } else {
567                         pane_resize(p, p->x, p->y, p->w, p->h + size);
568                         pane_resize(other, other->x, other->y,
569                                     other->w, other->h - size);
570                 }
571                 tile_adjust(p->parent);
572                 return True;
573         }
574
575         /* Hoping to grow if there is room for others to shrink */
576         tile_avail(p->parent, p);
577         tip = p->parent->data;
578         if (ti->direction == (horiz ? Horiz : Vert))
579                 avail = tip->avail_inline;
580         else
581                 avail = tip->avail_perp;
582         if (avail < size)
583                 return False;
584         if (ti->direction == Horiz)
585                 pane_resize(p, p->x, p->y, p->w + size, p->h);
586         else
587                 pane_resize(p, p->x, p->y, p->w, p->h + size);
588
589         ti->avail_inline = 0; /* make sure this one doesn't suffer */
590         tile_adjust(p->parent);
591         return True;
592 }
593
594 static struct pane *next_child(struct pane *parent, struct pane *prev, bool popup)
595 {
596         struct pane *p2;
597         list_for_each_entry(p2, &parent->children, siblings) {
598                 if (p2 == prev) {
599                         prev = NULL;
600                         continue;
601                 }
602                 if (prev)
603                         continue;
604                 if (mine(p2) == popup)
605                         continue;
606                 return p2;
607         }
608         return NULL;
609 }
610
611 static struct tileinfo *tile_first(struct tileinfo *ti safe)
612 {
613         while (!ti->leaf) {
614                 struct pane *p = next_child(ti->p, NULL, 0);
615                 if (!p)
616                         return NULL;
617                 ti = p->data;
618         }
619         return ti;
620 }
621
622 static bool tile_is_first(struct tileinfo *ti safe)
623 {
624         while (ti->direction != Neither) {
625                 if (ti->p != next_child(ti->p->parent, NULL, 0))
626                         return False;
627                 ti = ti->p->parent->data;
628         }
629         return True;
630 }
631
632 static struct pane *tile_root_popup(struct tileinfo *ti safe)
633 {
634         while (ti->direction != Neither)
635                 ti = ti->p->parent->data;
636         return next_child(ti->p, NULL, 1);
637 }
638
639 static struct tileinfo *safe tile_next_named(struct tileinfo *ti safe,
640                                              const char *name)
641 {
642         struct tileinfo *t = ti;
643         while ((t = list_next_entry(t, tiles)) != ti) {
644                 if (!name)
645                         return t;
646                 if (!t->name || strcmp(t->name, name) != 0)
647                         continue;
648                 return t;
649         }
650         return t;
651 }
652
653 static bool wrong_pane(struct cmd_info const *ci safe)
654 {
655         struct tileinfo *ti = ci->home->data;
656
657         if (ci->str || ti->group) {
658                 if (!ci->str || !ti->group)
659                         return True;
660                 if (strcmp(ci->str, ti->group) != 0)
661                         return True;
662                 /* same group - continue */
663         }
664         return False;
665 }
666
667 DEF_CMD(tile_window_next)
668 {
669         /* If currently on a popup, go to next popup if there is one, else
670          * to this tile.
671          * If was not on a pop-up, go to next tile and if there is a popup,
672          * go there.
673          */
674         struct pane *p = ci->home;
675         struct pane *p2;
676         struct tileinfo *ti = p->data;
677         struct tileinfo *t2;
678
679         if (wrong_pane(ci))
680                 return Efallthrough;
681         if (p->focus && p->focus->z) {
682                 p2 = next_child(p, p->focus, 1);
683                 if (p2) {
684                         pane_take_focus(p2);
685                         return 1;
686                 } else if (ti->leaf) {
687                         pane_take_focus(ti->content);
688                         return 1;
689                 }
690                 t2 = tile_first(ti);
691         } else {
692                 if (ti->leaf) {
693                         t2 = tile_next_named(ti, ci->str2);
694                         if (tile_is_first(t2) &&
695                             (p2 = tile_root_popup(t2)) != NULL) {
696                                 pane_take_focus(p2);
697                                 return 1;
698                         }
699                 } else
700                         t2 = tile_first(ti);
701         }
702         if (t2) {
703                 pane_take_focus(t2->p);
704                 p2 = next_child(t2->p, NULL, 1);
705                 if (p2)
706                         pane_take_focus(p2);
707         }
708         return 1;
709 }
710
711 DEF_CMD(tile_window_prev)
712 {
713         struct pane *p = ci->home;
714         struct tileinfo *ti = p->data;
715         struct tileinfo *t2;
716
717         if (wrong_pane(ci))
718                 return Efallthrough;
719         t2 = list_prev_entry(ti, tiles);
720         pane_take_focus(t2->p);
721         return 1;
722 }
723
724 DEF_CMD(tile_window_xplus)
725 {
726         struct pane *p = ci->home;
727
728         if (wrong_pane(ci))
729                 return Efallthrough;
730         tile_grow(p, 1, RPT_NUM(ci));
731         return 1;
732 }
733
734 DEF_CMD(tile_window_xminus)
735 {
736         struct pane *p = ci->home;
737
738         if (wrong_pane(ci))
739                 return Efallthrough;
740         tile_grow(p, 1, -RPT_NUM(ci));
741         return 1;
742 }
743 DEF_CMD(tile_window_yplus)
744 {
745         struct pane *p = ci->home;
746
747         if (wrong_pane(ci))
748                 return Efallthrough;
749         tile_grow(p, 0, RPT_NUM(ci));
750         return 1;
751 }
752 DEF_CMD(tile_window_yminus)
753 {
754         struct pane *p = ci->home;
755
756         if (wrong_pane(ci))
757                 return Efallthrough;
758         tile_grow(p, 0, -RPT_NUM(ci));
759         return 1;
760 }
761
762 DEF_CMD(tile_window_splitx)
763 {
764         struct pane *p = ci->home;
765         struct pane *p2;
766
767         if (wrong_pane(ci))
768                 return Efallthrough;
769         p2 = tile_split(&p, 1, 1, ci->str2);
770         pane_clone_children(p, p2);
771         return 1;
772 }
773
774 DEF_CMD(tile_window_splity)
775 {
776         struct pane *p = ci->home;
777         struct pane *p2;
778
779         if (wrong_pane(ci))
780                 return Efallthrough;
781         p2 = tile_split(&p, 0, 1, ci->str2);
782         pane_clone_children(p, p2);
783         return 1;
784 }
785
786 DEF_CMD(tile_window_close)
787 {
788         struct pane *p = ci->home;
789         struct tileinfo *ti = p->data;
790
791         if (wrong_pane(ci))
792                 return Efallthrough;
793         if (ti->direction != Neither)
794                 pane_close(p);
795         return 1;
796 }
797
798 DEF_CMD(tile_window_bury)
799 {
800         /* Bury the document in this tile.
801          * Find some other document to display
802          */
803         struct pane *doc;
804
805         if (wrong_pane(ci))
806                 return Efallthrough;
807
808         /* First, push the doc to the end of the 'recently used' list */
809         call("doc:notify:doc:revisit", ci->focus, -1);
810         /* Now choose a replacement */
811         doc = call_ret(pane, "docs:choose", ci->home);
812         if (doc)
813                 /* display that doc in this pane */
814                 home_call(doc, "doc:attach-view", ci->home);
815         return 1;
816 }
817
818 DEF_CMD(tile_window_close_others)
819 {
820         struct pane *p = ci->home;
821         struct pane *parent = p->parent;
822         struct tileinfo *ti = p->data;
823         bool found =  True;
824
825         if (wrong_pane(ci))
826                 return Efallthrough;
827         /* close sibling panes until ->parent changes, or there aren't any */
828         while (found && p->parent == parent) {
829                 struct pane *s;
830
831                 found = False;
832                 list_for_each_entry(s, &parent->children, siblings)
833                         if (s != p) {
834                                 found = True;
835                                 pane_close(s);
836                                 break;
837                         }
838         }
839         return ti->direction != Neither ? 1 : Efalse;
840 }
841
842 DEF_CMD(tile_other)
843 {
844         /* Choose some other tile.  If there aren't any, make one.
845          * Result is returned in ci->focus
846          * ci->num has flags:
847          *  1: if split is need, use 2 to determine direction, else default
848          *  2: if split needed, split horizontally, else vertically
849          *  4: if split needed use 8 to determine which is new, else default
850          *  8: if split is needed, new pane is to the right/down.
851          *  512: don't split, just return Efalse
852          */
853         struct pane *p = ci->home;
854         struct pane *p2;
855         struct tileinfo *ti = p->data;
856         struct tileinfo *ti2;
857         int horiz, after;
858
859         if (ci->str || ti->group) {
860                 if (!ci->str || !ti->group)
861                         return Efallthrough;
862                 if (strcmp(ci->str, ti->group) != 0)
863                         return Efallthrough;
864                 /* same group - continue */
865         }
866         if (!ti->leaf) {
867                 /* probably coming from a pop-up. Just use first tile */
868                 ti2 = tile_first(ti);
869                 if (!ti2)
870                         return Einval;
871                 if (ci->str2 && ti2->name && strcmp(ci->str2, ti2->name) == 0)
872                         return Einval;
873                 return comm_call(ci->comm2, "callback:pane", ti2->p);
874         }
875         if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
876                 return Einval;
877
878         ti2 = tile_next_named(ti, ci->str2);
879         if (ti2 != ti)
880                 return comm_call(ci->comm2, "callback:pane", ti2->p);
881
882         /* Need to create a tile.  If wider than 120 (FIXME configurable?),
883          * horiz-split else vert
884          */
885         if (ci->num & 512)
886                 return Efalse;
887
888         if (ci->num & 1) {
889                 horiz = ci->num & 2;
890         } else {
891                 struct xy xy = pane_scale(p);
892                 horiz = p->w * 1000 >= 1200 * xy.x;
893         }
894         if (ci->num & 4)
895                 after = ci->num & 8;
896         else
897                 after = 1;
898         p2 = tile_split(&p, horiz, after, ci->str2);
899         if (p2)
900                 return comm_call(ci->comm2, "callback:pane", p2);
901         return Efail;
902 }
903
904 DEF_CMD(tile_this)
905 {
906         struct tileinfo *ti = ci->home->data;
907
908         if (ci->str || ti->group) {
909                 if (!ci->str || !ti->group)
910                         return Efallthrough;
911                 if (strcmp(ci->str, ti->group) != 0)
912                         return Efallthrough;
913                 /* same group - continue */
914         }
915         if (!ti->leaf) {
916                 /* There is no clear 'This', use first. */
917                 ti = tile_first(ti);
918                 if (!ti)
919                         return Einval;
920                 if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
921                         return Einval;
922                 return comm_call(ci->comm2, "callback:pane", ti->p);
923         }
924         return comm_call(ci->comm2, "callback:pane", ci->home, 0,
925                          NULL, ti->name);
926 }
927
928 DEF_CMD(tile_doc)
929 {
930         /* Find the pane displaying given document, preferrably not
931          * this pane
932          */
933         struct tileinfo *ti = ci->home->data;
934         struct tileinfo *t;
935         char *name;
936
937         if (ci->str || ti->group) {
938                 if (!ci->str || !ti->group)
939                         return Efallthrough;
940                 if (strcmp(ci->str, ti->group) != 0)
941                         return Efallthrough;
942                 /* same group - continue */
943         }
944         /* Find where 'focus' is open */
945         name = pane_attr_get(ci->focus, "doc-name");
946         if (!name)
947                 return Efallthrough;
948         if (!ti->leaf)
949                 ti = tile_first(ti);
950         t = ti;
951         do {
952                 char *n;
953                 struct pane *f;
954                 t = list_next_entry(t, tiles);
955                 f = t->content;
956                 if (f) {
957                         f = pane_leaf(f);
958                         n = pane_attr_get(f, "doc-name");
959                         if (n && strcmp(n, name) == 0)
960                                 return comm_call(ci->comm2, "callback:pane",
961                                                  t->p,
962                                                  0, NULL, t->name);
963                 }
964         } while (t != ti);
965
966         return Efallthrough;
967 }
968
969 DEF_CMD(tile_root)
970 {
971         struct pane *p = ci->home;
972         struct tileinfo *ti = p->data;
973
974         if (ti->direction != Neither)
975                 return Efallthrough;
976         if (ci->str || ti->group) {
977                 if (!ci->str || !ti->group)
978                         return Efallthrough;
979                 if (strcmp(ci->str, ti->group) != 0)
980                         return Efallthrough;
981                 /* same group - continue */
982         }
983
984         return comm_call(ci->comm2, "callback:pane", p);
985 }
986
987 DEF_CMD(tile_child_notify)
988 {
989         struct pane *p = ci->home;
990         struct tileinfo *ti = p->data;
991         struct pane *c = ci->focus;
992
993         if (c->z)
994                 return 1;
995         if (ci->num > 0 && mine(c))
996                 /* always accept my own children */
997                 return 1;
998
999         if (ti->leaf != 1) {
1000                 if (ci->num > 0)
1001                         /* Sorry, new children not permitted */
1002                         return Efalse;
1003                 return 1;
1004         }
1005
1006         switch (ci->num) {
1007         case -1:
1008                 /* Child closed, but we weren't, so find something else to display */
1009                 ti->content = NULL;
1010                 c = call_ret(pane, "docs:choose", p);
1011                 if (c)
1012                         home_call(c, "doc:attach-view", p);
1013                 else if (ti->direction != Neither)
1014                         pane_close(p);
1015                 break;
1016         case 1:
1017                 /* New pane, discard the old */
1018                 p->focus = c;
1019                 if (ti->content) {
1020                         ti->leaf = 2;
1021                         pane_close(ti->content);
1022                         ti->leaf = 1;
1023                 }
1024                 ti->content = c;
1025                 break;
1026         case -2:
1027                 /* Child moved away - hopefully to be replaced */
1028                 ti->content = NULL;
1029                 break;
1030         case 2:
1031                 /* Simple replacement */
1032                 ti->content = c;
1033                 break;
1034         }
1035         return 1;
1036 }
1037
1038 void edlib_init(struct pane *ed safe)
1039 {
1040         tile_map = key_alloc();
1041
1042         key_add(tile_map, "Tile:next", &tile_window_next);
1043         key_add(tile_map, "Tile:prev", &tile_window_prev);
1044         key_add(tile_map, "Tile:x+", &tile_window_xplus);
1045         key_add(tile_map, "Tile:x-", &tile_window_xminus);
1046         key_add(tile_map, "Tile:y+", &tile_window_yplus);
1047         key_add(tile_map, "Tile:y-", &tile_window_yminus);
1048         key_add(tile_map, "Tile:split-x", &tile_window_splitx);
1049         key_add(tile_map, "Tile:split-y", &tile_window_splity);
1050         key_add(tile_map, "Tile:close", &tile_window_close);
1051         key_add(tile_map, "Tile:close-others", &tile_window_close_others);
1052         key_add(tile_map, "Tile:bury", &tile_window_bury);
1053
1054         key_add(tile_map, "OtherPane", &tile_other);
1055         key_add(tile_map, "ThisPane", &tile_this);
1056         key_add(tile_map, "DocPane", &tile_doc);
1057         key_add(tile_map, "RootPane", &tile_root);
1058         key_add(tile_map, "Clone", &tile_clone);
1059         key_add(tile_map, "Child-Notify", &tile_child_notify);
1060         key_add(tile_map, "Close", &tile_close);
1061         key_add(tile_map, "Refresh:size", &tile_refresh_size);
1062
1063         call_comm("global-set-command", ed, &tile_attach, 0, NULL, "attach-tile");
1064 }