Changeset 27

Show
Ignore:
Timestamp:
03/31/07 14:11:05 (2 years ago)
Author:
pmac
Message:

Added real event handling for UI changes.

  • mouse and other event support. (z-term.c, z-term.h)
  • fixed event-handling logic in UI patch. (util.c)
    (It pushed bad events back on the queue for non-keyboard events.)
  • event loop logic added (virtual classes event_target, event_listener, etc)
  • prototype version of menu replaced by one using a "real" event loop.
    (Much improved API) (ui.c)
  • cleaned up event handler in birth.c, and update API.
  • cleaned up menu initialization in cmd4.c, and update API
  • readded do_cmd_resize so that screen layout actions can
    refresh/abort on resize.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/src/Makefile.std

    r16 r27  
    128128#### Targets and objects ##### 
    129129 
    130 # Set a target here 
    131 default: $(EXE) 
     130# By default, copy the executable to ../ so that you don't find 
     131# yourself debugging a stale copy. 
     132default: install 
    132133 
    133134# Makefile.inc contains an up-to-date set of object files to compile, so 
  • trunk/src/birth.c

    r22 r27  
    687687static region class_region = {CLASS_COL, TABLE_ROW, 19, -2}; 
    688688 
    689 static void show_help(cptr helpfile, cptr topic) { 
    690         char buf[80]; 
    691         strnfmt(buf, sizeof(buf), "%s#%s", helpfile, topic); 
    692         screen_save(); 
    693         show_file(buf, NULL, 0, 0); 
    694         screen_load(); 
     689 
     690/* Event handler implementation */ 
     691static bool handler_aux(char cmd, int oid, byte *val, int max, int mask, cptr topic) 
     692
     693        if(cmd == '\xff' || cmd == '\r') { 
     694                *val = oid; 
     695        } 
     696        else if(cmd == '*') { 
     697                for(;;) { 
     698                        oid = rand_int(max); 
     699                        *val = oid; 
     700                        if(mask & (1L << oid)) break; 
     701                } 
     702        } 
     703        else if(cmd == '=') do_cmd_options(); 
     704        else if(cmd == KTRL('X')) quit(NULL); 
     705        else if(cmd == '?') { 
     706                char buf[80]; 
     707                strnfmt(buf, sizeof(buf), "%s#%s", "birth.txt", topic); 
     708                screen_save(); 
     709                show_file(buf, NULL, 0, 0); 
     710                screen_load(); 
     711        } 
     712        else return FALSE; 
     713 
     714        sp_ptr = &sex_info[p_ptr->psex]; 
     715        rp_ptr = &p_info[p_ptr->prace]; 
     716        cp_ptr = &c_info[p_ptr->pclass]; 
     717        mp_ptr = &cp_ptr->spells; 
     718        return TRUE; 
    695719} 
    696720 
    697721/* GENDER */ 
    698 /* Could make a general purpose display, but see no point. */ 
     722/* Display a gender */ 
    699723static void display_gender(menu_type *menu, int oid, bool cursor, 
    700724                                                        int row, int col, int width) 
     
    704728} 
    705729 
    706 /* Not worth writing a general purpose handler */ 
    707730static bool gender_handler(char cmd, const void *db, int oid) 
    708731{ 
    709         if(cmd == '\xff' || cmd == '\r') { 
    710                 p_ptr->psex = oid; 
    711                 sp_ptr = &sex_info[p_ptr->psex]; 
    712         } 
    713         else if(cmd == '*') { 
    714                 p_ptr->psex = rand_int(SEX_MALE+1); 
    715                 sp_ptr = &sex_info[p_ptr->psex]; 
    716         } 
    717         else if(cmd == '=') do_cmd_options(); 
    718         else if(cmd == KTRL('X')) quit(NULL); 
    719         else if(cmd == '?')  show_help("birth.txt", sex_info[oid].title); 
    720         else return FALSE; 
    721         return TRUE; 
     732        return handler_aux(cmd, oid, &p_ptr->psex, SEX_MALE+1, 
     733                                                        0xffffffff, sex_info[oid].title); 
    722734} 
    723735 
     
    732744static bool race_handler(char cmd, const void *db, int oid) 
    733745{ 
    734         if(cmd == '\xff' || cmd == '\r') { 
    735                 p_ptr->prace = oid; 
    736                 rp_ptr = &p_info[p_ptr->prace]; 
    737         } 
    738         else if(cmd == '*') { 
    739                 p_ptr->prace = rand_int(z_info->p_max); 
    740                 rp_ptr = &p_info[p_ptr->prace]; 
    741         } 
    742         else if(cmd == '=') do_cmd_options(); 
    743         else if(cmd == KTRL('X')) quit(NULL); 
    744         else if(cmd == '?')  show_help("birth.txt", p_name+p_info[oid].name); 
    745         else return FALSE; 
    746         return TRUE; 
    747 
    748  
     746        return handler_aux(cmd, oid, &p_ptr->prace, z_info->p_max, 
     747                                                        0xffffffff, p_name+p_info[oid].name); 
     748
    749749 
    750750/* CLASS */ 
     
    758758static bool class_handler(char cmd, const void *db, int oid) 
    759759{ 
    760         if(cmd == '\xff' || cmd == '\r') { 
    761                 p_ptr->pclass = oid; 
    762                 cp_ptr = &c_info[p_ptr->pclass]; 
    763                 mp_ptr = &cp_ptr->spells; 
    764         } 
    765         else if(cmd == '*') { 
    766                 for(;;) { 
    767                         oid = rand_int(z_info->c_max); 
    768                         p_ptr->pclass = oid; 
    769                         cp_ptr = &c_info[p_ptr->pclass]; 
    770                         mp_ptr = &cp_ptr->spells; 
    771                         if((rp_ptr->choice & (1L << oid))) 
    772                                 break; 
    773                 } 
    774         } 
    775         else if(cmd == '=') do_cmd_options(); 
    776         else if(cmd == KTRL('X')) quit(NULL); 
    777         else if(cmd == '?') show_help("birth.txt", c_name+c_info[oid].name); 
    778         else return FALSE; 
    779         return TRUE; 
    780 
    781  
    782  
    783 static const menu_class menu_defs[] = { 
     760        return handler_aux(cmd, oid, &p_ptr->pclass, z_info->c_max, 
     761                                                        (rp_ptr->choice),  c_name+c_info[oid].name); 
     762
     763 
     764 
     765static const menu_iter menu_defs[] = { 
    784766        {0, 0, 0, display_gender, gender_handler }, 
    785767        {0, 0, 0, display_race, race_handler }, 
     
    805787        menu_type menu; 
    806788        WIPE(&menu, menu); 
    807         menu.cmd_keys = "?*\r\x18";             /* ?, *, \n, <ctl-X> */ 
     789        menu.cmd_keys = "?=*\r\n\x18";          /* ?, ,= *, \n, <ctl-X> */ 
    808790 
    809791        i = 0; 
    810792        while(i < N_ELEMENTS(menu_defs)) 
    811793        { 
    812                 menu.flags = MN_NO_TAGS | MN_DBL_TAP; 
    813                 menu_init(&menu); 
    814794                key_event cx; 
    815795                int cursor = *values[i]; 
     796                menu.flags = MN_NO_TAGS | MN_DBL_TAP; 
     797                menu.count = limits[i]; 
     798                menu.browse_hook = browse[i]; 
     799                menu_init2(&menu, find_menu_skin(MN_SCROLL), &menu_defs[i], regions[i]); 
    816800                clear_question(); 
     801 
    817802                Term_putstr(QUESTION_COL, QUESTION_ROW, -1, TERM_YELLOW, hints[i]); 
    818                 menu.count = limits[i]; 
    819                 menu_set_class(&menu, &menu_defs[i]); 
    820                 menu.browse_hook = browse[i]; 
    821  
    822                 cx = menu_select(&menu, 0, menu.count, &cursor, *regions[i]); 
     803                cx = menu_select(&menu, &cursor, 0); 
    823804                if(cx.key == ESCAPE) { 
    824805                        return FALSE; /* restart */ 
    825806                } 
    826807                else if(cx.type == EVT_BACK) { 
     808                        *values[i] = cursor; 
    827809                        region_erase(regions[i]); 
    828810                        i--; 
     
    836818        } 
    837819        return TRUE; 
    838  
    839820} 
    840821 
  • trunk/src/cmd4.c

    r23 r27  
    16281628} 
    16291629 
     1630void do_cmd_resize() { 
     1631        /* Escape to main screen on resize */ 
     1632        Term_key_push(ESCAPE); 
     1633        do_cmd_redraw(); 
     1634} 
    16301635 
    16311636 
     
    20212026 
    20222027 
    2023 static void display_option(menu_type *menu, int oid, bool cursor, 
    2024                                                         int row, int col, int width) 
     2028static void display_option(menu_type *menu, int oid, 
     2029                                                        bool cursor, int row, int col, int width) 
    20252030{ 
    20262031        byte attr = curs_attrs[CURS_KNOWN][(int)cursor]; 
     
    20422047                op_ptr->opt[oid] = FALSE; 
    20432048                break; 
    2044         case 'T': case '5': 
     2049        case 'T': case '5': case '\xff': 
    20452050                op_ptr->opt[oid] = !op_ptr->opt[oid]; 
    20462051                break; 
     
    20552060} 
    20562061 
     2062static const menu_iter options_iter = { 
     2063        0, 
     2064        NULL, 
     2065        NULL, 
     2066        display_option,         /* label */ 
     2067        update_option           /* updater */ 
     2068}; 
     2069 
     2070static menu_type option_toggle_menu; 
     2071 
    20572072 
    20582073/* 
     
    20662081        int cursor_pos = 0; 
    20672082 
    2068         /* TODO: make an initializer that takes a few common args */ 
    2069         /* Title, prompt, cmd, choices, and flags -- everything else is ignored */ 
    2070         /* for MN_ONCE  */ 
    2071         menu_type menu = { 
    2072                 0,                                      /* menu_id */ 
    2073                 info,                           /* title */ 
    2074                 "Set option (y/n/t) '?' for information", /* prompt */ 
    2075                 "?5YyNnTt",                     /* cmd_keys */ 
    2076                 default_choice,         /* selections */ 
    2077                 MN_REL_TAGS|MN_SCROLL|MN_NO_ACT|MN_ONCE,        /* flags */ 
    2078                 0,                                      /* count */ 
    2079  
    2080                 vpage,                          /* menu data */ 
    2081                 update_option,          /* updater */ 
    2082                 NULL,                           /* browse */ 
    2083                 display_option,         /* label */ 
    2084                 0,                                      /* tagger */ 
    2085                 0,                                      /* skin */ 
    2086         }; 
    2087          
     2083        menu_type *menu = &option_toggle_menu; 
     2084        menu->title = info; 
     2085        menu_layout(menu, &SCREEN_REGION); 
     2086 
    20882087        screen_save(); 
    20892088        Term_clear(); 
     
    20972096                } 
    20982097        } 
    2099         menu.count = n; 
     2098        menu_set_filter(menu, opt, n); 
     2099        menu->menu_data = vpage; 
     2100 
     2101        menu_layout(menu, &SCREEN_REGION); 
     2102 
    21002103        for(;;) 
    21012104        { 
    21022105                key_event cx; 
    2103                 cx = menu_select(&menu, opt, n, &cursor_pos, SCREEN_REGION); 
    2104                 if (ESCAPE == cx.key) break; 
     2106                cx = menu_select(menu, &cursor_pos, EVT_MOVE); 
     2107                if (cx.type == EVT_BACK || ESCAPE == cx.key) break; 
    21052108                if(cx.type == EVT_MOVE) cursor_pos = cx.index; 
    2106                 else if(cx.key != '\xff') { 
    2107                         update_option(cx.key, vpage, opt[cursor_pos]); 
    2108                         if(strchr("YNyn", cx.key)) cursor_pos++; 
    2109                 } 
     2109                if(cx.type == EVT_SELECT && strchr("YN", toupper(cx.key))) 
     2110                        cursor_pos++; 
    21102111                cursor_pos = (cursor_pos+n)%n; 
    21112112        } 
     
    28282829}; 
    28292830 
    2830 static menu_type macro_menu = { 
    2831         'mcro', 
    2832         0, 
    2833         0, 
    2834         0, 
    2835         default_choice, 
    2836  
    2837         MN_EVT, 
    2838         N_ELEMENTS(macro_actions), 
    2839         macro_actions 
    2840         /* ,0,0,0,0*/ 
    2841 }; 
     2831static menu_type macro_menu; 
    28422832 
    28432833 
     
    28682858        FILE_TYPE(FILE_TYPE_TEXT); 
    28692859 
     2860 
    28702861        screen_save(); 
     2862 
     2863        region loc = {0, 1, 0, 11}; 
     2864        menu_layout(&macro_menu, &loc); 
    28712865 
    28722866        /* Process requests until done */ 
    28732867        while (1) 
    28742868        { 
    2875                 region loc = {0, 1, 0, 11}; 
    28762869                key_event c; 
    28772870                int evt; 
     
    28872880                /* Display the current action */ 
    28882881                prt(tmp, 13, 0); 
    2889                 c = menu_select(&macro_menu, 0, macro_menu.count, &cursor, loc); 
     2882                c = menu_select(&macro_menu, &cursor, EVT_CMD); 
    28902883 
    28912884                if(ESCAPE == c.key)  
     
    33403333}; 
    33413334 
    3342 static menu_type visual_menu = { 
    3343         'visu', 
    3344         "Interact with visuals", 
    3345         "", 
    3346         "Command: ", 
    3347         default_choice, 
    3348         MN_EVT, 
    3349         N_ELEMENTS(visual_menu_items), 
    3350         visual_menu_items 
    3351         /* ,0,0,0,0 */ 
    3352 }; 
     3335static menu_type visual_menu; 
    33533336 
    33543337 
     
    33633346        screen_save(); 
    33643347 
     3348        menu_layout(&visual_menu, &SCREEN_REGION); 
    33653349 
    33663350        /* Interact until done */ 
     
    33703354                int evt = -1; 
    33713355                Term_clear(); 
    3372                 key = menu_select(&visual_menu, 0, visual_menu.count, &cursor, SCREEN_REGION); 
     3356                key = menu_select(&visual_menu, &cursor, EVT_CMD); 
    33733357                if(key.key == ESCAPE)  
    33743358                        break; 
     
    35163500}; 
    35173501 
    3518 static menu_type color_menu = { 
    3519         'colr', 
    3520         "Interact with colors", 
    3521         "Command: ", 
    3522         0, 
    3523         default_choice, 
    3524         MN_EVT, 
    3525         N_ELEMENTS(color_events), 
    3526         color_events 
    3527         /* 0,0,0,0 */ 
    3528 }; 
    3529  
     3502static menu_type color_menu; 
    35303503 
    35313504 
     
    35453518        screen_save(); 
    35463519 
     3520        menu_layout(&color_menu, &SCREEN_REGION); 
    35473521        /* Interact until done */ 
    35483522        while (1) 
     
    35513525                int evt; 
    35523526                Term_clear(); 
    3553                 key = menu_select(&color_menu, 0, color_menu.count, &cursor, SCREEN_REGION); 
     3527                key = menu_select(&color_menu, &cursor, EVT_CMD); 
    35543528 
    35553529                /* Done */ 
     
    41564130}; 
    41574131 
    4158 static menu_type option_menu = { 
    4159         'opti', 
    4160         "Display Options", 
    4161         "Prompt: ", 
    4162         0, 
    4163         0, 
    4164         MN_ACT, 
    4165         N_ELEMENTS(option_actions), 
    4166         option_actions 
    4167         /* ,0,0,0,0 */ 
    4168 };  
     4132static menu_type option_menu; 
    41694133 
    41704134static menu_item knowledge_actions[] = 
     
    41814145}; 
    41824146 
    4183 static menu_type knowledge_menu = { 
    4184         'know', 
    4185         "Display current options", 
    4186         "Prompt: ", 
    4187         0, 
    4188         0, 
    4189         MN_ACT, 
    4190         N_ELEMENTS(knowledge_actions), 
    4191         knowledge_actions 
    4192 };  
     4147static menu_type knowledge_menu; 
     4148 
    41934149 
    41944150/* Keep macro counts happy. */ 
     
    42034159        screen_save(); 
    42044160 
     4161        menu_layout(&option_menu, &SCREEN_REGION); 
    42054162        for(;;) { 
    42064163                key_event c; 
    42074164                Term_clear(); 
    4208                 c = menu_select(&option_menu, 0, option_menu.count, &cursor, SCREEN_REGION); 
     4165                c = menu_select(&option_menu, &cursor, 0); 
    42094166                if(ESCAPE == c.key) break; 
    42104167        } 
     
    42174174        int cursor = -1; 
    42184175 
    4219         /* initialize static variables */ 
     4176        screen_save(); 
     4177 
     4178        menu_layout(&knowledge_menu, &SCREEN_REGION); 
     4179        for(;;) { 
     4180                key_event c; 
     4181                Term_clear(); 
     4182                c = menu_select(&knowledge_menu, &cursor, 0); 
     4183                if(ESCAPE == c.key) break; 
     4184        } 
     4185 
     4186        screen_load(); 
     4187
     4188 
     4189 
     4190void init_cmd4_c(void) 
     4191
     4192        /* Initialize the menus */ 
     4193        menu_type *menu; 
     4194 
     4195        /* Initialize the options toggle menu */ 
     4196        menu = &option_toggle_menu; 
     4197        WIPE(menu, menu_type); 
     4198        menu->prompt = "Set option (y/n/t), '?' for information"; 
     4199        menu->cmd_keys = "?YyNnTt"; 
     4200        menu->selections = default_choice; 
     4201        menu->count = OPT_PAGE_PER; 
     4202        menu->flags = MN_DBL_TAP; 
     4203        menu_init2(menu, find_menu_skin(MN_SCROLL), &options_iter, &SCREEN_REGION); 
     4204 
     4205        /* options screen selection menu */ 
     4206        menu = &option_menu; 
     4207        WIPE(menu, menu_type); 
     4208        menu_set_id(menu, 'opti'); 
     4209        menu->title = "Options Menu"; 
     4210        menu->count = N_ELEMENTS(option_actions); 
     4211        menu->menu_data = option_actions; 
     4212        menu_init(menu, MN_SCROLL, MN_ACT, &SCREEN_REGION); 
     4213 
     4214        /* knowledge menu */ 
     4215        menu = &knowledge_menu; 
     4216        WIPE(menu, menu_type); 
     4217        menu_set_id(menu, 'know'); 
     4218        menu->title = "Display current knowledge"; 
     4219        menu->count = N_ELEMENTS(knowledge_actions), 
     4220        menu->menu_data = knowledge_actions; 
     4221        menu_init(menu, MN_SCROLL, MN_ACT, &SCREEN_REGION); 
     4222         
     4223        /* visuals menu */ 
     4224        menu = &visual_menu; 
     4225        WIPE(menu, menu_type); 
     4226        menu_set_id(menu, 'visu'); 
     4227        menu->title = "Interact with visuals"; 
     4228        menu->selections = default_choice; 
     4229        menu->count = N_ELEMENTS(visual_menu_items); 
     4230        menu->menu_data = visual_menu_items;  
     4231        menu_init(menu, MN_SCROLL, MN_EVT, &SCREEN_REGION); 
     4232 
     4233        /* colors menu */ 
     4234        menu = &color_menu; 
     4235        WIPE(menu, menu_type); 
     4236        menu_set_id(menu, 'colr'); 
     4237        menu->title = "Interact with colors"; 
     4238        menu->selections = default_choice; 
     4239        menu->count = N_ELEMENTS(color_events), 
     4240        menu->menu_data = color_events; 
     4241        menu_init(menu, MN_SCROLL, MN_EVT, &SCREEN_REGION); 
     4242 
     4243        /* initialize other static variables */ 
    42204244        if(!obj_group_order) { 
    42214245                int i; 
     
    42304254                } 
    42314255        } 
    4232  
    4233         screen_save(); 
    4234  
    4235         for(;;) { 
    4236                 key_event c; 
    4237                 Term_clear(); 
    4238                 c = menu_select(&knowledge_menu, 0, knowledge_menu.count, &cursor, SCREEN_REGION); 
    4239                 if(ESCAPE == c.key) break; 
    4240         } 
    4241  
    4242         screen_load(); 
    4243 
     4256
  • trunk/src/dungeon.c

    r24 r27  
    27302730 
    27312731        /* Set screen resize hook */ 
    2732         Term_set_resize_hook(do_cmd_redraw); 
     2732        Term_set_resize_hook(do_cmd_resize); 
    27332733 
    27342734        /* Attempt to load */ 
  • trunk/src/externs.h

    r24 r27  
    345345/* cmd4.c */ 
    346346extern void do_cmd_redraw(void); 
     347extern void do_cmd_resize(void); 
    347348extern void do_cmd_change_name(void); 
    348349extern void do_cmd_message_one(void); 
     
    359360extern void do_cmd_save_screen(void); 
    360361extern void do_cmd_knowledge(void); 
     362extern void init_cmd4_c(void); 
    361363 
    362364/* cmd5.c */ 
  • trunk/src/init2.c

    r16 r27  
    20102010 
    20112011 
     2012        /* initialize the menus. This must occur before preference files are read */ 
     2013        init_cmd4_c(); 
     2014         
    20122015        /*** Initialize some arrays ***/ 
     2016 
    20132017 
    20142018        /* Initialize size info */ 
  • trunk/src/ui.c

    r22 r27  
    77 * incorporate modifications in all Angband variants as defined in the 
    88 * Angband variants FAQ. See rec.games.roguelike.angband for FAQ. 
    9  * 
    10  */ 
     9 */  
     10 
     11/* 
     12Description: 
     13Implementation of Extremely Basic Event Model. 
     14  Limits: 
     15    all events are of the concrete type key_event (see z-util.h),  
     16    which are supposed to model simple UI actions: 
     17        - < escape > 
     18        - keystroke 
     19        - mousepress 
     20        - select menu element 
     21        - move menu cursor 
     22        - back to parent (hierarchical menu escape) 
     23 
     24There are 3 basic event-related classes: 
     25The key_event. 
     26Concrete event, with at most 32 distinct types. 
     27 
     28The event_listener observer for key events 
     29 
     30The event_target   The registrar for event_listeners. 
     31For convenience, the event target is also an event_listener. 
     32 
     33 
     34*/ 
    1135 
    1236#include "angband.h" 
    1337 
     38/* Some useful constants */ 
    1439 
    1540const char default_choice[] = 
     
    2045int jumpscroll = 0; 
    2146int menu_width = 23; 
     47 
     48/* forward declarations */ 
     49static void display_menu_row(menu_type *menu, int pos, int top, 
     50                                                        bool cursor, int row, int col, int width); 
     51 
     52/* =================== GEOMETRY ================= */ 
    2253 
    2354void region_erase(const region *loc) 
     
    3667} 
    3768 
    38 static void display_menu_row(menu_type *menu, const int object_list[], int pos, 
    39                                                                 int top, bool cursor, int row, int col, int width); 
    40  
    41 /* Display an event, with posssble preference overrides */ 
    42  
     69bool region_inside(const region *loc, const key_event *key) 
     70
     71        if((loc->col > key->mousex) || (loc->col + loc->width <= key->mousex)) 
     72                return FALSE; 
     73        if((loc->row > key->mousey) || (loc->row + loc->page_rows <= key->mousey)) 
     74                return FALSE; 
     75        return TRUE; 
     76
     77 
     78/* ======================= EVENTS ======================== */ 
     79 
     80/* Helper class for event_target */ 
     81struct listener_list 
     82
     83        event_listener *listener; 
     84        struct listener_list *next; 
     85}; 
     86 
     87 
     88void stop_event_loop() 
     89
     90        key_event stop = { EVT_STOP }; 
     91        /* Stop right away! */ 
     92        Term_event_push(&stop); 
     93
     94 
     95/* 
     96 * Primitive event loop. 
     97 *  - target = the event target 
     98 *  - forever - if false, stop at first unhandled event. Otherwise, stop only 
     99 *    for STOP events 
     100 *  - start - optional initial event that allows you to prime the loop without 
     101 *    pushing the event queue. 
     102 *  Returns: 
     103 *    EVT_STOP - the loop was halted. 
     104 *    EVT_AGAIN - start was not handled, and forever is false 
     105 *    The first unhandled event - forever is false. 
     106 */ 
     107key_event run_event_loop(event_target *target, bool forever, const key_event *start) 
     108
     109        key_event ke; 
     110        bool handled = TRUE; 
     111        while (forever || handled) { 
     112                listener_list *list = target->observers; 
     113                handled = FALSE; 
     114                if(start) ke = *start; 
     115                else ke = inkey_ex(); 
     116                if(ke.type == EVT_STOP) 
     117                        break; 
     118 
     119                handled = target->self.handler(target->self.object, &ke); 
     120                if(target->is_modal) 
     121                        continue; 
     122 
     123                while(list && !handled) { 
     124                        if(ke.type & list->listener->events.evt_flags) { 
     125                                handled = list->listener->handler(list->listener->object, &ke); 
     126                        } 
     127                        list = list->next; 
     128                } 
     129                if(handled) start = NULL; 
     130        } 
     131        if(start) { 
     132                ke.type = EVT_AGAIN; 
     133                ke.key = '\xff'; 
     134        } 
     135        return ke; 
     136
     137 
     138void add_listener(event_target *target, event_listener *observer) 
     139
     140        listener_list *link; 
     141        MAKE(link, listener_list); 
     142        link->listener = observer; 
     143        link->next = target->observers; 
     144        target->observers = link; 
     145
     146 
     147void remove_listener(event_target *target, event_listener *observer) 
     148
     149        listener_list *cur = target->observers; 
     150        listener_list **prev = &target->observers; 
     151        while(cur) { 
     152                if(cur->listener == observer) { 
     153                        *prev = cur->next; 
     154                        FREE(cur); 
     155                        break; 
     156                } 
     157        } 
     158        bell("remove_listener: no such observer"); 
     159
     160 
     161 
     162 
     163/* ======================= MN_EVT HELPER FUNCTIONS ====================== */ 
     164 
     165/* Display an event, with possible preference overrides */ 
    43166static void display_event_aux(event_action *event, int menuID, byte color, 
    44167                                                        int row, int col, int wid) 
     
    51174} 
    52175 
    53 static bool inside(const region *loc, const key_event *key) 
    54 { 
    55         if((loc->col > key->mousex) || (loc->col + loc->width <= key->mousex)) 
    56                 return FALSE; 
    57         if((loc->row > key->mousey) || (loc->row + loc->page_rows <= key->mousey)) 
    58                 return FALSE; 
    59         return TRUE; 
    60 } 
    61  
    62 /*======================= MN_EVT HELPER FUNCTIONS ====================== */ 
    63  
    64176static void display_event(menu_type *menu, int oid, bool cursor,  
    65177                                                        int row, int col, int width) 
     
    67179        event_action *evts = (event_action*)menu->menu_data; 
    68180        byte color = curs_attrs[CURS_KNOWN][0 != cursor]; 
    69         display_event_aux(&evts[oid], menu->menuID, color, row, col, width); 
     181        display_event_aux(&evts[oid], menu->target.self.object_id, color, row, col, width); 
    70182} 
    71183 
     
    91203 
    92204/* Virtual function table for action_events */ 
    93 static const menu_class class_menu_event = { 
     205static const menu_iter menu_iter_event = { 
    94206        MN_EVT, 
    95207        0, 
     
    114226        byte color = 
    115227                curs_attrs[!(items[oid].flags & (MN_GRAYED|MN_DISABLED))][0 != cursor]; 
    116         display_event_aux(&items[oid].evt, menu->menuID, color, row, col, width); 
     228        display_event_aux(&items[oid].evt, menu->target.self.object_id, color, row, col, width); 
    117229} 
    118230 
     
    141253 
    142254/* Virtual function table for menu items */ 
    143 static menu_class class_menu_item = { 
     255static const menu_iter menu_iter_item = { 
    144256        MN_ACT, 
    145257        tag_menu_item, 
     
    147259        display_menu_item, 
    148260        handle_menu_item 
    149          
    150261}; 
    151262 
     
    155266 
    156267/* Scrolling menu */ 
    157 static int scrolling_get_cursor(int row, int col, int n, 
    158                                                                        int top, region *loc) 
     268/* Find the position of a cursor given a screen address */ 
     269static int scrolling_get_cursor(int row, int col, int n, int top, region *loc) 
    159270{ 
    160271        int cursor = row - loc->row + top; 
     
    168279 
    169280 
    170 void display_scrolling (menu_type *menu, const int object_list[], int n, 
    171                                                int cursor, int *top, region *loc) 
     281/* Display current view of a skin */ 
     282static void display_scrolling (menu_type *menu, int cursor, int *top, region *loc) 
    172283{ 
    173284        int col = loc->col; 
    174285        int row = loc->row; 
    175286        int rows_per_page = loc->page_rows; 
     287        int n = menu->filter_count; 
    176288        int i; 
    177289 
    178         if(cursor < *top
    179                 *top = cursor - jumpscroll
    180         if(cursor >= *top + rows_per_page
    181                  *top = cursor - rows_per_page-1 + jumpscroll; 
     290        if(cursor <= *top && *top > 0
     291                *top = cursor - jumpscroll - 1
     292        if(cursor >= *top + (rows_per_page-1)
     293                 *top = cursor - (rows_per_page-1) + 1 + jumpscroll; 
    182294        if(*top > n - rows_per_page) *top = n - rows_per_page; 
    183295        if(*top < 0) *top = 0; 
     
    186298        { 
    187299                bool is_curs = (cursor == i - *top); 
    188                 display_menu_row(menu, object_list, i, *top, is_curs, row+i, col, loc->width); 
     300                display_menu_row(menu, i, *top, is_curs, row+i, col, loc->width); 
    189301        } 
    190302        if(cursor >= 0) { 
    191303                Term_gotoxy(col, row + cursor-*top); 
    192304        } 
     305} 
     306 
     307static char scroll_get_tag(menu_type *menu, int pos) 
     308{ 
     309        return pos - menu->top; 
    193310} 
    194311 
     
    197314        MN_SCROLL, 
    198315        scrolling_get_cursor, 
    199         display_scrolling 
     316        display_scrolling, 
     317        scroll_get_tag 
    200318}; 
    201          
    202  
    203 /* multi-column menu */ 
     319 
     320 
     321/* Multi-column menu */ 
     322/* Find the position of a cursor given a screen address */ 
    204323static int columns_get_cursor(int row, int col, int n, int top, region *loc) 
    205324{ 
     
    214333} 
    215334 
    216 void display_columns (menu_type *menu, const int object_list[], int n, 
    217                                                 int cursor, int *top, region *loc) 
     335void display_columns (menu_type *menu, int cursor, int *top, region *loc) 
    218336{ 
    219337        int c, r; 
    220338        int w, h; 
     339        int n = menu->filter_count; 
    221340        int col = loc->col; 
    222341        int row = loc->row; 
     
    232351                        int pos = c*rows_per_page + r; 
    233352                        bool is_cursor = (pos == cursor); 
    234                         display_menu_row(menu, object_list, pos, 0, is_cursor, 
    235                                                                 row+r, col+c*colw, colw); 
    236                 } 
    237         } 
     353                        display_menu_row(menu, pos, 0, is_cursor, row+r, col+c*colw, colw); 
     354                } 
     355        } 
     356
     357 
     358static char column_get_tag(menu_type *menu, int pos) 
     359
     360        return pos; 
    238361} 
    239362 
     
    242365        MN_COLUMNS, 
    243366        columns_get_cursor, 
    244         display_columns 
     367        display_columns, 
     368        column_get_tag 
    245369}; 
    246370 
    247371/* ================== IMPLICIT MENU FOR KEY SELECTION ================== */ 
    248372 
    249 static void display_nothing (menu_type *menu, const int object_list[], int n, 
    250                                     int cursor, int *top, region *loc) 
     373static void display_nothing (menu_type *menu, int cursor, int *top, region *loc) 
    251374{ 
    252375} 
     
    264387 
    265388 
    266  
    267389/* ================== GENERIC HELPER FUNCTIONS ============== */ 
    268390 
    269 static char dummy_get_tag(menu_type *menu, int cursor) 
    270 
    271         return menu->selections[cursor]; 
    272 
    273  
    274 static bool is_valid_row(menu_type *menu, const int object_list[], int cursor) 
     391static bool is_valid_row(menu_type *menu, int cursor) 
    275392{ 
    276393        int oid = cursor; 
    277         if(cursor < 0 || cursor > menu->count) return FALSE; 
    278         if(!menu->valid_row) return TRUE; 
    279         if(object_list) { 
    280                 oid = object_list[cursor];  
    281         } 
    282         return menu->valid_row(menu, oid); 
    283 
    284  
    285 static int get_cursor_key(menu_type *menu, const int object_index[], int n, 
    286                                                         int top, char key) 
     394        if(cursor < 0 || cursor >= menu->filter_count) return FALSE; 
     395        if(menu->object_list) { 
     396                oid = menu->object_list[cursor];  
     397        } 
     398        if(!menu->row_funcs->valid_row) return TRUE; 
     399        return menu->row_funcs->valid_row(menu, oid); 
     400
     401 
     402static int get_cursor_key(menu_type *menu, int top, char key) 
    287403{ 
    288404        int i; 
    289         if(menu->flags & MN_REL_TAGS) { 
     405        int n = menu->filter_count; 
     406        if(menu->flags & MN_NO_TAGS) return -1; 
     407        else if(menu->flags & MN_REL_TAGS) { 
    290408                for(i = 0; i < n; i++) { 
    291                         char c = menu->get_tag(menu, i); 
     409                        char c = menu->skin->get_tag(menu, i); 
    292410                        if(c && c == key) 
    293                                 return i + top; 
    294                 }        
    295         } 
    296         for(i = 0; i < n; i++) { 
    297                 int oid = object_index ? object_index[i] : i; 
    298                 char c = menu->get_tag(menu, oid); 
    299                 if(c && c == key) 
    300                         return i; 
     411                                return i + menu->top; 
     412                } 
     413        } 
     414        else if(!(menu->flags & MN_PVT_TAGS) && menu->selections) { 
     415                for(i = 0; menu->selections[i] ; i++) 
     416                        if(menu->selections[i] == key) return i; 
     417        } 
     418        else if(menu->row_funcs->get_tag) { 
     419                for(i = 0; i < n; i++) { 
     420                        int oid = menu->object_list ? menu->object_list[i] : i; 
     421                        char c = menu->row_funcs->get_tag(menu, oid); 
     422                        if(c && c == key) 
     423                                return i; 
     424                } 
    301425        } 
    302426        return -1; 
     
    307431 * Filters unhandled keys & conditions  
    308432 */ 
    309 static bool handle_menu_key(char cmd, menu_type *menu, int oid) 
    310 
     433static bool handle_menu_key(char cmd, menu_type *menu, int cursor) 
     434
     435        int oid = cursor; 
     436        if(menu->object_list) oid = menu->object_list[cursor]; 
    311437        int flags = menu->flags; 
    312438        if(flags & MN_NO_ACT) return FALSE; 
     
    318444                        return FALSE; 
    319445 
    320         if(menu->handler) 
    321                 return menu->handler(cmd, menu->menu_data, oid); 
     446        if(menu->row_funcs->row_handler && 
     447                                                menu->row_funcs->row_handler(cmd, menu->menu_data, oid)) { 
     448                key_event ke; 
     449                ke.type = EVT_SELECT; 
     450                ke.key = cmd; 
     451                ke.index = cursor; 
     452                Term_event_push(&ke); 
     453                return TRUE; 
     454        } 
    322455        return FALSE; 
    323456} 
    324457 
    325  
    326 static void display_menu_row(menu_type *menu, const int object_list[], int pos
    327                                                                 int top, bool cursor, int row, int col, int width) 
     458/* Modal display of menu */ 
     459static void display_menu_row(menu_type *menu, int pos, int top
     460                                                                bool cursor, int row, int col, int width) 
    328461{ 
    329462        int flags = menu->flags; 
    330463        char sel = 0; 
    33