xref: /netbsd-src/external/bsd/tmux/dist/layout-set.c (revision deb6f0161a9109e7de9b519dc8dfb9478668dcdd)
1 /* $OpenBSD$ */
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_h(struct window *w)
119 {
120 	struct window_pane	*wp;
121 	struct layout_cell	*lc, *lcnew;
122 	u_int			 i, n, width, xoff;
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 	/* How many can we fit? */
132 	width = (w->sx - (n - 1)) / n;
133 	if (width < PANE_MINIMUM)
134 		width = PANE_MINIMUM;
135 
136 	/* Free the old root and construct a new. */
137 	layout_free(w);
138 	lc = w->layout_root = layout_create_cell(NULL);
139 	layout_set_size(lc, w->sx, w->sy, 0, 0);
140 	layout_make_node(lc, LAYOUT_LEFTRIGHT);
141 
142 	/* Build new leaf cells. */
143 	i = xoff = 0;
144 	TAILQ_FOREACH(wp, &w->panes, entry) {
145 		/* Create child cell. */
146 		lcnew = layout_create_cell(lc);
147 		layout_set_size(lcnew, width, w->sy, xoff, 0);
148 		layout_make_leaf(lcnew, wp);
149 		TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
150 
151 		i++;
152 		xoff += width + 1;
153 	}
154 
155 	/* Allocate any remaining space. */
156 	if (w->sx > xoff - 1) {
157 		lc = TAILQ_LAST(&lc->cells, layout_cells);
158 		layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT,
159 		    w->sx - (xoff - 1));
160 	}
161 
162 	/* Fix cell offsets. */
163 	layout_fix_offsets(lc);
164 	layout_fix_panes(w, w->sx, w->sy);
165 
166 	layout_print_cell(w->layout_root, __func__, 1);
167 
168 	notify_window("window-layout-changed", w);
169 	server_redraw_window(w);
170 }
171 
172 static void
173 layout_set_even_v(struct window *w)
174 {
175 	struct window_pane	*wp;
176 	struct layout_cell	*lc, *lcnew;
177 	u_int			 i, n, height, yoff;
178 
179 	layout_print_cell(w->layout_root, __func__, 1);
180 
181 	/* Get number of panes. */
182 	n = window_count_panes(w);
183 	if (n <= 1)
184 		return;
185 
186 	/* How many can we fit? */
187 	height = (w->sy - (n - 1)) / n;
188 	if (height < PANE_MINIMUM)
189 		height = PANE_MINIMUM;
190 
191 	/* Free the old root and construct a new. */
192 	layout_free(w);
193 	lc = w->layout_root = layout_create_cell(NULL);
194 	layout_set_size(lc, w->sx, w->sy, 0, 0);
195 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
196 
197 	/* Build new leaf cells. */
198 	i = yoff = 0;
199 	TAILQ_FOREACH(wp, &w->panes, entry) {
200 		/* Create child cell. */
201 		lcnew = layout_create_cell(lc);
202 		layout_set_size(lcnew, w->sx, height, 0, yoff);
203 		layout_make_leaf(lcnew, wp);
204 		TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
205 
206 		i++;
207 		yoff += height + 1;
208 	}
209 
210 	/* Allocate any remaining space. */
211 	if (w->sy > yoff - 1) {
212 		lc = TAILQ_LAST(&lc->cells, layout_cells);
213 		layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM,
214 		    w->sy - (yoff - 1));
215 	}
216 
217 	/* Fix cell offsets. */
218 	layout_fix_offsets(lc);
219 	layout_fix_panes(w, w->sx, w->sy);
220 
221 	layout_print_cell(w->layout_root, __func__, 1);
222 
223 	notify_window("window-layout-changed", w);
224 	server_redraw_window(w);
225 }
226 
227 static void
228 layout_set_main_h(struct window *w)
229 {
230 	struct window_pane	*wp;
231 	struct layout_cell	*lc, *lcmain, *lcrow, *lcchild;
232 	u_int			 n, mainheight, otherheight, width, height;
233 	u_int			 used, i, j, columns, rows, totalrows;
234 
235 	layout_print_cell(w->layout_root, __func__, 1);
236 
237 	/* Get number of panes. */
238 	n = window_count_panes(w);
239 	if (n <= 1)
240 		return;
241 	n--;	/* take off main pane */
242 
243 	/* How many rows and columns will be needed, not counting main? */
244 	columns = (w->sx + 1) / (PANE_MINIMUM + 1);	/* maximum columns */
245 	if (columns == 0)
246 		columns = 1;
247 	rows = 1 + (n - 1) / columns;
248 	columns = 1 + (n - 1) / rows;
249 	width = (w->sx - (n - 1)) / columns;
250 
251 	/* Get the main pane height and add one for separator line. */
252 	mainheight = options_get_number(w->options, "main-pane-height") + 1;
253 
254 	/* Get the optional other pane height and add one for separator line. */
255 	otherheight = options_get_number(w->options, "other-pane-height") + 1;
256 
257 	/*
258 	 * If an other pane height was specified, honour it so long as it
259 	 * doesn't shrink the main height to less than the main-pane-height
260 	 */
261 	if (otherheight > 1 && w->sy - otherheight > mainheight)
262 		mainheight = w->sy - otherheight;
263 	if (mainheight < PANE_MINIMUM + 1)
264 		mainheight = PANE_MINIMUM + 1;
265 
266 	/* Try and make everything fit. */
267 	totalrows = rows * (PANE_MINIMUM + 1) - 1;
268 	if (mainheight + totalrows > w->sy) {
269 		if (totalrows + PANE_MINIMUM + 1 > w->sy)
270 			mainheight = PANE_MINIMUM + 2;
271 		else
272 			mainheight = w->sy - totalrows;
273 		height = PANE_MINIMUM;
274 	} else
275 		height = (w->sy - mainheight - (rows - 1)) / rows;
276 
277 	/* Free old tree and create a new root. */
278 	layout_free(w);
279 	lc = w->layout_root = layout_create_cell(NULL);
280 	layout_set_size(lc, w->sx, mainheight + rows * (height + 1) - 1, 0, 0);
281 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
282 
283 	/* Create the main pane. */
284 	lcmain = layout_create_cell(lc);
285 	layout_set_size(lcmain, w->sx, mainheight - 1, 0, 0);
286 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
287 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
288 
289 	/* Create a grid of the remaining cells. */
290 	wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
291 	for (j = 0; j < rows; j++) {
292 		/* If this is the last cell, all done. */
293 		if (wp == NULL)
294 			break;
295 
296 		/* Create the new row. */
297 		lcrow = layout_create_cell(lc);
298 		layout_set_size(lcrow, w->sx, height, 0, 0);
299 		TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
300 
301 		/* If only one column, just use the row directly. */
302 		if (columns == 1) {
303 			layout_make_leaf(lcrow, wp);
304 			wp = TAILQ_NEXT(wp, entry);
305 			continue;
306 		}
307 
308 		/* Add in the columns. */
309 		layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
310 		for (i = 0; i < columns; i++) {
311 			/* Create and add a pane cell. */
312 			lcchild = layout_create_cell(lcrow);
313 			layout_set_size(lcchild, width, height, 0, 0);
314 			layout_make_leaf(lcchild, wp);
315 			TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
316 
317 			/* Move to the next cell. */
318 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
319 				break;
320 		}
321 
322 		/* Adjust the row to fit the full width if necessary. */
323 		if (i == columns)
324 			i--;
325 		used = ((i + 1) * (width + 1)) - 1;
326 		if (w->sx <= used)
327 			continue;
328 		lcchild = TAILQ_LAST(&lcrow->cells, layout_cells);
329 		layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT,
330 		    w->sx - used);
331 	}
332 
333 	/* Adjust the last row height to fit if necessary. */
334 	used = mainheight + (rows * height) + rows - 1;
335 	if (w->sy > used) {
336 		lcrow = TAILQ_LAST(&lc->cells, layout_cells);
337 		layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
338 		    w->sy - used);
339 	}
340 
341 	/* Fix cell offsets. */
342 	layout_fix_offsets(lc);
343 	layout_fix_panes(w, w->sx, w->sy);
344 
345 	layout_print_cell(w->layout_root, __func__, 1);
346 
347 	notify_window("window-layout-changed", w);
348 	server_redraw_window(w);
349 }
350 
351 static void
352 layout_set_main_v(struct window *w)
353 {
354 	struct window_pane	*wp;
355 	struct layout_cell	*lc, *lcmain, *lccolumn, *lcchild;
356 	u_int			 n, mainwidth, otherwidth, width, height;
357 	u_int			 used, i, j, columns, rows, totalcolumns;
358 
359 	layout_print_cell(w->layout_root, __func__, 1);
360 
361 	/* Get number of panes. */
362 	n = window_count_panes(w);
363 	if (n <= 1)
364 		return;
365 	n--;	/* take off main pane */
366 
367 	/* How many rows and columns will be needed, not counting main? */
368 	rows = (w->sy + 1) / (PANE_MINIMUM + 1);	/* maximum rows */
369 	if (rows == 0)
370 		rows = 1;
371 	columns = 1 + (n - 1) / rows;
372 	rows = 1 + (n - 1) / columns;
373 	height = (w->sy - (n - 1)) / rows;
374 
375 	/* Get the main pane width and add one for separator line. */
376 	mainwidth = options_get_number(w->options, "main-pane-width") + 1;
377 
378 	/* Get the optional other pane width and add one for separator line. */
379 	otherwidth = options_get_number(w->options, "other-pane-width") + 1;
380 
381 	/*
382 	 * If an other pane width was specified, honour it so long as it
383 	 * doesn't shrink the main width to less than the main-pane-width
384 	 */
385 	if (otherwidth > 1 && w->sx - otherwidth > mainwidth)
386 		mainwidth = w->sx - otherwidth;
387 	if (mainwidth < PANE_MINIMUM + 1)
388 		mainwidth = PANE_MINIMUM + 1;
389 
390 	/* Try and make everything fit. */
391 	totalcolumns = columns * (PANE_MINIMUM + 1) - 1;
392 	if (mainwidth + totalcolumns > w->sx) {
393 		if (totalcolumns + PANE_MINIMUM + 1 > w->sx)
394 			mainwidth = PANE_MINIMUM + 2;
395 		else
396 			mainwidth = w->sx - totalcolumns;
397 		width = PANE_MINIMUM;
398 	} else
399 		width = (w->sx - mainwidth - (columns - 1)) / columns;
400 
401 	/* Free old tree and create a new root. */
402 	layout_free(w);
403 	lc = w->layout_root = layout_create_cell(NULL);
404 	layout_set_size(lc, mainwidth + columns * (width + 1) - 1, w->sy, 0, 0);
405 	layout_make_node(lc, LAYOUT_LEFTRIGHT);
406 
407 	/* Create the main pane. */
408 	lcmain = layout_create_cell(lc);
409 	layout_set_size(lcmain, mainwidth - 1, w->sy, 0, 0);
410 	layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
411 	TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
412 
413 	/* Create a grid of the remaining cells. */
414 	wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
415 	for (j = 0; j < columns; j++) {
416 		/* If this is the last cell, all done. */
417 		if (wp == NULL)
418 			break;
419 
420 		/* Create the new column. */
421 		lccolumn = layout_create_cell(lc);
422 		layout_set_size(lccolumn, width, w->sy, 0, 0);
423 		TAILQ_INSERT_TAIL(&lc->cells, lccolumn, entry);
424 
425 		/* If only one row, just use the row directly. */
426 		if (rows == 1) {
427 			layout_make_leaf(lccolumn, wp);
428 			wp = TAILQ_NEXT(wp, entry);
429 			continue;
430 		}
431 
432 		/* Add in the rows. */
433 		layout_make_node(lccolumn, LAYOUT_TOPBOTTOM);
434 		for (i = 0; i < rows; i++) {
435 			/* Create and add a pane cell. */
436 			lcchild = layout_create_cell(lccolumn);
437 			layout_set_size(lcchild, width, height, 0, 0);
438 			layout_make_leaf(lcchild, wp);
439 			TAILQ_INSERT_TAIL(&lccolumn->cells, lcchild, entry);
440 
441 			/* Move to the next cell. */
442 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
443 				break;
444 		}
445 
446 		/* Adjust the column to fit the full height if necessary. */
447 		if (i == rows)
448 			i--;
449 		used = ((i + 1) * (height + 1)) - 1;
450 		if (w->sy <= used)
451 			continue;
452 		lcchild = TAILQ_LAST(&lccolumn->cells, layout_cells);
453 		layout_resize_adjust(w, lcchild, LAYOUT_TOPBOTTOM,
454 		    w->sy - used);
455 	}
456 
457 	/* Adjust the last column width to fit if necessary. */
458 	used = mainwidth + (columns * width) + columns - 1;
459 	if (w->sx > used) {
460 		lccolumn = TAILQ_LAST(&lc->cells, layout_cells);
461 		layout_resize_adjust(w, lccolumn, LAYOUT_LEFTRIGHT,
462 		    w->sx - used);
463 	}
464 
465 	/* Fix cell offsets. */
466 	layout_fix_offsets(lc);
467 	layout_fix_panes(w, w->sx, w->sy);
468 
469 	layout_print_cell(w->layout_root, __func__, 1);
470 
471 	notify_window("window-layout-changed", w);
472 	server_redraw_window(w);
473 }
474 
475 void
476 layout_set_tiled(struct window *w)
477 {
478 	struct window_pane	*wp;
479 	struct layout_cell	*lc, *lcrow, *lcchild;
480 	u_int			 n, width, height, used;
481 	u_int			 i, j, columns, rows;
482 
483 	layout_print_cell(w->layout_root, __func__, 1);
484 
485 	/* Get number of panes. */
486 	n = window_count_panes(w);
487 	if (n <= 1)
488 		return;
489 
490 	/* How many rows and columns are wanted? */
491 	rows = columns = 1;
492 	while (rows * columns < n) {
493 		rows++;
494 		if (rows * columns < n)
495 			columns++;
496 	}
497 
498 	/* What width and height should they be? */
499 	width = (w->sx - (columns - 1)) / columns;
500 	if (width < PANE_MINIMUM)
501 		width = PANE_MINIMUM;
502 	height = (w->sy - (rows - 1)) / rows;
503 	if (height < PANE_MINIMUM)
504 		height = PANE_MINIMUM;
505 
506 	/* Free old tree and create a new root. */
507 	layout_free(w);
508 	lc = w->layout_root = layout_create_cell(NULL);
509 	layout_set_size(lc, (width + 1) * columns - 1,
510 	    (height + 1) * rows - 1, 0, 0);
511 	layout_make_node(lc, LAYOUT_TOPBOTTOM);
512 
513 	/* Create a grid of the cells. */
514 	wp = TAILQ_FIRST(&w->panes);
515 	for (j = 0; j < rows; j++) {
516 		/* If this is the last cell, all done. */
517 		if (wp == NULL)
518 			break;
519 
520 		/* Create the new row. */
521 		lcrow = layout_create_cell(lc);
522 		layout_set_size(lcrow, w->sx, height, 0, 0);
523 		TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
524 
525 		/* If only one column, just use the row directly. */
526 		if (n - (j * columns) == 1 || columns == 1) {
527 			layout_make_leaf(lcrow, wp);
528 			wp = TAILQ_NEXT(wp, entry);
529 			continue;
530 		}
531 
532 		/* Add in the columns. */
533 		layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
534 		for (i = 0; i < columns; i++) {
535 			/* Create and add a pane cell. */
536 			lcchild = layout_create_cell(lcrow);
537 			layout_set_size(lcchild, width, height, 0, 0);
538 			layout_make_leaf(lcchild, wp);
539 			TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
540 
541 			/* Move to the next cell. */
542 			if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
543 				break;
544 		}
545 
546 		/*
547 		 * Adjust the row and columns to fit the full width if
548 		 * necessary.
549 		 */
550 		if (i == columns)
551 			i--;
552 		used = ((i + 1) * (width + 1)) - 1;
553 		if (w->sx <= used)
554 			continue;
555 		lcchild = TAILQ_LAST(&lcrow->cells, layout_cells);
556 		layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT,
557 		    w->sx - used);
558 	}
559 
560 	/* Adjust the last row height to fit if necessary. */
561 	used = (rows * height) + rows - 1;
562 	if (w->sy > used) {
563 		lcrow = TAILQ_LAST(&lc->cells, layout_cells);
564 		layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
565 		    w->sy - used);
566 	}
567 
568 	/* Fix cell offsets. */
569 	layout_fix_offsets(lc);
570 	layout_fix_panes(w, w->sx, w->sy);
571 
572 	layout_print_cell(w->layout_root, __func__, 1);
573 
574 	notify_window("window-layout-changed", w);
575 	server_redraw_window(w);
576 }
577