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