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