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