xref: /openbsd/usr.bin/tmux/layout-set.c (revision d415bd75)
1 /* $OpenBSD: layout-set.c,v 1.30 2021/03/11 06:31:05 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 <stdlib.h>
22 #include <string.h>
23 
24 #include "tmux.h"
25 
26 /*
27  * Set window layouts - predefined methods to arrange windows. These are
28  * one-off and generate a layout tree.
29  */
30 
31 static void	layout_set_even_h(struct window *);
32 static void	layout_set_even_v(struct window *);
33 static void	layout_set_main_h(struct window *);
34 static void	layout_set_main_v(struct window *);
35 static void	layout_set_tiled(struct window *);
36 
37 static const struct {
38 	const char	*name;
39 	void	      	(*arrange)(struct window *);
40 } layout_sets[] = {
41 	{ "even-horizontal", layout_set_even_h },
42 	{ "even-vertical", layout_set_even_v },
43 	{ "main-horizontal", layout_set_main_h },
44 	{ "main-vertical", layout_set_main_v },
45 	{ "tiled", layout_set_tiled },
46 };
47 
48 int
49 layout_set_lookup(const char *name)
50 {
51 	u_int	i;
52 	int	matched = -1;
53 
54 	for (i = 0; i < nitems(layout_sets); i++) {
55 		if (strncmp(layout_sets[i].name, name, strlen(name)) == 0) {
56 			if (matched != -1)	/* ambiguous */
57 				return (-1);
58 			matched = i;
59 		}
60 	}
61 
62 	return (matched);
63 }
64 
65 u_int
66 layout_set_select(struct window *w, u_int layout)
67 {
68 	if (layout > nitems(layout_sets) - 1)
69 		layout = nitems(layout_sets) - 1;
70 
71 	if (layout_sets[layout].arrange != NULL)
72 		layout_sets[layout].arrange(w);
73 
74 	w->lastlayout = layout;
75 	return (layout);
76 }
77 
78 u_int
79 layout_set_next(struct window *w)
80 {
81 	u_int	layout;
82 
83 	if (w->lastlayout == -1)
84 		layout = 0;
85 	else {
86 		layout = w->lastlayout + 1;
87 		if (layout > nitems(layout_sets) - 1)
88 			layout = 0;
89 	}
90 
91 	if (layout_sets[layout].arrange != NULL)
92 		layout_sets[layout].arrange(w);
93 	w->lastlayout = layout;
94 	return (layout);
95 }
96 
97 u_int
98 layout_set_previous(struct window *w)
99 {
100 	u_int	layout;
101 
102 	if (w->lastlayout == -1)
103 		layout = nitems(layout_sets) - 1;
104 	else {
105 		layout = w->lastlayout;
106 		if (layout == 0)
107 			layout = nitems(layout_sets) - 1;
108 		else
109 			layout--;
110 	}
111 
112 	if (layout_sets[layout].arrange != NULL)
113 		layout_sets[layout].arrange(w);
114 	w->lastlayout = layout;
115 	return (layout);
116 }
117 
118 static void
119 layout_set_even(struct window *w, enum layout_type type)
120 {
121 	struct window_pane	*wp;
122 	struct layout_cell	*lc, *lcnew;
123 	u_int			 n, sx, sy;
124 
125 	layout_print_cell(w->layout_root, __func__, 1);
126 
127 	/* Get number of panes. */
128 	n = window_count_panes(w);
129 	if (n <= 1)
130 		return;
131 
132 	/* Free the old root and construct a new. */
133 	layout_free(w);
134 	lc = w->layout_root = layout_create_cell(NULL);
135 	if (type == LAYOUT_LEFTRIGHT) {
136 		sx = (n * (PANE_MINIMUM + 1)) - 1;
137 		if (sx < w->sx)
138 			sx = w->sx;
139 		sy = w->sy;
140 	} else {
141 		sy = (n * (PANE_MINIMUM + 1)) - 1;
142 		if (sy < w->sy)
143 			sy = w->sy;
144 		sx = w->sx;
145 	}
146 	layout_set_size(lc, sx, sy, 0, 0);
147 	layout_make_node(lc, type);
148 
149 	/* Build new leaf cells. */
150 	TAILQ_FOREACH(wp, &w->panes, entry) {
151 		lcnew = layout_create_cell(lc);
152 		layout_make_leaf(lcnew, wp);
153 		lcnew->sx = w->sx;
154 		lcnew->sy = w->sy;
155 		TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
156 	}
157 
158 	/* Spread out cells. */
159 	layout_spread_cell(w, lc);
160 
161 	/* Fix cell offsets. */
162 	layout_fix_offsets(w);
163 	layout_fix_panes(w, NULL);
164 
165 	layout_print_cell(w->layout_root, __func__, 1);
166 
167 	window_resize(w, lc->sx, lc->sy, -1, -1);
168 	notify_window("window-layout-changed", w);
169 	server_redraw_window(w);
170 }
171 
172 static void
173 layout_set_even_h(struct window *w)
174 {
175 	layout_set_even(w, LAYOUT_LEFTRIGHT);
176 }
177 
178 static void
179 layout_set_even_v(struct window *w)
180 {
181 	layout_set_even(w, LAYOUT_TOPBOTTOM);
182 }
183 
184 static void
185 layout_set_main_h(struct window *w)
186 {
187 	struct window_pane	*wp;
188 	struct layout_cell	*lc, *lcmain, *lcother, *lcchild;
189 	u_int			 n, mainh, otherh, sx, sy;
190 	char			*cause;
191 	const char		*s;
192 
193 	layout_print_cell(w->layout_root, __func__, 1);
194 
195 	/* Get number of panes. */
196 	n = window_count_panes(w);
197 	if (n <= 1)
198 		return;
199 	n--;	/* take off main pane */
200 
201 	/* Find available height - take off one line for the border. */
202 	sy = w->sy - 1;
203 
204 	/* Get the main pane height. */
205 	s = options_get_string(w->options, "main-pane-height");
206 	mainh = args_string_percentage(s, 0, sy, sy, &cause);
207 	if (cause != NULL) {
208 		mainh = 24;
209 		free(cause);
210 	}
211 
212 	/* Work out the other pane height. */
213 	if (mainh + PANE_MINIMUM >= sy) {
214 		if (sy <= PANE_MINIMUM + PANE_MINIMUM)
215 			mainh = PANE_MINIMUM;
216 		else
217 			mainh = sy - PANE_MINIMUM;
218 		otherh = PANE_MINIMUM;
219 	} else {
220 		s = options_get_string(w->options, "other-pane-height");
221 		otherh = args_string_percentage(s, 0, sy, sy, &cause);
222 		if (cause != NULL || otherh == 0) {
223 			otherh = sy - mainh;
224 			free(cause);
225 		} else if (otherh > sy || sy - otherh < mainh)
226 			otherh = sy - mainh;
227 		else
228 			mainh = sy - otherh;
229 	}
230 
231 	/* Work out what width is needed. */
232 	sx = (n * (PANE_MINIMUM + 1)) - 1;
233 	if (sx < w->sx)
234 		sx = w->sx;
235 
236 	/* Free old tree and create a new root. */
237 	layout_free(w);
238 	lc = w->layout_root = layout_create_cell(NULL);
239 	layout_set_size(lc, sx, mainh + otherh + 1, 0, 0);
240 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
241 
242 	/* Create the main pane. */
243 	lcmain = layout_create_cell(lc);
244 	layout_set_size(lcmain, sx, mainh, 0, 0);
245 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
246 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
247 
248 	/* Create the other pane. */
249 	lcother = layout_create_cell(lc);
250 	layout_set_size(lcother, sx, otherh, 0, 0);
251 	if (n == 1) {
252 		wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
253 		layout_make_leaf(lcother, wp);
254 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
255 	} else {
256 		layout_make_node(lcother, LAYOUT_LEFTRIGHT);
257 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
258 
259 		/* Add the remaining panes as children. */
260 		TAILQ_FOREACH(wp, &w->panes, entry) {
261 			if (wp == TAILQ_FIRST(&w->panes))
262 				continue;
263 			lcchild = layout_create_cell(lcother);
264 			layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
265 			layout_make_leaf(lcchild, wp);
266 			TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
267 		}
268 		layout_spread_cell(w, lcother);
269 	}
270 
271 	/* Fix cell offsets. */
272 	layout_fix_offsets(w);
273 	layout_fix_panes(w, NULL);
274 
275 	layout_print_cell(w->layout_root, __func__, 1);
276 
277 	window_resize(w, lc->sx, lc->sy, -1, -1);
278 	notify_window("window-layout-changed", w);
279 	server_redraw_window(w);
280 }
281 
282 static void
283 layout_set_main_v(struct window *w)
284 {
285 	struct window_pane	*wp;
286 	struct layout_cell	*lc, *lcmain, *lcother, *lcchild;
287 	u_int			 n, mainw, otherw, sx, sy;
288 	char			*cause;
289 	const char		*s;
290 
291 	layout_print_cell(w->layout_root, __func__, 1);
292 
293 	/* Get number of panes. */
294 	n = window_count_panes(w);
295 	if (n <= 1)
296 		return;
297 	n--;	/* take off main pane */
298 
299 	/* Find available width - take off one line for the border. */
300 	sx = w->sx - 1;
301 
302 	/* Get the main pane width. */
303 	s = options_get_string(w->options, "main-pane-width");
304 	mainw = args_string_percentage(s, 0, sx, sx, &cause);
305 	if (cause != NULL) {
306 		mainw = 80;
307 		free(cause);
308 	}
309 
310 	/* Work out the other pane width. */
311 	if (mainw + PANE_MINIMUM >= sx) {
312 		if (sx <= PANE_MINIMUM + PANE_MINIMUM)
313 			mainw = PANE_MINIMUM;
314 		else
315 			mainw = sx - PANE_MINIMUM;
316 		otherw = PANE_MINIMUM;
317 	} else {
318 		s = options_get_string(w->options, "other-pane-width");
319 		otherw = args_string_percentage(s, 0, sx, sx, &cause);
320 		if (cause != NULL || otherw == 0) {
321 			otherw = sx - mainw;
322 			free(cause);
323 		} else if (otherw > sx || sx - otherw < mainw)
324 			otherw = sx - mainw;
325 		else
326 			mainw = sx - otherw;
327 	}
328 
329 	/* Work out what height is needed. */
330 	sy = (n * (PANE_MINIMUM + 1)) - 1;
331 	if (sy < w->sy)
332 		sy = w->sy;
333 
334 	/* Free old tree and create a new root. */
335 	layout_free(w);
336 	lc = w->layout_root = layout_create_cell(NULL);
337 	layout_set_size(lc, mainw + otherw + 1, sy, 0, 0);
338 	layout_make_node(lc, LAYOUT_LEFTRIGHT);
339 
340 	/* Create the main pane. */
341 	lcmain = layout_create_cell(lc);
342 	layout_set_size(lcmain, mainw, sy, 0, 0);
343 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
344 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
345 
346 	/* Create the other pane. */
347 	lcother = layout_create_cell(lc);
348 	layout_set_size(lcother, otherw, sy, 0, 0);
349 	if (n == 1) {
350 		wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
351 		layout_make_leaf(lcother, wp);
352 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
353 	} else {
354 		layout_make_node(lcother, LAYOUT_TOPBOTTOM);
355 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
356 
357 		/* Add the remaining panes as children. */
358 		TAILQ_FOREACH(wp, &w->panes, entry) {
359 			if (wp == TAILQ_FIRST(&w->panes))
360 				continue;
361 			lcchild = layout_create_cell(lcother);
362 			layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
363 			layout_make_leaf(lcchild, wp);
364 			TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
365 		}
366 		layout_spread_cell(w, lcother);
367 	}
368 
369 	/* Fix cell offsets. */
370 	layout_fix_offsets(w);
371 	layout_fix_panes(w, NULL);
372 
373 	layout_print_cell(w->layout_root, __func__, 1);
374 
375 	window_resize(w, lc->sx, lc->sy, -1, -1);
376 	notify_window("window-layout-changed", w);
377 	server_redraw_window(w);
378 }
379 
380 void
381 layout_set_tiled(struct window *w)
382 {
383 	struct window_pane	*wp;
384 	struct layout_cell	*lc, *lcrow, *lcchild;
385 	u_int			 n, width, height, used, sx, sy;
386 	u_int			 i, j, columns, rows;
387 
388 	layout_print_cell(w->layout_root, __func__, 1);
389 
390 	/* Get number of panes. */
391 	n = window_count_panes(w);
392 	if (n <= 1)
393 		return;
394 
395 	/* How many rows and columns are wanted? */
396 	rows = columns = 1;
397 	while (rows * columns < n) {
398 		rows++;
399 		if (rows * columns < n)
400 			columns++;
401 	}
402 
403 	/* What width and height should they be? */
404 	width = (w->sx - (columns - 1)) / columns;
405 	if (width < PANE_MINIMUM)
406 		width = PANE_MINIMUM;
407 	height = (w->sy - (rows - 1)) / rows;
408 	if (height < PANE_MINIMUM)
409 		height = PANE_MINIMUM;
410 
411 	/* Free old tree and create a new root. */
412 	layout_free(w);
413 	lc = w->layout_root = layout_create_cell(NULL);
414 	sx = ((width + 1) * columns) - 1;
415 	if (sx < w->sx)
416 		sx = w->sx;
417 	sy = ((height + 1) * rows) - 1;
418 	if (sy < w->sy)
419 		sy = w->sy;
420 	layout_set_size(lc, sx, sy, 0, 0);
421 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
422 
423 	/* Create a grid of the cells. */
424 	wp = TAILQ_FIRST(&w->panes);
425 	for (j = 0; j < rows; j++) {
426 		/* If this is the last cell, all done. */
427 		if (wp == NULL)
428 			break;
429 
430 		/* Create the new row. */
431 		lcrow = layout_create_cell(lc);
432 		layout_set_size(lcrow, w->sx, height, 0, 0);
433 		TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
434 
435 		/* If only one column, just use the row directly. */
436 		if (n - (j * columns) == 1 || columns == 1) {
437 			layout_make_leaf(lcrow, wp);
438 			wp = TAILQ_NEXT(wp, entry);
439 			continue;
440 		}
441 
442 		/* Add in the columns. */
443 		layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
444 		for (i = 0; i < columns; i++) {
445 			/* Create and add a pane cell. */
446 			lcchild = layout_create_cell(lcrow);
447 			layout_set_size(lcchild, width, height, 0, 0);
448 			layout_make_leaf(lcchild, wp);
449 			TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
450 
451 			/* Move to the next cell. */
452 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
453 				break;
454 		}
455 
456 		/*
457 		 * Adjust the row and columns to fit the full width if
458 		 * necessary.
459 		 */
460 		if (i == columns)
461 			i--;
462 		used = ((i + 1) * (width + 1)) - 1;
463 		if (w->sx <= used)
464 			continue;
465 		lcchild = TAILQ_LAST(&lcrow->cells, layout_cells);
466 		layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT,
467 		    w->sx - used);
468 	}
469 
470 	/* Adjust the last row height to fit if necessary. */
471 	used = (rows * height) + rows - 1;
472 	if (w->sy > used) {
473 		lcrow = TAILQ_LAST(&lc->cells, layout_cells);
474 		layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
475 		    w->sy - used);
476 	}
477 
478 	/* Fix cell offsets. */
479 	layout_fix_offsets(w);
480 	layout_fix_panes(w, NULL);
481 
482 	layout_print_cell(w->layout_root, __func__, 1);
483 
484 	window_resize(w, lc->sx, lc->sy, -1, -1);
485 	notify_window("window-layout-changed", w);
486 	server_redraw_window(w);
487 }
488