xref: /netbsd-src/external/bsd/tmux/dist/menu.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
16483eba0Schristos /* $OpenBSD$ */
26483eba0Schristos 
36483eba0Schristos /*
46483eba0Schristos  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
56483eba0Schristos  *
66483eba0Schristos  * Permission to use, copy, modify, and distribute this software for any
76483eba0Schristos  * purpose with or without fee is hereby granted, provided that the above
86483eba0Schristos  * copyright notice and this permission notice appear in all copies.
96483eba0Schristos  *
106483eba0Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
116483eba0Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
126483eba0Schristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
136483eba0Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
146483eba0Schristos  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
156483eba0Schristos  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
166483eba0Schristos  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
176483eba0Schristos  */
186483eba0Schristos 
196483eba0Schristos #include <sys/types.h>
206483eba0Schristos 
216483eba0Schristos #include <stdlib.h>
226483eba0Schristos #include <string.h>
236483eba0Schristos 
246483eba0Schristos #include "tmux.h"
256483eba0Schristos 
266483eba0Schristos struct menu_data {
276483eba0Schristos 	struct cmdq_item	*item;
286483eba0Schristos 	int			 flags;
296483eba0Schristos 
30f844e94eSwiz 	struct grid_cell	 style;
31f844e94eSwiz 	struct grid_cell	 border_style;
32f844e94eSwiz 	struct grid_cell	 selected_style;
33f844e94eSwiz 	enum box_lines		 border_lines;
34f844e94eSwiz 
356483eba0Schristos 	struct cmd_find_state	 fs;
366483eba0Schristos 	struct screen		 s;
376483eba0Schristos 
386483eba0Schristos 	u_int			 px;
396483eba0Schristos 	u_int			 py;
406483eba0Schristos 
416483eba0Schristos 	struct menu		*menu;
426483eba0Schristos 	int			 choice;
436483eba0Schristos 
446483eba0Schristos 	menu_choice_cb		 cb;
456483eba0Schristos 	void			*data;
466483eba0Schristos };
476483eba0Schristos 
486483eba0Schristos void
496483eba0Schristos menu_add_items(struct menu *menu, const struct menu_item *items,
506483eba0Schristos     struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
516483eba0Schristos {
526483eba0Schristos 	const struct menu_item	*loop;
536483eba0Schristos 
546483eba0Schristos 	for (loop = items; loop->name != NULL; loop++)
556483eba0Schristos 		menu_add_item(menu, loop, qitem, c, fs);
566483eba0Schristos }
576483eba0Schristos 
586483eba0Schristos void
596483eba0Schristos menu_add_item(struct menu *menu, const struct menu_item *item,
606483eba0Schristos     struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
616483eba0Schristos {
626483eba0Schristos 	struct menu_item	*new_item;
6346548964Swiz 	const char		*key = NULL, *cmd, *suffix = "";
6446548964Swiz 	char			*s, *trimmed, *name;
6546548964Swiz 	u_int			 width, max_width;
666483eba0Schristos 	int			 line;
6746548964Swiz 	size_t			 keylen, slen;
686483eba0Schristos 
696483eba0Schristos 	line = (item == NULL || item->name == NULL || *item->name == '\0');
706483eba0Schristos 	if (line && menu->count == 0)
716483eba0Schristos 		return;
72f844e94eSwiz 	if (line && menu->items[menu->count - 1].name == NULL)
73f844e94eSwiz 		return;
746483eba0Schristos 
756483eba0Schristos 	menu->items = xreallocarray(menu->items, menu->count + 1,
766483eba0Schristos 	    sizeof *menu->items);
776483eba0Schristos 	new_item = &menu->items[menu->count++];
786483eba0Schristos 	memset(new_item, 0, sizeof *new_item);
796483eba0Schristos 
806483eba0Schristos 	if (line)
816483eba0Schristos 		return;
826483eba0Schristos 
836483eba0Schristos 	if (fs != NULL)
84e271dbb8Schristos 		s = format_single_from_state(qitem, item->name, c, fs);
856483eba0Schristos 	else
866483eba0Schristos 		s = format_single(qitem, item->name, c, NULL, NULL, NULL);
876483eba0Schristos 	if (*s == '\0') { /* no item if empty after format expanded */
886483eba0Schristos 		menu->count--;
896483eba0Schristos 		return;
906483eba0Schristos 	}
9146548964Swiz 	max_width = c->tty.sx - 4;
9246548964Swiz 
9346548964Swiz 	slen = strlen(s);
946483eba0Schristos 	if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) {
95e271dbb8Schristos 		key = key_string_lookup_key(item->key, 0);
9646548964Swiz 		keylen = strlen(key) + 3; /* 3 = space and two brackets */
9746548964Swiz 
9846548964Swiz 		/*
9946548964Swiz 		 * Add the key if it is shorter than a quarter of the available
10046548964Swiz 		 * space or there is space for the entire item text and the
10146548964Swiz 		 * key.
10246548964Swiz 		 */
10346548964Swiz 		if (keylen <= max_width / 4)
10446548964Swiz 			max_width -= keylen;
10546548964Swiz 		else if (keylen >= max_width || slen >= max_width - keylen)
10646548964Swiz 			key = NULL;
10746548964Swiz 	}
10846548964Swiz 
10946548964Swiz 	if (slen > max_width) {
11046548964Swiz 		max_width--;
11146548964Swiz 		suffix = ">";
11246548964Swiz 	}
11346548964Swiz 	trimmed = format_trim_right(s, max_width);
11446548964Swiz 	if (key != NULL) {
11546548964Swiz 		xasprintf(&name, "%s%s#[default] #[align=right](%s)",
11646548964Swiz 		    trimmed, suffix, key);
1176483eba0Schristos 	} else
11846548964Swiz 		xasprintf(&name, "%s%s", trimmed, suffix);
11946548964Swiz 	free(trimmed);
12046548964Swiz 
1216483eba0Schristos 	new_item->name = name;
1226483eba0Schristos 	free(s);
1236483eba0Schristos 
1246483eba0Schristos 	cmd = item->command;
1256483eba0Schristos 	if (cmd != NULL) {
1266483eba0Schristos 		if (fs != NULL)
127e271dbb8Schristos 			s = format_single_from_state(qitem, cmd, c, fs);
1286483eba0Schristos 		else
1296483eba0Schristos 			s = format_single(qitem, cmd, c, NULL, NULL, NULL);
1306483eba0Schristos 	} else
1316483eba0Schristos 		s = NULL;
1326483eba0Schristos 	new_item->command = s;
1336483eba0Schristos 	new_item->key = item->key;
1346483eba0Schristos 
1356483eba0Schristos 	width = format_width(new_item->name);
13646548964Swiz 	if (*new_item->name == '-')
13746548964Swiz 		width--;
1386483eba0Schristos 	if (width > menu->width)
1396483eba0Schristos 		menu->width = width;
1406483eba0Schristos }
1416483eba0Schristos 
1426483eba0Schristos struct menu *
1436483eba0Schristos menu_create(const char *title)
1446483eba0Schristos {
1456483eba0Schristos 	struct menu	*menu;
1466483eba0Schristos 
1476483eba0Schristos 	menu = xcalloc(1, sizeof *menu);
1486483eba0Schristos 	menu->title = xstrdup(title);
149e271dbb8Schristos 	menu->width = format_width(title);
1506483eba0Schristos 
1516483eba0Schristos 	return (menu);
1526483eba0Schristos }
1536483eba0Schristos 
1546483eba0Schristos void
1556483eba0Schristos menu_free(struct menu *menu)
1566483eba0Schristos {
1576483eba0Schristos 	u_int	i;
1586483eba0Schristos 
1596483eba0Schristos 	for (i = 0; i < menu->count; i++) {
16030744affSchristos 		free(__UNCONST(menu->items[i].name));
16130744affSchristos 		free(__UNCONST(menu->items[i].command));
1626483eba0Schristos 	}
1636483eba0Schristos 	free(menu->items);
1646483eba0Schristos 
16530744affSchristos 	free(__UNCONST(menu->title));
1666483eba0Schristos 	free(menu);
1676483eba0Schristos }
1686483eba0Schristos 
16946548964Swiz struct screen *
170f844e94eSwiz menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
171e271dbb8Schristos {
17246548964Swiz 	struct menu_data	*md = data;
173e271dbb8Schristos 
174f844e94eSwiz 	*cx = md->px + 2;
175f844e94eSwiz 	if (md->choice == -1)
176f844e94eSwiz 		*cy = md->py;
177f844e94eSwiz 	else
178f844e94eSwiz 		*cy = md->py + 1 + md->choice;
179f844e94eSwiz 
180e271dbb8Schristos 	return (&md->s);
181e271dbb8Schristos }
182e271dbb8Schristos 
18346548964Swiz /* Return parts of the input range which are not obstructed by the menu. */
18446548964Swiz void
18546548964Swiz menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py,
18646548964Swiz     u_int nx, struct overlay_ranges *r)
1876483eba0Schristos {
18846548964Swiz 	struct menu_data	*md = data;
18946548964Swiz 	struct menu		*menu = md->menu;
19046548964Swiz 
19146548964Swiz 	server_client_overlay_range(md->px, md->py, menu->width + 4,
19246548964Swiz 	    menu->count + 2, px, py, nx, r);
19346548964Swiz }
19446548964Swiz 
19546548964Swiz void
19646548964Swiz menu_draw_cb(struct client *c, void *data,
19746548964Swiz     __unused struct screen_redraw_ctx *rctx)
19846548964Swiz {
19946548964Swiz 	struct menu_data	*md = data;
2006483eba0Schristos 	struct tty		*tty = &c->tty;
2016483eba0Schristos 	struct screen		*s = &md->s;
2026483eba0Schristos 	struct menu		*menu = md->menu;
2036483eba0Schristos 	struct screen_write_ctx	 ctx;
204e271dbb8Schristos 	u_int			 i, px = md->px, py = md->py;
205e271dbb8Schristos 
206e271dbb8Schristos 	screen_write_start(&ctx, s);
2076483eba0Schristos 	screen_write_clearscreen(&ctx, 8);
208f844e94eSwiz 
209f844e94eSwiz 	if (md->border_lines != BOX_LINES_NONE) {
210f844e94eSwiz 		screen_write_box(&ctx, menu->width + 4, menu->count + 2,
211f844e94eSwiz 		    md->border_lines, &md->border_style, menu->title);
212f844e94eSwiz 	}
213f844e94eSwiz 
214f844e94eSwiz 	screen_write_menu(&ctx, menu, md->choice, md->border_lines,
215f844e94eSwiz 	    &md->style, &md->border_style, &md->selected_style);
2166483eba0Schristos 	screen_write_stop(&ctx);
2176483eba0Schristos 
218e271dbb8Schristos 	for (i = 0; i < screen_size_y(&md->s); i++) {
219e271dbb8Schristos 		tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i,
220e271dbb8Schristos 		    &grid_default_cell, NULL);
221e271dbb8Schristos 	}
2226483eba0Schristos }
2236483eba0Schristos 
22446548964Swiz void
22546548964Swiz menu_free_cb(__unused struct client *c, void *data)
2266483eba0Schristos {
22746548964Swiz 	struct menu_data	*md = data;
2286483eba0Schristos 
2296483eba0Schristos 	if (md->item != NULL)
2306483eba0Schristos 		cmdq_continue(md->item);
2316483eba0Schristos 
2326483eba0Schristos 	if (md->cb != NULL)
2336483eba0Schristos 		md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data);
2346483eba0Schristos 
2356483eba0Schristos 	screen_free(&md->s);
2366483eba0Schristos 	menu_free(md->menu);
2376483eba0Schristos 	free(md);
2386483eba0Schristos }
2396483eba0Schristos 
24046548964Swiz int
24146548964Swiz menu_key_cb(struct client *c, void *data, struct key_event *event)
2426483eba0Schristos {
24346548964Swiz 	struct menu_data		*md = data;
2446483eba0Schristos 	struct menu			*menu = md->menu;
2456483eba0Schristos 	struct mouse_event		*m = &event->m;
2466483eba0Schristos 	u_int				 i;
2476483eba0Schristos 	int				 count = menu->count, old = md->choice;
248e271dbb8Schristos 	const char			*name = NULL;
2496483eba0Schristos 	const struct menu_item		*item;
250e271dbb8Schristos 	struct cmdq_state		*state;
251e271dbb8Schristos 	enum cmd_parse_status		 status;
252e271dbb8Schristos 	char				*error;
2536483eba0Schristos 
2546483eba0Schristos 	if (KEYC_IS_MOUSE(event->key)) {
2556483eba0Schristos 		if (md->flags & MENU_NOMOUSE) {
25646548964Swiz 			if (MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1)
2576483eba0Schristos 				return (1);
2586483eba0Schristos 			return (0);
2596483eba0Schristos 		}
2606483eba0Schristos 		if (m->x < md->px ||
2616483eba0Schristos 		    m->x > md->px + 4 + menu->width ||
2626483eba0Schristos 		    m->y < md->py + 1 ||
2636483eba0Schristos 		    m->y > md->py + 1 + count - 1) {
264e271dbb8Schristos 			if (~md->flags & MENU_STAYOPEN) {
2656483eba0Schristos 				if (MOUSE_RELEASE(m->b))
2666483eba0Schristos 					return (1);
267e271dbb8Schristos 			} else {
268e271dbb8Schristos 				if (!MOUSE_RELEASE(m->b) &&
26946548964Swiz 				    !MOUSE_WHEEL(m->b) &&
270e271dbb8Schristos 				    !MOUSE_DRAG(m->b))
271e271dbb8Schristos 					return (1);
272e271dbb8Schristos 			}
2736483eba0Schristos 			if (md->choice != -1) {
2746483eba0Schristos 				md->choice = -1;
2756483eba0Schristos 				c->flags |= CLIENT_REDRAWOVERLAY;
2766483eba0Schristos 			}
2776483eba0Schristos 			return (0);
2786483eba0Schristos 		}
279e271dbb8Schristos 		if (~md->flags & MENU_STAYOPEN) {
2806483eba0Schristos 			if (MOUSE_RELEASE(m->b))
2816483eba0Schristos 				goto chosen;
282e271dbb8Schristos 		} else {
28346548964Swiz 			if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
284e271dbb8Schristos 				goto chosen;
285e271dbb8Schristos 		}
2866483eba0Schristos 		md->choice = m->y - (md->py + 1);
2876483eba0Schristos 		if (md->choice != old)
2886483eba0Schristos 			c->flags |= CLIENT_REDRAWOVERLAY;
2896483eba0Schristos 		return (0);
2906483eba0Schristos 	}
2916483eba0Schristos 	for (i = 0; i < (u_int)count; i++) {
2926483eba0Schristos 		name = menu->items[i].name;
2936483eba0Schristos 		if (name == NULL || *name == '-')
2946483eba0Schristos 			continue;
2956483eba0Schristos 		if (event->key == menu->items[i].key) {
2966483eba0Schristos 			md->choice = i;
2976483eba0Schristos 			goto chosen;
2986483eba0Schristos 		}
2996483eba0Schristos 	}
300e271dbb8Schristos 	switch (event->key & ~KEYC_MASK_FLAGS) {
3016483eba0Schristos 	case KEYC_UP:
3026483eba0Schristos 	case 'k':
3036483eba0Schristos 		if (old == -1)
3046483eba0Schristos 			old = 0;
3056483eba0Schristos 		do {
3066483eba0Schristos 			if (md->choice == -1 || md->choice == 0)
3076483eba0Schristos 				md->choice = count - 1;
3086483eba0Schristos 			else
3096483eba0Schristos 				md->choice--;
3106483eba0Schristos 			name = menu->items[md->choice].name;
3116483eba0Schristos 		} while ((name == NULL || *name == '-') && md->choice != old);
3126483eba0Schristos 		c->flags |= CLIENT_REDRAWOVERLAY;
3136483eba0Schristos 		return (0);
314e271dbb8Schristos 	case KEYC_BSPACE:
315e271dbb8Schristos 		if (~md->flags & MENU_TAB)
316e271dbb8Schristos 			break;
317e271dbb8Schristos 		return (1);
318e271dbb8Schristos 	case '\011': /* Tab */
319e271dbb8Schristos 		if (~md->flags & MENU_TAB)
320e271dbb8Schristos 			break;
321e271dbb8Schristos 		if (md->choice == count - 1)
322e271dbb8Schristos 			return (1);
323e271dbb8Schristos 		/* FALLTHROUGH */
3246483eba0Schristos 	case KEYC_DOWN:
3256483eba0Schristos 	case 'j':
3266483eba0Schristos 		if (old == -1)
3276483eba0Schristos 			old = 0;
3286483eba0Schristos 		do {
3296483eba0Schristos 			if (md->choice == -1 || md->choice == count - 1)
3306483eba0Schristos 				md->choice = 0;
3316483eba0Schristos 			else
3326483eba0Schristos 				md->choice++;
3336483eba0Schristos 			name = menu->items[md->choice].name;
3346483eba0Schristos 		} while ((name == NULL || *name == '-') && md->choice != old);
3356483eba0Schristos 		c->flags |= CLIENT_REDRAWOVERLAY;
3366483eba0Schristos 		return (0);
337e271dbb8Schristos 	case KEYC_PPAGE:
338*890b6d91Swiz 	case 'b'|KEYC_CTRL:
339f844e94eSwiz 		if (md->choice < 6)
340e271dbb8Schristos 			md->choice = 0;
341f844e94eSwiz 		else {
342f844e94eSwiz 			i = 5;
343f844e94eSwiz 			while (i > 0) {
344f844e94eSwiz 				md->choice--;
345f844e94eSwiz 				name = menu->items[md->choice].name;
346f844e94eSwiz 				if (md->choice != 0 &&
347f844e94eSwiz 				    (name != NULL && *name != '-'))
348f844e94eSwiz 					i--;
349f844e94eSwiz 				else if (md->choice == 0)
350f844e94eSwiz 					break;
351f844e94eSwiz 			}
352f844e94eSwiz 		}
353f844e94eSwiz 		c->flags |= CLIENT_REDRAWOVERLAY;
354f844e94eSwiz 		break;
355f844e94eSwiz 	case KEYC_NPAGE:
356f844e94eSwiz 		if (md->choice > count - 6) {
357f844e94eSwiz 			md->choice = count - 1;
358f844e94eSwiz 			name = menu->items[md->choice].name;
359f844e94eSwiz 		} else {
360f844e94eSwiz 			i = 5;
361f844e94eSwiz 			while (i > 0) {
362e271dbb8Schristos 				md->choice++;
363f844e94eSwiz 				name = menu->items[md->choice].name;
364f844e94eSwiz 				if (md->choice != count - 1 &&
365f844e94eSwiz 				    (name != NULL && *name != '-'))
366f844e94eSwiz 					i++;
367f844e94eSwiz 				else if (md->choice == count - 1)
368f844e94eSwiz 					break;
369f844e94eSwiz 			}
370f844e94eSwiz 		}
371f844e94eSwiz 		while (name == NULL || *name == '-') {
372f844e94eSwiz 			md->choice--;
373f844e94eSwiz 			name = menu->items[md->choice].name;
374f844e94eSwiz 		}
375f844e94eSwiz 		c->flags |= CLIENT_REDRAWOVERLAY;
376f844e94eSwiz 		break;
377f844e94eSwiz 	case 'g':
378f844e94eSwiz 	case KEYC_HOME:
379f844e94eSwiz 		md->choice = 0;
380f844e94eSwiz 		name = menu->items[md->choice].name;
381f844e94eSwiz 		while (name == NULL || *name == '-') {
382f844e94eSwiz 			md->choice++;
383f844e94eSwiz 			name = menu->items[md->choice].name;
384f844e94eSwiz 		}
385e271dbb8Schristos 		c->flags |= CLIENT_REDRAWOVERLAY;
386e271dbb8Schristos 		break;
387e271dbb8Schristos 	case 'G':
388f844e94eSwiz 	case KEYC_END:
389e271dbb8Schristos 		md->choice = count - 1;
390f844e94eSwiz 		name = menu->items[md->choice].name;
391f844e94eSwiz 		while (name == NULL || *name == '-') {
392e271dbb8Schristos 			md->choice--;
393f844e94eSwiz 			name = menu->items[md->choice].name;
394f844e94eSwiz 		}
395e271dbb8Schristos 		c->flags |= CLIENT_REDRAWOVERLAY;
396e271dbb8Schristos 		break;
397*890b6d91Swiz 	case 'f'|KEYC_CTRL:
398e271dbb8Schristos 		break;
3996483eba0Schristos 	case '\r':
4006483eba0Schristos 		goto chosen;
4016483eba0Schristos 	case '\033': /* Escape */
402*890b6d91Swiz 	case 'c'|KEYC_CTRL:
403*890b6d91Swiz 	case 'g'|KEYC_CTRL:
4046483eba0Schristos 	case 'q':
4056483eba0Schristos 		return (1);
4066483eba0Schristos 	}
4076483eba0Schristos 	return (0);
4086483eba0Schristos 
4096483eba0Schristos chosen:
4106483eba0Schristos 	if (md->choice == -1)
4116483eba0Schristos 		return (1);
4126483eba0Schristos 	item = &menu->items[md->choice];
413e271dbb8Schristos 	if (item->name == NULL || *item->name == '-') {
414e271dbb8Schristos 		if (md->flags & MENU_STAYOPEN)
415e271dbb8Schristos 			return (0);
4166483eba0Schristos 		return (1);
417e271dbb8Schristos 	}
4186483eba0Schristos 	if (md->cb != NULL) {
4196483eba0Schristos 	    md->cb(md->menu, md->choice, item->key, md->data);
4206483eba0Schristos 	    md->cb = NULL;
4216483eba0Schristos 	    return (1);
4226483eba0Schristos 	}
4236483eba0Schristos 
4246483eba0Schristos 	if (md->item != NULL)
425e271dbb8Schristos 		event = cmdq_get_event(md->item);
4266483eba0Schristos 	else
427e271dbb8Schristos 		event = NULL;
428e271dbb8Schristos 	state = cmdq_new_state(&md->fs, event, 0);
429e271dbb8Schristos 
430e271dbb8Schristos 	status = cmd_parse_and_append(item->command, NULL, c, state, &error);
431e271dbb8Schristos 	if (status == CMD_PARSE_ERROR) {
432e271dbb8Schristos 		cmdq_append(c, cmdq_get_error(error));
433e271dbb8Schristos 		free(error);
4346483eba0Schristos 	}
435e271dbb8Schristos 	cmdq_free_state(state);
436e271dbb8Schristos 
4376483eba0Schristos 	return (1);
4386483eba0Schristos }
4396483eba0Schristos 
440f844e94eSwiz static void
441f844e94eSwiz menu_set_style(struct client *c, struct grid_cell *gc, const char *style,
442f844e94eSwiz     const char *option)
443f844e94eSwiz {
444f844e94eSwiz 	struct style	 sytmp;
445f844e94eSwiz 	struct options	*o = c->session->curw->window->options;
446f844e94eSwiz 
447f844e94eSwiz 	memcpy(gc, &grid_default_cell, sizeof *gc);
448f844e94eSwiz 	style_apply(gc, o, option, NULL);
449f844e94eSwiz 	if (style != NULL) {
450f844e94eSwiz 		style_set(&sytmp, &grid_default_cell);
451f844e94eSwiz 		if (style_parse(&sytmp, gc, style) == 0) {
452f844e94eSwiz 			gc->fg = sytmp.gc.fg;
453f844e94eSwiz 			gc->bg = sytmp.gc.bg;
454f844e94eSwiz 		}
455f844e94eSwiz 	}
456f844e94eSwiz 	gc->attr = 0;
457f844e94eSwiz }
458f844e94eSwiz 
45946548964Swiz struct menu_data *
460f844e94eSwiz menu_prepare(struct menu *menu, int flags, int starting_choice,
461f844e94eSwiz     struct cmdq_item *item, u_int px, u_int py, struct client *c,
462f844e94eSwiz     enum box_lines lines, const char *style, const char *selected_style,
463f844e94eSwiz     const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb,
4646483eba0Schristos     void *data)
4656483eba0Schristos {
4666483eba0Schristos 	struct menu_data	*md;
467f844e94eSwiz 	int			 choice;
468e271dbb8Schristos 	const char		*name;
469f844e94eSwiz 	struct options		*o = c->session->curw->window->options;
4706483eba0Schristos 
4716483eba0Schristos 	if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2)
47246548964Swiz 		return (NULL);
473e271dbb8Schristos 	if (px + menu->width + 4 > c->tty.sx)
474e271dbb8Schristos 		px = c->tty.sx - menu->width - 4;
475e271dbb8Schristos 	if (py + menu->count + 2 > c->tty.sy)
476e271dbb8Schristos 		py = c->tty.sy - menu->count - 2;
4776483eba0Schristos 
478f844e94eSwiz 	if (lines == BOX_LINES_DEFAULT)
479f844e94eSwiz 		lines = options_get_number(o, "menu-border-lines");
480f844e94eSwiz 
4816483eba0Schristos 	md = xcalloc(1, sizeof *md);
4826483eba0Schristos 	md->item = item;
4836483eba0Schristos 	md->flags = flags;
484f844e94eSwiz 	md->border_lines = lines;
485f844e94eSwiz 
486f844e94eSwiz 	menu_set_style(c, &md->style, style, "menu-style");
487f844e94eSwiz 	menu_set_style(c, &md->selected_style, selected_style,
488f844e94eSwiz 	    "menu-selected-style");
489f844e94eSwiz 	menu_set_style(c, &md->border_style, border_style, "menu-border-style");
4906483eba0Schristos 
4916483eba0Schristos 	if (fs != NULL)
4926483eba0Schristos 		cmd_find_copy_state(&md->fs, fs);
4936483eba0Schristos 	screen_init(&md->s, menu->width + 4, menu->count + 2, 0);
494e271dbb8Schristos 	if (~md->flags & MENU_NOMOUSE)
49546548964Swiz 		md->s.mode |= (MODE_MOUSE_ALL|MODE_MOUSE_BUTTON);
496e271dbb8Schristos 	md->s.mode &= ~MODE_CURSOR;
4976483eba0Schristos 
4986483eba0Schristos 	md->px = px;
4996483eba0Schristos 	md->py = py;
5006483eba0Schristos 
5016483eba0Schristos 	md->menu = menu;
502f844e94eSwiz 	md->choice = -1;
503f844e94eSwiz 
504e271dbb8Schristos 	if (md->flags & MENU_NOMOUSE) {
505f844e94eSwiz 		if (starting_choice >= (int)menu->count) {
506f844e94eSwiz 			starting_choice = menu->count - 1;
507f844e94eSwiz 			choice = starting_choice + 1;
508f844e94eSwiz 			for (;;) {
509f844e94eSwiz 				name = menu->items[choice - 1].name;
510f844e94eSwiz 				if (name != NULL && *name != '-') {
511f844e94eSwiz 					md->choice = choice - 1;
512e271dbb8Schristos 					break;
513e271dbb8Schristos 				}
514f844e94eSwiz 				if (--choice == 0)
515f844e94eSwiz 					choice = menu->count;
516f844e94eSwiz 				if (choice == starting_choice + 1)
517f844e94eSwiz 					break;
518f844e94eSwiz 			}
519f844e94eSwiz 		} else if (starting_choice >= 0) {
520f844e94eSwiz 			choice = starting_choice;
521f844e94eSwiz 			for (;;) {
522f844e94eSwiz 				name = menu->items[choice].name;
523f844e94eSwiz 				if (name != NULL && *name != '-') {
524f844e94eSwiz 					md->choice = choice;
525f844e94eSwiz 					break;
526f844e94eSwiz 				}
527f844e94eSwiz 				if (++choice == (int)menu->count)
528f844e94eSwiz 					choice = 0;
529f844e94eSwiz 				if (choice == starting_choice)
530f844e94eSwiz 					break;
531f844e94eSwiz 			}
532f844e94eSwiz 		}
533f844e94eSwiz 	}
5346483eba0Schristos 
5356483eba0Schristos 	md->cb = cb;
5366483eba0Schristos 	md->data = data;
53746548964Swiz 	return (md);
53846548964Swiz }
5396483eba0Schristos 
54046548964Swiz int
541f844e94eSwiz menu_display(struct menu *menu, int flags, int starting_choice,
542f844e94eSwiz     struct cmdq_item *item, u_int px, u_int py, struct client *c,
543f844e94eSwiz     enum box_lines lines, const char *style, const char *selected_style,
544f844e94eSwiz     const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb,
54546548964Swiz     void *data)
54646548964Swiz {
54746548964Swiz 	struct menu_data	*md;
54846548964Swiz 
549f844e94eSwiz 	md = menu_prepare(menu, flags, starting_choice, item, px, py, c, lines,
550f844e94eSwiz 	    style, selected_style, border_style, fs, cb, data);
55146548964Swiz 	if (md == NULL)
55246548964Swiz 		return (-1);
553e271dbb8Schristos 	server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb,
55446548964Swiz 	    menu_key_cb, menu_free_cb, NULL, md);
5556483eba0Schristos 	return (0);
5566483eba0Schristos }
557