xref: /openbsd-src/usr.bin/tmux/layout-set.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /* $OpenBSD: layout-set.c,v 1.28 2019/11/28 09:45:15 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(w);
162 	layout_fix_panes(w);
163 
164 	layout_print_cell(w->layout_root, __func__, 1);
165 
166 	window_resize(w, lc->sx, lc->sy, -1, -1);
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, *lcother, *lcchild;
188 	u_int			 n, mainh, otherh, sx, sy;
189 
190 	layout_print_cell(w->layout_root, __func__, 1);
191 
192 	/* Get number of panes. */
193 	n = window_count_panes(w);
194 	if (n <= 1)
195 		return;
196 	n--;	/* take off main pane */
197 
198 	/* Find available height - take off one line for the border. */
199 	sy = w->sy - 1;
200 
201 	/* Get the main pane height and work out the other pane height. */
202 	mainh = options_get_number(w->options, "main-pane-height");
203 	if (mainh + PANE_MINIMUM >= sy) {
204 		if (sy <= PANE_MINIMUM + PANE_MINIMUM)
205 			mainh = PANE_MINIMUM;
206 		else
207 			mainh = sy - PANE_MINIMUM;
208 		otherh = PANE_MINIMUM;
209 	} else {
210 		otherh = options_get_number(w->options, "other-pane-height");
211 		if (otherh == 0)
212 			otherh = sy - mainh;
213 		else if (otherh > sy || sy - otherh < mainh)
214 			otherh = sy - mainh;
215 		else
216 			mainh = sy - otherh;
217 	}
218 
219 	/* Work out what width is needed. */
220 	sx = (n * (PANE_MINIMUM + 1)) - 1;
221 	if (sx < w->sx)
222 		sx = w->sx;
223 
224 	/* Free old tree and create a new root. */
225 	layout_free(w);
226 	lc = w->layout_root = layout_create_cell(NULL);
227 	layout_set_size(lc, sx, mainh + otherh + 1, 0, 0);
228 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
229 
230 	/* Create the main pane. */
231 	lcmain = layout_create_cell(lc);
232 	layout_set_size(lcmain, sx, mainh, 0, 0);
233 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
234 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
235 
236 	/* Create the other pane. */
237 	lcother = layout_create_cell(lc);
238 	layout_set_size(lcother, sx, otherh, 0, 0);
239 	if (n == 1) {
240 		wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
241 		layout_make_leaf(lcother, wp);
242 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
243 	} else {
244 		layout_make_node(lcother, LAYOUT_LEFTRIGHT);
245 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
246 
247 		/* Add the remaining panes as children. */
248 		TAILQ_FOREACH(wp, &w->panes, entry) {
249 			if (wp == TAILQ_FIRST(&w->panes))
250 				continue;
251 			lcchild = layout_create_cell(lcother);
252 			layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
253 			layout_make_leaf(lcchild, wp);
254 			TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
255 		}
256 		layout_spread_cell(w, lcother);
257 	}
258 
259 	/* Fix cell offsets. */
260 	layout_fix_offsets(w);
261 	layout_fix_panes(w);
262 
263 	layout_print_cell(w->layout_root, __func__, 1);
264 
265 	window_resize(w, lc->sx, lc->sy, -1, -1);
266 	notify_window("window-layout-changed", w);
267 	server_redraw_window(w);
268 }
269 
270 static void
271 layout_set_main_v(struct window *w)
272 {
273 	struct window_pane	*wp;
274 	struct layout_cell	*lc, *lcmain, *lcother, *lcchild;
275 	u_int			 n, mainw, otherw, sx, sy;
276 
277 	layout_print_cell(w->layout_root, __func__, 1);
278 
279 	/* Get number of panes. */
280 	n = window_count_panes(w);
281 	if (n <= 1)
282 		return;
283 	n--;	/* take off main pane */
284 
285 	/* Find available width - take off one line for the border. */
286 	sx = w->sx - 1;
287 
288 	/* Get the main pane width and work out the other pane width. */
289 	mainw = options_get_number(w->options, "main-pane-width");
290 	if (mainw + PANE_MINIMUM >= sx) {
291 		if (sx <= PANE_MINIMUM + PANE_MINIMUM)
292 			mainw = PANE_MINIMUM;
293 		else
294 			mainw = sx - PANE_MINIMUM;
295 		otherw = PANE_MINIMUM;
296 	} else {
297 		otherw = options_get_number(w->options, "other-pane-width");
298 		if (otherw == 0)
299 			otherw = sx - mainw;
300 		else if (otherw > sx || sx - otherw < mainw)
301 			otherw = sx - mainw;
302 		else
303 			mainw = sx - otherw;
304 	}
305 
306 	/* Work out what height is needed. */
307 	sy = (n * (PANE_MINIMUM + 1)) - 1;
308 	if (sy < w->sy)
309 		sy = w->sy;
310 
311 	/* Free old tree and create a new root. */
312 	layout_free(w);
313 	lc = w->layout_root = layout_create_cell(NULL);
314 	layout_set_size(lc, mainw + otherw + 1, sy, 0, 0);
315 	layout_make_node(lc, LAYOUT_LEFTRIGHT);
316 
317 	/* Create the main pane. */
318 	lcmain = layout_create_cell(lc);
319 	layout_set_size(lcmain, mainw, sy, 0, 0);
320 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
321 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
322 
323 	/* Create the other pane. */
324 	lcother = layout_create_cell(lc);
325 	layout_set_size(lcother, otherw, sy, 0, 0);
326 	if (n == 1) {
327 		wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
328 		layout_make_leaf(lcother, wp);
329 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
330 	} else {
331 		layout_make_node(lcother, LAYOUT_TOPBOTTOM);
332 		TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
333 
334 		/* Add the remaining panes as children. */
335 		TAILQ_FOREACH(wp, &w->panes, entry) {
336 			if (wp == TAILQ_FIRST(&w->panes))
337 				continue;
338 			lcchild = layout_create_cell(lcother);
339 			layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
340 			layout_make_leaf(lcchild, wp);
341 			TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
342 		}
343 		layout_spread_cell(w, lcother);
344 	}
345 
346 	/* Fix cell offsets. */
347 	layout_fix_offsets(w);
348 	layout_fix_panes(w);
349 
350 	layout_print_cell(w->layout_root, __func__, 1);
351 
352 	window_resize(w, lc->sx, lc->sy, -1, -1);
353 	notify_window("window-layout-changed", w);
354 	server_redraw_window(w);
355 }
356 
357 void
358 layout_set_tiled(struct window *w)
359 {
360 	struct window_pane	*wp;
361 	struct layout_cell	*lc, *lcrow, *lcchild;
362 	u_int			 n, width, height, used, sx, sy;
363 	u_int			 i, j, columns, rows;
364 
365 	layout_print_cell(w->layout_root, __func__, 1);
366 
367 	/* Get number of panes. */
368 	n = window_count_panes(w);
369 	if (n <= 1)
370 		return;
371 
372 	/* How many rows and columns are wanted? */
373 	rows = columns = 1;
374 	while (rows * columns < n) {
375 		rows++;
376 		if (rows * columns < n)
377 			columns++;
378 	}
379 
380 	/* What width and height should they be? */
381 	width = (w->sx - (columns - 1)) / columns;
382 	if (width < PANE_MINIMUM)
383 		width = PANE_MINIMUM;
384 	height = (w->sy - (rows - 1)) / rows;
385 	if (height < PANE_MINIMUM)
386 		height = PANE_MINIMUM;
387 
388 	/* Free old tree and create a new root. */
389 	layout_free(w);
390 	lc = w->layout_root = layout_create_cell(NULL);
391 	sx = ((width + 1) * columns) - 1;
392 	if (sx < w->sx)
393 		sx = w->sx;
394 	sy = ((height + 1) * rows) - 1;
395 	if (sy < w->sy)
396 		sy = w->sy;
397 	layout_set_size(lc, sx, sy, 0, 0);
398 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
399 
400 	/* Create a grid of the cells. */
401 	wp = TAILQ_FIRST(&w->panes);
402 	for (j = 0; j < rows; j++) {
403 		/* If this is the last cell, all done. */
404 		if (wp == NULL)
405 			break;
406 
407 		/* Create the new row. */
408 		lcrow = layout_create_cell(lc);
409 		layout_set_size(lcrow, w->sx, height, 0, 0);
410 		TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
411 
412 		/* If only one column, just use the row directly. */
413 		if (n - (j * columns) == 1 || columns == 1) {
414 			layout_make_leaf(lcrow, wp);
415 			wp = TAILQ_NEXT(wp, entry);
416 			continue;
417 		}
418 
419 		/* Add in the columns. */
420 		layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
421 		for (i = 0; i < columns; i++) {
422 			/* Create and add a pane cell. */
423 			lcchild = layout_create_cell(lcrow);
424 			layout_set_size(lcchild, width, height, 0, 0);
425 			layout_make_leaf(lcchild, wp);
426 			TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
427 
428 			/* Move to the next cell. */
429 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
430 				break;
431 		}
432 
433 		/*
434 		 * Adjust the row and columns to fit the full width if
435 		 * necessary.
436 		 */
437 		if (i == columns)
438 			i--;
439 		used = ((i + 1) * (width + 1)) - 1;
440 		if (w->sx <= used)
441 			continue;
442 		lcchild = TAILQ_LAST(&lcrow->cells, layout_cells);
443 		layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT,
444 		    w->sx - used);
445 	}
446 
447 	/* Adjust the last row height to fit if necessary. */
448 	used = (rows * height) + rows - 1;
449 	if (w->sy > used) {
450 		lcrow = TAILQ_LAST(&lc->cells, layout_cells);
451 		layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
452 		    w->sy - used);
453 	}
454 
455 	/* Fix cell offsets. */
456 	layout_fix_offsets(w);
457 	layout_fix_panes(w);
458 
459 	layout_print_cell(w->layout_root, __func__, 1);
460 
461 	window_resize(w, lc->sx, lc->sy, -1, -1);
462 	notify_window("window-layout-changed", w);
463 	server_redraw_window(w);
464 }
465