Angband 3.0.9b.
[angband.git] / src / ui.c
blobd2c62b3f39ba32a3fcad314125813907e0fb317a
1 /*
2 * File: ui.c
3 * Purpose: Generic menu interaction functions
5 * Copyright (c) 2007 Pete Mack and others.
7 * This work is free software; you can redistribute it and/or modify it
8 * under the terms of either:
10 * a) the GNU General Public License as published by the Free Software
11 * Foundation, version 2, or
13 * b) the "Angband licence":
14 * This software may be copied and distributed for educational, research,
15 * and not for profit purposes provided that this copyright and statement
16 * are included in all such copies. Other copyrights may also apply.
18 #include "angband.h"
21 * Implementation of Extremely Basic Event Model.
22 * Limits:
23 * all events are of the concrete type event_type (see z-util.h),
24 * which are supposed to model simple UI actions:
25 * - < escape >
26 * - keystroke
27 * - mousepress
28 * - select menu element
29 * - move menu cursor
30 * - back to parent (hierarchical menu escape)
32 * There are 3 basic event-related classes:
33 * The event_type.
34 * Concrete event, with at most 32 distinct types.
36 * The event_listener observer for key events
38 * The event_target The registrar for event_listeners.
39 * For convenience, the event target is also an event_listener.
42 /* Some useful constants */
43 const char default_choice[] =
44 "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
46 const char lower_case[] = "abcdefghijklmnopqrstuvwxyz";
47 const char upper_case[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
49 int jumpscroll = 0;
50 int menu_width = 23;
52 /* forward declarations */
53 static void display_menu_row(menu_type *menu, int pos, int top,
54 bool cursor, int row, int col, int width);
56 /* =================== GEOMETRY ================= */
58 void region_erase(const region *loc)
60 int i = 0;
61 int w = loc->width;
62 int h = loc->page_rows;
64 if (loc->width <= 0 || loc->page_rows <= 0)
66 Term_get_size(&w, &h);
67 if (loc->width <= 0) w -= loc->width;
68 if (loc->page_rows <= 0) h -= loc->page_rows;
71 for (i = 0; i < h; i++)
72 Term_erase(loc->col, loc->row + i, w);
75 bool region_inside(const region *loc, const event_type *key)
77 if ((loc->col > key->mousex) || (loc->col + loc->width <= key->mousex))
78 return FALSE;
80 if ((loc->row > key->mousey) || (loc->row + loc->page_rows <= key->mousey))
81 return FALSE;
83 return TRUE;
86 /* ======================= EVENTS ======================== */
88 /* List of event listeners--Helper class for event_target and the event loop */
89 struct listener_list
91 event_listener *listener;
92 struct listener_list *next;
96 void stop_event_loop()
98 event_type stop = { EVT_STOP, 0, 0, 0, 0 };
100 /* Stop right away! */
101 Term_event_push(&stop);
105 * Primitive event loop.
106 * - target = the event target
107 * - forever - if false, stop at first unhandled event. Otherwise, stop only
108 * for STOP events
109 * - start - optional initial event that allows you to prime the loop without
110 * pushing the event queue.
111 * Returns:
112 * EVT_STOP - the loop was halted.
113 * EVT_AGAIN - start was not handled, and forever is false
114 * The first unhandled event - forever is false.
116 event_type run_event_loop(event_target * target, bool forever, const event_type *start)
118 event_type ke;
119 bool handled = TRUE;
121 while (forever || handled)
123 listener_list *list = target->observers;
124 handled = FALSE;
126 if (start) ke = *start;
127 else ke = inkey_ex();
129 if (ke.type == EVT_STOP)
130 break;
132 if (ke.type & target->self.events.evt_flags)
133 handled = target->self.handler(target->self.object, &ke);
135 if (!target->is_modal)
137 while (list && !handled)
139 if (ke.type & list->listener->events.evt_flags)
140 handled = list->listener->handler(list->listener->object, &ke);
142 list = list->next;
146 if (handled) start = NULL;
149 if (start)
151 ke.type = EVT_AGAIN;
152 ke.key = '\xff';
155 return ke;
158 void add_listener(event_target * target, event_listener * observer)
160 listener_list *link;
162 MAKE(link, listener_list);
163 link->listener = observer;
164 link->next = target->observers;
165 target->observers = link;
168 void remove_listener(event_target * target, event_listener * observer)
170 listener_list *cur = target->observers;
171 listener_list **prev = &target->observers;
173 while (cur)
175 if (cur->listener == observer)
177 *prev = cur->next;
178 FREE(cur);
179 break;
183 bell("remove_listener: no such observer");
188 /* ======================= MN_EVT HELPER FUNCTIONS ====================== */
190 /* Display an event, with possible preference overrides */
191 static void display_event_aux(event_action *event, int menu_id, byte color, int row, int col, int wid)
193 /* TODO: add preference support */
194 /* TODO: wizard mode should show more data */
195 Term_erase(col, row, wid);
197 if (event->name)
198 Term_putstr(col, row, wid, color, event->name);
201 static void display_event(menu_type *menu, int oid, bool cursor, int row, int col, int width)
203 event_action *evts = (event_action *)menu->menu_data;
204 byte color = curs_attrs[CURS_KNOWN][0 != cursor];
206 display_event_aux(&evts[oid], menu->target.self.object_id, color, row, col,
207 width);
210 /* act on selection only */
211 /* Return: true if handled. */
212 static bool handle_menu_item_event(char cmd, void *db, int oid)
214 event_action *evt = &((event_action *)db)[oid];
216 if (cmd == '\xff' && evt->action)
218 evt->action(evt->data, evt->name);
219 return TRUE;
221 else if (cmd == '\xff')
223 return TRUE;
226 return FALSE;
229 static int valid_menu_event(menu_type *menu, int oid)
231 event_action *evts = (event_action *)menu->menu_data;
232 return (NULL != evts[oid].name);
235 /* Virtual function table for action_events */
236 static const menu_iter menu_iter_event =
238 MN_EVT,
240 valid_menu_event,
241 display_event,
242 handle_menu_item_event
246 /*======================= MN_ACT HELPER FUNCTIONS ====================== */
248 static char tag_menu_item(menu_type *menu, int oid)
250 menu_item *items = (menu_item *)menu->menu_data;
251 return items[oid].sel;
254 static void display_menu_item(menu_type *menu, int oid, bool cursor, int row, int col, int width)
256 menu_item *items = (menu_item *)menu->menu_data;
257 byte color = curs_attrs[!(items[oid].flags & (MN_GRAYED))][0 != cursor];
259 display_event_aux(&items[oid].evt, menu->target.self.object_id, color, row,
260 col, width);
263 /* act on selection only */
264 static bool handle_menu_item(char cmd, void *db, int oid)
266 if (cmd == '\xff')
268 menu_item *item = &((menu_item *)db)[oid];
270 if (item->flags & MN_DISABLED)
271 return TRUE;
272 if (item->evt.action)
273 item->evt.action(item->evt.data, item->evt.name);
274 if (item->flags & MN_SELECTABLE)
275 item->flags ^= MN_SELECTED;
277 return TRUE;
280 return FALSE;
283 static int valid_menu_item(menu_type *menu, int oid)
285 menu_item *items = (menu_item *)menu->menu_data;
287 if (items[oid].flags & MN_HIDDEN)
288 return 2;
290 return (NULL != items[oid].evt.name);
293 /* Virtual function table for menu items */
294 static const menu_iter menu_iter_item =
296 MN_ACT,
297 tag_menu_item,
298 valid_menu_item,
299 display_menu_item,
300 handle_menu_item
303 /* Simple strings - display and selection only */
304 static void display_string(menu_type *menu, int oid, bool cursor,
305 int row, int col, int width)
307 const char **items = (const char **)menu->menu_data;
308 byte color = curs_attrs[CURS_KNOWN][0 != cursor];
309 Term_putstr(col, row, width, color, items[oid]);
312 /* Virtual function table for displaying arrays of strings */
313 static const menu_iter menu_iter_string =
314 { MN_STRING, 0, 0, display_string, 0 };
319 /* ================== SKINS ============== */
322 /* Scrolling menu */
323 /* Find the position of a cursor given a screen address */
324 static int scrolling_get_cursor(int row, int col, int n, int top, region *loc)
326 int cursor = row - loc->row + top;
327 if (cursor >= n) cursor = n - 1;
329 return cursor;
333 /* Display current view of a skin */
334 static void display_scrolling(menu_type *menu, int cursor, int *top, region *loc)
336 int col = loc->col;
337 int row = loc->row;
338 int rows_per_page = loc->page_rows;
339 int n = menu->filter_count;
340 int i;
342 if ((cursor <= *top) && (*top > 0))
344 if(menu->flags & MN_PAGE) *top = cursor;
345 else *top = cursor - jumpscroll - 1;
348 if (cursor >= *top + (rows_per_page - 1))
350 if (menu->flags & MN_PAGE && cursor + rows_per_page < n)
351 *top = cursor;
352 else if (menu->flags & MN_PAGE)
353 *top = n - rows_per_page;
354 else
355 *top = cursor - (rows_per_page - 1) + 1 + jumpscroll;
358 if (*top > n - rows_per_page)
359 *top = n - rows_per_page;
360 if (*top < 0)
361 *top = 0;
363 for (i = 0; i < rows_per_page && i < n; i++)
365 bool is_curs = (i == cursor - *top);
366 display_menu_row(menu, i + *top, *top, is_curs, row + i, col,
367 loc->width);
370 if (menu->cursor >= 0)
371 Term_gotoxy(col, row + cursor - *top);
374 static char scroll_get_tag(menu_type *menu, int pos)
376 if (menu->selections)
377 return menu->selections[pos - menu->top];
379 return 0;
382 /* Virtual function table for scrollable menu skin */
383 static const menu_skin scroll_skin =
385 MN_SCROLL,
386 scrolling_get_cursor,
387 display_scrolling,
388 scroll_get_tag
392 /* Multi-column menu */
393 /* Find the position of a cursor given a screen address */
394 static int columns_get_cursor(int row, int col, int n, int top, region *loc)
396 int rows_per_page = loc->page_rows;
397 int colw = loc->width / (n + rows_per_page - 1) / rows_per_page;
398 int cursor = row + rows_per_page * (col - loc->col) / colw;
400 if (cursor < 0) cursor = 0; /* assert: This should never happen */
401 if (cursor >= n) cursor = n - 1;
403 return cursor;
406 void display_columns(menu_type *menu, int cursor, int *top, region *loc)
408 int c, r;
409 int w, h;
410 int n = menu->filter_count;
411 int col = loc->col;
412 int row = loc->row;
413 int rows_per_page = loc->page_rows;
414 int cols = (n + rows_per_page - 1) / rows_per_page;
415 int colw = menu_width;
417 Term_get_size(&w, &h);
419 if ((colw * cols) > (w - col))
420 colw = (w - col) / cols;
422 for (c = 0; c < cols; c++)
424 for (r = 0; r < rows_per_page; r++)
426 int pos = c * rows_per_page + r;
427 bool is_cursor = (pos == cursor);
428 display_menu_row(menu, pos, 0, is_cursor, row + r, col + c * colw,
429 colw);
434 static char column_get_tag(menu_type *menu, int pos)
436 if (menu->selections)
437 return menu->selections[pos];
439 return 0;
442 /* Virtual function table for multi-column menu skin */
443 static const menu_skin column_skin =
445 MN_COLUMNS,
446 columns_get_cursor,
447 display_columns,
448 column_get_tag
451 /* ================== IMPLICIT MENU FOR KEY SELECTION ================== */
453 static void display_nothing(menu_type *menu, int cursor, int *top, region *loc)
457 static int no_cursor(int row, int col, int n, int top, region *loc)
459 return -1;
462 static const menu_skin key_select_skin =
464 MN_KEY_ONLY,
465 no_cursor,
466 display_nothing,
470 /* ================== GENERIC HELPER FUNCTIONS ============== */
472 static bool is_valid_row(menu_type *menu, int cursor)
474 int oid = cursor;
476 if (cursor < 0 || cursor >= menu->filter_count)
477 return FALSE;
479 if (menu->object_list)
480 oid = menu->object_list[cursor];
482 if (!menu->row_funcs->valid_row)
483 return TRUE;
485 return menu->row_funcs->valid_row(menu, oid);
489 * Return a new position in the menu based on the key
490 * pressed and the flags and various handler functions.
492 static int get_cursor_key(menu_type *menu, int top, char key)
494 int i;
495 int n = menu->filter_count;
497 if (menu->flags & MN_CASELESS_TAGS)
498 key = toupper((unsigned char) key);
500 if (menu->flags & MN_NO_TAGS)
502 return -1;
504 else if (menu->flags & MN_REL_TAGS)
506 for (i = 0; i < n; i++)
508 char c = menu->skin->get_tag(menu, i);
510 if ((menu->flags & MN_CASELESS_TAGS) && c)
511 c = toupper((unsigned char) c);
513 if (c && c == key)
514 return i + menu->top;
517 else if (!(menu->flags & MN_PVT_TAGS) && menu->selections)
519 for (i = 0; menu->selections[i]; i++)
521 char c = menu->selections[i];
523 if (menu->flags & MN_CASELESS_TAGS)
524 c = toupper((unsigned char) c);
526 if (c == key)
527 return i;
530 else if (menu->row_funcs->get_tag)
532 for (i = 0; i < n; i++)
534 int oid = menu->object_list ? menu->object_list[i] : i;
535 char c = menu->row_funcs->get_tag(menu, oid);
537 if ((menu->flags & MN_CASELESS_TAGS) && c)
538 c = toupper((unsigned char) c);
540 if (c && c == key)
541 return i;
545 return -1;
549 * Event handler wrapper function
550 * Filters unhandled keys & conditions
552 static bool handle_menu_key(char cmd, menu_type *menu, int cursor)
554 int oid = cursor;
555 int flags = menu->flags;
557 if (menu->object_list) oid = menu->object_list[cursor];
558 if (flags & MN_NO_ACT) return FALSE;
560 if (cmd == ESCAPE) return FALSE;
561 if (!cmd == '\xff' && (!menu->cmd_keys || !strchr(menu->cmd_keys, cmd)))
562 return FALSE;
564 if (menu->row_funcs->row_handler &&
565 menu->row_funcs->row_handler(cmd, (void *)menu->menu_data, oid))
567 event_type ke;
568 ke.type = EVT_SELECT;
569 ke.key = cmd;
570 ke.index = cursor;
571 Term_event_push(&ke);
572 return TRUE;
575 return FALSE;
578 /* Modal display of menu */
579 static void display_menu_row(menu_type *menu, int pos, int top,
580 bool cursor, int row, int col, int width)
582 int flags = menu->flags;
583 char sel = 0;
584 int oid = pos;
586 if (menu->object_list)
587 oid = menu->object_list[oid];
589 if (menu->row_funcs->valid_row && menu->row_funcs->valid_row(menu, oid) == 2)
590 return;
592 if (!(flags & MN_NO_TAGS))
594 if (flags & MN_REL_TAGS)
595 sel = menu->skin->get_tag(menu, pos);
596 else if (menu->selections && !(flags & MN_PVT_TAGS))
597 sel = menu->selections[pos];
598 else if (menu->row_funcs->get_tag)
599 sel = menu->row_funcs->get_tag(menu, oid);
602 if (sel)
604 /* TODO: CHECK FOR VALID */
605 byte color = curs_attrs[CURS_KNOWN][0 != (cursor)];
606 Term_putstr(col, row, 3, color, format("%c) ", sel));
607 col += 3;
608 width -= 3;
611 menu->row_funcs->display_row(menu, oid, cursor, row, col, width);
614 void menu_refresh(menu_type *menu)
616 region *loc = &menu->boundary;
617 int oid = menu->cursor;
619 if (menu->object_list && menu->cursor >= 0)
620 oid = menu->object_list[oid];
622 region_erase(&menu->boundary);
624 if (menu->title)
625 Term_putstr(loc->col, loc->row, loc->width, TERM_WHITE, menu->title);
627 if (menu->prompt)
628 Term_putstr(loc->col, loc->row + loc->page_rows - 1, loc->width,
629 TERM_WHITE, menu->prompt);
631 if (menu->browse_hook && oid >= 0)
632 menu->browse_hook(oid, (void*) menu->menu_data, loc);
634 menu->skin->display_list(menu, menu->cursor, &menu->top, &menu->active);
637 /* The menu event loop */
638 static bool menu_handle_event(menu_type *menu, const event_type *in)
640 int n = menu->filter_count;
641 int *cursor = &menu->cursor;
642 event_type out;
644 out.key = '\xff';
646 if (menu->target.observers)
648 /* TODO: need a panel dispatcher here, not a generic target */
649 event_target t = { { 0, 0, 0, 0, { 0 } }, FALSE, 0 /* menu->target.observers */};
650 t.observers = menu->target.observers;
651 out = run_event_loop(&t, FALSE, in);
653 if (out.type != EVT_AGAIN)
655 if (out.type == EVT_SELECT)
657 /* HACK: can't return selection event from submenu (no ID) */
658 out.type = EVT_REFRESH;
659 Term_event_push(&out);
662 return TRUE;
666 switch (in->type)
668 case EVT_MOUSE:
670 int m_curs;
672 if (!region_inside(&menu->active, in))
674 if (!region_inside(&menu->boundary, in))
676 return FALSE;
679 /* used for heirarchical menus */
680 if (in->mousex < menu->active.col)
682 out.type = EVT_BACK;
683 break;
686 return FALSE;
689 m_curs = menu->skin->get_cursor(in->mousey, in->mousex,
690 menu->filter_count, menu->top,
691 &menu->active);
693 /* Ignore this event - retry */
694 if (!is_valid_row(menu, m_curs))
695 return TRUE;
697 out.index = m_curs;
699 if (*cursor == m_curs || !(menu->flags & MN_DBL_TAP))
701 if (*cursor != m_curs)
703 *cursor = m_curs;
704 menu_refresh(menu);
706 out.type = EVT_SELECT;
708 else
710 out.type = EVT_MOVE;
713 *cursor = m_curs;
715 break;
718 case EVT_KBRD:
720 int c, dir;
722 /* could install handle_menu_key as a handler */
723 if ((menu->cmd_keys && strchr(menu->cmd_keys, in->key))
724 || in->key == ESCAPE)
726 if (menu->flags & MN_NO_ACT)
727 return FALSE;
728 else
729 return handle_menu_key(in->key, menu, *cursor);
732 c = get_cursor_key(menu, menu->top, in->key);
734 /* keypress shortcuts are allowed, but the choice */
735 /* was invalid - try again! */
736 if (c > 0 && !is_valid_row(menu, c))
738 return FALSE;
740 /* Valid selection */
741 else if (c >= 0)
743 out.index = c;
745 if (*cursor == c || !(menu->flags & MN_DBL_TAP))
747 if (*cursor != c)
749 *cursor = c;
750 menu_refresh(menu);
752 out.type = EVT_SELECT;
754 else
756 out.type = EVT_MOVE;
759 *cursor = c;
761 break;
764 /* Not handled */
765 if (menu->flags & MN_NO_CURSOR)
766 return FALSE;
768 if (in->key == ' ' && (menu->flags & MN_PAGE))
770 /* Go to start of next page */
771 *cursor += menu->active.page_rows - (*cursor % menu->active.page_rows);
773 if (*cursor >= menu->filter_count)
774 *cursor = 0;
776 out.type = EVT_MOVE;
777 out.index = *cursor;
779 break;
782 /* Cursor movement */
783 dir = target_dir(in->key);
785 /* Handle Enter */
786 if (in->key == '\n' || in->key == '\r')
788 out.type = EVT_SELECT;
789 out.index = *cursor;
791 /* Reject diagonals */
792 else if (ddx[dir] && ddy[dir])
794 return FALSE;
796 /* Forward/back */
797 else if (ddx[dir])
799 out.type = ddx[dir] < 0 ? EVT_BACK : EVT_SELECT;
800 out.index = *cursor;
802 /* Move up or down to the next valid & visible row */
803 else if (ddy[dir])
805 int ind;
806 int dy = ddy[dir];
808 out.type = EVT_MOVE;
809 for (ind = *cursor + dy; ind < n && ind >= 0
810 && (TRUE != is_valid_row(menu, ind)); ind += dy) ;
811 out.index = ind;
812 if (ind < n && ind >= 0) *cursor = ind;
814 else
816 return FALSE;
819 break;
822 case EVT_REFRESH:
824 menu_refresh(menu);
825 return FALSE;
828 default:
830 return FALSE;
834 if (out.type == EVT_SELECT && handle_menu_key('\xff', menu, *cursor))
835 return TRUE;
837 if (out.type == EVT_MOVE)
838 menu_refresh(menu);
840 Term_event_push(&out);
842 return TRUE;
846 /* VTAB for menus */
847 static const panel_type menu_target =
850 {0, /* listener.object_id */
851 (handler_f) menu_handle_event, /* listener.handler */
852 (release_f) menu_destroy, /* listener.release */
853 0, /* listener.object */
854 {EVT_KBRD | EVT_MOUSE | EVT_REFRESH} /* listener.events */
856 TRUE, /* target.is_modal */
858 menu_refresh, /* refresh() */
859 {0, 0, 0, 0} /* boundary */
863 * Modal selection from a menu.
864 * Arguments:
865 * - menu - the menu
866 * - cursor - the row in which the cursor should start.
867 * - no_handle - Don't handle these events. ( bitwise or of event_class)
868 * 0 - return values below are limited to the set below.
869 * Additional examples:
870 * EVT_MOVE - return values also include menu movement.
871 * EVT_CMD - return values also include command IDs to process.
872 * Returns: an event, possibly requiring further handling.
873 * Return values:
874 * EVT_SELECT - success. event_type::index is set to the cursor position.
875 * *cursor is also set to the cursor position.
876 * EVT_OK - success. A command event was handled.
877 * *cursor is also set to the associated row.
878 * EVT_BACK - no selection; go to previous menu in hierarchy
879 * EVT_ESCAPE - Abandon modal interaction
880 * EVT_KBRD - An unhandled keyboard event
882 event_type menu_select(menu_type *menu, int *cursor, int no_handle)
884 event_type ke;
886 menu->cursor = *cursor;
888 /* Menu shall not handle these */
889 no_handle |= (EVT_SELECT | EVT_BACK | EVT_ESCAPE | EVT_STOP);
890 no_handle &= ~(EVT_REFRESH);
892 if (!menu->object_list)
893 menu->filter_count = menu->count;
895 ke.type = EVT_REFRESH;
896 (void)run_event_loop(&menu->target, FALSE, &ke);
898 /* Check for command flag */
899 if (p_ptr->command_new)
901 Term_key_push(p_ptr->command_new);
902 p_ptr->command_new = 0;
905 /* Stop on first unhandled event. */
906 while (!(ke.type & no_handle))
908 ke = run_event_loop(&menu->target, FALSE, 0);
910 switch (ke.type)
912 /* menu always returns these */
913 case EVT_SELECT:
915 if (*cursor != ke.index)
917 *cursor = ke.index;
918 /* One last time */
919 menu->refresh(menu);
920 break;
923 /* return sometimes-interesting things here */
926 case EVT_MOVE:
928 /* EVT_MOVE uses -1, n to allow modular cursor */
929 if (ke.index < menu->filter_count && ke.index >= 0)
930 *cursor = menu->cursor;
933 case EVT_KBRD:
935 /* Just in case */
936 if (ke.key == ESCAPE)
937 ke.type = EVT_ESCAPE;
939 break;
942 default:
944 break;
948 return ke;
952 /* ================== MENU ACCESSORS ================ */
955 * The menu skin registry. In the unlikely event you need to register
956 * more skins, make the array bigger.
958 static menu_skin const *menu_skin_reg[20] =
960 &scroll_skin,
961 &column_skin,
962 &key_select_skin,
967 * The menu row-iterator registry.
968 * Note that there's no need to register "anonymous" row iterators, that is,
969 * iterators always accessed by address, and not available in pref files.
971 static menu_iter const *menu_iter_reg[20] =
973 &menu_iter_event,
974 &menu_iter_item,
975 &menu_iter_string,
980 const menu_iter *find_menu_iter(menu_iter_id id)
982 size_t i;
983 for (i = 0; i < N_ELEMENTS(menu_iter_reg) && menu_iter_reg[i]; i++)
985 if (menu_iter_reg[i]->id == id)
986 return menu_iter_reg[i];
988 return NULL;
991 const menu_skin *find_menu_skin(skin_id id)
993 size_t i;
994 for (i = 0; i < N_ELEMENTS(menu_skin_reg) && menu_skin_reg[i]; i++)
996 if (menu_skin_reg[i]->id == id)
997 return menu_skin_reg[i];
999 return NULL;
1002 void add_menu_skin(const menu_skin *skin, skin_id id)
1004 size_t i;
1006 assert(skin->id == id);
1007 for (i = 0; i < N_ELEMENTS(menu_skin_reg) && menu_skin_reg[i]; i++)
1008 assert(skin->id != menu_skin_reg[id]->id);
1010 if (i == N_ELEMENTS(menu_skin_reg))
1011 quit("too many registered skins!");
1013 menu_skin_reg[i] = skin;
1016 void add_menu_iter(const menu_iter * iter, menu_iter_id id)
1018 size_t i;
1020 assert(iter->id == id);
1021 for (i = 0; i < N_ELEMENTS(menu_iter_reg) && menu_iter_reg[i]; i++)
1022 assert(iter->id != menu_iter_reg[id]->id);
1024 if (i == N_ELEMENTS(menu_iter_reg))
1025 quit("too many registered iters!");
1027 menu_iter_reg[i] = iter;
1031 * Set the filter to a new value.
1032 * IMPORTANT: The filter is assumed to be owned by the menu.
1033 * To remove a filter that is not owned by the menu, use:
1034 * menu_set_filter(m, NULL, m->count);
1036 void menu_set_filter(menu_type *menu, const int object_list[], int n)
1038 menu->object_list = object_list;
1039 menu->filter_count = n;
1042 /* Delete the filter */
1043 /* HACK: returns old filter for possible destruction */
1044 /* as: FREE(menu_remove_filter(m)); */
1045 void menu_release_filter(menu_type *menu)
1047 if (menu->object_list)
1048 FREE((void *)menu->object_list);
1049 menu->object_list = 0;
1050 menu->filter_count = menu->count;
1053 void menu_set_id(menu_type *menu, int id)
1055 menu->target.self.object_id = id;
1058 /* ======================== MENU INITIALIZATION ==================== */
1060 /* This is extremely primitive, barely sufficient to the job done */
1061 bool menu_layout(menu_type *menu, const region *loc)
1063 region active;
1065 if (!loc) return TRUE;
1066 active = *loc;
1068 if (active.width <= 0 || active.page_rows <= 0)
1070 int w, h;
1071 Term_get_size(&w, &h);
1072 if (active.width <= 0)
1073 active.width = w + active.width - active.col;
1074 if (active.page_rows <= 0)
1075 active.page_rows = h + loc->page_rows - active.row;
1078 menu->boundary = active;
1080 if (menu->title)
1082 active.row += 2;
1083 active.page_rows -= 2;
1084 /* TODO: handle small screens */
1085 active.col += 4;
1087 if (menu->prompt)
1089 if (active.page_rows > 1)
1090 active.page_rows--;
1091 else
1093 int offset = strlen(menu->prompt) + 2;
1094 active.col += offset;
1095 active.width -= offset;
1098 /* TODO: */
1099 /* if(menu->cmd_keys) active.page_rows--; */
1101 menu->active = active;
1102 return (active.width > 0 && active.page_rows > 0);
1106 bool menu_init2(menu_type *menu, const menu_skin *skin,
1107 const menu_iter * iter, const region *loc)
1109 /* VTAB */
1110 *((panel_type *) menu) = menu_target;
1111 menu->target.self.object = menu;
1112 menu->target.is_modal = TRUE;
1113 menu->row_funcs = iter;
1114 menu->skin = skin;
1115 menu->target.self.events.evt_flags = (EVT_MOUSE | EVT_REFRESH | EVT_KBRD);
1117 if (menu->count && !menu->object_list) menu->filter_count = menu->count;
1119 if (!loc) loc = &SCREEN_REGION;
1122 menu_layout(menu, loc);
1124 /* TODO: Check for collisions in selections & command keys here */
1125 return TRUE;
1128 bool menu_init(menu_type *menu, skin_id skin_id,
1129 menu_iter_id iter_id, const region *loc)
1131 const menu_skin *skin = find_menu_skin(skin_id);
1132 const menu_iter *iter = find_menu_iter(iter_id);
1134 if (!iter || !skin)
1136 msg_format("could not find menu VTAB (%d, %d)!", skin_id, iter_id);
1137 return FALSE;
1140 return menu_init2(menu, skin, iter, loc);
1143 void menu_destroy(menu_type *menu)
1145 if (menu->object_list)
1146 FREE((void *)menu->object_list);
1151 /*** Miscellaneous things ***/
1154 * A Hengband-like 'window' function, that draws a surround box in ASCII art.
1156 void window_make(int origin_x, int origin_y, int end_x, int end_y)
1158 int n;
1159 region to_clear;
1161 to_clear.col = origin_x;
1162 to_clear.row = origin_y;
1163 to_clear.width = end_x - origin_x;
1164 to_clear.page_rows = end_y - origin_y;
1166 region_erase(&to_clear);
1168 Term_putch(origin_x, origin_y, TERM_WHITE, '+');
1169 Term_putch(end_x, origin_y, TERM_WHITE, '+');
1170 Term_putch(origin_x, end_y, TERM_WHITE, '+');
1171 Term_putch(end_x, end_y, TERM_WHITE, '+');
1173 for (n = 1; n < (end_x - origin_x); n++)
1175 Term_putch(origin_x + n, origin_y, TERM_WHITE, '-');
1176 Term_putch(origin_x + n, end_y, TERM_WHITE, '-');
1179 for (n = 1; n < (end_y - origin_y); n++)
1181 Term_putch(origin_x, origin_y + n, TERM_WHITE, '|');
1182 Term_putch(end_x, origin_y + n, TERM_WHITE, '|');