1 /**
2  * \file ui-menu.c
3  * \brief Generic menu interaction functions
4  *
5  * Copyright (c) 2007 Pete Mack
6  * Copyright (c) 2010 Andi Sidwell
7  *
8  * This work is free software; you can redistribute it and/or modify it
9  * under the terms of either:
10  *
11  * a) the GNU General Public License as published by the Free Software
12  *    Foundation, version 2, or
13  *
14  * b) the "Angband licence":
15  *    This software may be copied and distributed for educational, research,
16  *    and not for profit purposes provided that this copyright and statement
17  *    are included in all such copies.  Other copyrights may also apply.
18  */
19 #include "angband.h"
20 #include "cave.h"
21 #include "ui-target.h"
22 #include "ui-event.h"
23 #include "ui-input.h"
24 #include "ui-menu.h"
25 
26 /**
27  * Cursor colours
28  */
29 const byte curs_attrs[2][2] =
30 {
31 	{ COLOUR_SLATE, COLOUR_BLUE },      /* Greyed row */
32 	{ COLOUR_WHITE, COLOUR_L_BLUE }     /* Valid row */
33 };
34 
35 /**
36  * Some useful constants
37  */
38 const char lower_case[] = "abcdefghijklmnopqrstuvwxyz";
39 const char upper_case[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
40 const char all_letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
41 
42 /**
43  * Forward declarations
44  */
45 static void display_menu_row(struct menu *menu, int pos, int top,
46 			     bool cursor, int row, int col, int width);
47 static bool menu_calc_size(struct menu *menu);
48 static bool is_valid_row(struct menu *menu, int cursor);
49 static bool no_valid_row(struct menu *menu, int count);
50 
51 
52 /**
53  * Display an event, with possible preference overrides
54  */
display_action_aux(menu_action * act,byte color,int row,int col,int wid)55 static void display_action_aux(menu_action *act, byte color, int row, int col, int wid)
56 {
57 	/* TODO: add preference support */
58 	/* TODO: wizard mode should show more data */
59 	Term_erase(col, row, wid);
60 
61 	if (act->name)
62 		Term_putstr(col, row, wid, color, act->name);
63 }
64 
65 /* ------------------------------------------------------------------------
66  * MN_ACTIONS HELPER FUNCTIONS
67  *
68  * MN_ACTIONS is the type of menu iterator that displays a simple list of
69  * menu_actions.
70  * ------------------------------------------------------------------------ */
71 
menu_action_tag(struct menu * m,int oid)72 static char menu_action_tag(struct menu *m, int oid)
73 {
74 	menu_action *acts = menu_priv(m);
75 	return acts[oid].tag;
76 }
77 
menu_action_valid(struct menu * m,int oid)78 static int menu_action_valid(struct menu *m, int oid)
79 {
80 	menu_action *acts = menu_priv(m);
81 
82 	if (acts[oid].flags & MN_ACT_HIDDEN)
83 		return 2;
84 
85 	return acts[oid].name ? true : false;
86 }
87 
menu_action_display(struct menu * m,int oid,bool cursor,int row,int col,int width)88 static void menu_action_display(struct menu *m, int oid, bool cursor, int row, int col, int width)
89 {
90 	menu_action *acts = menu_priv(m);
91 	byte color = curs_attrs[!(acts[oid].flags & (MN_ACT_GRAYED))][0 != cursor];
92 
93 	display_action_aux(&acts[oid], color, row, col, width);
94 }
95 
menu_action_handle(struct menu * m,const ui_event * event,int oid)96 static bool menu_action_handle(struct menu *m, const ui_event *event, int oid)
97 {
98 	menu_action *acts = menu_priv(m);
99 
100 	if (event->type == EVT_SELECT) {
101 		if (!(acts->flags & MN_ACT_GRAYED) && acts[oid].action) {
102 			acts[oid].action(acts[oid].name, m->cursor);
103 			return true;
104 		}
105 	} else if (m->keys_hook && event->type == EVT_KBRD) {
106 		return m->keys_hook(m, event, oid);
107 	}
108 
109 	return false;
110 }
111 
112 
113 /**
114  * Virtual function table for action_events
115  */
116 static const menu_iter menu_iter_actions =
117 {
118 	menu_action_tag,
119 	menu_action_valid,
120 	menu_action_display,
121 	menu_action_handle,
122 	NULL
123 };
124 
125 
126 /* ------------------------------------------------------------------------
127  * MN_STRINGS HELPER FUNCTIONS
128  *
129  * MN_STRINGS is the type of menu iterator that displays a simple list of
130  * strings - an action is associated, but only for cmd_keys and switch_keys
131  * handling via keys_hook as selection will just return the index.
132  * ------------------------------------------------------------------------ */
display_string(struct menu * m,int oid,bool cursor,int row,int col,int width)133 static void display_string(struct menu *m, int oid, bool cursor,
134 		int row, int col, int width)
135 {
136 	const char **items = menu_priv(m);
137 	byte color = curs_attrs[CURS_KNOWN][0 != cursor];
138 	Term_putstr(col, row, width, color, items[oid]);
139 }
140 
handle_string(struct menu * m,const ui_event * event,int oid)141 static bool handle_string(struct menu *m, const ui_event *event, int oid)
142 {
143 	if (m->keys_hook && event->type == EVT_KBRD) {
144 		return m->keys_hook(m, event, oid);
145 	}
146 	return false;
147 }
148 
149 /* Virtual function table for displaying arrays of strings */
150 static const menu_iter menu_iter_strings =
151 {
152 	NULL,              /* get_tag() */
153 	NULL,              /* valid_row() */
154 	display_string,    /* display_row() */
155 	handle_string, 	   /* row_handler() */
156 	NULL
157 };
158 
159 /* ================== SKINS ============== */
160 
161 
162 /*** Scrolling menu ***/
163 
164 /**
165  * Find the position of a cursor given a screen address
166  */
scrolling_get_cursor(int row,int col,int n,int top,region * loc)167 static int scrolling_get_cursor(int row, int col, int n, int top, region *loc)
168 {
169 	int cursor = row - loc->row + top;
170 	if (cursor >= n) cursor = n - 1;
171 
172 	return cursor;
173 }
174 
175 
176 /**
177  * Display current view of a skin
178  */
display_scrolling(struct menu * menu,int cursor,int * top,region * loc)179 static void display_scrolling(struct menu *menu, int cursor, int *top, region *loc)
180 {
181 	int col = loc->col;
182 	int row = loc->row;
183 	int rows_per_page = loc->page_rows;
184 	int n = menu->filter_list ? menu->filter_count : menu->count;
185 	int i;
186 
187 	/* Keep a certain distance from the top when possible */
188 	if ((cursor <= *top) && (*top > 0))
189 		*top = cursor - 1;
190 
191 	/* Keep a certain distance from the bottom when possible */
192 	if (cursor >= *top + (rows_per_page - 1))
193 		*top = cursor - (rows_per_page - 1) + 1;
194 
195 	/* Limit the top to legal places */
196 	*top = MIN(*top, n - rows_per_page);
197 	*top = MAX(*top, 0);
198 
199 	for (i = 0; i < rows_per_page; i++) {
200 		/* Blank all lines */
201 		Term_erase(col, row + i, loc->width);
202 		if (i < n) {
203 			/* Redraw the line if it's within the number of menu items */
204 			bool is_curs = (i == cursor - *top);
205 			display_menu_row(menu, i + *top, *top, is_curs, row + i, col,
206 							loc->width);
207 		}
208 	}
209 
210 	if (menu->cursor >= 0)
211 		Term_gotoxy(col + menu->cursor_x_offset, row + cursor - *top);
212 }
213 
scroll_get_tag(struct menu * menu,int pos)214 static char scroll_get_tag(struct menu *menu, int pos)
215 {
216 	if (menu->selections)
217 		return menu->selections[pos - menu->top];
218 
219 	return 0;
220 }
221 
scroll_process_direction(struct menu * m,int dir)222 static ui_event scroll_process_direction(struct menu *m, int dir)
223 {
224 	ui_event out = EVENT_EMPTY;
225 
226 	/* Reject diagonals */
227 	if (ddx[dir] && ddy[dir])
228 		;
229 
230 	/* Forward/back */
231 	else if (ddx[dir])
232 		out.type = ddx[dir] < 0 ? EVT_ESCAPE : EVT_SELECT;
233 
234 	/* Move up or down to the next valid & visible row */
235 	else if (ddy[dir]) {
236 		m->cursor += ddy[dir];
237 		out.type = EVT_MOVE;
238 	}
239 
240 	return out;
241 }
242 
243 /**
244  * Virtual function table for scrollable menu skin
245  */
246 static const menu_skin menu_skin_scroll =
247 {
248 	scrolling_get_cursor,
249 	display_scrolling,
250 	scroll_get_tag,
251 	scroll_process_direction
252 };
253 
254 
255 /*** Object menu skin ***/
256 
257 /**
258  * Find the position of a cursor given a screen address
259  */
object_skin_get_cursor(int row,int col,int n,int top,region * loc)260 static int object_skin_get_cursor(int row, int col, int n, int top, region *loc)
261 {
262 	int cursor = row - loc->row + top;
263 	if (cursor >= n) cursor = n - 1;
264 
265 	return cursor;
266 }
267 
268 
269 /**
270  * Display current view of a skin
271  */
object_skin_display(struct menu * menu,int cursor,int * top,region * loc)272 static void object_skin_display(struct menu *menu, int cursor, int *top, region *loc)
273 {
274 	int col = loc->col;
275 	int row = loc->row;
276 	int rows_per_page = loc->page_rows;
277 	int n = menu->filter_list ? menu->filter_count : menu->count;
278 	int i;
279 
280 	/* Keep a certain distance from the top when possible */
281 	if ((cursor <= *top) && (*top > 0))
282 		*top = cursor - 1;
283 
284 	/* Keep a certain distance from the bottom when possible */
285 	if (cursor >= *top + (rows_per_page - 1))
286 		*top = cursor - (rows_per_page - 1) + 1;
287 
288 	/* Limit the top to legal places */
289 	*top = MIN(*top, n - rows_per_page);
290 	*top = MAX(*top, 0);
291 
292 	for (i = 0; i < rows_per_page; i++) {
293 		/* Blank all lines */
294 		Term_erase(col, row + i, loc->width);
295 		if (i < n) {
296 			/* Redraw the line if it's within the number of menu items */
297 			bool is_curs = (i == cursor - *top);
298 			display_menu_row(menu, i + *top, *top, is_curs, row + i, col,
299 							loc->width);
300 		}
301 	}
302 
303 	if (menu->cursor >= 0)
304 		Term_gotoxy(col + menu->cursor_x_offset, row + cursor - *top);
305 }
306 
object_skin_get_tag(struct menu * menu,int pos)307 static char object_skin_get_tag(struct menu *menu, int pos)
308 {
309 	if (menu->selections)
310 		return menu->selections[pos - menu->top];
311 
312 	return 0;
313 }
314 
object_skin_process_direction(struct menu * m,int dir)315 static ui_event object_skin_process_direction(struct menu *m, int dir)
316 {
317 	ui_event out = EVENT_EMPTY;
318 
319 	/* Reject diagonals */
320 	if (ddx[dir] && ddy[dir])
321 		;
322 
323 	/* Prepare to switch menus */
324 	else if (ddx[dir]) {
325 		out.type = EVT_SWITCH;
326 		out.key.code = ddx[dir] < 0 ? ARROW_LEFT : ARROW_RIGHT;
327 	}
328 
329 	/* Move up or down to the next valid & visible row */
330 	else if (ddy[dir]) {
331 		m->cursor += ddy[dir];
332 		out.type = EVT_MOVE;
333 	}
334 
335 	return out;
336 }
337 
338 /**
339  * Virtual function table for object menu skin
340  */
341 static const menu_skin menu_skin_object =
342 {
343 	object_skin_get_cursor,
344 	object_skin_display,
345 	object_skin_get_tag,
346 	object_skin_process_direction
347 };
348 
349 
350 /*** Multi-column menus ***/
351 
columns_get_cursor(int row,int col,int n,int top,region * loc)352 static int columns_get_cursor(int row, int col, int n, int top, region *loc)
353 {
354 	int w, h, cursor;
355         int rows_per_page = loc->page_rows;
356         int cols = (n + rows_per_page - 1) / rows_per_page;
357 	int colw = 23;
358 
359 	Term_get_size(&w, &h);
360 
361 	if ((colw * cols) > (w - col))
362 		colw = (w - col) / cols;
363 
364 	cursor = (row - loc->row) + rows_per_page * ((col - loc->col) / colw);
365 	if (cursor < 0) cursor = 0;	/* assert: This should never happen */
366 	if (cursor >= n) cursor = n - 1;
367 
368 	return cursor;
369 }
370 
display_columns(struct menu * menu,int cursor,int * top,region * loc)371 static void display_columns(struct menu *menu, int cursor, int *top, region *loc)
372 {
373 	int c, r;
374 	int w, h;
375 	int n = menu->filter_list ? menu->filter_count : menu->count;
376 	int col = loc->col;
377 	int row = loc->row;
378 	int rows_per_page = loc->page_rows;
379 	int cols = (n + rows_per_page - 1) / rows_per_page;
380 	int colw = 23;
381 
382 	Term_get_size(&w, &h);
383 
384 	if ((colw * cols) > (w - col))
385 		colw = (w - col) / cols;
386 
387 	for (c = 0; c < cols; c++) {
388 		for (r = 0; r < rows_per_page; r++) {
389 			int pos = c * rows_per_page + r;
390 			bool is_cursor = (pos == cursor);
391 
392 			if (pos < n)
393 				display_menu_row(menu, pos, 0, is_cursor,
394 						row + r, col + c * colw, colw);
395 		}
396 	}
397 
398 	if (menu->cursor >= 0)
399 		Term_gotoxy(col + (cursor / rows_per_page) * colw + menu->cursor_x_offset,
400 				row + (cursor % rows_per_page) - *top);
401 }
402 
column_get_tag(struct menu * menu,int pos)403 static char column_get_tag(struct menu *menu, int pos)
404 {
405 	if (menu->selections)
406 		return menu->selections[pos];
407 
408 	return 0;
409 }
410 
column_process_direction(struct menu * m,int dir)411 static ui_event column_process_direction(struct menu *m, int dir)
412 {
413 	ui_event out = EVENT_EMPTY;
414 
415 	int n = m->filter_list ? m->filter_count : m->count;
416 
417 	region *loc = &m->active;
418 	int rows_per_page = loc->page_rows;
419 	int cols = (n + rows_per_page - 1) / rows_per_page;
420 
421 	if (ddx[dir])
422 		m->cursor += ddx[dir] * rows_per_page;
423 	if (ddy[dir])
424 		m->cursor += ddy[dir];
425 
426 	/* Adjust to the correct locations (roughly) */
427 	if (m->cursor > n)
428 		m->cursor = m->cursor % rows_per_page;
429 	else if (m->cursor < 0)
430 		m->cursor = (rows_per_page * cols) + m->cursor;
431 
432 	out.type = EVT_MOVE;
433 	return out;
434 }
435 
436 /* Virtual function table for multi-column menu skin */
437 static const menu_skin menu_skin_column =
438 {
439 	columns_get_cursor,
440 	display_columns,
441 	column_get_tag,
442 	column_process_direction
443 };
444 
445 
446 /* ================== GENERIC HELPER FUNCTIONS ============== */
447 
is_valid_row(struct menu * menu,int cursor)448 static bool is_valid_row(struct menu *menu, int cursor)
449 {
450 	int oid;
451 	int count = menu->filter_list ? menu->filter_count : menu->count;
452 
453 	if (cursor < 0 || cursor >= count)
454 		return false;
455 
456 	oid = menu->filter_list ? menu->filter_list[cursor] : cursor;
457 
458 	if (menu->row_funcs->valid_row)
459 		return menu->row_funcs->valid_row(menu, oid);
460 
461 	return true;
462 }
463 
no_valid_row(struct menu * menu,int count)464 static bool no_valid_row(struct menu *menu, int count)
465 {
466 	int i;
467 
468 	for (i = 0; i < count; i++)
469 		if (is_valid_row(menu, i))
470 			return false;
471 
472 	return true;
473 }
474 
475 /*
476  * Return a new position in the menu based on the key
477  * pressed and the flags and various handler functions.
478  */
get_cursor_key(struct menu * menu,int top,struct keypress key)479 static int get_cursor_key(struct menu *menu, int top, struct keypress key)
480 {
481 	int i;
482 	int n = menu->filter_list ? menu->filter_count : menu->count;
483 
484 	if (menu->flags & MN_CASELESS_TAGS)
485 		key.code = toupper((unsigned char) key.code);
486 
487 	if ((menu->flags & MN_INSCRIP_TAGS) && isdigit((unsigned char)key.code)
488 		&& menu->inscriptions[D2I(key.code)])
489 		key.code = menu->inscriptions[D2I(key.code)];
490 
491 	if (menu->flags & MN_NO_TAGS) {
492 		return -1;
493 	} else if (menu->flags & MN_REL_TAGS) {
494 		for (i = 0; i < n; i++) {
495 			char c = menu->skin->get_tag(menu, i);
496 
497 			if ((menu->flags & MN_CASELESS_TAGS) && c)
498 				c = toupper((unsigned char) c);
499 
500 			if (c && c == (char)key.code)
501 				return i + menu->top;
502 		}
503 	} else if (!(menu->flags & MN_PVT_TAGS) && menu->selections) {
504 		for (i = 0; menu->selections[i]; i++) {
505 			char c = menu->selections[i];
506 
507 			if (menu->flags & MN_CASELESS_TAGS)
508 				c = toupper((unsigned char) c);
509 
510 			if (c == (char)key.code)
511 				return i;
512 		}
513 	} else if (menu->row_funcs->get_tag) {
514 		for (i = 0; i < n; i++) {
515 			int oid = menu->filter_list ? menu->filter_list[i] : i;
516 			char c = menu->row_funcs->get_tag(menu, oid);
517 
518 			if ((menu->flags & MN_CASELESS_TAGS) && c)
519 				c = toupper((unsigned char) c);
520 
521 			if (c && c == (char)key.code)
522 				return i;
523 		}
524 	}
525 
526 	return -1;
527 }
528 
menu_row_style_for_validity(menu_row_validity_t row_valid)529 static menu_row_style_t menu_row_style_for_validity(menu_row_validity_t row_valid)
530 {
531 	menu_row_style_t style;
532 
533 	switch (row_valid) {
534 		case MN_ROW_INVALID:
535 		case MN_ROW_HIDDEN:
536 			style = MN_ROW_STYLE_DISABLED;
537 			break;
538 		case MN_ROW_VALID:
539 		default:
540 			style = MN_ROW_STYLE_ENABLED;
541 			break;
542 	}
543 
544 	return style;
545 }
546 
547 /**
548  * Modal display of menu
549  */
display_menu_row(struct menu * menu,int pos,int top,bool cursor,int row,int col,int width)550 static void display_menu_row(struct menu *menu, int pos, int top,
551                              bool cursor, int row, int col, int width)
552 {
553 	int flags = menu->flags;
554 	char sel = 0;
555 	int oid = pos;
556 	menu_row_validity_t row_valid = MN_ROW_VALID;
557 
558 	if (menu->filter_list)
559 		oid = menu->filter_list[oid];
560 
561 	if (menu->row_funcs->valid_row)
562 		row_valid = menu->row_funcs->valid_row(menu, oid);
563 
564 	if (row_valid == MN_ROW_HIDDEN)
565 		return;
566 
567 	if (!(flags & MN_NO_TAGS)) {
568 		if (flags & MN_REL_TAGS)
569 			sel = menu->skin->get_tag(menu, pos);
570 		else if (menu->selections && !(flags & MN_PVT_TAGS))
571 			sel = menu->selections[pos];
572 		else if (menu->row_funcs->get_tag)
573 			sel = menu->row_funcs->get_tag(menu, oid);
574 	}
575 
576 	if (sel) {
577 		menu_row_style_t style = menu_row_style_for_validity(row_valid);
578 		byte color = curs_attrs[style][0 != (cursor)];
579 		Term_putstr(col, row, 3, color, format("%c) ", sel));
580 		col += 3;
581 		width -= 3;
582 	}
583 
584 	menu->row_funcs->display_row(menu, oid, cursor, row, col, width);
585 }
586 
menu_refresh(struct menu * menu,bool reset_screen)587 void menu_refresh(struct menu *menu, bool reset_screen)
588 {
589 	int oid = menu->cursor;
590 	region *loc = &menu->active;
591 
592 	if (reset_screen) {
593 		screen_load();
594 		screen_save();
595 	}
596 
597 	if (menu->filter_list && menu->cursor >= 0)
598 		oid = menu->filter_list[oid];
599 
600 	if (menu->title)
601 		Term_putstr(menu->boundary.col, menu->boundary.row,
602 				loc->width, COLOUR_WHITE, menu->title);
603 
604 	if (menu->header)
605 		Term_putstr(loc->col, loc->row - 1, loc->width,
606 				COLOUR_WHITE, menu->header);
607 
608 	if (menu->prompt)
609 		Term_putstr(menu->boundary.col, loc->row + loc->page_rows,
610 				loc->width, COLOUR_WHITE, menu->prompt);
611 
612 	if (menu->browse_hook && oid >= 0)
613 		menu->browse_hook(oid, menu->menu_data, loc);
614 
615 	menu->skin->display_list(menu, menu->cursor, &menu->top, loc);
616 }
617 
618 
619 /*** MENU RUNNING AND INPUT HANDLING CODE ***/
620 
621 /**
622  * Handle mouse input in a menu.
623  *
624  * Mouse output is either moving, selecting, escaping, or nothing.  Returns
625  * true if something changes as a result of the click.
626  */
menu_handle_mouse(struct menu * menu,const ui_event * in,ui_event * out)627 bool menu_handle_mouse(struct menu *menu, const ui_event *in,
628 		ui_event *out)
629 {
630 	int new_cursor;
631 
632 	if (in->mouse.button == 2) {
633 		out->type = EVT_ESCAPE;
634 	} else if (!region_inside(&menu->active, in)) {
635 		/* A click to the left of the active region is 'back' */
636 		if (!region_inside(&menu->active, in) &&
637 				in->mouse.x < menu->active.col)
638 			out->type = EVT_ESCAPE;
639 	} else {
640 		int count = menu->filter_list ? menu->filter_count : menu->count;
641 
642 		new_cursor = menu->skin->get_cursor(in->mouse.y, in->mouse.x,
643 				count, menu->top, &menu->active);
644 
645 		if (is_valid_row(menu, new_cursor)) {
646 			if (new_cursor == menu->cursor || !(menu->flags & MN_DBL_TAP))
647 				out->type = EVT_SELECT;
648 			else
649 				out->type = EVT_MOVE;
650 
651 			menu->cursor = new_cursor;
652 		}
653 	}
654 
655 	return out->type != EVT_NONE;
656 }
657 
658 
659 /**
660  * Handle any menu command keys / SELECT events.
661  *
662  * Returns true if the key was handled at all (including if it's not handled
663  * and just ignored).
664  */
menu_handle_action(struct menu * m,const ui_event * in)665 static bool menu_handle_action(struct menu *m, const ui_event *in)
666 {
667 	if (m->row_funcs->row_handler) {
668 		int oid = m->cursor;
669 		if (m->filter_list)
670 			oid = m->filter_list[m->cursor];
671 
672 		return m->row_funcs->row_handler(m, in, oid);
673 	}
674 
675 	return false;
676 }
677 
678 
679 /**
680  * Handle navigation keypresses.
681  *
682  * Returns true if they key was intelligible as navigation, regardless of
683  * whether any action was taken.
684  */
menu_handle_keypress(struct menu * menu,const ui_event * in,ui_event * out)685 bool menu_handle_keypress(struct menu *menu, const ui_event *in,
686 		ui_event *out)
687 {
688 	bool eat = false;
689 	int count = menu->filter_list ? menu->filter_count : menu->count;
690 
691 	/* Get the new cursor position from the menu item tags */
692 	int new_cursor = get_cursor_key(menu, menu->top, in->key);
693 	if (new_cursor >= 0 && is_valid_row(menu, new_cursor)) {
694 		if (!(menu->flags & MN_DBL_TAP) || new_cursor == menu->cursor)
695 			out->type = EVT_SELECT;
696 		else
697 			out->type = EVT_MOVE;
698 
699 		menu->cursor = new_cursor;
700 	} else if (in->key.code == ESCAPE) {
701 		/* Escape stops us here */
702 		out->type = EVT_ESCAPE;
703 	} else if (count <= 0) {
704 		/* Menus with no rows can't be navigated or used, so eat keypresses */
705 		eat = true;
706 	} else if (in->key.code == ' ') {
707 		/* Try existing, known keys */
708 		int rows = menu->active.page_rows;
709 		int total = count;
710 
711 		if (rows < total) {
712 			/* Go to start of next page */
713 			menu->cursor += menu->active.page_rows;
714 			if (menu->cursor >= total - 1) menu->cursor = 0;
715 			menu->top = menu->cursor;
716 
717 			out->type = EVT_MOVE;
718 		} else {
719 			eat = true;
720 		}
721 	} else if (in->key.code == KC_ENTER) {
722 		out->type = EVT_SELECT;
723 	} else {
724 		/* Try directional movement */
725 		int dir = target_dir(in->key);
726 
727 		if (dir && !no_valid_row(menu, count)) {
728 			*out = menu->skin->process_dir(menu, dir);
729 
730 			if (out->type == EVT_MOVE) {
731 				while (!is_valid_row(menu, menu->cursor)) {
732 					/* Loop around */
733 					if (menu->cursor > count - 1)
734 						menu->cursor = 0;
735 					else if (menu->cursor < 0)
736 						menu->cursor = count - 1;
737 					else
738 						menu->cursor += ddy[dir];
739 				}
740 
741 				assert(menu->cursor >= 0);
742 				assert(menu->cursor < count);
743 			}
744 		}
745 	}
746 
747 	return eat;
748 }
749 
750 
751 /**
752  * Run a menu.
753  *
754  * If popup is true, the screen is saved before the menu is drawn, and
755  * restored afterwards. Each time a popup menu is redrawn, it resets the
756  * screen before redrawing.
757  */
menu_select(struct menu * menu,int notify,bool popup)758 ui_event menu_select(struct menu *menu, int notify, bool popup)
759 {
760 	ui_event in = EVENT_EMPTY;
761 	bool no_act = (menu->flags & MN_NO_ACTION) ? true : false;
762 
763 	assert(menu->active.width != 0 && menu->active.page_rows != 0);
764 
765 	notify |= (EVT_SELECT | EVT_ESCAPE | EVT_SWITCH);
766 	if (popup)
767 		screen_save();
768 
769 	/* Stop on first unhandled event */
770 	while (!(in.type & notify)) {
771 		ui_event out = EVENT_EMPTY;
772 		int cursor = menu->cursor;
773 
774 		menu_refresh(menu, popup);
775 		in = inkey_ex();
776 
777 		/* Handle mouse & keyboard commands */
778 		if (in.type == EVT_MOUSE) {
779 			if (!no_act && menu_handle_action(menu, &in)) {
780 				continue;
781 			}
782 			menu_handle_mouse(menu, &in, &out);
783 		} else if (in.type == EVT_KBRD) {
784 			/* Command key */
785 			if (!no_act && menu->cmd_keys &&
786 				strchr(menu->cmd_keys, (char)in.key.code) &&
787 				menu_handle_action(menu, &in))
788 				continue;
789 
790 			/* Switch key */
791 			if (!no_act && menu->switch_keys &&
792 				strchr(menu->switch_keys, (char)in.key.code)) {
793 				menu_handle_action(menu, &in);
794 				if (popup)
795 					screen_load();
796 				return in;
797 			}
798 
799 			menu_handle_keypress(menu, &in, &out);
800 		} else if (in.type == EVT_RESIZE) {
801 			menu_calc_size(menu);
802 			if (menu->row_funcs->resize)
803 				menu->row_funcs->resize(menu);
804 		}
805 
806 		/* Redraw menu here if cursor has moved */
807 		if (cursor != menu->cursor) {
808 			menu_refresh(menu, popup);
809 		}
810 
811 		/* If we've selected an item, then send that event out */
812 		if (out.type == EVT_SELECT && !no_act && menu_handle_action(menu, &out))
813 			continue;
814 
815 		/* Notify about the outgoing type */
816 		if (notify & out.type) {
817 			if (popup)
818 				screen_load();
819 			return out;
820 		}
821 	}
822 
823 	if (popup)
824 		screen_load();
825 	return in;
826 }
827 
828 
829 /* ================== MENU ACCESSORS ================ */
830 
831 /**
832  * Return the menu iter struct for a given iter ID.
833  */
menu_find_iter(menu_iter_id id)834 const menu_iter *menu_find_iter(menu_iter_id id)
835 {
836 	switch (id)
837 	{
838 		case MN_ITER_ACTIONS:
839 			return &menu_iter_actions;
840 
841 		case MN_ITER_STRINGS:
842 			return &menu_iter_strings;
843 	}
844 
845 	return NULL;
846 }
847 
848 /*
849  * Return the skin behaviour struct for a given skin ID.
850  */
menu_find_skin(skin_id id)851 static const menu_skin *menu_find_skin(skin_id id)
852 {
853 	switch (id)
854 	{
855 		case MN_SKIN_SCROLL:
856 			return &menu_skin_scroll;
857 
858 		case MN_SKIN_OBJECT:
859 			return &menu_skin_object;
860 
861 		case MN_SKIN_COLUMNS:
862 			return &menu_skin_column;
863 	}
864 
865 	return NULL;
866 }
867 
868 
menu_set_filter(struct menu * menu,const int filter_list[],int n)869 void menu_set_filter(struct menu *menu, const int filter_list[], int n)
870 {
871 	menu->filter_list = filter_list;
872 	menu->filter_count = n;
873 
874 	menu_ensure_cursor_valid(menu);
875 }
876 
menu_release_filter(struct menu * menu)877 void menu_release_filter(struct menu *menu)
878 {
879 	menu->filter_list = NULL;
880 	menu->filter_count = 0;
881 
882 	menu_ensure_cursor_valid(menu);
883 
884 }
885 
menu_ensure_cursor_valid(struct menu * m)886 void menu_ensure_cursor_valid(struct menu *m)
887 {
888 	int row;
889 	int count = m->filter_list ? m->filter_count : m->count;
890 
891 	for (row = m->cursor; row < count; row++) {
892 		if (is_valid_row(m, row)) {
893 			m->cursor = row;
894 			return;
895 		}
896 	}
897 
898 	/* If we've run off the end, without finding a valid row, put cursor
899 	 * on the last row */
900 	m->cursor = count - 1;
901 }
902 
903 /* ======================== MENU INITIALIZATION ==================== */
904 
menu_calc_size(struct menu * menu)905 static bool menu_calc_size(struct menu *menu)
906 {
907 	/* Calculate term-relative positions */
908 	menu->active = region_calculate(menu->boundary);
909 
910 	if (menu->title) {
911 		menu->active.row += 2;
912 		menu->active.page_rows -= 2;
913 		menu->active.col += 4;
914 	}
915 
916 	if (menu->header) {
917 		menu->active.row++;
918 		menu->active.page_rows--;
919 	}
920 
921 	if (menu->prompt) {
922 		if (menu->active.page_rows > 1) {
923 			menu->active.page_rows--;
924 		} else {
925 			int offset = strlen(menu->prompt) + 2;
926 			menu->active.col += offset;
927 			menu->active.width -= offset;
928 		}
929 	}
930 
931 	return (menu->active.width > 0 && menu->active.page_rows > 0);
932 }
933 
menu_layout(struct menu * m,const region * loc)934 bool menu_layout(struct menu *m, const region *loc)
935 {
936 	m->boundary = *loc;
937 	return menu_calc_size(m);
938 }
939 
menu_setpriv(struct menu * menu,int count,void * data)940 void menu_setpriv(struct menu *menu, int count, void *data)
941 {
942 	menu->count = count;
943 	menu->menu_data = data;
944 
945 	menu_ensure_cursor_valid(menu);
946 }
947 
menu_priv(struct menu * menu)948 void *menu_priv(struct menu *menu)
949 {
950 	return menu->menu_data;
951 }
952 
menu_init(struct menu * menu,skin_id skin_id,const menu_iter * iter)953 void menu_init(struct menu *menu, skin_id skin_id, const menu_iter *iter)
954 {
955 	const menu_skin *skin = menu_find_skin(skin_id);
956 	assert(skin && "menu skin not found!");
957 	assert(iter && "menu iter not found!");
958 
959 	/* Wipe the struct */
960 	memset(menu, 0, sizeof *menu);
961 
962 	/* Menu-specific initialisation */
963 	menu->row_funcs = iter;
964 	menu->skin = skin;
965 	menu->cursor = 0;
966 	menu->cursor_x_offset = 0;
967 }
968 
menu_new(skin_id skin_id,const menu_iter * iter)969 struct menu *menu_new(skin_id skin_id, const menu_iter *iter)
970 {
971 	struct menu *m = mem_alloc(sizeof *m);
972 	menu_init(m, skin_id, iter);
973 	return m;
974 }
975 
menu_new_action(menu_action * acts,size_t n)976 struct menu *menu_new_action(menu_action *acts, size_t n)
977 {
978 	struct menu *m = menu_new(MN_SKIN_SCROLL, menu_find_iter(MN_ITER_ACTIONS));
979 	menu_setpriv(m, n, acts);
980 	return m;
981 }
982 
menu_free(struct menu * m)983 void menu_free(struct menu *m)
984 {
985 	mem_free(m);
986 }
987 
menu_set_cursor_x_offset(struct menu * m,int offset)988 void menu_set_cursor_x_offset(struct menu *m, int offset)
989 {
990 	/* This value is used in the menu skin's display_list() function. */
991 	m->cursor_x_offset = offset;
992 }
993 
994 /*** Dynamic menu handling ***/
995 
996 struct menu_entry {
997 	char *text;
998 	int value;
999 	menu_row_validity_t valid;
1000 
1001 	struct menu_entry *next;
1002 };
1003 
dynamic_valid(struct menu * m,int oid)1004 static int dynamic_valid(struct menu *m, int oid)
1005 {
1006 	struct menu_entry *entry;
1007 
1008 	for (entry = menu_priv(m); oid; oid--) {
1009 		entry = entry->next;
1010 		assert(entry);
1011 	}
1012 
1013 	return entry->valid;
1014 }
1015 
dynamic_display(struct menu * m,int oid,bool cursor,int row,int col,int width)1016 static void dynamic_display(struct menu *m, int oid, bool cursor,
1017 		int row, int col, int width)
1018 {
1019 	struct menu_entry *entry;
1020 	byte color = curs_attrs[MN_ROW_STYLE_ENABLED][0 != cursor];
1021 
1022 	/* Hack? While row_funcs is private, we need to be consistent with what the menu will do. */
1023 	if (m->row_funcs->valid_row) {
1024 		menu_row_validity_t row_valid = m->row_funcs->valid_row(m, oid);
1025 		menu_row_style_t style = menu_row_style_for_validity(row_valid);
1026 		color = curs_attrs[style][0 != cursor];
1027 	}
1028 
1029 	for (entry = menu_priv(m); oid; oid--) {
1030 		entry = entry->next;
1031 		assert(entry);
1032 	}
1033 
1034 	Term_putstr(col, row, width, color, entry->text);
1035 }
1036 
1037 static const menu_iter dynamic_iter = {
1038 	NULL,	/* tag */
1039 	dynamic_valid,
1040 	dynamic_display,
1041 	NULL,	/* handler */
1042 	NULL	/* resize */
1043 };
1044 
menu_dynamic_new(void)1045 struct menu *menu_dynamic_new(void)
1046 {
1047 	struct menu *m = menu_new(MN_SKIN_SCROLL, &dynamic_iter);
1048 	menu_setpriv(m, 0, NULL);
1049 	return m;
1050 }
1051 
menu_dynamic_add_valid(struct menu * m,const char * text,int value,menu_row_validity_t valid)1052 void menu_dynamic_add_valid(struct menu *m, const char *text, int value, menu_row_validity_t valid)
1053 {
1054 	struct menu_entry *head = menu_priv(m);
1055 	struct menu_entry *new = mem_zalloc(sizeof *new);
1056 
1057 	assert(m->row_funcs == &dynamic_iter);
1058 
1059 	new->text = string_make(text);
1060 	new->value = value;
1061 	new->valid = valid;
1062 
1063 	if (head) {
1064 		struct menu_entry *tail = head;
1065 		while (1) {
1066 			if (tail->next)
1067 				tail = tail->next;
1068 			else
1069 				break;
1070 		}
1071 
1072 		tail->next = new;
1073 		menu_setpriv(m, m->count + 1, head);
1074 	} else {
1075 		menu_setpriv(m, m->count + 1, new);
1076 	}
1077 }
1078 
menu_dynamic_add(struct menu * m,const char * text,int value)1079 void menu_dynamic_add(struct menu *m, const char *text, int value)
1080 {
1081 	menu_dynamic_add_valid(m, text, value, MN_ROW_VALID);
1082 }
1083 
menu_dynamic_add_label_valid(struct menu * m,const char * text,const char label,int value,char * label_list,menu_row_validity_t valid)1084 void menu_dynamic_add_label_valid(struct menu *m, const char *text, const char label, int value, char *label_list, menu_row_validity_t valid)
1085 {
1086 	if (label && m->selections && (m->selections == label_list)) {
1087 		label_list[m->count] = label;
1088 	}
1089 	menu_dynamic_add_valid(m,text,value, valid);
1090 }
1091 
menu_dynamic_add_label(struct menu * m,const char * text,const char label,int value,char * label_list)1092 void menu_dynamic_add_label(struct menu *m, const char *text, const char label, int value, char *label_list)
1093 {
1094 	menu_dynamic_add_label_valid(m, text, label, value, label_list, MN_ROW_VALID);
1095 }
1096 
menu_dynamic_longest_entry(struct menu * m)1097 size_t menu_dynamic_longest_entry(struct menu *m)
1098 {
1099 	size_t biggest = 0;
1100 	size_t current;
1101 
1102 	struct menu_entry *entry;
1103 
1104 	for (entry = menu_priv(m); entry; entry = entry->next) {
1105 		current = strlen(entry->text);
1106 		if (current > biggest)
1107 			biggest = current;
1108 	}
1109 
1110 	return biggest;
1111 }
1112 
menu_dynamic_calc_location(struct menu * m,int mx,int my)1113 void menu_dynamic_calc_location(struct menu *m, int mx, int my)
1114 {
1115 	region r;
1116 
1117 	/* work out display region */
1118 	r.width = menu_dynamic_longest_entry(m) + 3 + 2; /* +3 for tag, 2 for pad */
1119 	if (mx > Term->wid - r.width - 1) {
1120 		r.col = Term->wid - r.width - 1;
1121 	} else {
1122 		r.col = mx + 1;
1123 	}
1124 	r.page_rows = m->count;
1125 	if (my > Term->hgt - r.page_rows - 1) {
1126 		if (my - r.page_rows - 1 <= 0) {
1127 			/* menu has too many items, so put in upper right corner */
1128 			r.row = 1;
1129 			r.col = Term->wid - r.width - 1;
1130 		} else {
1131 			r.row = Term->hgt - r.page_rows - 1;
1132 		}
1133 	} else {
1134 		r.row = my + 1;
1135 	}
1136 
1137 	menu_layout(m, &r);
1138 }
1139 
menu_dynamic_select(struct menu * m)1140 int menu_dynamic_select(struct menu *m)
1141 {
1142 	ui_event e = menu_select(m, 0, true);
1143 	struct menu_entry *entry;
1144 	int cursor = m->cursor;
1145 
1146 	if (e.type == EVT_ESCAPE)
1147 		return -1;
1148 
1149 	for (entry = menu_priv(m); cursor; cursor--) {
1150 		entry = entry->next;
1151 		assert(entry);
1152 	}
1153 
1154 	return entry->value;
1155 }
1156 
menu_dynamic_free(struct menu * m)1157 void menu_dynamic_free(struct menu *m)
1158 {
1159 	struct menu_entry *entry = menu_priv(m);
1160 	while (entry) {
1161 		struct menu_entry *next = entry->next;
1162 		string_free(entry->text);
1163 		mem_free(entry);
1164 		entry = next;
1165 	}
1166 	mem_free(m);
1167 }
1168 
1169