xref: /openbsd-src/usr.bin/tmux/menu.c (revision f3288aa0cc28680eacfc061c81b7a773bdca40f2)
1*f3288aa0Snicm /* $OpenBSD: menu.c,v 1.54 2024/10/17 17:10:41 nicm Exp $ */
23546f4c9Snicm 
33546f4c9Snicm /*
43546f4c9Snicm  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
53546f4c9Snicm  *
63546f4c9Snicm  * Permission to use, copy, modify, and distribute this software for any
73546f4c9Snicm  * purpose with or without fee is hereby granted, provided that the above
83546f4c9Snicm  * copyright notice and this permission notice appear in all copies.
93546f4c9Snicm  *
103546f4c9Snicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
113546f4c9Snicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
123546f4c9Snicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
133546f4c9Snicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
143546f4c9Snicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
153546f4c9Snicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
163546f4c9Snicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
173546f4c9Snicm  */
183546f4c9Snicm 
193546f4c9Snicm #include <sys/types.h>
203546f4c9Snicm 
213546f4c9Snicm #include <stdlib.h>
223546f4c9Snicm #include <string.h>
233546f4c9Snicm 
243546f4c9Snicm #include "tmux.h"
253546f4c9Snicm 
263546f4c9Snicm struct menu_data {
273546f4c9Snicm 	struct cmdq_item	*item;
283546f4c9Snicm 	int			 flags;
293546f4c9Snicm 
3017d7ce67Snicm 	struct grid_cell	 style;
3117d7ce67Snicm 	struct grid_cell	 border_style;
3219c94b00Snicm 	struct grid_cell	 selected_style;
3317d7ce67Snicm 	enum box_lines		 border_lines;
3417d7ce67Snicm 
353546f4c9Snicm 	struct cmd_find_state	 fs;
363546f4c9Snicm 	struct screen		 s;
373546f4c9Snicm 
383546f4c9Snicm 	u_int			 px;
393546f4c9Snicm 	u_int			 py;
403546f4c9Snicm 
413546f4c9Snicm 	struct menu		*menu;
423546f4c9Snicm 	int			 choice;
433546f4c9Snicm 
443546f4c9Snicm 	menu_choice_cb		 cb;
453546f4c9Snicm 	void			*data;
463546f4c9Snicm };
473546f4c9Snicm 
481335341aSnicm void
491335341aSnicm menu_add_items(struct menu *menu, const struct menu_item *items,
501335341aSnicm     struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
511335341aSnicm {
521335341aSnicm 	const struct menu_item	*loop;
531335341aSnicm 
541335341aSnicm 	for (loop = items; loop->name != NULL; loop++)
551335341aSnicm 		menu_add_item(menu, loop, qitem, c, fs);
561335341aSnicm }
571335341aSnicm 
581335341aSnicm void
591335341aSnicm menu_add_item(struct menu *menu, const struct menu_item *item,
607db4c597Snicm     struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
613546f4c9Snicm {
623546f4c9Snicm 	struct menu_item	*new_item;
63c6c9946fSnicm 	const char		*key = NULL, *cmd, *suffix = "";
64310774ddSnicm 	char			*s, *trimmed, *name;
65c6c9946fSnicm 	u_int			 width, max_width;
661335341aSnicm 	int			 line;
67c6c9946fSnicm 	size_t			 keylen, slen;
681335341aSnicm 
691335341aSnicm 	line = (item == NULL || item->name == NULL || *item->name == '\0');
701335341aSnicm 	if (line && menu->count == 0)
711335341aSnicm 		return;
72422252e0Snicm 	if (line && menu->items[menu->count - 1].name == NULL)
73422252e0Snicm 		return;
743546f4c9Snicm 
753546f4c9Snicm 	menu->items = xreallocarray(menu->items, menu->count + 1,
763546f4c9Snicm 	    sizeof *menu->items);
773546f4c9Snicm 	new_item = &menu->items[menu->count++];
783546f4c9Snicm 	memset(new_item, 0, sizeof *new_item);
793546f4c9Snicm 
801335341aSnicm 	if (line)
813546f4c9Snicm 		return;
821335341aSnicm 
831335341aSnicm 	if (fs != NULL)
84bf46d0ceSnicm 		s = format_single_from_state(qitem, item->name, c, fs);
851335341aSnicm 	else
861335341aSnicm 		s = format_single(qitem, item->name, c, NULL, NULL, NULL);
871335341aSnicm 	if (*s == '\0') { /* no item if empty after format expanded */
883546f4c9Snicm 		menu->count--;
893546f4c9Snicm 		return;
903546f4c9Snicm 	}
91c6c9946fSnicm 	max_width = c->tty.sx - 4;
92c6c9946fSnicm 
93c6c9946fSnicm 	slen = strlen(s);
94f05a5af3Snicm 	if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) {
955416581eSnicm 		key = key_string_lookup_key(item->key, 0);
96c6c9946fSnicm 		keylen = strlen(key) + 3; /* 3 = space and two brackets */
97c6c9946fSnicm 
98c6c9946fSnicm 		/*
99d4e11490Snicm 		 * Add the key if it is shorter than a quarter of the available
100d4e11490Snicm 		 * space or there is space for the entire item text and the
101d4e11490Snicm 		 * key.
102c6c9946fSnicm 		 */
103d4e11490Snicm 		if (keylen <= max_width / 4)
104d4e11490Snicm 			max_width -= keylen;
105d4e11490Snicm 		else if (keylen >= max_width || slen >= max_width - keylen)
106c6c9946fSnicm 			key = NULL;
107c6c9946fSnicm 	}
108c6c9946fSnicm 
109c6c9946fSnicm 	if (slen > max_width) {
110c6c9946fSnicm 		max_width--;
111c6c9946fSnicm 		suffix = ">";
112c6c9946fSnicm 	}
113310774ddSnicm 	trimmed = format_trim_right(s, max_width);
114310774ddSnicm 	if (key != NULL) {
115310774ddSnicm 		xasprintf(&name, "%s%s#[default] #[align=right](%s)",
116310774ddSnicm 		    trimmed, suffix, key);
117310774ddSnicm 	} else
118310774ddSnicm 		xasprintf(&name, "%s%s", trimmed, suffix);
119310774ddSnicm 	free(trimmed);
120d4e11490Snicm 
1211335341aSnicm 	new_item->name = name;
1221335341aSnicm 	free(s);
1233546f4c9Snicm 
1241335341aSnicm 	cmd = item->command;
1251335341aSnicm 	if (cmd != NULL) {
1261335341aSnicm 		if (fs != NULL)
127bf46d0ceSnicm 			s = format_single_from_state(qitem, cmd, c, fs);
1281335341aSnicm 		else
1291335341aSnicm 			s = format_single(qitem, cmd, c, NULL, NULL, NULL);
1307db4c597Snicm 	} else
1311335341aSnicm 		s = NULL;
1321335341aSnicm 	new_item->command = s;
1333546f4c9Snicm 	new_item->key = item->key;
1343546f4c9Snicm 
1353546f4c9Snicm 	width = format_width(new_item->name);
13631f1d149Snicm 	if (*new_item->name == '-')
13731f1d149Snicm 		width--;
1383546f4c9Snicm 	if (width > menu->width)
1393546f4c9Snicm 		menu->width = width;
1403546f4c9Snicm }
1413546f4c9Snicm 
1423546f4c9Snicm struct menu *
1431335341aSnicm menu_create(const char *title)
1443546f4c9Snicm {
1453546f4c9Snicm 	struct menu	*menu;
1463546f4c9Snicm 
1473546f4c9Snicm 	menu = xcalloc(1, sizeof *menu);
1483546f4c9Snicm 	menu->title = xstrdup(title);
1492746cc45Snicm 	menu->width = format_width(title);
1503546f4c9Snicm 
1513546f4c9Snicm 	return (menu);
1523546f4c9Snicm }
1533546f4c9Snicm 
1543546f4c9Snicm void
1553546f4c9Snicm menu_free(struct menu *menu)
1563546f4c9Snicm {
1573546f4c9Snicm 	u_int	i;
1583546f4c9Snicm 
1593546f4c9Snicm 	for (i = 0; i < menu->count; i++) {
1601335341aSnicm 		free((void *)menu->items[i].name);
1611335341aSnicm 		free((void *)menu->items[i].command);
1623546f4c9Snicm 	}
1633546f4c9Snicm 	free(menu->items);
1643546f4c9Snicm 
1651335341aSnicm 	free((void *)menu->title);
1663546f4c9Snicm 	free(menu);
1673546f4c9Snicm }
1683546f4c9Snicm 
169f38784aeSnicm struct screen *
17076013b85Snicm menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
1710cac3d2dSnicm {
172f38784aeSnicm 	struct menu_data	*md = data;
1730cac3d2dSnicm 
17476013b85Snicm 	*cx = md->px + 2;
17576013b85Snicm 	if (md->choice == -1)
17676013b85Snicm 		*cy = md->py;
17776013b85Snicm 	else
17876013b85Snicm 		*cy = md->py + 1 + md->choice;
17976013b85Snicm 
18083e83a91Snicm 	return (&md->s);
1810cac3d2dSnicm }
1820cac3d2dSnicm 
1837f16de32Snicm /* Return parts of the input range which are not obstructed by the menu. */
1847f16de32Snicm void
1857f16de32Snicm menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py,
1867f16de32Snicm     u_int nx, struct overlay_ranges *r)
1873546f4c9Snicm {
188f38784aeSnicm 	struct menu_data	*md = data;
189f38784aeSnicm 	struct menu		*menu = md->menu;
190f38784aeSnicm 
1917f16de32Snicm 	server_client_overlay_range(md->px, md->py, menu->width + 4,
1927f16de32Snicm 	    menu->count + 2, px, py, nx, r);
193f38784aeSnicm }
194f38784aeSnicm 
195f38784aeSnicm void
196f38784aeSnicm menu_draw_cb(struct client *c, void *data,
197f38784aeSnicm     __unused struct screen_redraw_ctx *rctx)
198f38784aeSnicm {
199f38784aeSnicm 	struct menu_data	*md = data;
2003546f4c9Snicm 	struct tty		*tty = &c->tty;
2013546f4c9Snicm 	struct screen		*s = &md->s;
2023546f4c9Snicm 	struct menu		*menu = md->menu;
2033546f4c9Snicm 	struct screen_write_ctx	 ctx;
20408198138Snicm 	u_int			 i, px = md->px, py = md->py;
20548a3a827Snicm 
20683e83a91Snicm 	screen_write_start(&ctx, s);
2073546f4c9Snicm 	screen_write_clearscreen(&ctx, 8);
20817d7ce67Snicm 
20917d7ce67Snicm 	if (md->border_lines != BOX_LINES_NONE) {
21017d7ce67Snicm 		screen_write_box(&ctx, menu->width + 4, menu->count + 2,
21117d7ce67Snicm 		    md->border_lines, &md->border_style, menu->title);
21217d7ce67Snicm 	}
21317d7ce67Snicm 
21417d7ce67Snicm 	screen_write_menu(&ctx, menu, md->choice, md->border_lines,
21519c94b00Snicm 	    &md->style, &md->border_style, &md->selected_style);
2163546f4c9Snicm 	screen_write_stop(&ctx);
2173546f4c9Snicm 
21883e83a91Snicm 	for (i = 0; i < screen_size_y(&md->s); i++) {
21983e83a91Snicm 		tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i,
22083e83a91Snicm 		    &grid_default_cell, NULL);
22183e83a91Snicm 	}
2223546f4c9Snicm }
2233546f4c9Snicm 
224f38784aeSnicm void
225f38784aeSnicm menu_free_cb(__unused struct client *c, void *data)
2263546f4c9Snicm {
227f38784aeSnicm 	struct menu_data	*md = data;
2283546f4c9Snicm 
2293546f4c9Snicm 	if (md->item != NULL)
230780ca620Snicm 		cmdq_continue(md->item);
2313546f4c9Snicm 
232f43bc87cSnicm 	if (md->cb != NULL)
233f43bc87cSnicm 		md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data);
234f43bc87cSnicm 
2353546f4c9Snicm 	screen_free(&md->s);
2363546f4c9Snicm 	menu_free(md->menu);
2373546f4c9Snicm 	free(md);
2383546f4c9Snicm }
2393546f4c9Snicm 
240f38784aeSnicm int
241f38784aeSnicm menu_key_cb(struct client *c, void *data, struct key_event *event)
2423546f4c9Snicm {
243f38784aeSnicm 	struct menu_data		*md = data;
2443546f4c9Snicm 	struct menu			*menu = md->menu;
2453546f4c9Snicm 	struct mouse_event		*m = &event->m;
2463546f4c9Snicm 	u_int				 i;
2473546f4c9Snicm 	int				 count = menu->count, old = md->choice;
24810e1651aSnicm 	const char			*name = NULL;
2491c43462cSnicm 	const struct menu_item		*item;
2501c43462cSnicm 	struct cmdq_state		*state;
2511c43462cSnicm 	enum cmd_parse_status		 status;
2521c43462cSnicm 	char				*error;
2533546f4c9Snicm 
2543546f4c9Snicm 	if (KEYC_IS_MOUSE(event->key)) {
25545f44af0Snicm 		if (md->flags & MENU_NOMOUSE) {
2564e5846a2Snicm 			if (MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1)
25745f44af0Snicm 				return (1);
2583546f4c9Snicm 			return (0);
25945f44af0Snicm 		}
2603546f4c9Snicm 		if (m->x < md->px ||
2613546f4c9Snicm 		    m->x > md->px + 4 + menu->width ||
2623546f4c9Snicm 		    m->y < md->py + 1 ||
2633546f4c9Snicm 		    m->y > md->py + 1 + count - 1) {
2645971ac85Snicm 			if (~md->flags & MENU_STAYOPEN) {
2653546f4c9Snicm 				if (MOUSE_RELEASE(m->b))
2663546f4c9Snicm 					return (1);
2675971ac85Snicm 			} else {
2685971ac85Snicm 				if (!MOUSE_RELEASE(m->b) &&
2694e5846a2Snicm 				    !MOUSE_WHEEL(m->b) &&
2705971ac85Snicm 				    !MOUSE_DRAG(m->b))
2715971ac85Snicm 					return (1);
2725971ac85Snicm 			}
2733546f4c9Snicm 			if (md->choice != -1) {
2743546f4c9Snicm 				md->choice = -1;
2753546f4c9Snicm 				c->flags |= CLIENT_REDRAWOVERLAY;
2763546f4c9Snicm 			}
2773546f4c9Snicm 			return (0);
2783546f4c9Snicm 		}
2795971ac85Snicm 		if (~md->flags & MENU_STAYOPEN) {
2803546f4c9Snicm 			if (MOUSE_RELEASE(m->b))
2813546f4c9Snicm 				goto chosen;
2825971ac85Snicm 		} else {
2834e5846a2Snicm 			if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
2845971ac85Snicm 				goto chosen;
2855971ac85Snicm 		}
286245334a3Snicm 		md->choice = m->y - (md->py + 1);
2873546f4c9Snicm 		if (md->choice != old)
2883546f4c9Snicm 			c->flags |= CLIENT_REDRAWOVERLAY;
2893546f4c9Snicm 		return (0);
2903546f4c9Snicm 	}
291bf2321beSnicm 	for (i = 0; i < (u_int)count; i++) {
292bf2321beSnicm 		name = menu->items[i].name;
293bf2321beSnicm 		if (name == NULL || *name == '-')
294bf2321beSnicm 			continue;
295bf2321beSnicm 		if (event->key == menu->items[i].key) {
296bf2321beSnicm 			md->choice = i;
297bf2321beSnicm 			goto chosen;
298bf2321beSnicm 		}
299bf2321beSnicm 	}
3005416581eSnicm 	switch (event->key & ~KEYC_MASK_FLAGS) {
3013546f4c9Snicm 	case KEYC_UP:
302bf2321beSnicm 	case 'k':
303f05a5af3Snicm 		if (old == -1)
304f05a5af3Snicm 			old = 0;
3053546f4c9Snicm 		do {
3063546f4c9Snicm 			if (md->choice == -1 || md->choice == 0)
3073546f4c9Snicm 				md->choice = count - 1;
3083546f4c9Snicm 			else
3093546f4c9Snicm 				md->choice--;
310f05a5af3Snicm 			name = menu->items[md->choice].name;
311f05a5af3Snicm 		} while ((name == NULL || *name == '-') && md->choice != old);
3123546f4c9Snicm 		c->flags |= CLIENT_REDRAWOVERLAY;
3133546f4c9Snicm 		return (0);
314d6317402Snicm 	case KEYC_BSPACE:
315d6317402Snicm 		if (~md->flags & MENU_TAB)
316d6317402Snicm 			break;
317d6317402Snicm 		return (1);
318d6317402Snicm 	case '\011': /* Tab */
319d6317402Snicm 		if (~md->flags & MENU_TAB)
320d6317402Snicm 			break;
321d6317402Snicm 		if (md->choice == count - 1)
322d6317402Snicm 			return (1);
323d6317402Snicm 		/* FALLTHROUGH */
3243546f4c9Snicm 	case KEYC_DOWN:
325bf2321beSnicm 	case 'j':
326f05a5af3Snicm 		if (old == -1)
327f05a5af3Snicm 			old = 0;
3283546f4c9Snicm 		do {
3293546f4c9Snicm 			if (md->choice == -1 || md->choice == count - 1)
3303546f4c9Snicm 				md->choice = 0;
3313546f4c9Snicm 			else
3323546f4c9Snicm 				md->choice++;
333f05a5af3Snicm 			name = menu->items[md->choice].name;
334f05a5af3Snicm 		} while ((name == NULL || *name == '-') && md->choice != old);
3353546f4c9Snicm 		c->flags |= CLIENT_REDRAWOVERLAY;
3363546f4c9Snicm 		return (0);
337d6317402Snicm 	case KEYC_PPAGE:
338719f5715Snicm 	case 'b'|KEYC_CTRL:
33903a39b52Snicm 		if (md->choice < 6)
340d6317402Snicm 			md->choice = 0;
34103a39b52Snicm 		else {
34203a39b52Snicm 			i = 5;
34303a39b52Snicm 			while (i > 0) {
34403a39b52Snicm 				md->choice--;
34503a39b52Snicm 				name = menu->items[md->choice].name;
34603a39b52Snicm 				if (md->choice != 0 &&
34703a39b52Snicm 				    (name != NULL && *name != '-'))
34803a39b52Snicm 					i--;
34903a39b52Snicm 				else if (md->choice == 0)
35003a39b52Snicm 					break;
35103a39b52Snicm 			}
35203a39b52Snicm 		}
35303a39b52Snicm 		c->flags |= CLIENT_REDRAWOVERLAY;
35403a39b52Snicm 		break;
35503a39b52Snicm 	case KEYC_NPAGE:
35603a39b52Snicm 		if (md->choice > count - 6) {
35703a39b52Snicm 			md->choice = count - 1;
35803a39b52Snicm 			name = menu->items[md->choice].name;
35903a39b52Snicm 		} else {
36003a39b52Snicm 			i = 5;
36103a39b52Snicm 			while (i > 0) {
362d6317402Snicm 				md->choice++;
36303a39b52Snicm 				name = menu->items[md->choice].name;
36403a39b52Snicm 				if (md->choice != count - 1 &&
36503a39b52Snicm 				    (name != NULL && *name != '-'))
36603a39b52Snicm 					i++;
36703a39b52Snicm 				else if (md->choice == count - 1)
36803a39b52Snicm 					break;
36903a39b52Snicm 			}
37003a39b52Snicm 		}
37103a39b52Snicm 		while (name == NULL || *name == '-') {
37203a39b52Snicm 			md->choice--;
37303a39b52Snicm 			name = menu->items[md->choice].name;
37403a39b52Snicm 		}
37503a39b52Snicm 		c->flags |= CLIENT_REDRAWOVERLAY;
37603a39b52Snicm 		break;
37703a39b52Snicm 	case 'g':
37803a39b52Snicm 	case KEYC_HOME:
37903a39b52Snicm 		md->choice = 0;
38003a39b52Snicm 		name = menu->items[md->choice].name;
38103a39b52Snicm 		while (name == NULL || *name == '-') {
38203a39b52Snicm 			md->choice++;
38303a39b52Snicm 			name = menu->items[md->choice].name;
38403a39b52Snicm 		}
385d6317402Snicm 		c->flags |= CLIENT_REDRAWOVERLAY;
386d6317402Snicm 		break;
387d6317402Snicm 	case 'G':
38803a39b52Snicm 	case KEYC_END:
389d6317402Snicm 		md->choice = count - 1;
39003a39b52Snicm 		name = menu->items[md->choice].name;
39103a39b52Snicm 		while (name == NULL || *name == '-') {
392d6317402Snicm 			md->choice--;
39303a39b52Snicm 			name = menu->items[md->choice].name;
39403a39b52Snicm 		}
395d6317402Snicm 		c->flags |= CLIENT_REDRAWOVERLAY;
396d6317402Snicm 		break;
397719f5715Snicm 	case 'f'|KEYC_CTRL:
398d6317402Snicm 		break;
3993546f4c9Snicm 	case '\r':
4003546f4c9Snicm 		goto chosen;
4013546f4c9Snicm 	case '\033': /* Escape */
402719f5715Snicm 	case 'c'|KEYC_CTRL:
403719f5715Snicm 	case 'g'|KEYC_CTRL:
4043546f4c9Snicm 	case 'q':
4053546f4c9Snicm 		return (1);
4063546f4c9Snicm 	}
4073546f4c9Snicm 	return (0);
4083546f4c9Snicm 
4093546f4c9Snicm chosen:
4103546f4c9Snicm 	if (md->choice == -1)
4113546f4c9Snicm 		return (1);
4123546f4c9Snicm 	item = &menu->items[md->choice];
413e579339dSnicm 	if (item->name == NULL || *item->name == '-') {
414e579339dSnicm 		if (md->flags & MENU_STAYOPEN)
4155971ac85Snicm 			return (0);
4163546f4c9Snicm 		return (1);
417e579339dSnicm 	}
4183546f4c9Snicm 	if (md->cb != NULL) {
4193546f4c9Snicm 	    md->cb(md->menu, md->choice, item->key, md->data);
420f43bc87cSnicm 	    md->cb = NULL;
4213546f4c9Snicm 	    return (1);
4223546f4c9Snicm 	}
423df6ab229Snicm 
4247db4c597Snicm 	if (md->item != NULL)
425823b6d6dSnicm 		event = cmdq_get_event(md->item);
4267db4c597Snicm 	else
427823b6d6dSnicm 		event = NULL;
4281c43462cSnicm 	state = cmdq_new_state(&md->fs, event, 0);
4291c43462cSnicm 
4301c43462cSnicm 	status = cmd_parse_and_append(item->command, NULL, c, state, &error);
4311c43462cSnicm 	if (status == CMD_PARSE_ERROR) {
4321c43462cSnicm 		cmdq_append(c, cmdq_get_error(error));
4331c43462cSnicm 		free(error);
4343546f4c9Snicm 	}
4351c43462cSnicm 	cmdq_free_state(state);
4361c43462cSnicm 
4373546f4c9Snicm 	return (1);
4383546f4c9Snicm }
4393546f4c9Snicm 
44019c94b00Snicm static void
44119c94b00Snicm menu_set_style(struct client *c, struct grid_cell *gc, const char *style,
44219c94b00Snicm     const char *option)
44319c94b00Snicm {
44419c94b00Snicm 	struct style	 sytmp;
44519c94b00Snicm 	struct options	*o = c->session->curw->window->options;
44619c94b00Snicm 
44719c94b00Snicm 	memcpy(gc, &grid_default_cell, sizeof *gc);
44819c94b00Snicm 	style_apply(gc, o, option, NULL);
44919c94b00Snicm 	if (style != NULL) {
45019c94b00Snicm 		style_set(&sytmp, &grid_default_cell);
45119c94b00Snicm 		if (style_parse(&sytmp, gc, style) == 0) {
45219c94b00Snicm 			gc->fg = sytmp.gc.fg;
45319c94b00Snicm 			gc->bg = sytmp.gc.bg;
45419c94b00Snicm 		}
45519c94b00Snicm 	}
45619c94b00Snicm }
45719c94b00Snicm 
458f38784aeSnicm struct menu_data *
459009dd187Snicm menu_prepare(struct menu *menu, int flags, int starting_choice,
460009dd187Snicm     struct cmdq_item *item, u_int px, u_int py, struct client *c,
46119c94b00Snicm     enum box_lines lines, const char *style, const char *selected_style,
46219c94b00Snicm     const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb,
46319c94b00Snicm     void *data)
4643546f4c9Snicm {
4653546f4c9Snicm 	struct menu_data	*md;
466009dd187Snicm 	int			 choice;
467b6543d77Snicm 	const char		*name;
46817d7ce67Snicm 	struct options		*o = c->session->curw->window->options;
4693546f4c9Snicm 
4703546f4c9Snicm 	if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2)
471f38784aeSnicm 		return (NULL);
472e38aac43Snicm 	if (px + menu->width + 4 > c->tty.sx)
473e38aac43Snicm 		px = c->tty.sx - menu->width - 4;
474e38aac43Snicm 	if (py + menu->count + 2 > c->tty.sy)
475e38aac43Snicm 		py = c->tty.sy - menu->count - 2;
4763546f4c9Snicm 
47717d7ce67Snicm 	if (lines == BOX_LINES_DEFAULT)
47817d7ce67Snicm 		lines = options_get_number(o, "menu-border-lines");
47917d7ce67Snicm 
4803546f4c9Snicm 	md = xcalloc(1, sizeof *md);
4813546f4c9Snicm 	md->item = item;
4823546f4c9Snicm 	md->flags = flags;
48317d7ce67Snicm 	md->border_lines = lines;
48417d7ce67Snicm 
48519c94b00Snicm 	menu_set_style(c, &md->style, style, "menu-style");
48619c94b00Snicm 	menu_set_style(c, &md->selected_style, selected_style,
48719c94b00Snicm 	    "menu-selected-style");
48819c94b00Snicm 	menu_set_style(c, &md->border_style, border_style, "menu-border-style");
4893546f4c9Snicm 
490f43bc87cSnicm 	if (fs != NULL)
4913546f4c9Snicm 		cmd_find_copy_state(&md->fs, fs);
4923546f4c9Snicm 	screen_init(&md->s, menu->width + 4, menu->count + 2, 0);
49383e83a91Snicm 	if (~md->flags & MENU_NOMOUSE)
4942b7bea19Snicm 		md->s.mode |= (MODE_MOUSE_ALL|MODE_MOUSE_BUTTON);
495e620b8e2Snicm 	md->s.mode &= ~MODE_CURSOR;
4963546f4c9Snicm 
4973546f4c9Snicm 	md->px = px;
4983546f4c9Snicm 	md->py = py;
4993546f4c9Snicm 
5003546f4c9Snicm 	md->menu = menu;
501009dd187Snicm 	md->choice = -1;
502009dd187Snicm 
503b6543d77Snicm 	if (md->flags & MENU_NOMOUSE) {
504009dd187Snicm 		if (starting_choice >= (int)menu->count) {
505009dd187Snicm 			starting_choice = menu->count - 1;
506009dd187Snicm 			choice = starting_choice + 1;
507009dd187Snicm 			for (;;) {
508009dd187Snicm 				name = menu->items[choice - 1].name;
509009dd187Snicm 				if (name != NULL && *name != '-') {
510009dd187Snicm 					md->choice = choice - 1;
511b6543d77Snicm 					break;
512b6543d77Snicm 				}
513009dd187Snicm 				if (--choice == 0)
514009dd187Snicm 					choice = menu->count;
515009dd187Snicm 				if (choice == starting_choice + 1)
516009dd187Snicm 					break;
517009dd187Snicm 			}
518009dd187Snicm 		} else if (starting_choice >= 0) {
519009dd187Snicm 			choice = starting_choice;
520009dd187Snicm 			for (;;) {
521009dd187Snicm 				name = menu->items[choice].name;
522009dd187Snicm 				if (name != NULL && *name != '-') {
523009dd187Snicm 					md->choice = choice;
524009dd187Snicm 					break;
525009dd187Snicm 				}
526009dd187Snicm 				if (++choice == (int)menu->count)
527009dd187Snicm 					choice = 0;
528009dd187Snicm 				if (choice == starting_choice)
529009dd187Snicm 					break;
530009dd187Snicm 			}
531009dd187Snicm 		}
532009dd187Snicm 	}
5333546f4c9Snicm 
5343546f4c9Snicm 	md->cb = cb;
5353546f4c9Snicm 	md->data = data;
536f38784aeSnicm 	return (md);
537f38784aeSnicm }
5383546f4c9Snicm 
539f38784aeSnicm int
540009dd187Snicm menu_display(struct menu *menu, int flags, int starting_choice,
541009dd187Snicm     struct cmdq_item *item, u_int px, u_int py, struct client *c,
54219c94b00Snicm     enum box_lines lines, const char *style, const char *selected_style,
54319c94b00Snicm     const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb,
54419c94b00Snicm     void *data)
545f38784aeSnicm {
546f38784aeSnicm 	struct menu_data	*md;
547f38784aeSnicm 
54817d7ce67Snicm 	md = menu_prepare(menu, flags, starting_choice, item, px, py, c, lines,
54919c94b00Snicm 	    style, selected_style, border_style, fs, cb, data);
550f38784aeSnicm 	if (md == NULL)
551f38784aeSnicm 		return (-1);
5520cac3d2dSnicm 	server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb,
553c0287628Snicm 	    menu_key_cb, menu_free_cb, NULL, md);
5543546f4c9Snicm 	return (0);
5553546f4c9Snicm }
556