1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2009 Nicholas Marriott <[email protected]> |
5 | * Copyright (c) 2016 Stephen Kent <[email protected]> |
6 | * |
7 | * Permission to use, copy, modify, and distribute this software for any |
8 | * purpose with or without fee is hereby granted, provided that the above |
9 | * copyright notice and this permission notice appear in all copies. |
10 | * |
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
15 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
16 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
17 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
18 | */ |
19 | |
20 | #include <sys/types.h> |
21 | |
22 | #include <stdlib.h> |
23 | |
24 | #include "tmux.h" |
25 | |
26 | /* |
27 | * The window layout is a tree of cells each of which can be one of: a |
28 | * left-right container for a list of cells, a top-bottom container for a list |
29 | * of cells, or a container for a window pane. |
30 | * |
31 | * Each window has a pointer to the root of its layout tree (containing its |
32 | * panes), every pane has a pointer back to the cell containing it, and each |
33 | * cell a pointer to its parent cell. |
34 | */ |
35 | |
36 | static u_int layout_resize_check(struct window *, struct layout_cell *, |
37 | enum layout_type); |
38 | static int layout_resize_pane_grow(struct window *, struct layout_cell *, |
39 | enum layout_type, int, int); |
40 | static int layout_resize_pane_shrink(struct window *, struct layout_cell *, |
41 | enum layout_type, int); |
42 | static u_int layout_new_pane_size(struct window *, u_int, |
43 | struct layout_cell *, enum layout_type, u_int, u_int, |
44 | u_int); |
45 | static int layout_set_size_check(struct window *, struct layout_cell *, |
46 | enum layout_type, int); |
47 | static void layout_resize_child_cells(struct window *, |
48 | struct layout_cell *); |
49 | |
50 | struct layout_cell * |
51 | layout_create_cell(struct layout_cell *lcparent) |
52 | { |
53 | struct layout_cell *lc; |
54 | |
55 | lc = xmalloc(sizeof *lc); |
56 | lc->type = LAYOUT_WINDOWPANE; |
57 | lc->parent = lcparent; |
58 | |
59 | TAILQ_INIT(&lc->cells); |
60 | |
61 | lc->sx = UINT_MAX; |
62 | lc->sy = UINT_MAX; |
63 | |
64 | lc->xoff = UINT_MAX; |
65 | lc->yoff = UINT_MAX; |
66 | |
67 | lc->wp = NULL; |
68 | |
69 | return (lc); |
70 | } |
71 | |
72 | void |
73 | layout_free_cell(struct layout_cell *lc) |
74 | { |
75 | struct layout_cell *lcchild; |
76 | |
77 | switch (lc->type) { |
78 | case LAYOUT_LEFTRIGHT: |
79 | case LAYOUT_TOPBOTTOM: |
80 | while (!TAILQ_EMPTY(&lc->cells)) { |
81 | lcchild = TAILQ_FIRST(&lc->cells); |
82 | TAILQ_REMOVE(&lc->cells, lcchild, entry); |
83 | layout_free_cell(lcchild); |
84 | } |
85 | break; |
86 | case LAYOUT_WINDOWPANE: |
87 | if (lc->wp != NULL) |
88 | lc->wp->layout_cell = NULL; |
89 | break; |
90 | } |
91 | |
92 | free(lc); |
93 | } |
94 | |
95 | void |
96 | layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) |
97 | { |
98 | struct layout_cell *lcchild; |
99 | const char *type; |
100 | |
101 | switch (lc->type) { |
102 | case LAYOUT_LEFTRIGHT: |
103 | type = "LEFTRIGHT" ; |
104 | break; |
105 | case LAYOUT_TOPBOTTOM: |
106 | type = "TOPBOTTOM" ; |
107 | break; |
108 | case LAYOUT_WINDOWPANE: |
109 | type = "WINDOWPANE" ; |
110 | break; |
111 | default: |
112 | type = "UNKNOWN" ; |
113 | break; |
114 | } |
115 | log_debug("%s:%*s%p type %s [parent %p] wp=%p [%u,%u %ux%u]" , hdr, n, |
116 | " " , lc, type, lc->parent, lc->wp, lc->xoff, lc->yoff, lc->sx, |
117 | lc->sy); |
118 | switch (lc->type) { |
119 | case LAYOUT_LEFTRIGHT: |
120 | case LAYOUT_TOPBOTTOM: |
121 | TAILQ_FOREACH(lcchild, &lc->cells, entry) |
122 | layout_print_cell(lcchild, hdr, n + 1); |
123 | break; |
124 | case LAYOUT_WINDOWPANE: |
125 | break; |
126 | } |
127 | } |
128 | |
129 | struct layout_cell * |
130 | layout_search_by_border(struct layout_cell *lc, u_int x, u_int y) |
131 | { |
132 | struct layout_cell *lcchild, *last = NULL; |
133 | |
134 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
135 | if (x >= lcchild->xoff && x < lcchild->xoff + lcchild->sx && |
136 | y >= lcchild->yoff && y < lcchild->yoff + lcchild->sy) { |
137 | /* Inside the cell - recurse. */ |
138 | return (layout_search_by_border(lcchild, x, y)); |
139 | } |
140 | |
141 | if (last == NULL) { |
142 | last = lcchild; |
143 | continue; |
144 | } |
145 | |
146 | switch (lc->type) { |
147 | case LAYOUT_LEFTRIGHT: |
148 | if (x < lcchild->xoff && x >= last->xoff + last->sx) |
149 | return (last); |
150 | break; |
151 | case LAYOUT_TOPBOTTOM: |
152 | if (y < lcchild->yoff && y >= last->yoff + last->sy) |
153 | return (last); |
154 | break; |
155 | case LAYOUT_WINDOWPANE: |
156 | break; |
157 | } |
158 | |
159 | last = lcchild; |
160 | } |
161 | |
162 | return (NULL); |
163 | } |
164 | |
165 | void |
166 | layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, u_int xoff, |
167 | u_int yoff) |
168 | { |
169 | lc->sx = sx; |
170 | lc->sy = sy; |
171 | |
172 | lc->xoff = xoff; |
173 | lc->yoff = yoff; |
174 | } |
175 | |
176 | void |
177 | layout_make_leaf(struct layout_cell *lc, struct window_pane *wp) |
178 | { |
179 | lc->type = LAYOUT_WINDOWPANE; |
180 | |
181 | TAILQ_INIT(&lc->cells); |
182 | |
183 | wp->layout_cell = lc; |
184 | lc->wp = wp; |
185 | } |
186 | |
187 | void |
188 | layout_make_node(struct layout_cell *lc, enum layout_type type) |
189 | { |
190 | if (type == LAYOUT_WINDOWPANE) |
191 | fatalx("bad layout type" ); |
192 | lc->type = type; |
193 | |
194 | TAILQ_INIT(&lc->cells); |
195 | |
196 | if (lc->wp != NULL) |
197 | lc->wp->layout_cell = NULL; |
198 | lc->wp = NULL; |
199 | } |
200 | |
201 | /* Fix cell offsets for a child cell. */ |
202 | static void |
203 | layout_fix_offsets1(struct layout_cell *lc) |
204 | { |
205 | struct layout_cell *lcchild; |
206 | u_int xoff, yoff; |
207 | |
208 | if (lc->type == LAYOUT_LEFTRIGHT) { |
209 | xoff = lc->xoff; |
210 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
211 | lcchild->xoff = xoff; |
212 | lcchild->yoff = lc->yoff; |
213 | if (lcchild->type != LAYOUT_WINDOWPANE) |
214 | layout_fix_offsets1(lcchild); |
215 | xoff += lcchild->sx + 1; |
216 | } |
217 | } else { |
218 | yoff = lc->yoff; |
219 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
220 | lcchild->xoff = lc->xoff; |
221 | lcchild->yoff = yoff; |
222 | if (lcchild->type != LAYOUT_WINDOWPANE) |
223 | layout_fix_offsets1(lcchild); |
224 | yoff += lcchild->sy + 1; |
225 | } |
226 | } |
227 | } |
228 | |
229 | /* Update cell offsets based on their sizes. */ |
230 | void |
231 | layout_fix_offsets(struct window *w) |
232 | { |
233 | struct layout_cell *lc = w->layout_root; |
234 | |
235 | lc->xoff = 0; |
236 | lc->yoff = 0; |
237 | |
238 | layout_fix_offsets1(lc); |
239 | } |
240 | |
241 | /* Is this a top cell? */ |
242 | static int |
243 | layout_cell_is_top(struct window *w, struct layout_cell *lc) |
244 | { |
245 | struct layout_cell *next; |
246 | |
247 | while (lc != w->layout_root) { |
248 | next = lc->parent; |
249 | if (next->type == LAYOUT_TOPBOTTOM && |
250 | lc != TAILQ_FIRST(&next->cells)) |
251 | return (0); |
252 | lc = next; |
253 | } |
254 | return (1); |
255 | } |
256 | |
257 | /* Is this a bottom cell? */ |
258 | static int |
259 | layout_cell_is_bottom(struct window *w, struct layout_cell *lc) |
260 | { |
261 | struct layout_cell *next; |
262 | |
263 | while (lc != w->layout_root) { |
264 | next = lc->parent; |
265 | if (next->type == LAYOUT_TOPBOTTOM && |
266 | lc != TAILQ_LAST(&next->cells, layout_cells)) |
267 | return (0); |
268 | lc = next; |
269 | } |
270 | return (1); |
271 | } |
272 | |
273 | /* |
274 | * Returns 1 if we need to add an extra line for the pane status line. This is |
275 | * the case for the most upper or lower panes only. |
276 | */ |
277 | static int |
278 | layout_add_border(struct window *w, struct layout_cell *lc, int status) |
279 | { |
280 | if (status == PANE_STATUS_TOP) |
281 | return (layout_cell_is_top(w, lc)); |
282 | if (status == PANE_STATUS_BOTTOM) |
283 | return (layout_cell_is_bottom(w, lc)); |
284 | return (0); |
285 | } |
286 | |
287 | /* Update pane offsets and sizes based on their cells. */ |
288 | void |
289 | layout_fix_panes(struct window *w) |
290 | { |
291 | struct window_pane *wp; |
292 | struct layout_cell *lc; |
293 | int status; |
294 | |
295 | status = options_get_number(w->options, "pane-border-status" ); |
296 | TAILQ_FOREACH(wp, &w->panes, entry) { |
297 | if ((lc = wp->layout_cell) == NULL) |
298 | continue; |
299 | |
300 | wp->xoff = lc->xoff; |
301 | wp->yoff = lc->yoff; |
302 | |
303 | if (layout_add_border(w, lc, status)) { |
304 | if (status == PANE_STATUS_TOP) |
305 | wp->yoff++; |
306 | window_pane_resize(wp, lc->sx, lc->sy - 1); |
307 | } else |
308 | window_pane_resize(wp, lc->sx, lc->sy); |
309 | } |
310 | } |
311 | |
312 | /* Count the number of available cells in a layout. */ |
313 | u_int |
314 | layout_count_cells(struct layout_cell *lc) |
315 | { |
316 | struct layout_cell *lcchild; |
317 | u_int count; |
318 | |
319 | switch (lc->type) { |
320 | case LAYOUT_WINDOWPANE: |
321 | return (1); |
322 | case LAYOUT_LEFTRIGHT: |
323 | case LAYOUT_TOPBOTTOM: |
324 | count = 0; |
325 | TAILQ_FOREACH(lcchild, &lc->cells, entry) |
326 | count += layout_count_cells(lcchild); |
327 | return (count); |
328 | default: |
329 | fatalx("bad layout type" ); |
330 | } |
331 | } |
332 | |
333 | /* Calculate how much size is available to be removed from a cell. */ |
334 | static u_int |
335 | layout_resize_check(struct window *w, struct layout_cell *lc, |
336 | enum layout_type type) |
337 | { |
338 | struct layout_cell *lcchild; |
339 | u_int available, minimum; |
340 | int status; |
341 | |
342 | status = options_get_number(w->options, "pane-border-status" ); |
343 | if (lc->type == LAYOUT_WINDOWPANE) { |
344 | /* Space available in this cell only. */ |
345 | if (type == LAYOUT_LEFTRIGHT) { |
346 | available = lc->sx; |
347 | minimum = PANE_MINIMUM; |
348 | } else { |
349 | available = lc->sy; |
350 | if (layout_add_border(w, lc, status)) |
351 | minimum = PANE_MINIMUM + 1; |
352 | else |
353 | minimum = PANE_MINIMUM; |
354 | } |
355 | if (available > minimum) |
356 | available -= minimum; |
357 | else |
358 | available = 0; |
359 | } else if (lc->type == type) { |
360 | /* Same type: total of available space in all child cells. */ |
361 | available = 0; |
362 | TAILQ_FOREACH(lcchild, &lc->cells, entry) |
363 | available += layout_resize_check(w, lcchild, type); |
364 | } else { |
365 | /* Different type: minimum of available space in child cells. */ |
366 | minimum = UINT_MAX; |
367 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
368 | available = layout_resize_check(w, lcchild, type); |
369 | if (available < minimum) |
370 | minimum = available; |
371 | } |
372 | available = minimum; |
373 | } |
374 | |
375 | return (available); |
376 | } |
377 | |
378 | /* |
379 | * Adjust cell size evenly, including altering its children. This function |
380 | * expects the change to have already been bounded to the space available. |
381 | */ |
382 | void |
383 | layout_resize_adjust(struct window *w, struct layout_cell *lc, |
384 | enum layout_type type, int change) |
385 | { |
386 | struct layout_cell *lcchild; |
387 | |
388 | /* Adjust the cell size. */ |
389 | if (type == LAYOUT_LEFTRIGHT) |
390 | lc->sx += change; |
391 | else |
392 | lc->sy += change; |
393 | |
394 | /* If this is a leaf cell, that is all that is necessary. */ |
395 | if (type == LAYOUT_WINDOWPANE) |
396 | return; |
397 | |
398 | /* Child cell runs in a different direction. */ |
399 | if (lc->type != type) { |
400 | TAILQ_FOREACH(lcchild, &lc->cells, entry) |
401 | layout_resize_adjust(w, lcchild, type, change); |
402 | return; |
403 | } |
404 | |
405 | /* |
406 | * Child cell runs in the same direction. Adjust each child equally |
407 | * until no further change is possible. |
408 | */ |
409 | while (change != 0) { |
410 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
411 | if (change == 0) |
412 | break; |
413 | if (change > 0) { |
414 | layout_resize_adjust(w, lcchild, type, 1); |
415 | change--; |
416 | continue; |
417 | } |
418 | if (layout_resize_check(w, lcchild, type) > 0) { |
419 | layout_resize_adjust(w, lcchild, type, -1); |
420 | change++; |
421 | } |
422 | } |
423 | } |
424 | } |
425 | |
426 | /* Destroy a cell and redistribute the space. */ |
427 | void |
428 | layout_destroy_cell(struct window *w, struct layout_cell *lc, |
429 | struct layout_cell **lcroot) |
430 | { |
431 | struct layout_cell *lcother, *lcparent; |
432 | |
433 | /* |
434 | * If no parent, this is the last pane so window close is imminent and |
435 | * there is no need to resize anything. |
436 | */ |
437 | lcparent = lc->parent; |
438 | if (lcparent == NULL) { |
439 | layout_free_cell(lc); |
440 | *lcroot = NULL; |
441 | return; |
442 | } |
443 | |
444 | /* Merge the space into the previous or next cell. */ |
445 | if (lc == TAILQ_FIRST(&lcparent->cells)) |
446 | lcother = TAILQ_NEXT(lc, entry); |
447 | else |
448 | lcother = TAILQ_PREV(lc, layout_cells, entry); |
449 | if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) |
450 | layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); |
451 | else if (lcother != NULL) |
452 | layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); |
453 | |
454 | /* Remove this from the parent's list. */ |
455 | TAILQ_REMOVE(&lcparent->cells, lc, entry); |
456 | layout_free_cell(lc); |
457 | |
458 | /* |
459 | * If the parent now has one cell, remove the parent from the tree and |
460 | * replace it by that cell. |
461 | */ |
462 | lc = TAILQ_FIRST(&lcparent->cells); |
463 | if (TAILQ_NEXT(lc, entry) == NULL) { |
464 | TAILQ_REMOVE(&lcparent->cells, lc, entry); |
465 | |
466 | lc->parent = lcparent->parent; |
467 | if (lc->parent == NULL) { |
468 | lc->xoff = 0; lc->yoff = 0; |
469 | *lcroot = lc; |
470 | } else |
471 | TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry); |
472 | |
473 | layout_free_cell(lcparent); |
474 | } |
475 | } |
476 | |
477 | void |
478 | layout_init(struct window *w, struct window_pane *wp) |
479 | { |
480 | struct layout_cell *lc; |
481 | |
482 | lc = w->layout_root = layout_create_cell(NULL); |
483 | layout_set_size(lc, w->sx, w->sy, 0, 0); |
484 | layout_make_leaf(lc, wp); |
485 | layout_fix_panes(w); |
486 | } |
487 | |
488 | void |
489 | layout_free(struct window *w) |
490 | { |
491 | layout_free_cell(w->layout_root); |
492 | } |
493 | |
494 | /* Resize the entire layout after window resize. */ |
495 | void |
496 | layout_resize(struct window *w, u_int sx, u_int sy) |
497 | { |
498 | struct layout_cell *lc = w->layout_root; |
499 | int xlimit, ylimit, xchange, ychange; |
500 | |
501 | /* |
502 | * Adjust horizontally. Do not attempt to reduce the layout lower than |
503 | * the minimum (more than the amount returned by layout_resize_check). |
504 | * |
505 | * This can mean that the window size is smaller than the total layout |
506 | * size: redrawing this is handled at a higher level, but it does leave |
507 | * a problem with growing the window size here: if the current size is |
508 | * < the minimum, growing proportionately by adding to each pane is |
509 | * wrong as it would keep the layout size larger than the window size. |
510 | * Instead, spread the difference between the minimum and the new size |
511 | * out proportionately - this should leave the layout fitting the new |
512 | * window size. |
513 | */ |
514 | xchange = sx - lc->sx; |
515 | xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); |
516 | if (xchange < 0 && xchange < -xlimit) |
517 | xchange = -xlimit; |
518 | if (xlimit == 0) { |
519 | if (sx <= lc->sx) /* lc->sx is minimum possible */ |
520 | xchange = 0; |
521 | else |
522 | xchange = sx - lc->sx; |
523 | } |
524 | if (xchange != 0) |
525 | layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, xchange); |
526 | |
527 | /* Adjust vertically in a similar fashion. */ |
528 | ychange = sy - lc->sy; |
529 | ylimit = layout_resize_check(w, lc, LAYOUT_TOPBOTTOM); |
530 | if (ychange < 0 && ychange < -ylimit) |
531 | ychange = -ylimit; |
532 | if (ylimit == 0) { |
533 | if (sy <= lc->sy) /* lc->sy is minimum possible */ |
534 | ychange = 0; |
535 | else |
536 | ychange = sy - lc->sy; |
537 | } |
538 | if (ychange != 0) |
539 | layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, ychange); |
540 | |
541 | /* Fix cell offsets. */ |
542 | layout_fix_offsets(w); |
543 | layout_fix_panes(w); |
544 | } |
545 | |
546 | /* Resize a pane to an absolute size. */ |
547 | void |
548 | layout_resize_pane_to(struct window_pane *wp, enum layout_type type, |
549 | u_int new_size) |
550 | { |
551 | struct layout_cell *lc, *lcparent; |
552 | int change, size; |
553 | |
554 | lc = wp->layout_cell; |
555 | |
556 | /* Find next parent of the same type. */ |
557 | lcparent = lc->parent; |
558 | while (lcparent != NULL && lcparent->type != type) { |
559 | lc = lcparent; |
560 | lcparent = lc->parent; |
561 | } |
562 | if (lcparent == NULL) |
563 | return; |
564 | |
565 | /* Work out the size adjustment. */ |
566 | if (type == LAYOUT_LEFTRIGHT) |
567 | size = lc->sx; |
568 | else |
569 | size = lc->sy; |
570 | if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) |
571 | change = size - new_size; |
572 | else |
573 | change = new_size - size; |
574 | |
575 | /* Resize the pane. */ |
576 | layout_resize_pane(wp, type, change, 1); |
577 | } |
578 | |
579 | void |
580 | layout_resize_layout(struct window *w, struct layout_cell *lc, |
581 | enum layout_type type, int change, int opposite) |
582 | { |
583 | int needed, size; |
584 | |
585 | /* Grow or shrink the cell. */ |
586 | needed = change; |
587 | while (needed != 0) { |
588 | if (change > 0) { |
589 | size = layout_resize_pane_grow(w, lc, type, needed, |
590 | opposite); |
591 | needed -= size; |
592 | } else { |
593 | size = layout_resize_pane_shrink(w, lc, type, needed); |
594 | needed += size; |
595 | } |
596 | |
597 | if (size == 0) /* no more change possible */ |
598 | break; |
599 | } |
600 | |
601 | /* Fix cell offsets. */ |
602 | layout_fix_offsets(w); |
603 | layout_fix_panes(w); |
604 | notify_window("window-layout-changed" , w); |
605 | } |
606 | |
607 | /* Resize a single pane within the layout. */ |
608 | void |
609 | layout_resize_pane(struct window_pane *wp, enum layout_type type, int change, |
610 | int opposite) |
611 | { |
612 | struct layout_cell *lc, *lcparent; |
613 | |
614 | lc = wp->layout_cell; |
615 | |
616 | /* Find next parent of the same type. */ |
617 | lcparent = lc->parent; |
618 | while (lcparent != NULL && lcparent->type != type) { |
619 | lc = lcparent; |
620 | lcparent = lc->parent; |
621 | } |
622 | if (lcparent == NULL) |
623 | return; |
624 | |
625 | /* If this is the last cell, move back one. */ |
626 | if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) |
627 | lc = TAILQ_PREV(lc, layout_cells, entry); |
628 | |
629 | layout_resize_layout(wp->window, lc, type, change, opposite); |
630 | } |
631 | |
632 | /* Helper function to grow pane. */ |
633 | static int |
634 | layout_resize_pane_grow(struct window *w, struct layout_cell *lc, |
635 | enum layout_type type, int needed, int opposite) |
636 | { |
637 | struct layout_cell *lcadd, *lcremove; |
638 | u_int size = 0; |
639 | |
640 | /* Growing. Always add to the current cell. */ |
641 | lcadd = lc; |
642 | |
643 | /* Look towards the tail for a suitable cell for reduction. */ |
644 | lcremove = TAILQ_NEXT(lc, entry); |
645 | while (lcremove != NULL) { |
646 | size = layout_resize_check(w, lcremove, type); |
647 | if (size > 0) |
648 | break; |
649 | lcremove = TAILQ_NEXT(lcremove, entry); |
650 | } |
651 | |
652 | /* If none found, look towards the head. */ |
653 | if (opposite && lcremove == NULL) { |
654 | lcremove = TAILQ_PREV(lc, layout_cells, entry); |
655 | while (lcremove != NULL) { |
656 | size = layout_resize_check(w, lcremove, type); |
657 | if (size > 0) |
658 | break; |
659 | lcremove = TAILQ_PREV(lcremove, layout_cells, entry); |
660 | } |
661 | } |
662 | if (lcremove == NULL) |
663 | return (0); |
664 | |
665 | /* Change the cells. */ |
666 | if (size > (u_int) needed) |
667 | size = needed; |
668 | layout_resize_adjust(w, lcadd, type, size); |
669 | layout_resize_adjust(w, lcremove, type, -size); |
670 | return (size); |
671 | } |
672 | |
673 | /* Helper function to shrink pane. */ |
674 | static int |
675 | layout_resize_pane_shrink(struct window *w, struct layout_cell *lc, |
676 | enum layout_type type, int needed) |
677 | { |
678 | struct layout_cell *lcadd, *lcremove; |
679 | u_int size; |
680 | |
681 | /* Shrinking. Find cell to remove from by walking towards head. */ |
682 | lcremove = lc; |
683 | do { |
684 | size = layout_resize_check(w, lcremove, type); |
685 | if (size != 0) |
686 | break; |
687 | lcremove = TAILQ_PREV(lcremove, layout_cells, entry); |
688 | } while (lcremove != NULL); |
689 | if (lcremove == NULL) |
690 | return (0); |
691 | |
692 | /* And add onto the next cell (from the original cell). */ |
693 | lcadd = TAILQ_NEXT(lc, entry); |
694 | if (lcadd == NULL) |
695 | return (0); |
696 | |
697 | /* Change the cells. */ |
698 | if (size > (u_int) -needed) |
699 | size = -needed; |
700 | layout_resize_adjust(w, lcadd, type, size); |
701 | layout_resize_adjust(w, lcremove, type, -size); |
702 | return (size); |
703 | } |
704 | |
705 | /* Assign window pane to newly split cell. */ |
706 | void |
707 | layout_assign_pane(struct layout_cell *lc, struct window_pane *wp) |
708 | { |
709 | layout_make_leaf(lc, wp); |
710 | layout_fix_panes(wp->window); |
711 | } |
712 | |
713 | /* Calculate the new pane size for resized parent. */ |
714 | static u_int |
715 | layout_new_pane_size(struct window *w, u_int previous, struct layout_cell *lc, |
716 | enum layout_type type, u_int size, u_int count_left, u_int size_left) |
717 | { |
718 | u_int new_size, min, max, available; |
719 | |
720 | /* If this is the last cell, it can take all of the remaining size. */ |
721 | if (count_left == 1) |
722 | return (size_left); |
723 | |
724 | /* How much is available in this parent? */ |
725 | available = layout_resize_check(w, lc, type); |
726 | |
727 | /* |
728 | * Work out the minimum size of this cell and the new size |
729 | * proportionate to the previous size. |
730 | */ |
731 | min = (PANE_MINIMUM + 1) * (count_left - 1); |
732 | if (type == LAYOUT_LEFTRIGHT) { |
733 | if (lc->sx - available > min) |
734 | min = lc->sx - available; |
735 | new_size = (lc->sx * size) / previous; |
736 | } else { |
737 | if (lc->sy - available > min) |
738 | min = lc->sy - available; |
739 | new_size = (lc->sy * size) / previous; |
740 | } |
741 | |
742 | /* Check against the maximum and minimum size. */ |
743 | max = size_left - min; |
744 | if (new_size > max) |
745 | new_size = max; |
746 | if (new_size < PANE_MINIMUM) |
747 | new_size = PANE_MINIMUM; |
748 | return (new_size); |
749 | } |
750 | |
751 | /* Check if the cell and all its children can be resized to a specific size. */ |
752 | static int |
753 | layout_set_size_check(struct window *w, struct layout_cell *lc, |
754 | enum layout_type type, int size) |
755 | { |
756 | struct layout_cell *lcchild; |
757 | u_int new_size, available, previous, count, idx; |
758 | |
759 | /* Cells with no children must just be bigger than minimum. */ |
760 | if (lc->type == LAYOUT_WINDOWPANE) |
761 | return (size >= PANE_MINIMUM); |
762 | available = size; |
763 | |
764 | /* Count number of children. */ |
765 | count = 0; |
766 | TAILQ_FOREACH(lcchild, &lc->cells, entry) |
767 | count++; |
768 | |
769 | /* Check new size will work for each child. */ |
770 | if (lc->type == type) { |
771 | if (available < (count * 2) - 1) |
772 | return (0); |
773 | |
774 | if (type == LAYOUT_LEFTRIGHT) |
775 | previous = lc->sx; |
776 | else |
777 | previous = lc->sy; |
778 | |
779 | idx = 0; |
780 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
781 | new_size = layout_new_pane_size(w, previous, lcchild, |
782 | type, size, count - idx, available); |
783 | if (idx == count - 1) { |
784 | if (new_size > available) |
785 | return (0); |
786 | available -= new_size; |
787 | } else { |
788 | if (new_size + 1 > available) |
789 | return (0); |
790 | available -= new_size + 1; |
791 | } |
792 | if (!layout_set_size_check(w, lcchild, type, new_size)) |
793 | return (0); |
794 | idx++; |
795 | } |
796 | } else { |
797 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
798 | if (lcchild->type == LAYOUT_WINDOWPANE) |
799 | continue; |
800 | if (!layout_set_size_check(w, lcchild, type, size)) |
801 | return (0); |
802 | } |
803 | } |
804 | |
805 | return (1); |
806 | } |
807 | |
808 | /* Resize all child cells to fit within the current cell. */ |
809 | static void |
810 | layout_resize_child_cells(struct window *w, struct layout_cell *lc) |
811 | { |
812 | struct layout_cell *lcchild; |
813 | u_int previous, available, count, idx; |
814 | |
815 | if (lc->type == LAYOUT_WINDOWPANE) |
816 | return; |
817 | |
818 | /* What is the current size used? */ |
819 | count = 0; |
820 | previous = 0; |
821 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
822 | count++; |
823 | if (lc->type == LAYOUT_LEFTRIGHT) |
824 | previous += lcchild->sx; |
825 | else if (lc->type == LAYOUT_TOPBOTTOM) |
826 | previous += lcchild->sy; |
827 | } |
828 | previous += (count - 1); |
829 | |
830 | /* And how much is available? */ |
831 | available = 0; |
832 | if (lc->type == LAYOUT_LEFTRIGHT) |
833 | available = lc->sx; |
834 | else if (lc->type == LAYOUT_TOPBOTTOM) |
835 | available = lc->sy; |
836 | |
837 | /* Resize children into the new size. */ |
838 | idx = 0; |
839 | TAILQ_FOREACH(lcchild, &lc->cells, entry) { |
840 | if (lc->type == LAYOUT_TOPBOTTOM) { |
841 | lcchild->sx = lc->sx; |
842 | lcchild->xoff = lc->xoff; |
843 | } else { |
844 | lcchild->sx = layout_new_pane_size(w, previous, lcchild, |
845 | lc->type, lc->sx, count - idx, available); |
846 | available -= (lcchild->sx + 1); |
847 | } |
848 | if (lc->type == LAYOUT_LEFTRIGHT) |
849 | lcchild->sy = lc->sy; |
850 | else { |
851 | lcchild->sy = layout_new_pane_size(w, previous, lcchild, |
852 | lc->type, lc->sy, count - idx, available); |
853 | available -= (lcchild->sy + 1); |
854 | } |
855 | layout_resize_child_cells(w, lcchild); |
856 | idx++; |
857 | } |
858 | } |
859 | |
860 | /* |
861 | * Split a pane into two. size is a hint, or -1 for default half/half |
862 | * split. This must be followed by layout_assign_pane before much else happens! |
863 | */ |
864 | struct layout_cell * |
865 | layout_split_pane(struct window_pane *wp, enum layout_type type, int size, |
866 | int flags) |
867 | { |
868 | struct layout_cell *lc, *lcparent, *lcnew, *lc1, *lc2; |
869 | u_int sx, sy, xoff, yoff, size1, size2, minimum; |
870 | u_int new_size, saved_size, resize_first = 0; |
871 | int full_size = (flags & SPAWN_FULLSIZE), status; |
872 | |
873 | /* |
874 | * If full_size is specified, add a new cell at the top of the window |
875 | * layout. Otherwise, split the cell for the current pane. |
876 | */ |
877 | if (full_size) |
878 | lc = wp->window->layout_root; |
879 | else |
880 | lc = wp->layout_cell; |
881 | status = options_get_number(wp->window->options, "pane-border-status" ); |
882 | |
883 | /* Copy the old cell size. */ |
884 | sx = lc->sx; |
885 | sy = lc->sy; |
886 | xoff = lc->xoff; |
887 | yoff = lc->yoff; |
888 | |
889 | /* Check there is enough space for the two new panes. */ |
890 | switch (type) { |
891 | case LAYOUT_LEFTRIGHT: |
892 | if (sx < PANE_MINIMUM * 2 + 1) |
893 | return (NULL); |
894 | break; |
895 | case LAYOUT_TOPBOTTOM: |
896 | if (layout_add_border(wp->window, lc, status)) |
897 | minimum = PANE_MINIMUM * 2 + 2; |
898 | else |
899 | minimum = PANE_MINIMUM * 2 + 1; |
900 | if (sy < minimum) |
901 | return (NULL); |
902 | break; |
903 | default: |
904 | fatalx("bad layout type" ); |
905 | } |
906 | |
907 | /* |
908 | * Calculate new cell sizes. size is the target size or -1 for middle |
909 | * split, size1 is the size of the top/left and size2 the bottom/right. |
910 | */ |
911 | if (type == LAYOUT_LEFTRIGHT) |
912 | saved_size = sx; |
913 | else |
914 | saved_size = sy; |
915 | if (size < 0) |
916 | size2 = ((saved_size + 1) / 2) - 1; |
917 | else if (flags & SPAWN_BEFORE) |
918 | size2 = saved_size - size - 1; |
919 | else |
920 | size2 = size; |
921 | if (size2 < PANE_MINIMUM) |
922 | size2 = PANE_MINIMUM; |
923 | else if (size2 > saved_size - 2) |
924 | size2 = saved_size - 2; |
925 | size1 = saved_size - 1 - size2; |
926 | |
927 | /* Which size are we using? */ |
928 | if (flags & SPAWN_BEFORE) |
929 | new_size = size2; |
930 | else |
931 | new_size = size1; |
932 | |
933 | /* Confirm there is enough space for full size pane. */ |
934 | if (full_size && !layout_set_size_check(wp->window, lc, type, new_size)) |
935 | return (NULL); |
936 | |
937 | if (lc->parent != NULL && lc->parent->type == type) { |
938 | /* |
939 | * If the parent exists and is of the same type as the split, |
940 | * create a new cell and insert it after this one. |
941 | */ |
942 | lcparent = lc->parent; |
943 | lcnew = layout_create_cell(lcparent); |
944 | if (flags & SPAWN_BEFORE) |
945 | TAILQ_INSERT_BEFORE(lc, lcnew, entry); |
946 | else |
947 | TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry); |
948 | } else if (full_size && lc->parent == NULL && lc->type == type) { |
949 | /* |
950 | * If the new full size pane is the same type as the root |
951 | * split, insert the new pane under the existing root cell |
952 | * instead of creating a new root cell. The existing layout |
953 | * must be resized before inserting the new cell. |
954 | */ |
955 | if (lc->type == LAYOUT_LEFTRIGHT) { |
956 | lc->sx = new_size; |
957 | layout_resize_child_cells(wp->window, lc); |
958 | lc->sx = saved_size; |
959 | } else if (lc->type == LAYOUT_TOPBOTTOM) { |
960 | lc->sy = new_size; |
961 | layout_resize_child_cells(wp->window, lc); |
962 | lc->sy = saved_size; |
963 | } |
964 | resize_first = 1; |
965 | |
966 | /* Create the new cell. */ |
967 | lcnew = layout_create_cell(lc); |
968 | size = saved_size - 1 - new_size; |
969 | if (lc->type == LAYOUT_LEFTRIGHT) |
970 | layout_set_size(lcnew, size, sy, 0, 0); |
971 | else if (lc->type == LAYOUT_TOPBOTTOM) |
972 | layout_set_size(lcnew, sx, size, 0, 0); |
973 | if (flags & SPAWN_BEFORE) |
974 | TAILQ_INSERT_HEAD(&lc->cells, lcnew, entry); |
975 | else |
976 | TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry); |
977 | } else { |
978 | /* |
979 | * Otherwise create a new parent and insert it. |
980 | */ |
981 | |
982 | /* Create and insert the replacement parent. */ |
983 | lcparent = layout_create_cell(lc->parent); |
984 | layout_make_node(lcparent, type); |
985 | layout_set_size(lcparent, sx, sy, xoff, yoff); |
986 | if (lc->parent == NULL) |
987 | wp->window->layout_root = lcparent; |
988 | else |
989 | TAILQ_REPLACE(&lc->parent->cells, lc, lcparent, entry); |
990 | |
991 | /* Insert the old cell. */ |
992 | lc->parent = lcparent; |
993 | TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); |
994 | |
995 | /* Create the new child cell. */ |
996 | lcnew = layout_create_cell(lcparent); |
997 | if (flags & SPAWN_BEFORE) |
998 | TAILQ_INSERT_HEAD(&lcparent->cells, lcnew, entry); |
999 | else |
1000 | TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry); |
1001 | } |
1002 | if (flags & SPAWN_BEFORE) { |
1003 | lc1 = lcnew; |
1004 | lc2 = lc; |
1005 | } else { |
1006 | lc1 = lc; |
1007 | lc2 = lcnew; |
1008 | } |
1009 | |
1010 | /* |
1011 | * Set new cell sizes. size1 is the size of the top/left and size2 the |
1012 | * bottom/right. |
1013 | */ |
1014 | if (!resize_first && type == LAYOUT_LEFTRIGHT) { |
1015 | layout_set_size(lc1, size1, sy, xoff, yoff); |
1016 | layout_set_size(lc2, size2, sy, xoff + lc1->sx + 1, yoff); |
1017 | } else if (!resize_first && type == LAYOUT_TOPBOTTOM) { |
1018 | layout_set_size(lc1, sx, size1, xoff, yoff); |
1019 | layout_set_size(lc2, sx, size2, xoff, yoff + lc1->sy + 1); |
1020 | } |
1021 | if (full_size) { |
1022 | if (!resize_first) |
1023 | layout_resize_child_cells(wp->window, lc); |
1024 | layout_fix_offsets(wp->window); |
1025 | } else |
1026 | layout_make_leaf(lc, wp); |
1027 | |
1028 | return (lcnew); |
1029 | } |
1030 | |
1031 | /* Destroy the cell associated with a pane. */ |
1032 | void |
1033 | layout_close_pane(struct window_pane *wp) |
1034 | { |
1035 | struct window *w = wp->window; |
1036 | |
1037 | /* Remove the cell. */ |
1038 | layout_destroy_cell(w, wp->layout_cell, &w->layout_root); |
1039 | |
1040 | /* Fix pane offsets and sizes. */ |
1041 | if (w->layout_root != NULL) { |
1042 | layout_fix_offsets(w); |
1043 | layout_fix_panes(w); |
1044 | } |
1045 | notify_window("window-layout-changed" , w); |
1046 | } |
1047 | |
1048 | int |
1049 | layout_spread_cell(struct window *w, struct layout_cell *parent) |
1050 | { |
1051 | struct layout_cell *lc; |
1052 | u_int number, each, size, this; |
1053 | int change, changed, status; |
1054 | |
1055 | number = 0; |
1056 | TAILQ_FOREACH (lc, &parent->cells, entry) |
1057 | number++; |
1058 | if (number <= 1) |
1059 | return (0); |
1060 | status = options_get_number(w->options, "pane-border-status" ); |
1061 | |
1062 | if (parent->type == LAYOUT_LEFTRIGHT) |
1063 | size = parent->sx; |
1064 | else if (parent->type == LAYOUT_TOPBOTTOM) { |
1065 | if (layout_add_border(w, parent, status)) |
1066 | size = parent->sy - 1; |
1067 | else |
1068 | size = parent->sy; |
1069 | } else |
1070 | return (0); |
1071 | if (size < number - 1) |
1072 | return (0); |
1073 | each = (size - (number - 1)) / number; |
1074 | if (each == 0) |
1075 | return (0); |
1076 | |
1077 | changed = 0; |
1078 | TAILQ_FOREACH (lc, &parent->cells, entry) { |
1079 | if (TAILQ_NEXT(lc, entry) == NULL) |
1080 | each = size - ((each + 1) * (number - 1)); |
1081 | change = 0; |
1082 | if (parent->type == LAYOUT_LEFTRIGHT) { |
1083 | change = each - (int)lc->sx; |
1084 | layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, change); |
1085 | } else if (parent->type == LAYOUT_TOPBOTTOM) { |
1086 | if (layout_add_border(w, lc, status)) |
1087 | this = each + 1; |
1088 | else |
1089 | this = each; |
1090 | change = this - (int)lc->sy; |
1091 | layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, change); |
1092 | } |
1093 | if (change != 0) |
1094 | changed = 1; |
1095 | } |
1096 | return (changed); |
1097 | } |
1098 | |
1099 | void |
1100 | layout_spread_out(struct window_pane *wp) |
1101 | { |
1102 | struct layout_cell *parent; |
1103 | struct window *w = wp->window; |
1104 | |
1105 | parent = wp->layout_cell->parent; |
1106 | if (parent == NULL) |
1107 | return; |
1108 | |
1109 | do { |
1110 | if (layout_spread_cell(w, parent)) { |
1111 | layout_fix_offsets(w); |
1112 | layout_fix_panes(w); |
1113 | break; |
1114 | } |
1115 | } while ((parent = parent->parent) != NULL); |
1116 | } |
1117 | |