xref: /openbsd-src/usr.bin/tmux/layout-set.c (revision d4ddf7e1665cc214d55aeaa38156b8cf76b4c8bf)
1 /* $OpenBSD: layout-set.c,v 1.23 2019/04/17 14:43:49 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <string.h>
22 
23 #include "tmux.h"
24 
25 /*
26  * Set window layouts - predefined methods to arrange windows. These are
27  * one-off and generate a layout tree.
28  */
29 
30 static void	layout_set_even_h(struct window *);
31 static void	layout_set_even_v(struct window *);
32 static void	layout_set_main_h(struct window *);
33 static void	layout_set_main_v(struct window *);
34 static void	layout_set_tiled(struct window *);
35 
36 static const struct {
37 	const char	*name;
38 	void	      	(*arrange)(struct window *);
39 } layout_sets[] = {
40 	{ "even-horizontal", layout_set_even_h },
41 	{ "even-vertical", layout_set_even_v },
42 	{ "main-horizontal", layout_set_main_h },
43 	{ "main-vertical", layout_set_main_v },
44 	{ "tiled", layout_set_tiled },
45 };
46 
47 int
48 layout_set_lookup(const char *name)
49 {
50 	u_int	i;
51 	int	matched = -1;
52 
53 	for (i = 0; i < nitems(layout_sets); i++) {
54 		if (strncmp(layout_sets[i].name, name, strlen(name)) == 0) {
55 			if (matched != -1)	/* ambiguous */
56 				return (-1);
57 			matched = i;
58 		}
59 	}
60 
61 	return (matched);
62 }
63 
64 u_int
65 layout_set_select(struct window *w, u_int layout)
66 {
67 	if (layout > nitems(layout_sets) - 1)
68 		layout = nitems(layout_sets) - 1;
69 
70 	if (layout_sets[layout].arrange != NULL)
71 		layout_sets[layout].arrange(w);
72 
73 	w->lastlayout = layout;
74 	return (layout);
75 }
76 
77 u_int
78 layout_set_next(struct window *w)
79 {
80 	u_int	layout;
81 
82 	if (w->lastlayout == -1)
83 		layout = 0;
84 	else {
85 		layout = w->lastlayout + 1;
86 		if (layout > nitems(layout_sets) - 1)
87 			layout = 0;
88 	}
89 
90 	if (layout_sets[layout].arrange != NULL)
91 		layout_sets[layout].arrange(w);
92 	w->lastlayout = layout;
93 	return (layout);
94 }
95 
96 u_int
97 layout_set_previous(struct window *w)
98 {
99 	u_int	layout;
100 
101 	if (w->lastlayout == -1)
102 		layout = nitems(layout_sets) - 1;
103 	else {
104 		layout = w->lastlayout;
105 		if (layout == 0)
106 			layout = nitems(layout_sets) - 1;
107 		else
108 			layout--;
109 	}
110 
111 	if (layout_sets[layout].arrange != NULL)
112 		layout_sets[layout].arrange(w);
113 	w->lastlayout = layout;
114 	return (layout);
115 }
116 
117 static void
118 layout_set_even(struct window *w, enum layout_type type)
119 {
120 	struct window_pane	*wp;
121 	struct layout_cell	*lc, *lcnew;
122 	u_int			 n, sx, sy;
123 
124 	layout_print_cell(w->layout_root, __func__, 1);
125 
126 	/* Get number of panes. */
127 	n = window_count_panes(w);
128 	if (n <= 1)
129 		return;
130 
131 	/* Free the old root and construct a new. */
132 	layout_free(w);
133 	lc = w->layout_root = layout_create_cell(NULL);
134 	if (type == LAYOUT_LEFTRIGHT) {
135 		sx = (n * (PANE_MINIMUM + 1)) - 1;
136 		if (sx < w->sx)
137 			sx = w->sx;
138 		sy = w->sy;
139 	} else {
140 		sy = (n * (PANE_MINIMUM + 1)) - 1;
141 		if (sy < w->sy)
142 			sy = w->sy;
143 		sx = w->sx;
144 	}
145 	layout_set_size(lc, sx, sy, 0, 0);
146 	layout_make_node(lc, type);
147 
148 	/* Build new leaf cells. */
149 	TAILQ_FOREACH(wp, &w->panes, entry) {
150 		lcnew = layout_create_cell(lc);
151 		layout_make_leaf(lcnew, wp);
152 		lcnew->sx = w->sx;
153 		lcnew->sy = w->sy;
154 		TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
155 	}
156 
157 	/* Spread out cells. */
158 	layout_spread_cell(w, lc);
159 
160 	/* Fix cell offsets. */
161 	layout_fix_offsets(lc);
162 	layout_fix_panes(w);
163 
164 	layout_print_cell(w->layout_root, __func__, 1);
165 
166 	window_resize(w, lc->sx, lc->sy);
167 	notify_window("window-layout-changed", w);
168 	server_redraw_window(w);
169 }
170 
171 static void
172 layout_set_even_h(struct window *w)
173 {
174 	layout_set_even(w, LAYOUT_LEFTRIGHT);
175 }
176 
177 static void
178 layout_set_even_v(struct window *w)
179 {
180 	layout_set_even(w, LAYOUT_TOPBOTTOM);
181 }
182 
183 static void
184 layout_set_main_h(struct window *w)
185 {
186 	struct window_pane	*wp;
187 	struct layout_cell	*lc, *lcmain, *lcrow, *lcchild;
188 	u_int			 n, mainheight, otherheight, width, height;
189 	u_int			 used, i, j, columns, rows, totalrows;
190 
191 	layout_print_cell(w->layout_root, __func__, 1);
192 
193 	/* Get number of panes. */
194 	n = window_count_panes(w);
195 	if (n <= 1)
196 		return;
197 	n--;	/* take off main pane */
198 
199 	/* How many rows and columns will be needed, not counting main? */
200 	columns = (w->sx + 1) / (PANE_MINIMUM + 1);	/* maximum columns */
201 	if (columns == 0)
202 		columns = 1;
203 	rows = 1 + (n - 1) / columns;
204 	columns = 1 + (n - 1) / rows;
205 	width = (w->sx - (n - 1)) / columns;
206 
207 	/* Get the main pane height and add one for separator line. */
208 	mainheight = options_get_number(w->options, "main-pane-height") + 1;
209 
210 	/* Get the optional other pane height and add one for separator line. */
211 	otherheight = options_get_number(w->options, "other-pane-height") + 1;
212 
213 	/*
214 	 * If an other pane height was specified, honour it so long as it
215 	 * doesn't shrink the main height to less than the main-pane-height
216 	 */
217 	if (otherheight > 1 && w->sy - otherheight > mainheight)
218 		mainheight = w->sy - otherheight;
219 	if (mainheight < PANE_MINIMUM + 1)
220 		mainheight = PANE_MINIMUM + 1;
221 
222 	/* Try and make everything fit. */
223 	totalrows = rows * (PANE_MINIMUM + 1) - 1;
224 	if (mainheight + totalrows > w->sy) {
225 		if (totalrows + PANE_MINIMUM + 1 > w->sy)
226 			mainheight = PANE_MINIMUM + 2;
227 		else
228 			mainheight = w->sy - totalrows;
229 		height = PANE_MINIMUM;
230 	} else
231 		height = (w->sy - mainheight - (rows - 1)) / rows;
232 
233 	/* Free old tree and create a new root. */
234 	layout_free(w);
235 	lc = w->layout_root = layout_create_cell(NULL);
236 	layout_set_size(lc, w->sx, mainheight + rows * (height + 1) - 1, 0, 0);
237 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
238 
239 	/* Create the main pane. */
240 	lcmain = layout_create_cell(lc);
241 	layout_set_size(lcmain, w->sx, mainheight - 1, 0, 0);
242 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
243 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
244 
245 	/* Create a grid of the remaining cells. */
246 	wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
247 	for (j = 0; j < rows; j++) {
248 		/* If this is the last cell, all done. */
249 		if (wp == NULL)
250 			break;
251 
252 		/* Create the new row. */
253 		lcrow = layout_create_cell(lc);
254 		layout_set_size(lcrow, w->sx, height, 0, 0);
255 		TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
256 
257 		/* If only one column, just use the row directly. */
258 		if (columns == 1) {
259 			layout_make_leaf(lcrow, wp);
260 			wp = TAILQ_NEXT(wp, entry);
261 			continue;
262 		}
263 
264 		/* Add in the columns. */
265 		layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
266 		for (i = 0; i < columns; i++) {
267 			/* Create and add a pane cell. */
268 			lcchild = layout_create_cell(lcrow);
269 			layout_set_size(lcchild, width, height, 0, 0);
270 			layout_make_leaf(lcchild, wp);
271 			TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
272 
273 			/* Move to the next cell. */
274 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
275 				break;
276 		}
277 
278 		/* Adjust the row to fit the full width if necessary. */
279 		if (i == columns)
280 			i--;
281 		used = ((i + 1) * (width + 1)) - 1;
282 		if (w->sx <= used)
283 			continue;
284 		lcchild = TAILQ_LAST(&lcrow->cells, layout_cells);
285 		layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT,
286 		    w->sx - used);
287 	}
288 
289 	/* Adjust the last row height to fit if necessary. */
290 	used = mainheight + (rows * height) + rows - 1;
291 	if (w->sy > used) {
292 		lcrow = TAILQ_LAST(&lc->cells, layout_cells);
293 		layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
294 		    w->sy - used);
295 	}
296 
297 	/* Fix cell offsets. */
298 	layout_fix_offsets(lc);
299 	layout_fix_panes(w);
300 
301 	layout_print_cell(w->layout_root, __func__, 1);
302 
303 	window_resize(w, lc->sx, lc->sy);
304 	notify_window("window-layout-changed", w);
305 	server_redraw_window(w);
306 }
307 
308 static void
309 layout_set_main_v(struct window *w)
310 {
311 	struct window_pane	*wp;
312 	struct layout_cell	*lc, *lcmain, *lccolumn, *lcchild;
313 	u_int			 n, mainwidth, otherwidth, width, height;
314 	u_int			 used, i, j, columns, rows, totalcolumns;
315 
316 	layout_print_cell(w->layout_root, __func__, 1);
317 
318 	/* Get number of panes. */
319 	n = window_count_panes(w);
320 	if (n <= 1)
321 		return;
322 	n--;	/* take off main pane */
323 
324 	/* How many rows and columns will be needed, not counting main? */
325 	rows = (w->sy + 1) / (PANE_MINIMUM + 1);	/* maximum rows */
326 	if (rows == 0)
327 		rows = 1;
328 	columns = 1 + (n - 1) / rows;
329 	rows = 1 + (n - 1) / columns;
330 	height = (w->sy - (n - 1)) / rows;
331 
332 	/* Get the main pane width and add one for separator line. */
333 	mainwidth = options_get_number(w->options, "main-pane-width") + 1;
334 
335 	/* Get the optional other pane width and add one for separator line. */
336 	otherwidth = options_get_number(w->options, "other-pane-width") + 1;
337 
338 	/*
339 	 * If an other pane width was specified, honour it so long as it
340 	 * doesn't shrink the main width to less than the main-pane-width
341 	 */
342 	if (otherwidth > 1 && w->sx - otherwidth > mainwidth)
343 		mainwidth = w->sx - otherwidth;
344 	if (mainwidth < PANE_MINIMUM + 1)
345 		mainwidth = PANE_MINIMUM + 1;
346 
347 	/* Try and make everything fit. */
348 	totalcolumns = columns * (PANE_MINIMUM + 1) - 1;
349 	if (mainwidth + totalcolumns > w->sx) {
350 		if (totalcolumns + PANE_MINIMUM + 1 > w->sx)
351 			mainwidth = PANE_MINIMUM + 2;
352 		else
353 			mainwidth = w->sx - totalcolumns;
354 		width = PANE_MINIMUM;
355 	} else
356 		width = (w->sx - mainwidth - (columns - 1)) / columns;
357 
358 	/* Free old tree and create a new root. */
359 	layout_free(w);
360 	lc = w->layout_root = layout_create_cell(NULL);
361 	layout_set_size(lc, mainwidth + columns * (width + 1) - 1, w->sy, 0, 0);
362 	layout_make_node(lc, LAYOUT_LEFTRIGHT);
363 
364 	/* Create the main pane. */
365 	lcmain = layout_create_cell(lc);
366 	layout_set_size(lcmain, mainwidth - 1, w->sy, 0, 0);
367 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
368 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
369 
370 	/* Create a grid of the remaining cells. */
371 	wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
372 	for (j = 0; j < columns; j++) {
373 		/* If this is the last cell, all done. */
374 		if (wp == NULL)
375 			break;
376 
377 		/* Create the new column. */
378 		lccolumn = layout_create_cell(lc);
379 		layout_set_size(lccolumn, width, w->sy, 0, 0);
380 		TAILQ_INSERT_TAIL(&lc->cells, lccolumn, entry);
381 
382 		/* If only one row, just use the row directly. */
383 		if (rows == 1) {
384 			layout_make_leaf(lccolumn, wp);
385 			wp = TAILQ_NEXT(wp, entry);
386 			continue;
387 		}
388 
389 		/* Add in the rows. */
390 		layout_make_node(lccolumn, LAYOUT_TOPBOTTOM);
391 		for (i = 0; i < rows; i++) {
392 			/* Create and add a pane cell. */
393 			lcchild = layout_create_cell(lccolumn);
394 			layout_set_size(lcchild, width, height, 0, 0);
395 			layout_make_leaf(lcchild, wp);
396 			TAILQ_INSERT_TAIL(&lccolumn->cells, lcchild, entry);
397 
398 			/* Move to the next cell. */
399 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
400 				break;
401 		}
402 
403 		/* Adjust the column to fit the full height if necessary. */
404 		if (i == rows)
405 			i--;
406 		used = ((i + 1) * (height + 1)) - 1;
407 		if (w->sy <= used)
408 			continue;
409 		lcchild = TAILQ_LAST(&lccolumn->cells, layout_cells);
410 		layout_resize_adjust(w, lcchild, LAYOUT_TOPBOTTOM,
411 		    w->sy - used);
412 	}
413 
414 	/* Adjust the last column width to fit if necessary. */
415 	used = mainwidth + (columns * width) + columns - 1;
416 	if (w->sx > used) {
417 		lccolumn = TAILQ_LAST(&lc->cells, layout_cells);
418 		layout_resize_adjust(w, lccolumn, LAYOUT_LEFTRIGHT,
419 		    w->sx - used);
420 	}
421 
422 	/* Fix cell offsets. */
423 	layout_fix_offsets(lc);
424 	layout_fix_panes(w);
425 
426 	layout_print_cell(w->layout_root, __func__, 1);
427 
428 	window_resize(w, lc->sx, lc->sy);
429 	notify_window("window-layout-changed", w);
430 	server_redraw_window(w);
431 }
432 
433 void
434 layout_set_tiled(struct window *w)
435 {
436 	struct window_pane	*wp;
437 	struct layout_cell	*lc, *lcrow, *lcchild;
438 	u_int			 n, width, height, used, sx, sy;
439 	u_int			 i, j, columns, rows;
440 
441 	layout_print_cell(w->layout_root, __func__, 1);
442 
443 	/* Get number of panes. */
444 	n = window_count_panes(w);
445 	if (n <= 1)
446 		return;
447 
448 	/* How many rows and columns are wanted? */
449 	rows = columns = 1;
450 	while (rows * columns < n) {
451 		rows++;
452 		if (rows * columns < n)
453 			columns++;
454 	}
455 
456 	/* What width and height should they be? */
457 	width = (w->sx - (columns - 1)) / columns;
458 	if (width < PANE_MINIMUM)
459 		width = PANE_MINIMUM;
460 	height = (w->sy - (rows - 1)) / rows;
461 	if (height < PANE_MINIMUM)
462 		height = PANE_MINIMUM;
463 
464 	/* Free old tree and create a new root. */
465 	layout_free(w);
466 	lc = w->layout_root = layout_create_cell(NULL);
467 	sx = ((width + 1) * columns) - 1;
468 	if (sx < w->sx)
469 		sx = w->sx;
470 	sy = ((height + 1) * rows) - 1;
471 	if (sy < w->sy)
472 		sy = w->sy;
473 	layout_set_size(lc, sx, sy, 0, 0);
474 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
475 
476 	/* Create a grid of the cells. */
477 	wp = TAILQ_FIRST(&w->panes);
478 	for (j = 0; j < rows; j++) {
479 		/* If this is the last cell, all done. */
480 		if (wp == NULL)
481 			break;
482 
483 		/* Create the new row. */
484 		lcrow = layout_create_cell(lc);
485 		layout_set_size(lcrow, w->sx, height, 0, 0);
486 		TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
487 
488 		/* If only one column, just use the row directly. */
489 		if (n - (j * columns) == 1 || columns == 1) {
490 			layout_make_leaf(lcrow, wp);
491 			wp = TAILQ_NEXT(wp, entry);
492 			continue;
493 		}
494 
495 		/* Add in the columns. */
496 		layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
497 		for (i = 0; i < columns; i++) {
498 			/* Create and add a pane cell. */
499 			lcchild = layout_create_cell(lcrow);
500 			layout_set_size(lcchild, width, height, 0, 0);
501 			layout_make_leaf(lcchild, wp);
502 			TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
503 
504 			/* Move to the next cell. */
505 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
506 				break;
507 		}
508 
509 		/*
510 		 * Adjust the row and columns to fit the full width if
511 		 * necessary.
512 		 */
513 		if (i == columns)
514 			i--;
515 		used = ((i + 1) * (width + 1)) - 1;
516 		if (w->sx <= used)
517 			continue;
518 		lcchild = TAILQ_LAST(&lcrow->cells, layout_cells);
519 		layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT,
520 		    w->sx - used);
521 	}
522 
523 	/* Adjust the last row height to fit if necessary. */
524 	used = (rows * height) + rows - 1;
525 	if (w->sy > used) {
526 		lcrow = TAILQ_LAST(&lc->cells, layout_cells);
527 		layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
528 		    w->sy - used);
529 	}
530 
531 	/* Fix cell offsets. */
532 	layout_fix_offsets(lc);
533 	layout_fix_panes(w);
534 
535 	layout_print_cell(w->layout_root, __func__, 1);
536 
537 	window_resize(w, lc->sx, lc->sy);
538 	notify_window("window-layout-changed", w);
539 	server_redraw_window(w);
540 }
541