diff options
Diffstat (limited to 'trunk/src/emu/uimenu.c')
-rw-r--r-- | trunk/src/emu/uimenu.c | 3989 |
1 files changed, 3989 insertions, 0 deletions
diff --git a/trunk/src/emu/uimenu.c b/trunk/src/emu/uimenu.c new file mode 100644 index 00000000000..9c19131b62f --- /dev/null +++ b/trunk/src/emu/uimenu.c @@ -0,0 +1,3989 @@ +/********************************************************************* + + uimenu.c + + Internal MAME menus for the user interface. + + Copyright Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +*********************************************************************/ + +#include "emu.h" +#include "emuopts.h" +#include "ui.h" +#include "rendutil.h" +#include "cheat.h" +#include "uiimage.h" +#include "uiinput.h" +#include "uimenu.h" +#include "audit.h" +#include "crsshair.h" +#include <ctype.h> +#include "imagedev/cassette.h" +#include "imagedev/bitbngr.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define UI_MENU_POOL_SIZE 65536 +#define UI_MENU_ALLOC_ITEMS 256 + +#define VISIBLE_GAMES_IN_LIST 15 + +#define MAX_PHYSICAL_DIPS 10 +#define MAX_INPUT_PORTS 32 +#define MAX_BITS_PER_PORT 32 + +/* DIP switch rendering parameters */ +#define DIP_SWITCH_HEIGHT 0.05f +#define DIP_SWITCH_SPACING 0.01 +#define SINGLE_TOGGLE_SWITCH_FIELD_WIDTH 0.025f +#define SINGLE_TOGGLE_SWITCH_WIDTH 0.020f +/* make the switch 80% of the width space and 1/2 of the switch height */ +#define PERCENTAGE_OF_HALF_FIELD_USED 0.80f +#define SINGLE_TOGGLE_SWITCH_HEIGHT ((DIP_SWITCH_HEIGHT / 2) * PERCENTAGE_OF_HALF_FIELD_USED) + + +enum +{ + INPUT_TYPE_DIGITAL = 0, + INPUT_TYPE_ANALOG = 1, + INPUT_TYPE_ANALOG_DEC = INPUT_TYPE_ANALOG + SEQ_TYPE_DECREMENT, + INPUT_TYPE_ANALOG_INC = INPUT_TYPE_ANALOG + SEQ_TYPE_INCREMENT, + INPUT_TYPE_TOTAL = INPUT_TYPE_ANALOG + SEQ_TYPE_TOTAL +}; + +enum +{ + ANALOG_ITEM_KEYSPEED = 0, + ANALOG_ITEM_CENTERSPEED, + ANALOG_ITEM_REVERSE, + ANALOG_ITEM_SENSITIVITY, + ANALOG_ITEM_COUNT +}; + +enum +{ + MEMCARD_ITEM_SELECT = 1, + MEMCARD_ITEM_LOAD, + MEMCARD_ITEM_EJECT, + MEMCARD_ITEM_CREATE +}; + +enum +{ + VIDEO_ITEM_ROTATE = 0x80000000, + VIDEO_ITEM_BACKDROPS, + VIDEO_ITEM_OVERLAYS, + VIDEO_ITEM_BEZELS, + VIDEO_ITEM_CPANELS, + VIDEO_ITEM_MARQUEES, + VIDEO_ITEM_ZOOM, + VIDEO_ITEM_VIEW +}; + + +enum +{ + CROSSHAIR_ITEM_VIS = 0, + CROSSHAIR_ITEM_PIC, + CROSSHAIR_ITEM_AUTO_TIME +}; + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _ui_menu_pool ui_menu_pool; +struct _ui_menu_pool +{ + ui_menu_pool * next; /* chain to next one */ + UINT8 * top; /* top of the pool */ + UINT8 * end; /* end of the pool */ +}; + + +typedef struct _ui_menu_item ui_menu_item; +struct _ui_menu_item +{ + const char * text; + const char * subtext; + UINT32 flags; + void * ref; +}; + + +class ui_menu +{ +public: + ui_menu(running_machine &machine) + : m_machine(machine) { } + + running_machine &machine() const { return m_machine; } + + render_container * container; /* render_container we render to */ + ui_menu_handler_func handler; /* handler callback */ + void * parameter; /* parameter */ + ui_menu_event menu_event; /* the UI menu_event that occurred */ + ui_menu * parent; /* pointer to parent menu */ + void * state; /* menu-specific state */ + ui_menu_destroy_state_func destroy_state; /* destroy state callback */ + int resetpos; /* reset position */ + void * resetref; /* reset reference */ + int selected; /* which item is selected */ + int hover; /* which item is being hovered over */ + int visitems; /* number of visible items */ + int numitems; /* number of items in the menu */ + int allocitems; /* allocated size of array */ + ui_menu_item * item; /* pointer to array of items */ + ui_menu_custom_func custom; /* callback for custom rendering */ + float customtop; /* amount of extra height to add at the top */ + float custombottom; /* amount of extra height to add at the bottom */ + ui_menu_pool * pool; /* list of memory pools */ + +private: + running_machine & m_machine; /* machine we are attached to */ +}; + + +/* internal input menu item data */ +typedef struct _input_item_data input_item_data; +struct _input_item_data +{ + input_item_data * next; /* pointer to next item in the list */ + const void * ref; /* reference to type description for global inputs or field for game inputs */ + input_seq_type seqtype; /* sequence type */ + input_seq seq; /* copy of the live sequence */ + const input_seq * defseq; /* pointer to the default sequence */ + const char * name; /* pointer to the base name of the item */ + UINT16 sortorder; /* sorting information */ + UINT8 type; /* type of port */ +}; + + +/* internal analog menu item data */ +typedef struct _analog_item_data analog_item_data; +struct _analog_item_data +{ + input_field_config *field; + int type; + int min, max; + int cur; + int defvalue; +}; + + +/* DIP switch descriptor */ +typedef struct _dip_descriptor dip_descriptor; +struct _dip_descriptor +{ + dip_descriptor * next; + const char * name; + UINT32 mask; + UINT32 state; +}; + + +/* extended settings menu state */ +typedef struct _settings_menu_state settings_menu_state; +struct _settings_menu_state +{ + dip_descriptor * diplist; +}; + + +/* extended input menu state */ +typedef struct _input_menu_state input_menu_state; +struct _input_menu_state +{ + UINT16 last_sortorder; + const void * pollingref; + input_item_data * pollingitem; + UINT8 record_next; + input_seq starting_seq; +}; + + +/* extended select game menu state */ +typedef struct _select_game_state select_game_state; +struct _select_game_state +{ + UINT8 error; + UINT8 rerandomize; + char search[40]; + int matchlist[VISIBLE_GAMES_IN_LIST]; + const game_driver * driverlist[1]; +}; + + +/* internal crosshair menu item data */ +typedef struct _crosshair_item_data crosshair_item_data; +struct _crosshair_item_data +{ + UINT8 type; + UINT8 player; + UINT8 min, max; + UINT8 cur; + UINT8 defvalue; + char last_name[CROSSHAIR_PIC_NAME_LENGTH + 1]; + char next_name[CROSSHAIR_PIC_NAME_LENGTH + 1]; +}; + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +/* menu states */ +static ui_menu *menu_stack; +static ui_menu *menu_free; + +static bitmap_t *hilight_bitmap; +static render_texture *hilight_texture; + +static render_texture *arrow_texture; + +static const char priortext[] = "Return to Prior Menu"; +static const char backtext[] = "Return to " CAPSTARTGAMENOUN; +static const char exittext[] = "Exit"; + +// temporary hack until this is C++-ified +static driver_enumerator *drivlist; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static void ui_menu_exit(running_machine &machine); + +/* internal menu processing */ +static void ui_menu_draw(running_machine &machine, ui_menu *menu, int customonly); +static void ui_menu_draw_text_box(ui_menu *menu); +static void ui_menu_handle_events(ui_menu *menu); +static void ui_menu_handle_keys(ui_menu *menu, UINT32 flags); +static void ui_menu_validate_selection(ui_menu *menu, int scandir); +static void ui_menu_clear_free_list(running_machine &machine); + +/* menu handlers */ +static void menu_main(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_main_populate(running_machine &machine, ui_menu *menu, void *state); +static void menu_input_groups(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_input_groups_populate(running_machine &machine, ui_menu *menu, void *state); +static void menu_input_general(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_input_general_populate(running_machine &machine, ui_menu *menu, input_menu_state *menustate, int group); +static void menu_input_specific(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_input_specific_populate(running_machine &machine, ui_menu *menu, input_menu_state *menustate); +static void menu_input_common(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static int CLIB_DECL menu_input_compare_items(const void *i1, const void *i2); +static void menu_input_populate_and_sort(running_machine &machine, ui_menu *menu, input_item_data *itemlist, input_menu_state *menustate); +static void menu_settings_dip_switches(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_settings_driver_config(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_settings_common(running_machine &machine, ui_menu *menu, void *state, UINT32 type); +static void menu_settings_populate(running_machine &machine, ui_menu *menu, settings_menu_state *menustate, UINT32 type); +static void menu_analog(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_analog_populate(running_machine &machine, ui_menu *menu); +static void menu_bookkeeping(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_bookkeeping_populate(running_machine &machine, ui_menu *menu, attotime *curtime); +static void menu_game_info(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_cheat(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_cheat_populate(running_machine &machine, ui_menu *menu); +static void menu_memory_card(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_memory_card_populate(running_machine &machine, ui_menu *menu, int cardnum); +static void menu_sliders(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_sliders_populate(running_machine &machine, ui_menu *menu, int menuless_mode); +static void menu_sliders_custom_render(running_machine &machine, ui_menu *menu, void *state, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2); +static void menu_video_targets(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_video_targets_populate(running_machine &machine, ui_menu *menu); +static void menu_video_options(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_video_options_populate(running_machine &machine, ui_menu *menu, render_target *target); +static void menu_crosshair(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_crosshair_populate(running_machine &machine, ui_menu *menu); +static void menu_quit_game(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_select_game(running_machine &machine, ui_menu *menu, void *parameter, void *state); +static void menu_select_game_populate(running_machine &machine, ui_menu *menu, select_game_state *menustate); +static void menu_select_game_build_driver_list(ui_menu *menu, select_game_state *menustate); +static void menu_select_game_custom_render(running_machine &machine, ui_menu *menu, void *state, void *selectedref, float top, float bottom, float x, float y, float x2, float y2); + +/* menu helpers */ +static void menu_render_triangle(bitmap_t &dest, const bitmap_t &source, const rectangle &sbounds, void *param); +static void menu_settings_custom_render_one(render_container *container, float x1, float y1, float x2, float y2, const dip_descriptor *dip, UINT32 selectedmask); +static void menu_settings_custom_render(running_machine &machine, ui_menu *menu, void *state, void *selectedref, float top, float bottom, float x, float y, float x2, float y2); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + get_field_default_seq - return a pointer + to the default sequence for the given field +-------------------------------------------------*/ + +INLINE const input_seq &get_field_default_seq(input_field_config *field, input_seq_type seqtype) +{ + if (field->seq[seqtype].is_default()) + return input_type_seq(field->machine(), field->type, field->player, seqtype); + else + return field->seq[seqtype]; +} + + +/*------------------------------------------------- + toggle_none_default - toggle between "NONE" + and the default item +-------------------------------------------------*/ + +INLINE void toggle_none_default(input_seq &selected_seq, input_seq &original_seq, const input_seq &selected_defseq) +{ + /* if we used to be "none", toggle to the default value */ + if (original_seq.length() == 0) + selected_seq = selected_defseq; + + /* otherwise, toggle to "none" */ + else + selected_seq.reset(); +} + + +/*------------------------------------------------- + item_is_selectable - return TRUE if the given + item is selectable +-------------------------------------------------*/ + +INLINE int item_is_selectable(const ui_menu_item *item) +{ + return ((item->flags & (MENU_FLAG_MULTILINE | MENU_FLAG_DISABLE)) == 0 && strcmp(item->text, MENU_SEPARATOR_ITEM) != 0); +} + + +/*------------------------------------------------- + exclusive_input_pressed - return TRUE if the + given key is pressed and we haven't already + reported a key +-------------------------------------------------*/ + +INLINE int exclusive_input_pressed(ui_menu *menu, int key, int repeat) +{ + if (menu->menu_event.iptkey == IPT_INVALID && ui_input_pressed_repeat(menu->machine(), key, repeat)) + { + menu->menu_event.iptkey = key; + return TRUE; + } + return FALSE; +} + + + +/*************************************************************************** + CORE SYSTEM MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + ui_menu_init - initialize the menu system +-------------------------------------------------*/ + +void ui_menu_init(running_machine &machine) +{ + int x; + + /* initialize the menu stack */ + ui_menu_stack_reset(machine); + + /* create a texture for hilighting items */ + hilight_bitmap = auto_bitmap_alloc(machine, 256, 1, BITMAP_FORMAT_ARGB32); + for (x = 0; x < 256; x++) + { + int alpha = 0xff; + if (x < 25) alpha = 0xff * x / 25; + if (x > 256 - 25) alpha = 0xff * (255 - x) / 25; + *BITMAP_ADDR32(hilight_bitmap, 0, x) = MAKE_ARGB(alpha,0xff,0xff,0xff); + } + hilight_texture = machine.render().texture_alloc(); + hilight_texture->set_bitmap(hilight_bitmap, NULL, TEXFORMAT_ARGB32); + + /* create a texture for arrow icons */ + arrow_texture = machine.render().texture_alloc(menu_render_triangle); + + /* add an exit callback to free memory */ + machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(ui_menu_exit), &machine)); +} + + +/*------------------------------------------------- + ui_menu_exit - clean up after ourselves +-------------------------------------------------*/ + +static void ui_menu_exit(running_machine &machine) +{ + /* free menus */ + ui_menu_stack_reset(machine); + ui_menu_clear_free_list(machine); + + /* free textures */ + machine.render().texture_free(hilight_texture); + machine.render().texture_free(arrow_texture); + + if (drivlist) + { + global_free(drivlist); + drivlist = NULL; + } +} + + + +/*************************************************************************** + CORE MENU MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + ui_menu_alloc - allocate a new menu +-------------------------------------------------*/ + +ui_menu *ui_menu_alloc(running_machine &machine, render_container *container, ui_menu_handler_func handler, void *parameter) +{ + ui_menu *menu; + + /* allocate and clear memory for the menu and the state */ + menu = auto_alloc_clear(machine, ui_menu(machine)); + + /* initialize the state */ + menu->container = container; + menu->handler = handler; + menu->parameter = parameter; + + /* reset the menu (adds a default entry) */ + ui_menu_reset(menu, UI_MENU_RESET_SELECT_FIRST); + return menu; +} + + +/*------------------------------------------------- + ui_menu_free - free a menu +-------------------------------------------------*/ + +void ui_menu_free(ui_menu *menu) +{ + /* free the pools */ + while (menu->pool != NULL) + { + ui_menu_pool *pool = menu->pool; + menu->pool = pool->next; + auto_free(menu->machine(), pool); + } + + /* free the item array */ + if (menu->item != NULL) + auto_free(menu->machine(), menu->item); + + /* free the state */ + if (menu->state != NULL) + { + if (menu->destroy_state != NULL) + (*menu->destroy_state)(menu, menu->state); + auto_free(menu->machine(), menu->state); + } + + /* free the menu */ + auto_free(menu->machine(), menu); +} + + +/*------------------------------------------------- + ui_menu_reset - free all items in the menu, + and all memory allocated from the memory pool +-------------------------------------------------*/ + +void ui_menu_reset(ui_menu *menu, ui_menu_reset_options options) +{ + ui_menu_pool *pool; + + /* based on the reset option, set the reset info */ + menu->resetpos = 0; + menu->resetref = NULL; + if (options == UI_MENU_RESET_REMEMBER_POSITION) + menu->resetpos = menu->selected; + else if (options == UI_MENU_RESET_REMEMBER_REF) + menu->resetref = menu->item[menu->selected].ref; + + /* reset all the pools and the numitems back to 0 */ + for (pool = menu->pool; pool != NULL; pool = pool->next) + pool->top = (UINT8 *)(pool + 1); + menu->numitems = 0; + menu->visitems = 0; + menu->selected = 0; + + /* add an item to return */ + if (menu->parent == NULL) + ui_menu_item_append(menu, backtext, NULL, 0, NULL); + else if (menu->parent->handler == menu_quit_game) + ui_menu_item_append(menu, exittext, NULL, 0, NULL); + else + ui_menu_item_append(menu, priortext, NULL, 0, NULL); +} + + +/*------------------------------------------------- + ui_menu_populated - returns TRUE if the menu + has any non-default items in it +-------------------------------------------------*/ + +int ui_menu_populated(ui_menu *menu) +{ + return (menu->numitems > 1); +} + + +/*------------------------------------------------- + ui_menu_item_append - append a new item to the + end of the menu +-------------------------------------------------*/ + +void ui_menu_item_append(ui_menu *menu, const char *text, const char *subtext, UINT32 flags, void *ref) +{ + ui_menu_item *item; + int index; + + /* only allow multiline as the first item */ + if ((flags & MENU_FLAG_MULTILINE) != 0) + assert(menu->numitems == 1); + + /* only allow a single multi-line item */ + else if (menu->numitems >= 2) + assert((menu->item[0].flags & MENU_FLAG_MULTILINE) == 0); + + /* realloc the item array if necessary */ + if (menu->numitems >= menu->allocitems) + { + int olditems = menu->allocitems; + menu->allocitems += UI_MENU_ALLOC_ITEMS; + ui_menu_item *newitems = auto_alloc_array(menu->machine(), ui_menu_item, menu->allocitems); + for (int itemnum = 0; itemnum < olditems; itemnum++) + newitems[itemnum] = menu->item[itemnum]; + auto_free(menu->machine(), menu->item); + menu->item = newitems; + } + index = menu->numitems++; + + /* copy the previous last item to the next one */ + if (index != 0) + { + index--; + menu->item[index + 1] = menu->item[index]; + } + + /* allocate a new item and populte it */ + item = &menu->item[index]; + item->text = (text != NULL) ? ui_menu_pool_strdup(menu, text) : NULL; + item->subtext = (subtext != NULL) ? ui_menu_pool_strdup(menu, subtext) : NULL; + item->flags = flags; + item->ref = ref; + + /* update the selection if we need to */ + if (menu->resetpos == index || (menu->resetref != NULL && menu->resetref == ref)) + menu->selected = index; + if (menu->resetpos == menu->numitems - 1) + menu->selected = menu->numitems - 1; +} + + +/*------------------------------------------------- + ui_menu_process - process a menu, drawing it + and returning any interesting events +-------------------------------------------------*/ + +const ui_menu_event *ui_menu_process(running_machine &machine, ui_menu *menu, UINT32 flags) +{ + /* reset the menu_event */ + menu->menu_event.iptkey = IPT_INVALID; + + /* first make sure our selection is valid */ + ui_menu_validate_selection(menu, 1); + + /* draw the menu */ + if (menu->numitems > 1 && (menu->item[0].flags & MENU_FLAG_MULTILINE) != 0) + ui_menu_draw_text_box(menu); + else + ui_menu_draw(machine, menu, (flags & UI_MENU_PROCESS_CUSTOM_ONLY) != 0); + + /* process input */ + if (!(flags & UI_MENU_PROCESS_NOKEYS)) + { + /* read events */ + ui_menu_handle_events(menu); + + /* handle the keys if we don't already have an menu_event */ + if (menu->menu_event.iptkey == IPT_INVALID) + ui_menu_handle_keys(menu, flags); + } + + /* update the selected item in the menu_event */ + if (menu->menu_event.iptkey != IPT_INVALID && menu->selected >= 0 && menu->selected < menu->numitems) + { + menu->menu_event.itemref = menu->item[menu->selected].ref; + return &menu->menu_event; + } + return NULL; +} + + +/*------------------------------------------------- + ui_menu_set_custom_render - configure the menu + for custom rendering +-------------------------------------------------*/ + +void ui_menu_set_custom_render(ui_menu *menu, ui_menu_custom_func custom, float top, float bottom) +{ + menu->custom = custom; + menu->customtop = top; + menu->custombottom = bottom; +} + + +/*------------------------------------------------- + ui_menu_alloc_state - allocate permanent + memory to represent the menu's state +-------------------------------------------------*/ + +void *ui_menu_alloc_state(ui_menu *menu, size_t size, ui_menu_destroy_state_func destroy_state) +{ + if (menu->state != NULL) + { + if (menu->destroy_state != NULL) + (*menu->destroy_state)(menu, menu->state); + auto_free(menu->machine(), menu->state); + } + menu->state = auto_alloc_array_clear(menu->machine(), UINT8, size); + menu->destroy_state = destroy_state; + + return menu->state; +} + + +/*------------------------------------------------- + ui_menu_pool_alloc - allocate temporary memory + from the menu's memory pool +-------------------------------------------------*/ + +void *ui_menu_pool_alloc(ui_menu *menu, size_t size) +{ + ui_menu_pool *pool; + + assert(size < UI_MENU_POOL_SIZE); + + /* find a pool with enough room */ + for (pool = menu->pool; pool != NULL; pool = pool->next) + if (pool->end - pool->top >= size) + { + void *result = pool->top; + pool->top += size; + return result; + } + + /* allocate a new pool */ + pool = (ui_menu_pool *)auto_alloc_array_clear(menu->machine(), UINT8, sizeof(*pool) + UI_MENU_POOL_SIZE); + + /* wire it up */ + pool->next = menu->pool; + menu->pool = pool; + pool->top = (UINT8 *)(pool + 1); + pool->end = pool->top + UI_MENU_POOL_SIZE; + return ui_menu_pool_alloc(menu, size); +} + + +/*------------------------------------------------- + ui_menu_pool_strdup - make a temporary string + copy in the menu's memory pool +-------------------------------------------------*/ + +const char *ui_menu_pool_strdup(ui_menu *menu, const char *string) +{ + return strcpy((char *)ui_menu_pool_alloc(menu, strlen(string) + 1), string); +} + + +/*------------------------------------------------- + ui_menu_get_selection - retrieves the index + of the currently selected menu item +-------------------------------------------------*/ + +void *ui_menu_get_selection(ui_menu *menu) +{ + return (menu->selected >= 0 && menu->selected < menu->numitems) ? menu->item[menu->selected].ref : NULL; +} + + +/*------------------------------------------------- + ui_menu_set_selection - changes the index + of the currently selected menu item +-------------------------------------------------*/ + +void ui_menu_set_selection(ui_menu *menu, void *selected_itemref) +{ + int itemnum; + + menu->selected = -1; + for (itemnum = 0; itemnum < menu->numitems; itemnum++) + if (menu->item[itemnum].ref == selected_itemref) + { + menu->selected = itemnum; + break; + } +} + + + +/*************************************************************************** + INTERNAL MENU PROCESSING +***************************************************************************/ + +/*------------------------------------------------- + ui_menu_draw - draw a menu +-------------------------------------------------*/ + +static void ui_menu_draw(running_machine &machine, ui_menu *menu, int customonly) +{ + float line_height = ui_get_line_height(machine); + float lr_arrow_width = 0.4f * line_height * machine.render().ui_aspect(); + float ud_arrow_width = line_height * machine.render().ui_aspect(); + float gutter_width = lr_arrow_width * 1.3f; + float x1, y1, x2, y2; + + float effective_width, effective_left; + float visible_width, visible_main_menu_height; + float visible_extra_menu_height = 0; + float visible_top, visible_left; + int selected_subitem_too_big = FALSE; + int visible_lines; + int top_line; + int itemnum, linenum; + int mouse_hit, mouse_button; + render_target *mouse_target; + INT32 mouse_target_x, mouse_target_y; + float mouse_x = -1, mouse_y = -1; + + /* compute the width and height of the full menu */ + visible_width = 0; + visible_main_menu_height = 0; + for (itemnum = 0; itemnum < menu->numitems; itemnum++) + { + const ui_menu_item *item = &menu->item[itemnum]; + float total_width; + + /* compute width of left hand side */ + total_width = gutter_width + ui_get_string_width(machine, item->text) + gutter_width; + + /* add in width of right hand side */ + if (item->subtext) + total_width += 2.0f * gutter_width + ui_get_string_width(machine, item->subtext); + + /* track the maximum */ + if (total_width > visible_width) + visible_width = total_width; + + /* track the height as well */ + visible_main_menu_height += line_height; + } + + /* account for extra space at the top and bottom */ + visible_extra_menu_height = menu->customtop + menu->custombottom; + + /* add a little bit of slop for rounding */ + visible_width += 0.01f; + visible_main_menu_height += 0.01f; + + /* if we are too wide or too tall, clamp it down */ + if (visible_width + 2.0f * UI_BOX_LR_BORDER > 1.0f) + visible_width = 1.0f - 2.0f * UI_BOX_LR_BORDER; + + /* if the menu and extra menu won't fit, take away part of the regular menu, it will scroll */ + if (visible_main_menu_height + visible_extra_menu_height + 2.0f * UI_BOX_TB_BORDER > 1.0f) + visible_main_menu_height = 1.0f - 2.0f * UI_BOX_TB_BORDER - visible_extra_menu_height; + + visible_lines = floor(visible_main_menu_height / line_height); + visible_main_menu_height = (float)visible_lines * line_height; + + /* compute top/left of inner menu area by centering */ + visible_left = (1.0f - visible_width) * 0.5f; + visible_top = (1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f; + + /* if the menu is at the bottom of the extra, adjust */ + visible_top += menu->customtop; + + /* first add us a box */ + x1 = visible_left - UI_BOX_LR_BORDER; + y1 = visible_top - UI_BOX_TB_BORDER; + x2 = visible_left + visible_width + UI_BOX_LR_BORDER; + y2 = visible_top + visible_main_menu_height + UI_BOX_TB_BORDER; + if (!customonly) + ui_draw_outlined_box(menu->container, x1, y1, x2, y2, UI_BACKGROUND_COLOR); + + /* determine the first visible line based on the current selection */ + top_line = menu->selected - visible_lines / 2; + if (top_line < 0) + top_line = 0; + if (top_line + visible_lines >= menu->numitems) + top_line = menu->numitems - visible_lines; + + /* determine effective positions taking into account the hilighting arrows */ + effective_width = visible_width - 2.0f * gutter_width; + effective_left = visible_left + gutter_width; + + /* locate mouse */ + mouse_hit = FALSE; + mouse_button = FALSE; + if (!customonly) + { + mouse_target = ui_input_find_mouse(machine, &mouse_target_x, &mouse_target_y, &mouse_button); + if (mouse_target != NULL) + if (mouse_target->map_point_container(mouse_target_x, mouse_target_y, *menu->container, mouse_x, mouse_y)) + mouse_hit = TRUE; + } + + /* loop over visible lines */ + menu->hover = menu->numitems + 1; + if (!customonly) + for (linenum = 0; linenum < visible_lines; linenum++) + { + float line_y = visible_top + (float)linenum * line_height; + itemnum = top_line + linenum; + const ui_menu_item *item = &menu->item[itemnum]; + const char *itemtext = item->text; + rgb_t fgcolor = UI_TEXT_COLOR; + rgb_t bgcolor = UI_TEXT_BG_COLOR; + rgb_t fgcolor2 = UI_SUBITEM_COLOR; + rgb_t fgcolor3 = UI_CLONE_COLOR; + float line_x0 = x1 + 0.5f * UI_LINE_WIDTH; + float line_y0 = line_y; + float line_x1 = x2 - 0.5f * UI_LINE_WIDTH; + float line_y1 = line_y + line_height; + + /* set the hover if this is our item */ + if (mouse_hit && line_x0 <= mouse_x && line_x1 > mouse_x && line_y0 <= mouse_y && line_y1 > mouse_y && item_is_selectable(item)) + menu->hover = itemnum; + + /* if we're selected, draw with a different background */ + if (itemnum == menu->selected) + { + fgcolor = UI_SELECTED_COLOR; + bgcolor = UI_SELECTED_BG_COLOR; + fgcolor2 = UI_SELECTED_COLOR; + fgcolor3 = UI_SELECTED_COLOR; + } + + /* else if the mouse is over this item, draw with a different background */ + else if (itemnum == menu->hover) + { + fgcolor = UI_MOUSEOVER_COLOR; + bgcolor = UI_MOUSEOVER_BG_COLOR; + fgcolor2 = UI_MOUSEOVER_COLOR; + fgcolor3 = UI_MOUSEOVER_COLOR; + } + + /* if we have some background hilighting to do, add a quad behind everything else */ + if (bgcolor != UI_TEXT_BG_COLOR) + menu->container->add_quad(line_x0, line_y0, line_x1, line_y1, bgcolor, hilight_texture, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE)); + + /* if we're on the top line, display the up arrow */ + if (linenum == 0 && top_line != 0) + { + menu->container->add_quad( + 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, + line_y + 0.25f * line_height, + 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, + line_y + 0.75f * line_height, + fgcolor, + arrow_texture, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT0)); + if (menu->hover == itemnum) + menu->hover = -2; + } + + /* if we're on the bottom line, display the down arrow */ + else if (linenum == visible_lines - 1 && itemnum != menu->numitems - 1) + { + menu->container->add_quad( + 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, + line_y + 0.25f * line_height, + 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, + line_y + 0.75f * line_height, + fgcolor, + arrow_texture, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT0 ^ ORIENTATION_FLIP_Y)); + if (menu->hover == itemnum) + menu->hover = -1; + } + + /* if we're just a divider, draw a line */ + else if (strcmp(itemtext, MENU_SEPARATOR_ITEM) == 0) + menu->container->add_line(visible_left, line_y + 0.5f * line_height, visible_left + visible_width, line_y + 0.5f * line_height, UI_LINE_WIDTH, UI_BORDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + /* if we don't have a subitem, just draw the string centered */ + else if (item->subtext == NULL) + ui_draw_text_full(menu->container, itemtext, effective_left, line_y, effective_width, + JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, NULL, NULL); + + /* otherwise, draw the item on the left and the subitem text on the right */ + else + { + int subitem_invert = item->flags & MENU_FLAG_INVERT; + const char *subitem_text = item->subtext; + float item_width, subitem_width; + + /* draw the left-side text */ + ui_draw_text_full(menu->container, itemtext, effective_left, line_y, effective_width, + JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, &item_width, NULL); + + /* give 2 spaces worth of padding */ + item_width += 2.0f * gutter_width; + + /* if the subitem doesn't fit here, display dots */ + if (ui_get_string_width(machine, subitem_text) > effective_width - item_width) + { + subitem_text = "..."; + if (itemnum == menu->selected) + selected_subitem_too_big = TRUE; + } + + /* draw the subitem right-justified */ + ui_draw_text_full(menu->container, subitem_text, effective_left + item_width, line_y, effective_width - item_width, + JUSTIFY_RIGHT, WRAP_TRUNCATE, DRAW_NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, &subitem_width, NULL); + + /* apply arrows */ + if (itemnum == menu->selected && (item->flags & MENU_FLAG_LEFT_ARROW)) + { + menu->container->add_quad( + effective_left + effective_width - subitem_width - gutter_width, + line_y + 0.1f * line_height, + effective_left + effective_width - subitem_width - gutter_width + lr_arrow_width, + line_y + 0.9f * line_height, + fgcolor, + arrow_texture, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT90 ^ ORIENTATION_FLIP_X)); + } + if (itemnum == menu->selected && (item->flags & MENU_FLAG_RIGHT_ARROW)) + { + menu->container->add_quad( + effective_left + effective_width + gutter_width - lr_arrow_width, + line_y + 0.1f * line_height, + effective_left + effective_width + gutter_width, + line_y + 0.9f * line_height, + fgcolor, + arrow_texture, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT90)); + } + } + } + + /* if the selected subitem is too big, display it in a separate offset box */ + if (selected_subitem_too_big) + { + const ui_menu_item *item = &menu->item[menu->selected]; + int subitem_invert = item->flags & MENU_FLAG_INVERT; + linenum = menu->selected - top_line; + float line_y = visible_top + (float)linenum * line_height; + float target_width, target_height; + float target_x, target_y; + + /* compute the multi-line target width/height */ + ui_draw_text_full(menu->container, item->subtext, 0, 0, visible_width * 0.75f, + JUSTIFY_RIGHT, WRAP_WORD, DRAW_NONE, ARGB_WHITE, ARGB_BLACK, &target_width, &target_height); + + /* determine the target location */ + target_x = visible_left + visible_width - target_width - UI_BOX_LR_BORDER; + target_y = line_y + line_height + UI_BOX_TB_BORDER; + if (target_y + target_height + UI_BOX_TB_BORDER > visible_main_menu_height) + target_y = line_y - target_height - UI_BOX_TB_BORDER; + + /* add a box around that */ + ui_draw_outlined_box(menu->container, target_x - UI_BOX_LR_BORDER, + target_y - UI_BOX_TB_BORDER, + target_x + target_width + UI_BOX_LR_BORDER, + target_y + target_height + UI_BOX_TB_BORDER, subitem_invert ? UI_SELECTED_BG_COLOR : UI_BACKGROUND_COLOR); + ui_draw_text_full(menu->container, item->subtext, target_x, target_y, target_width, + JUSTIFY_RIGHT, WRAP_WORD, DRAW_NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, NULL, NULL); + } + + /* if there is something special to add, do it by calling the passed routine */ + if (menu->custom != NULL) + { + void *selectedref = (menu->selected >= 0 && menu->selected < menu->numitems) ? menu->item[menu->selected].ref : NULL; + (*menu->custom)(menu->machine(), menu, menu->state, selectedref, menu->customtop, menu->custombottom, x1, y1, x2, y2); + } + + /* return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow */ + menu->visitems = visible_lines - (top_line != 0) - (top_line + visible_lines != menu->numitems); +} + + +/*------------------------------------------------- + ui_menu_draw_text_box - draw a multiline + word-wrapped text box with a menu item at the + bottom +-------------------------------------------------*/ + +static void ui_menu_draw_text_box(ui_menu *menu) +{ + const char *text = menu->item[0].text; + const char *backtext = menu->item[1].text; + float line_height = ui_get_line_height(menu->machine()); + float lr_arrow_width = 0.4f * line_height * menu->machine().render().ui_aspect(); + float gutter_width = lr_arrow_width; + float target_width, target_height, prior_width; + float target_x, target_y; + + /* compute the multi-line target width/height */ + ui_draw_text_full(menu->container, text, 0, 0, 1.0f - 2.0f * UI_BOX_LR_BORDER - 2.0f * gutter_width, + JUSTIFY_LEFT, WRAP_WORD, DRAW_NONE, ARGB_WHITE, ARGB_BLACK, &target_width, &target_height); + target_height += 2.0f * line_height; + if (target_height > 1.0f - 2.0f * UI_BOX_TB_BORDER) + target_height = floor((1.0f - 2.0f * UI_BOX_TB_BORDER) / line_height) * line_height; + + /* maximum against "return to prior menu" text */ + prior_width = ui_get_string_width(menu->machine(), backtext) + 2.0f * gutter_width; + target_width = MAX(target_width, prior_width); + + /* determine the target location */ + target_x = 0.5f - 0.5f * target_width; + target_y = 0.5f - 0.5f * target_height; + + /* make sure we stay on-screen */ + if (target_x < UI_BOX_LR_BORDER + gutter_width) + target_x = UI_BOX_LR_BORDER + gutter_width; + if (target_x + target_width + gutter_width + UI_BOX_LR_BORDER > 1.0f) + target_x = 1.0f - UI_BOX_LR_BORDER - gutter_width - target_width; + if (target_y < UI_BOX_TB_BORDER) + target_y = UI_BOX_TB_BORDER; + if (target_y + target_height + UI_BOX_TB_BORDER > 1.0f) + target_y = 1.0f - UI_BOX_TB_BORDER - target_height; + + /* add a box around that */ + ui_draw_outlined_box(menu->container, target_x - UI_BOX_LR_BORDER - gutter_width, + target_y - UI_BOX_TB_BORDER, + target_x + target_width + gutter_width + UI_BOX_LR_BORDER, + target_y + target_height + UI_BOX_TB_BORDER, (menu->item[0].flags & MENU_FLAG_REDTEXT) ? UI_RED_COLOR : UI_BACKGROUND_COLOR); + ui_draw_text_full(menu->container, text, target_x, target_y, target_width, + JUSTIFY_LEFT, WRAP_WORD, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, NULL, NULL); + + /* draw the "return to prior menu" text with a hilight behind it */ + menu->container->add_quad( + target_x + 0.5f * UI_LINE_WIDTH, + target_y + target_height - line_height, + target_x + target_width - 0.5f * UI_LINE_WIDTH, + target_y + target_height, + UI_SELECTED_BG_COLOR, + hilight_texture, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE)); + ui_draw_text_full(menu->container, backtext, target_x, target_y + target_height - line_height, target_width, + JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, NULL, NULL); + + /* artificially set the hover to the last item so a double-click exits */ + menu->hover = menu->numitems - 1; +} + + +/*------------------------------------------------- + ui_menu_handle_events - generically handle + input events for a menu +-------------------------------------------------*/ + +static void ui_menu_handle_events(ui_menu *menu) +{ + int stop = FALSE; + ui_event menu_event; + + /* loop while we have interesting events */ + while (ui_input_pop_event(menu->machine(), &menu_event) && !stop) + { + switch (menu_event.event_type) + { + /* if we are hovering over a valid item, select it with a single click */ + case UI_EVENT_MOUSE_DOWN: + if (menu->hover >= 0 && menu->hover < menu->numitems) + menu->selected = menu->hover; + else if (menu->hover == -2) + { + menu->selected -= menu->visitems - 1; + ui_menu_validate_selection(menu, 1); + } + else if (menu->hover == -1) + { + menu->selected += menu->visitems - 1; + ui_menu_validate_selection(menu, 1); + } + break; + + /* if we are hovering over a valid item, fake a UI_SELECT with a double-click */ + case UI_EVENT_MOUSE_DOUBLE_CLICK: + if (menu->hover >= 0 && menu->hover < menu->numitems) + { + menu->selected = menu->hover; + if (menu_event.event_type == UI_EVENT_MOUSE_DOUBLE_CLICK) + { + menu->menu_event.iptkey = IPT_UI_SELECT; + if (menu->selected == menu->numitems - 1) + { + menu->menu_event.iptkey = IPT_UI_CANCEL; + ui_menu_stack_pop(menu->machine()); + } + } + stop = TRUE; + } + break; + + /* translate CHAR events into specials */ + case UI_EVENT_CHAR: + menu->menu_event.iptkey = IPT_SPECIAL; + menu->menu_event.unichar = menu_event.ch; + stop = TRUE; + break; + + /* ignore everything else */ + default: + break; + } + } +} + + +/*------------------------------------------------- + ui_menu_handle_keys - generically handle + keys for a menu +-------------------------------------------------*/ + +static void ui_menu_handle_keys(ui_menu *menu, UINT32 flags) +{ + int ignorepause = ui_menu_is_force_game_select(); + int ignoreright = FALSE; + int ignoreleft = FALSE; + int code; + + /* bail if no items */ + if (menu->numitems == 0) + return; + + /* if we hit select, return TRUE or pop the stack, depending on the item */ + if (exclusive_input_pressed(menu, IPT_UI_SELECT, 0)) + { + if (menu->selected == menu->numitems - 1) + { + menu->menu_event.iptkey = IPT_UI_CANCEL; + ui_menu_stack_pop(menu->machine()); + } + return; + } + + /* hitting cancel also pops the stack */ + if (exclusive_input_pressed(menu, IPT_UI_CANCEL, 0)) + { + ui_menu_stack_pop(menu->machine()); + return; + } + + /* validate the current selection */ + ui_menu_validate_selection(menu, 1); + + /* swallow left/right keys if they are not appropriate */ + ignoreleft = ((menu->item[menu->selected].flags & MENU_FLAG_LEFT_ARROW) == 0); + ignoreright = ((menu->item[menu->selected].flags & MENU_FLAG_RIGHT_ARROW) == 0); + + /* accept left/right keys as-is with repeat */ + if (!ignoreleft && exclusive_input_pressed(menu, IPT_UI_LEFT, (flags & UI_MENU_PROCESS_LR_REPEAT) ? 6 : 0)) + return; + if (!ignoreright && exclusive_input_pressed(menu, IPT_UI_RIGHT, (flags & UI_MENU_PROCESS_LR_REPEAT) ? 6 : 0)) + return; + + /* up backs up by one item */ + if (exclusive_input_pressed(menu, IPT_UI_UP, 6)) + { + menu->selected = (menu->selected + menu->numitems - 1) % menu->numitems; + ui_menu_validate_selection(menu, -1); + } + + /* down advances by one item */ + if (exclusive_input_pressed(menu, IPT_UI_DOWN, 6)) + { + menu->selected = (menu->selected + 1) % menu->numitems; + ui_menu_validate_selection(menu, 1); + } + + /* page up backs up by visitems */ + if (exclusive_input_pressed(menu, IPT_UI_PAGE_UP, 6)) + { + menu->selected -= menu->visitems - 1; + ui_menu_validate_selection(menu, 1); + } + + /* page down advances by visitems */ + if (exclusive_input_pressed(menu, IPT_UI_PAGE_DOWN, 6)) + { + menu->selected += menu->visitems - 1; + ui_menu_validate_selection(menu, -1); + } + + /* home goes to the start */ + if (exclusive_input_pressed(menu, IPT_UI_HOME, 0)) + { + menu->selected = 0; + ui_menu_validate_selection(menu, 1); + } + + /* end goes to the last */ + if (exclusive_input_pressed(menu, IPT_UI_END, 0)) + { + menu->selected = menu->numitems - 1; + ui_menu_validate_selection(menu, -1); + } + + /* pause enables/disables pause */ + if (!ignorepause && exclusive_input_pressed(menu, IPT_UI_PAUSE, 0)) + { + if (menu->machine().paused()) + menu->machine().resume(); + else + menu->machine().pause(); + } + + /* handle a toggle cheats request */ + if (ui_input_pressed_repeat(menu->machine(), IPT_UI_TOGGLE_CHEAT, 0)) + menu->machine().cheat().set_enable(!menu->machine().cheat().enabled()); + + /* see if any other UI keys are pressed */ + if (menu->menu_event.iptkey == IPT_INVALID) + for (code = __ipt_ui_start; code <= __ipt_ui_end; code++) + { + if (code == IPT_UI_CONFIGURE || (code == IPT_UI_LEFT && ignoreleft) || (code == IPT_UI_RIGHT && ignoreright) || (code == IPT_UI_PAUSE && ignorepause)) + continue; + if (exclusive_input_pressed(menu, code, 0)) + break; + } +} + + +/*------------------------------------------------- + ui_menu_validate_selection - validate the + current selection and ensure it is on a + correct item +-------------------------------------------------*/ + +static void ui_menu_validate_selection(ui_menu *menu, int scandir) +{ + /* clamp to be in range */ + if (menu->selected < 0) + menu->selected = 0; + else if (menu->selected >= menu->numitems) + menu->selected = menu->numitems - 1; + + /* skip past unselectable items */ + while (!item_is_selectable(&menu->item[menu->selected])) + menu->selected = (menu->selected + menu->numitems + scandir) % menu->numitems; +} + + + +/*------------------------------------------------- + ui_menu_clear_free_list - clear out anything + accumulated in the free list +-------------------------------------------------*/ + +static void ui_menu_clear_free_list(running_machine &machine) +{ + while (menu_free != NULL) + { + ui_menu *menu = menu_free; + menu_free = menu->parent; + ui_menu_free(menu); + } +} + + + +/*************************************************************************** + MENU STACK MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + ui_menu_stack_reset - reset the menu stack +-------------------------------------------------*/ + +void ui_menu_stack_reset(running_machine &machine) +{ + while (menu_stack != NULL) + ui_menu_stack_pop(machine); +} + + +/*------------------------------------------------- + ui_menu_stack_push - push a new menu onto the + stack +-------------------------------------------------*/ + +void ui_menu_stack_push(ui_menu *menu) +{ + menu->parent = menu_stack; + menu_stack = menu; + ui_menu_reset(menu, UI_MENU_RESET_SELECT_FIRST); + ui_input_reset(menu->machine()); +} + + +/*------------------------------------------------- + ui_menu_stack_pop - pop a menu from the stack +-------------------------------------------------*/ + +void ui_menu_stack_pop(running_machine &machine) +{ + if (menu_stack != NULL) + { + ui_menu *menu = menu_stack; + menu_stack = menu->parent; + menu->parent = menu_free; + menu_free = menu; + ui_input_reset(machine); + } +} + + + +/*************************************************************************** + UI SYSTEM INTERACTION +***************************************************************************/ + +/*------------------------------------------------- + ui_menu_ui_handler - displays the current menu + and calls the menu handler +-------------------------------------------------*/ + +UINT32 ui_menu_ui_handler(running_machine &machine, render_container *container, UINT32 state) +{ + /* if we have no menus stacked up, start with the main menu */ + if (menu_stack == NULL) + ui_menu_stack_push(ui_menu_alloc(machine, container, menu_main, NULL)); + + /* update the menu state */ + if (menu_stack != NULL) + { + ui_menu *menu = menu_stack; + (*menu->handler)(machine, menu, menu->parameter, menu->state); + } + + /* clear up anything pending to be released */ + ui_menu_clear_free_list(machine); + + /* if the menus are to be hidden, return a cancel here */ + if ((ui_input_pressed(machine, IPT_UI_CONFIGURE) && !ui_menu_is_force_game_select()) || menu_stack == NULL) + return UI_HANDLER_CANCEL; + + return 0; +} + + +/*------------------------------------------------- + ui_slider_ui_handler - pushes the slider + menu on the stack and hands off to the + standard menu handler +-------------------------------------------------*/ + +UINT32 ui_slider_ui_handler(running_machine &machine, render_container *container, UINT32 state) +{ + UINT32 result; + + /* if this is the first call, push the sliders menu */ + if (state) + ui_menu_stack_push(ui_menu_alloc(machine, container, menu_sliders, (void *)1)); + + /* handle standard menus */ + result = ui_menu_ui_handler(machine, container, state); + + /* if we are cancelled, pop the sliders menu */ + if (result == UI_HANDLER_CANCEL) + ui_menu_stack_pop(machine); + + return (menu_stack != NULL && menu_stack->handler == menu_sliders && menu_stack->parameter != NULL) ? 0 : UI_HANDLER_CANCEL; +} + + +/*------------------------------------------------- + ui_menu_force_game_select - force the game + select menu to be visible and inescapable +-------------------------------------------------*/ + +void ui_menu_force_game_select(running_machine &machine, render_container *container) +{ + char *gamename = (char *)machine.options().system_name(); + + /* reset the menu stack */ + ui_menu_stack_reset(machine); + + /* add the quit entry followed by the game select entry */ + ui_menu_stack_push(ui_menu_alloc(machine, container, menu_quit_game, NULL)); + ui_menu_stack_push(ui_menu_alloc(machine, container, menu_select_game, gamename)); + + /* force the menus on */ + ui_show_menu(); + + /* make sure MAME is paused */ + machine.pause(); +} + + +/*------------------------------------------------- + ui_menu_is_force_game_select - return true + if we are currently in "force game select" + mode +-------------------------------------------------*/ + +int ui_menu_is_force_game_select(void) +{ + ui_menu *menu; + + for (menu = menu_stack; menu != NULL; menu = menu->parent) + if (menu->handler == menu_quit_game && menu->parent == NULL) + return TRUE; + + return FALSE; +} + + + +/*************************************************************************** + MENU HANDLERS +***************************************************************************/ + +/*------------------------------------------------- + menu_main - handle the main menu +-------------------------------------------------*/ + +static void menu_main(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_main_populate(machine, menu, state); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + if (menu_event != NULL && menu_event->iptkey == IPT_UI_SELECT) + ui_menu_stack_push(ui_menu_alloc(machine, menu->container, (ui_menu_handler_func)menu_event->itemref, NULL)); +} + + +/*------------------------------------------------- + ui_menu_keyboard_mode - menu that +-------------------------------------------------*/ + +static void ui_menu_keyboard_mode(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + int natural = ui_get_use_natural_keyboard(machine); + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + { + ui_menu_item_append(menu, "Keyboard Mode:", natural ? "Natural" : "Emulated", natural ? MENU_FLAG_LEFT_ARROW : MENU_FLAG_RIGHT_ARROW, NULL); + } + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + + if (menu_event != NULL) + { + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) { + ui_set_use_natural_keyboard(machine, natural ^ TRUE); + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + } + } +} + + +/*------------------------------------------------- + ui_slot_get_current_index - returns +-------------------------------------------------*/ +int ui_slot_get_current_index(running_machine &machine, device_slot_interface *slot) +{ + const char *current = machine.options().value(slot->device().tag()); + const slot_interface* intf = slot->get_slot_interfaces(); + int val = -1; + for (int i = 0; intf[i].name != NULL; i++) { + if (strcmp(current, intf[i].name) == 0) val = i; + } + return val; +} + +/*------------------------------------------------- + ui_slot_get_length - returns +-------------------------------------------------*/ +int ui_slot_get_length(running_machine &machine, device_slot_interface *slot) +{ + const slot_interface* intf = slot->get_slot_interfaces(); + int val = 0; + for (int i = 0; intf[i].name != NULL; i++) val++; + return val; +} + +/*------------------------------------------------- + ui_slot_get_next - returns +-------------------------------------------------*/ +const char *ui_slot_get_next(running_machine &machine, device_slot_interface *slot) +{ + int idx = ui_slot_get_current_index(machine, slot) + 1; + if (idx==ui_slot_get_length(machine,slot)) return ""; + return slot->get_slot_interfaces()[idx].name; +} + +/*------------------------------------------------- + ui_slot_get_prev - returns +-------------------------------------------------*/ +const char *ui_slot_get_prev(running_machine &machine, device_slot_interface *slot) +{ + int idx = ui_slot_get_current_index(machine, slot) - 1; + if (idx==-1) return ""; + if (idx==-2) idx = ui_slot_get_length(machine,slot) -1; + if (idx==-1) return ""; + return slot->get_slot_interfaces()[idx].name; +} + +/*------------------------------------------------- + ui_get_slot_device - returns +-------------------------------------------------*/ +const char *ui_get_slot_device(running_machine &machine, device_slot_interface *slot) +{ + return machine.options().value(slot->device().tag()); +} + + +/*------------------------------------------------- + ui_set_use_natural_keyboard - specifies + whether the natural keyboard is active +-------------------------------------------------*/ + +void ui_set_slot_device(running_machine &machine, device_slot_interface *slot, const char *val) +{ + astring error; + machine.options().set_value(slot->device().tag(), val, OPTION_PRIORITY_CMDLINE, error); + assert(!error); +} + +/*------------------------------------------------- + menu_slot_devices_populate - populates the main + slot device menu +-------------------------------------------------*/ + +static void menu_slot_devices_populate(running_machine &machine, ui_menu *menu, void *state) +{ + device_slot_interface *slot = NULL; + + /* cycle through all devices for this system */ + for (bool gotone = machine.devicelist().first(slot); gotone; gotone = slot->next(slot)) + { + /* record the menu item */ + const char *title = ui_get_slot_device(machine,slot); + ui_menu_item_append(menu, slot->device().tag(), strcmp(title,"")==0 ? "------" : title, MENU_FLAG_LEFT_ARROW | MENU_FLAG_RIGHT_ARROW, (void *)slot); + } + ui_menu_item_append(menu, MENU_SEPARATOR_ITEM, NULL, 0, NULL); + ui_menu_item_append(menu, "Reset", NULL, 0, NULL); +} + +/*------------------------------------------------- + ui_menu_slot_devices - menu that +-------------------------------------------------*/ + +static void ui_menu_slot_devices(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + { + menu_slot_devices_populate(machine, menu, state); + } + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + + if (menu_event != NULL && menu_event->itemref != NULL) + { + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) { + device_slot_interface *slot = (device_slot_interface *)menu_event->itemref; + const char *val = (menu_event->iptkey == IPT_UI_LEFT) ? ui_slot_get_prev(machine,slot) : ui_slot_get_next(machine,slot); + ui_set_slot_device(machine,slot,val); + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + } + } else if (menu_event != NULL && menu_event->iptkey == IPT_UI_SELECT) { + machine.schedule_hard_reset(); + } +} + + +/*------------------------------------------------- + menu_main_populate - populate the main menu +-------------------------------------------------*/ + +static void menu_main_populate(running_machine &machine, ui_menu *menu, void *state) +{ + input_field_config *field; + input_port_config *port; + int has_configs = FALSE; + int has_analog = FALSE; + int has_dips = FALSE; + + /* scan the input port array to see what options we need to enable */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + for (field = port->fieldlist().first(); field != NULL; field = field->next()) + { + if (field->type == IPT_DIPSWITCH) + has_dips = TRUE; + if (field->type == IPT_CONFIG) + has_configs = TRUE; + if (input_type_is_analog(field->type)) + has_analog = TRUE; + } + + /* add input menu items */ + ui_menu_item_append(menu, "Input (general)", NULL, 0, (void *)menu_input_groups); + ui_menu_item_append(menu, "Input (this " CAPSTARTGAMENOUN ")", NULL, 0, (void *)menu_input_specific); + + /* add optional input-related menus */ + if (has_dips) + ui_menu_item_append(menu, "Dip Switches", NULL, 0, (void *)menu_settings_dip_switches); + if (has_configs) + ui_menu_item_append(menu, "Driver Configuration", NULL, 0, (void *)menu_settings_driver_config); + if (has_analog) + ui_menu_item_append(menu, "Analog Controls", NULL, 0, (void *)menu_analog); + + /* add bookkeeping menu */ + ui_menu_item_append(menu, "Bookkeeping Info", NULL, 0, (void *)menu_bookkeeping); + + /* add game info menu */ + ui_menu_item_append(menu, CAPSTARTGAMENOUN " Information", NULL, 0, (void *)menu_game_info); + + device_image_interface *image = NULL; + if (machine.devicelist().first(image)) + { + /* add image info menu */ + ui_menu_item_append(menu, "Image Information", NULL, 0, (void*)ui_image_menu_image_info); + + /* add file manager menu */ + ui_menu_item_append(menu, "File Manager", NULL, 0, (void*)ui_image_menu_file_manager); + + /* add tape control menu */ + if (machine.devicelist().first(CASSETTE)) + ui_menu_item_append(menu, "Tape Control", NULL, 0, (void*)ui_mess_menu_tape_control); + + /* add bitbanger control menu */ + if (machine.devicelist().first(BITBANGER)) + ui_menu_item_append(menu, "Bitbanger Control", NULL, 0, (void*)ui_mess_menu_bitbanger_control); + } + + device_slot_interface *slot = NULL; + if (machine.devicelist().first(slot)) + { + /* add image info menu */ + ui_menu_item_append(menu, "Slot Devices", NULL, 0, (void*)ui_menu_slot_devices); + } + + /* add keyboard mode menu */ + if (input_machine_has_keyboard(machine) && inputx_can_post(machine)) + ui_menu_item_append(menu, "Keyboard Mode", NULL, 0, (void *)ui_menu_keyboard_mode); + + /* add sliders menu */ + ui_menu_item_append(menu, "Slider Controls", NULL, 0, (void *)menu_sliders); + + /* add video options menu */ + ui_menu_item_append(menu, "Video Options", NULL, 0, (machine.render().target_by_index(1) != NULL) ? (void *)menu_video_targets : (void *)menu_video_options); + + /* add crosshair options menu */ + if (crosshair_get_usage(machine)) + ui_menu_item_append(menu, "Crosshair Options", NULL, 0, (void *)menu_crosshair); + + /* add cheat menu */ + if (machine.options().cheat() && machine.cheat().first() != NULL) + ui_menu_item_append(menu, "Cheat", NULL, 0, (void *)menu_cheat); + + /* add memory card menu */ + if (machine.config().m_memcard_handler != NULL) + ui_menu_item_append(menu, "Memory Card", NULL, 0, (void *)menu_memory_card); + + /* add reset and exit menus */ + ui_menu_item_append(menu, "Select New " CAPSTARTGAMENOUN, NULL, 0, (void *)menu_select_game); +} + + +/*------------------------------------------------- + menu_input_groups - handle the input groups + menu +-------------------------------------------------*/ + +static void menu_input_groups(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_input_groups_populate(machine, menu, state); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + if (menu_event != NULL && menu_event->iptkey == IPT_UI_SELECT) + ui_menu_stack_push(ui_menu_alloc(machine, menu->container, menu_input_general, menu_event->itemref)); +} + + +/*------------------------------------------------- + menu_input_groups_populate - populate the + input groups menu +-------------------------------------------------*/ + +static void menu_input_groups_populate(running_machine &machine, ui_menu *menu, void *state) +{ + int player; + + /* build up the menu */ + ui_menu_item_append(menu, "User Interface", NULL, 0, (void *)(IPG_UI + 1)); + for (player = 0; player < MAX_PLAYERS; player++) + { + char buffer[40]; + sprintf(buffer, "Player %d Controls", player + 1); + ui_menu_item_append(menu, buffer, NULL, 0, (void *)(FPTR)(IPG_PLAYER1 + player + 1)); + } + ui_menu_item_append(menu, "Other Controls", NULL, 0, (void *)(FPTR)(IPG_OTHER + 1)); +} + + + +/*------------------------------------------------- + menu_input_general - handle the general + input menu +-------------------------------------------------*/ + +static void menu_input_general(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + menu_input_common(machine, menu, parameter, state); +} + + +/*------------------------------------------------- + menu_input_general_populate - populate the + general input menu +-------------------------------------------------*/ + +static void menu_input_general_populate(running_machine &machine, ui_menu *menu, input_menu_state *menustate, int group) +{ + input_item_data *itemlist = NULL; + int suborder[SEQ_TYPE_TOTAL]; + astring tempstring; + int sortorder = 1; + + /* create a mini lookup table for sort order based on sequence type */ + suborder[SEQ_TYPE_STANDARD] = 0; + suborder[SEQ_TYPE_DECREMENT] = 1; + suborder[SEQ_TYPE_INCREMENT] = 2; + + /* iterate over the input ports and add menu items */ + for (input_type_entry *entry = input_type_list(machine).first(); entry != NULL; entry = entry->next()) + + /* add if we match the group and we have a valid name */ + if (entry->group == group && entry->name != NULL && entry->name[0] != 0) + { + input_seq_type seqtype; + + /* loop over all sequence types */ + sortorder++; + for (seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++) + { + /* build an entry for the standard sequence */ + input_item_data *item = (input_item_data *)ui_menu_pool_alloc(menu, sizeof(*item)); + memset(item, 0, sizeof(*item)); + item->ref = entry; + item->seqtype = seqtype; + item->seq = input_type_seq(machine, entry->type, entry->player, seqtype); + item->defseq = &entry->defseq[seqtype]; + item->sortorder = sortorder * 4 + suborder[seqtype]; + item->type = input_type_is_analog(entry->type) ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; + item->name = entry->name; + item->next = itemlist; + itemlist = item; + + /* stop after one, unless we're analog */ + if (item->type == INPUT_TYPE_DIGITAL) + break; + } + } + + /* sort and populate the menu in a standard fashion */ + menu_input_populate_and_sort(machine, menu, itemlist, menustate); +} + + +/*------------------------------------------------- + menu_input_specific - handle the game-specific + input menu +-------------------------------------------------*/ + +static void menu_input_specific(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + menu_input_common(machine, menu, NULL, state); +} + + +/*------------------------------------------------- + menu_input_specific_populate - populate + the input menu with game-specific items +-------------------------------------------------*/ + +static void menu_input_specific_populate(running_machine &machine, ui_menu *menu, input_menu_state *menustate) +{ + input_item_data *itemlist = NULL; + input_field_config *field; + input_port_config *port; + int suborder[SEQ_TYPE_TOTAL]; + astring tempstring; + + /* create a mini lookup table for sort order based on sequence type */ + suborder[SEQ_TYPE_STANDARD] = 0; + suborder[SEQ_TYPE_DECREMENT] = 1; + suborder[SEQ_TYPE_INCREMENT] = 2; + + /* iterate over the input ports and add menu items */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + for (field = port->fieldlist().first(); field != NULL; field = field->next()) + { + const char *name = input_field_name(field); + + /* add if we match the group and we have a valid name */ + if (name != NULL && input_condition_true(machine, &field->condition, port->owner()) && + ((field->type == IPT_OTHER && field->name != NULL) || input_type_group(machine, field->type, field->player) != IPG_INVALID)) + { + input_seq_type seqtype; + UINT16 sortorder; + + /* determine the sorting order */ + if (field->type >= IPT_START1 && field->type <= __ipt_analog_end) + sortorder = (field->type << 2) | (field->player << 12); + else + sortorder = field->type | 0xf000; + + /* loop over all sequence types */ + for (seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++) + { + /* build an entry for the standard sequence */ + input_item_data *item = (input_item_data *)ui_menu_pool_alloc(menu, sizeof(*item)); + memset(item, 0, sizeof(*item)); + item->ref = field; + item->seqtype = seqtype; + item->seq = input_field_seq(field, seqtype); + item->defseq = &get_field_default_seq(field, seqtype); + item->sortorder = sortorder + suborder[seqtype]; + item->type = input_type_is_analog(field->type) ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; + item->name = name; + item->next = itemlist; + itemlist = item; + + /* stop after one, unless we're analog */ + if (item->type == INPUT_TYPE_DIGITAL) + break; + } + } + } + + /* sort and populate the menu in a standard fashion */ + menu_input_populate_and_sort(machine, menu, itemlist, menustate); +} + + +/*------------------------------------------------- + menu_input - display a menu for inputs +-------------------------------------------------*/ + +static void menu_input_common(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + input_item_data *seqchangeditem = NULL; + input_menu_state *menustate; + const ui_menu_event *menu_event; + int invalidate = FALSE; + + /* if no state, allocate now */ + if (state == NULL) + state = ui_menu_alloc_state(menu, sizeof(*menustate), NULL); + menustate = (input_menu_state *)state; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + { + if (parameter != NULL) + menu_input_general_populate(machine, menu, menustate, (FPTR)parameter - 1); + else + menu_input_specific_populate(machine, menu, menustate); + } + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, (menustate->pollingitem != NULL) ? UI_MENU_PROCESS_NOKEYS : 0); + + /* if we are polling, handle as a special case */ + if (menustate->pollingitem != NULL) + { + input_item_data *item = menustate->pollingitem; + input_seq newseq; + + /* if UI_CANCEL is pressed, abort */ + if (ui_input_pressed(machine, IPT_UI_CANCEL)) + { + menustate->pollingitem = NULL; + menustate->record_next = FALSE; + toggle_none_default(item->seq, menustate->starting_seq, *item->defseq); + seqchangeditem = item; + } + + /* poll again; if finished, update the sequence */ + if (machine.input().seq_poll()) + { + menustate->pollingitem = NULL; + menustate->record_next = TRUE; + item->seq = machine.input().seq_poll_final(); + seqchangeditem = item; + } + } + + /* otherwise, handle the events */ + else if (menu_event != NULL && menu_event->itemref != NULL) + { + input_item_data *item = (input_item_data *)menu_event->itemref; + switch (menu_event->iptkey) + { + /* an item was selected: begin polling */ + case IPT_UI_SELECT: + menustate->pollingitem = item; + menustate->last_sortorder = item->sortorder; + menustate->starting_seq = item->seq; + machine.input().seq_poll_start((item->type == INPUT_TYPE_ANALOG) ? ITEM_CLASS_ABSOLUTE : ITEM_CLASS_SWITCH, menustate->record_next ? &item->seq : NULL); + invalidate = TRUE; + break; + + /* if the clear key was pressed, reset the selected item */ + case IPT_UI_CLEAR: + toggle_none_default(item->seq, item->seq, *item->defseq); + menustate->record_next = FALSE; + seqchangeditem = item; + break; + } + + /* if the selection changed, reset the "record next" flag */ + if (item->sortorder != menustate->last_sortorder) + menustate->record_next = FALSE; + menustate->last_sortorder = item->sortorder; + } + + /* if the sequence changed, update it */ + if (seqchangeditem != NULL) + { + /* update a general input */ + if (parameter != NULL) + { + const input_type_entry *entry = (const input_type_entry *)seqchangeditem->ref; + input_type_set_seq(machine, entry->type, entry->player, seqchangeditem->seqtype, &seqchangeditem->seq); + } + + /* update a game-specific input */ + else + { + input_field_user_settings settings; + + input_field_get_user_settings((input_field_config *)seqchangeditem->ref, &settings); + settings.seq[seqchangeditem->seqtype] = seqchangeditem->seq; + input_field_set_user_settings((input_field_config *)seqchangeditem->ref, &settings); + } + + /* invalidate the menu to force an update */ + invalidate = TRUE; + } + + /* if the menu is invalidated, clear it now */ + if (invalidate) + { + menustate->pollingref = NULL; + if (menustate->pollingitem != NULL) + menustate->pollingref = menustate->pollingitem->ref; + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_POSITION); + } +} + + +/*------------------------------------------------- + menu_input_compare_items - compare two + items for quicksort +-------------------------------------------------*/ + +static int menu_input_compare_items(const void *i1, const void *i2) +{ + const input_item_data * const *data1 = (const input_item_data * const *)i1; + const input_item_data * const *data2 = (const input_item_data * const *)i2; + if ((*data1)->sortorder < (*data2)->sortorder) + return -1; + if ((*data1)->sortorder > (*data2)->sortorder) + return 1; + return 0; +} + + +/*------------------------------------------------- + menu_input_populate_and_sort - take a list + of input_item_data objects and build up the + menu from them +-------------------------------------------------*/ + +static void menu_input_populate_and_sort(running_machine &machine, ui_menu *menu, input_item_data *itemlist, input_menu_state *menustate) +{ + const char *nameformat[INPUT_TYPE_TOTAL] = { 0 }; + input_item_data **itemarray, *item; + int numitems = 0, curitem; + astring subtext; + astring text; + + /* create a mini lookup table for name format based on type */ + nameformat[INPUT_TYPE_DIGITAL] = "%s"; + nameformat[INPUT_TYPE_ANALOG] = "%s Analog"; + nameformat[INPUT_TYPE_ANALOG_INC] = "%s Analog Inc"; + nameformat[INPUT_TYPE_ANALOG_DEC] = "%s Analog Dec"; + + /* first count the number of items */ + for (item = itemlist; item != NULL; item = item->next) + numitems++; + + /* now allocate an array of items and fill it up */ + itemarray = (input_item_data **)ui_menu_pool_alloc(menu, sizeof(*itemarray) * numitems); + for (item = itemlist, curitem = 0; item != NULL; item = item->next) + itemarray[curitem++] = item; + + /* sort it */ + qsort(itemarray, numitems, sizeof(*itemarray), menu_input_compare_items); + + /* build the menu */ + for (curitem = 0; curitem < numitems; curitem++) + { + UINT32 flags = 0; + + /* generate the name of the item itself, based off the base name and the type */ + item = itemarray[curitem]; + assert(nameformat[item->type] != NULL); + text.printf(nameformat[item->type], item->name); + + /* if we're polling this item, use some spaces with left/right arrows */ + if (menustate->pollingref == item->ref) + { + subtext.cpy(" "); + flags |= MENU_FLAG_LEFT_ARROW | MENU_FLAG_RIGHT_ARROW; + } + + /* otherwise, generate the sequence name and invert it if different from the default */ + else + { + machine.input().seq_name(subtext, item->seq); + flags |= (item->seq != *item->defseq) ? MENU_FLAG_INVERT : 0; + } + + /* add the item */ + ui_menu_item_append(menu, text, subtext, flags, item); + } +} + + +/*------------------------------------------------- + menu_settings_dip_switches - handle the DIP + switches menu +-------------------------------------------------*/ + +static void menu_settings_dip_switches(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + menu_settings_common(machine, menu, state, IPT_DIPSWITCH); +} + + +/*------------------------------------------------- + menu_settings_driver_config - handle the + driver config menu +-------------------------------------------------*/ + +static void menu_settings_driver_config(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + menu_settings_common(machine, menu, state, IPT_CONFIG); +} + + +/*------------------------------------------------- + menu_settings_common - handle one of the + switches menus +-------------------------------------------------*/ + +static void menu_settings_common(running_machine &machine, ui_menu *menu, void *state, UINT32 type) +{ + settings_menu_state *menustate; + const ui_menu_event *menu_event; + + /* if no state, allocate now */ + if (state == NULL) + state = ui_menu_alloc_state(menu, sizeof(*menustate), NULL); + menustate = (settings_menu_state *)state; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_settings_populate(machine, menu, menustate, type); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + + /* handle events */ + if (menu_event != NULL && menu_event->itemref != NULL) + { + input_field_config *field = (input_field_config *)menu_event->itemref; + input_field_user_settings settings; + int changed = FALSE; + + switch (menu_event->iptkey) + { + /* if selected, reset to default value */ + case IPT_UI_SELECT: + input_field_get_user_settings(field, &settings); + settings.value = field->defvalue; + input_field_set_user_settings(field, &settings); + changed = TRUE; + break; + + /* left goes to previous setting */ + case IPT_UI_LEFT: + input_field_select_previous_setting(field); + changed = TRUE; + break; + + /* right goes to next setting */ + case IPT_UI_RIGHT: + input_field_select_next_setting(field); + changed = TRUE; + break; + } + + /* if anything changed, rebuild the menu, trying to stay on the same field */ + if (changed) + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + } +} + + +/*------------------------------------------------- + menu_settings_populate - populate one of the + switches menus +-------------------------------------------------*/ + +static void menu_settings_populate(running_machine &machine, ui_menu *menu, settings_menu_state *menustate, UINT32 type) +{ + input_field_config *field; + input_port_config *port; + dip_descriptor **diplist_tailptr; + int dipcount = 0; + + /* reset the dip switch tracking */ + menustate->diplist = NULL; + diplist_tailptr = &menustate->diplist; + + /* loop over input ports and set up the current values */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + for (field = port->fieldlist().first(); field != NULL; field = field->next()) + if (field->type == type && input_condition_true(machine, &field->condition, port->owner())) + { + UINT32 flags = 0; + + /* set the left/right flags appropriately */ + if (input_field_has_previous_setting(field)) + flags |= MENU_FLAG_LEFT_ARROW; + if (input_field_has_next_setting(field)) + flags |= MENU_FLAG_RIGHT_ARROW; + + /* add the menu item */ + ui_menu_item_append(menu, input_field_name(field), input_field_setting_name(field), flags, (void *)field); + + /* for DIP switches, build up the model */ + if (type == IPT_DIPSWITCH && field->diploclist().count() != 0) + { + const input_field_diplocation *diploc; + input_field_user_settings settings; + UINT32 accummask = field->mask; + + /* get current settings */ + input_field_get_user_settings(field, &settings); + + /* iterate over each bit in the field */ + for (diploc = field->diploclist().first(); diploc != NULL; diploc = diploc->next()) + { + UINT32 mask = accummask & ~(accummask - 1); + dip_descriptor *dip; + + /* find the matching switch name */ + for (dip = menustate->diplist; dip != NULL; dip = dip->next) + if (strcmp(dip->name, diploc->swname) == 0) + break; + + /* allocate new if none */ + if (dip == NULL) + { + dip = (dip_descriptor *)ui_menu_pool_alloc(menu, sizeof(*dip)); + dip->next = NULL; + dip->name = diploc->swname; + dip->mask = dip->state = 0; + *diplist_tailptr = dip; + diplist_tailptr = &dip->next; + dipcount++; + } + + /* apply the bits */ + dip->mask |= 1 << (diploc->swnum - 1); + if (((settings.value & mask) != 0 && !diploc->invert) || ((settings.value & mask) == 0 && diploc->invert)) + dip->state |= 1 << (diploc->swnum - 1); + + /* clear the relevant bit in the accumulated mask */ + accummask &= ~mask; + } + } + } + + /* configure the extra menu */ + if (type == IPT_DIPSWITCH && menustate->diplist != NULL) + ui_menu_set_custom_render(menu, menu_settings_custom_render, 0.0f, dipcount * (DIP_SWITCH_HEIGHT + DIP_SWITCH_SPACING) + DIP_SWITCH_SPACING); +} + + +/*------------------------------------------------- + menu_settings_custom_render - perform our special + rendering +-------------------------------------------------*/ + +static void menu_settings_custom_render(running_machine &machine, ui_menu *menu, void *state, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ + input_field_config *field = (input_field_config *)selectedref; + settings_menu_state *menustate = (settings_menu_state *)state; + dip_descriptor *dip; + + /* add borders */ + y1 = y2 + UI_BOX_TB_BORDER; + y2 = y1 + bottom; + + /* draw extra menu area */ + ui_draw_outlined_box(menu->container, x1, y1, x2, y2, UI_BACKGROUND_COLOR); + y1 += (float)DIP_SWITCH_SPACING; + + /* iterate over DIP switches */ + for (dip = menustate->diplist; dip != NULL; dip = dip->next) + { + const input_field_diplocation *diploc; + UINT32 selectedmask = 0; + + /* determine the mask of selected bits */ + if (field != NULL) + for (diploc = field->diploclist().first(); diploc != NULL; diploc = diploc->next()) + if (strcmp(dip->name, diploc->swname) == 0) + selectedmask |= 1 << (diploc->swnum - 1); + + /* draw one switch */ + menu_settings_custom_render_one(menu->container, x1, y1, x2, y1 + DIP_SWITCH_HEIGHT, dip, selectedmask); + y1 += (float)(DIP_SWITCH_SPACING + DIP_SWITCH_HEIGHT); + } +} + + +/*------------------------------------------------- + menu_settings_custom_render_one - draw a single + DIP switch +-------------------------------------------------*/ + +static void menu_settings_custom_render_one(render_container *container, float x1, float y1, float x2, float y2, const dip_descriptor *dip, UINT32 selectedmask) +{ + float switch_field_width = SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * container->manager().ui_aspect(); + float switch_width = SINGLE_TOGGLE_SWITCH_WIDTH * container->manager().ui_aspect(); + int numtoggles, toggle; + float switch_toggle_gap; + float y1_off, y1_on; + + /* determine the number of toggles in the DIP */ + numtoggles = 32 - count_leading_zeros(dip->mask); + + /* center based on the number of switches */ + x1 += (x2 - x1 - numtoggles * switch_field_width) / 2; + + /* draw the dip switch name */ + ui_draw_text_full( container, + dip->name, + 0, + y1 + (DIP_SWITCH_HEIGHT - UI_TARGET_FONT_HEIGHT) / 2, + x1 - ui_get_string_width(container->manager().machine(), " "), + JUSTIFY_RIGHT, + WRAP_NEVER, + DRAW_NORMAL, + UI_TEXT_COLOR, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA), + NULL , + NULL); + + /* compute top and bottom for on and off positions */ + switch_toggle_gap = ((DIP_SWITCH_HEIGHT/2) - SINGLE_TOGGLE_SWITCH_HEIGHT)/2; + y1_off = y1 + UI_LINE_WIDTH + switch_toggle_gap; + y1_on = y1 + DIP_SWITCH_HEIGHT/2 + switch_toggle_gap; + + /* iterate over toggles */ + for (toggle = 0; toggle < numtoggles; toggle++) + { + float innerx1; + + /* first outline the switch */ + ui_draw_outlined_box(container, x1, y1, x1 + switch_field_width, y2, UI_BACKGROUND_COLOR); + + /* compute x1/x2 for the inner filled in switch */ + innerx1 = x1 + (switch_field_width - switch_width) / 2; + + /* see if the switch is actually used */ + if (dip->mask & (1 << toggle)) + { + float innery1 = (dip->state & (1 << toggle)) ? y1_on : y1_off; + container->add_rect(innerx1, innery1, innerx1 + switch_width, innery1 + SINGLE_TOGGLE_SWITCH_HEIGHT, + (selectedmask & (1 << toggle)) ? UI_DIPSW_COLOR : UI_TEXT_COLOR, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + else + { + container->add_rect(innerx1, y1_off, innerx1 + switch_width, y1_on + SINGLE_TOGGLE_SWITCH_HEIGHT, + UI_UNAVAILABLE_COLOR, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + + /* advance to the next switch */ + x1 += switch_field_width; + } +} + + +/*------------------------------------------------- + menu_analog - handle the analog settings menu +-------------------------------------------------*/ + +static void menu_analog(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_analog_populate(machine, menu); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, UI_MENU_PROCESS_LR_REPEAT); + + /* handle events */ + if (menu_event != NULL && menu_event->itemref != NULL) + { + analog_item_data *data = (analog_item_data *)menu_event->itemref; + int newval = data->cur; + + switch (menu_event->iptkey) + { + /* if selected, reset to default value */ + case IPT_UI_SELECT: + newval = data->defvalue; + break; + + /* left decrements */ + case IPT_UI_LEFT: + newval -= machine.input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; + break; + + /* right increments */ + case IPT_UI_RIGHT: + newval += machine.input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; + break; + } + + /* clamp to range */ + if (newval < data->min) + newval = data->min; + if (newval > data->max) + newval = data->max; + + /* if things changed, update */ + if (newval != data->cur) + { + input_field_user_settings settings; + + /* get the settings and set the new value */ + input_field_get_user_settings(data->field, &settings); + switch (data->type) + { + case ANALOG_ITEM_KEYSPEED: settings.delta = newval; break; + case ANALOG_ITEM_CENTERSPEED: settings.centerdelta = newval; break; + case ANALOG_ITEM_REVERSE: settings.reverse = newval; break; + case ANALOG_ITEM_SENSITIVITY: settings.sensitivity = newval; break; + } + input_field_set_user_settings(data->field, &settings); + + /* rebuild the menu */ + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_POSITION); + } + } +} + + +/*------------------------------------------------- + menu_analog_populate - populate the analog + settings menu +-------------------------------------------------*/ + +static void menu_analog_populate(running_machine &machine, ui_menu *menu) +{ + input_field_config *field; + input_port_config *port; + astring subtext; + astring text; + + /* loop over input ports and add the items */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + for (field = port->fieldlist().first(); field != NULL; field = field->next()) + if (input_type_is_analog(field->type) && input_condition_true(machine, &field->condition, port->owner())) + { + input_field_user_settings settings; + int use_autocenter = FALSE; + int type; + + /* based on the type, determine if we enable autocenter */ + switch (field->type) + { + case IPT_POSITIONAL: + case IPT_POSITIONAL_V: + if (field->flags & ANALOG_FLAG_WRAPS) + break; + + case IPT_PEDAL: + case IPT_PEDAL2: + case IPT_PEDAL3: + case IPT_PADDLE: + case IPT_PADDLE_V: + case IPT_AD_STICK_X: + case IPT_AD_STICK_Y: + case IPT_AD_STICK_Z: + use_autocenter = TRUE; + break; + } + + /* get the user settings */ + input_field_get_user_settings(field, &settings); + + /* iterate over types */ + for (type = 0; type < ANALOG_ITEM_COUNT; type++) + if (type != ANALOG_ITEM_CENTERSPEED || use_autocenter) + { + analog_item_data *data; + UINT32 flags = 0; + + /* allocate a data item for tracking what this menu item refers to */ + data = (analog_item_data *)ui_menu_pool_alloc(menu, sizeof(*data)); + data->field = field; + data->type = type; + + /* determine the properties of this item */ + switch (type) + { + default: + case ANALOG_ITEM_KEYSPEED: + text.printf("%s Digital Speed", input_field_name(field)); + subtext.printf("%d", settings.delta); + data->min = 0; + data->max = 255; + data->cur = settings.delta; + data->defvalue = field->delta; + break; + + case ANALOG_ITEM_CENTERSPEED: + text.printf("%s Autocenter Speed", input_field_name(field)); + subtext.printf("%d", settings.centerdelta); + data->min = 0; + data->max = 255; + data->cur = settings.centerdelta; + data->defvalue = field->centerdelta; + break; + + case ANALOG_ITEM_REVERSE: + text.printf("%s Reverse", input_field_name(field)); + subtext.cpy(settings.reverse ? "On" : "Off"); + data->min = 0; + data->max = 1; + data->cur = settings.reverse; + data->defvalue = ((field->flags & ANALOG_FLAG_REVERSE) != 0); + break; + + case ANALOG_ITEM_SENSITIVITY: + text.printf("%s Sensitivity", input_field_name(field)); + subtext.printf("%d", settings.sensitivity); + data->min = 1; + data->max = 255; + data->cur = settings.sensitivity; + data->defvalue = field->sensitivity; + break; + } + + /* put on arrows */ + if (data->cur > data->min) + flags |= MENU_FLAG_LEFT_ARROW; + if (data->cur < data->max) + flags |= MENU_FLAG_RIGHT_ARROW; + + /* append a menu item */ + ui_menu_item_append(menu, text, subtext, flags, data); + } + } +} + + +/*------------------------------------------------- + menu_bookkeeping - handle the bookkeeping + information menu +-------------------------------------------------*/ + +static void menu_bookkeeping(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + attotime *prevtime; + attotime curtime; + + /* if no state, allocate some */ + if (state == NULL) + state = ui_menu_alloc_state(menu, sizeof(*prevtime), NULL); + prevtime = (attotime *)state; + + /* if the time has rolled over another second, regenerate */ + curtime = machine.time(); + if (prevtime->seconds != curtime.seconds) + { + ui_menu_reset(menu, UI_MENU_RESET_SELECT_FIRST); + *prevtime = curtime; + menu_bookkeeping_populate(machine, menu, prevtime); + } + + /* process the menu */ + ui_menu_process(machine, menu, 0); +} + + +/*------------------------------------------------- + menu_bookkeeping - handle the bookkeeping + information menu +-------------------------------------------------*/ + +static void menu_bookkeeping_populate(running_machine &machine, ui_menu *menu, attotime *curtime) +{ + int tickets = get_dispensed_tickets(machine); + astring tempstring; + int ctrnum; + + /* show total time first */ + if (curtime->seconds >= 60 * 60) + tempstring.catprintf("Uptime: %d:%02d:%02d\n\n", curtime->seconds / (60*60), (curtime->seconds / 60) % 60, curtime->seconds % 60); + else + tempstring.catprintf("Uptime: %d:%02d\n\n", (curtime->seconds / 60) % 60, curtime->seconds % 60); + + /* show tickets at the top */ + if (tickets > 0) + tempstring.catprintf("Tickets dispensed: %d\n\n", tickets); + + /* loop over coin counters */ + for (ctrnum = 0; ctrnum < COIN_COUNTERS; ctrnum++) + { + int count = coin_counter_get_count(machine, ctrnum); + + /* display the coin counter number */ + tempstring.catprintf("Coin %c: ", ctrnum + 'A'); + + /* display how many coins */ + if (count == 0) + tempstring.cat("NA"); + else + tempstring.catprintf("%d", count); + + /* display whether or not we are locked out */ + if (coin_lockout_get_state(machine, ctrnum)) + tempstring.cat(" (locked)"); + tempstring.cat("\n"); + } + + /* append the single item */ + ui_menu_item_append(menu, tempstring, NULL, MENU_FLAG_MULTILINE, NULL); +} + + +/*------------------------------------------------- + menu_game_info - handle the game information + menu +-------------------------------------------------*/ + +static void menu_game_info(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + { + astring tempstring; + ui_menu_item_append(menu, game_info_astring(machine, tempstring), NULL, MENU_FLAG_MULTILINE, NULL); + } + + /* process the menu */ + ui_menu_process(machine, menu, 0); +} + + +/*------------------------------------------------- + menu_cheat - handle the cheat menu +-------------------------------------------------*/ + +static void menu_cheat(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_cheat_populate(machine, menu); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, UI_MENU_PROCESS_LR_REPEAT); + + /* handle events */ + if (menu_event != NULL && menu_event->itemref != NULL) + { + bool changed = false; + + /* clear cheat comment on any movement or keypress */ + popmessage(NULL); + + /* handle reset all + reset all cheats for reload all option */ + if ((FPTR)menu_event->itemref < 3 && menu_event->iptkey == IPT_UI_SELECT) + { + for (cheat_entry *curcheat = machine.cheat().first(); curcheat != NULL; curcheat = curcheat->next()) + if (curcheat->select_default_state()) + changed = true; + } + + + /* handle individual cheats */ + else if ((FPTR)menu_event->itemref > 2) + { + cheat_entry *curcheat = reinterpret_cast<cheat_entry *>(menu_event->itemref); + const char *string; + switch (menu_event->iptkey) + { + /* if selected, activate a oneshot */ + case IPT_UI_SELECT: + changed = curcheat->activate(); + break; + + /* if cleared, reset to default value */ + case IPT_UI_CLEAR: + changed = curcheat->select_default_state(); + break; + + /* left decrements */ + case IPT_UI_LEFT: + changed = curcheat->select_previous_state(); + break; + + /* right increments */ + case IPT_UI_RIGHT: + changed = curcheat->select_next_state(); + break; + + /* bring up display comment if one exists */ + case IPT_UI_DISPLAY_COMMENT: + case IPT_UI_UP: + case IPT_UI_DOWN: + string = curcheat->comment(); + if (string != NULL && string[0] != 0) + popmessage("Cheat Comment:\n%s", string); + break; + } + } + + /* handle reload all */ + if ((FPTR)menu_event->itemref == 2 && menu_event->iptkey == IPT_UI_SELECT) + { + /* re-init cheat engine and thus reload cheats/cheats have already been turned off by here */ + machine.cheat().reload(); + + /* display the reloaded cheats */ + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + popmessage("All cheats reloaded"); + } + + /* if things changed, update */ + if (changed) + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + } +} + + +/*------------------------------------------------- + menu_cheat_populate - populate the cheat menu +-------------------------------------------------*/ + +static void menu_cheat_populate(running_machine &machine, ui_menu *menu) +{ + /* iterate over cheats */ + astring text; + astring subtext; + for (cheat_entry *curcheat = machine.cheat().first(); curcheat != NULL; curcheat = curcheat->next()) + { + UINT32 flags; + curcheat->menu_text(text, subtext, flags); + ui_menu_item_append(menu, text, subtext, flags, curcheat); + } + + /* add a separator */ + ui_menu_item_append(menu, MENU_SEPARATOR_ITEM, NULL, 0, NULL); + + /* add a reset all option */ + ui_menu_item_append(menu, "Reset All", NULL, 0, (void *)1); + + /* add a reload all cheats option */ + ui_menu_item_append(menu, "Reload All", NULL, 0, (void *)2); +} + + +/*------------------------------------------------- + menu_memory_card - handle the memory card + menu +-------------------------------------------------*/ + +static void menu_memory_card(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + int *cardnum; + + /* if no state, allocate some */ + if (state == NULL) + state = ui_menu_alloc_state(menu, sizeof(*cardnum), NULL); + cardnum = (int *)state; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_memory_card_populate(machine, menu, *cardnum); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, UI_MENU_PROCESS_LR_REPEAT); + + /* if something was selected, act on it */ + if (menu_event != NULL && menu_event->itemref != NULL) + { + FPTR item = (FPTR)menu_event->itemref; + + /* select executes actions on some of the items */ + if (menu_event->iptkey == IPT_UI_SELECT) + { + switch (item) + { + /* handle card loading; if we succeed, clear the menus */ + case MEMCARD_ITEM_LOAD: + if (memcard_insert(menu->machine(), *cardnum) == 0) + { + popmessage("Memory card loaded"); + ui_menu_stack_reset(menu->machine()); + } + else + popmessage("Error loading memory card"); + break; + + /* handle card ejecting */ + case MEMCARD_ITEM_EJECT: + memcard_eject(menu->machine()); + popmessage("Memory card ejected"); + break; + + /* handle card creating */ + case MEMCARD_ITEM_CREATE: + if (memcard_create(menu->machine(), *cardnum, FALSE) == 0) + popmessage("Memory card created"); + else + popmessage("Error creating memory card\n(Card may already exist)"); + break; + } + } + + /* the select item has extra keys */ + else if (item == MEMCARD_ITEM_SELECT) + { + switch (menu_event->iptkey) + { + /* left decrements the card number */ + case IPT_UI_LEFT: + *cardnum -= 1; + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + break; + + /* right decrements the card number */ + case IPT_UI_RIGHT: + *cardnum += 1; + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + break; + } + } + } +} + + +/*------------------------------------------------- + menu_memory_card_populate - populate the + memory card menu +-------------------------------------------------*/ + +static void menu_memory_card_populate(running_machine &machine, ui_menu *menu, int cardnum) +{ + char tempstring[20]; + UINT32 flags = 0; + + /* add the card select menu */ + sprintf(tempstring, "%d", cardnum); + if (cardnum > 0) + flags |= MENU_FLAG_LEFT_ARROW; + if (cardnum < 1000) + flags |= MENU_FLAG_RIGHT_ARROW; + ui_menu_item_append(menu, "Card Number:", tempstring, flags, (void *)MEMCARD_ITEM_SELECT); + + /* add the remaining items */ + ui_menu_item_append(menu, "Load Selected Card", NULL, 0, (void *)MEMCARD_ITEM_LOAD); + if (memcard_present(machine) != -1) + ui_menu_item_append(menu, "Eject Current Card", NULL, 0, (void *)MEMCARD_ITEM_EJECT); + ui_menu_item_append(menu, "Create New Card", NULL, 0, (void *)MEMCARD_ITEM_CREATE); +} + + +/*------------------------------------------------- + menu_sliders - handle the sliders menu +-------------------------------------------------*/ + +static void menu_sliders(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + int menuless_mode = (parameter != NULL); + const ui_menu_event *menu_event; + UINT8 *hidden = (UINT8 *)state; + + /* if no state, allocate some */ + if (hidden == NULL) + hidden = (UINT8 *)ui_menu_alloc_state(menu, sizeof(*hidden), NULL); + if (menuless_mode) + *hidden = TRUE; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_sliders_populate(machine, menu, menuless_mode); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, UI_MENU_PROCESS_LR_REPEAT | (*hidden ? UI_MENU_PROCESS_CUSTOM_ONLY : 0)); + if (menu_event != NULL) + { + /* handle keys if there is a valid item selected */ + if (menu_event->itemref != NULL) + { + const slider_state *slider = (const slider_state *)menu_event->itemref; + INT32 curvalue = (*slider->update)(machine, slider->arg, NULL, SLIDER_NOCHANGE); + INT32 increment = 0; + + switch (menu_event->iptkey) + { + /* toggle visibility */ + case IPT_UI_ON_SCREEN_DISPLAY: + if (menuless_mode) + ui_menu_stack_pop(machine); + else + *hidden = !*hidden; + break; + + /* decrease value */ + case IPT_UI_LEFT: + if (machine.input().code_pressed(KEYCODE_LALT) || machine.input().code_pressed(KEYCODE_RALT)) + increment = -1; + else if (machine.input().code_pressed(KEYCODE_LSHIFT) || machine.input().code_pressed(KEYCODE_RSHIFT)) + increment = (slider->incval > 10) ? -(slider->incval / 10) : -1; + else if (machine.input().code_pressed(KEYCODE_LCONTROL) || machine.input().code_pressed(KEYCODE_RCONTROL)) + increment = -slider->incval * 10; + else + increment = -slider->incval; + break; + + /* increase value */ + case IPT_UI_RIGHT: + if (machine.input().code_pressed(KEYCODE_LALT) || machine.input().code_pressed(KEYCODE_RALT)) + increment = 1; + else if (machine.input().code_pressed(KEYCODE_LSHIFT) || machine.input().code_pressed(KEYCODE_RSHIFT)) + increment = (slider->incval > 10) ? (slider->incval / 10) : 1; + else if (machine.input().code_pressed(KEYCODE_LCONTROL) || machine.input().code_pressed(KEYCODE_RCONTROL)) + increment = slider->incval * 10; + else + increment = slider->incval; + break; + + /* restore default */ + case IPT_UI_SELECT: + increment = slider->defval - curvalue; + break; + } + + /* handle any changes */ + if (increment != 0) + { + INT32 newvalue = curvalue + increment; + + /* clamp within bounds */ + if (newvalue < slider->minval) + newvalue = slider->minval; + if (newvalue > slider->maxval) + newvalue = slider->maxval; + + /* update the slider and recompute the menu */ + (*slider->update)(machine, slider->arg, NULL, newvalue); + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + } + } + + /* if we are selecting an invalid item and we are hidden, skip to the next one */ + else if (*hidden) + { + /* if we got here via up or page up, select the previous item */ + if (menu_event->iptkey == IPT_UI_UP || menu_event->iptkey == IPT_UI_PAGE_UP) + { + menu->selected = (menu->selected + menu->numitems - 1) % menu->numitems; + ui_menu_validate_selection(menu, -1); + } + + /* otherwise select the next item */ + else if (menu_event->iptkey == IPT_UI_DOWN || menu_event->iptkey == IPT_UI_PAGE_DOWN) + { + menu->selected = (menu->selected + 1) % menu->numitems; + ui_menu_validate_selection(menu, 1); + } + } + } +} + + +/*------------------------------------------------- + menu_sliders_populate - populate the sliders + menu +-------------------------------------------------*/ + +static void menu_sliders_populate(running_machine &machine, ui_menu *menu, int menuless_mode) +{ + const slider_state *curslider; + astring tempstring; + + /* add all sliders */ + for (curslider = ui_get_slider_list(); curslider != NULL; curslider = curslider->next) + { + INT32 curval = (*curslider->update)(machine, curslider->arg, &tempstring, SLIDER_NOCHANGE); + UINT32 flags = 0; + if (curval > curslider->minval) + flags |= MENU_FLAG_LEFT_ARROW; + if (curval < curslider->maxval) + flags |= MENU_FLAG_RIGHT_ARROW; + ui_menu_item_append(menu, curslider->description, tempstring, flags, (void *)curslider); + + if (menuless_mode) + break; + } + + /* add all sliders */ + for (curslider = (slider_state*)machine.osd().get_slider_list(); curslider != NULL; curslider = curslider->next) + { + INT32 curval = (*curslider->update)(machine, curslider->arg, &tempstring, SLIDER_NOCHANGE); + UINT32 flags = 0; + if (curval > curslider->minval) + flags |= MENU_FLAG_LEFT_ARROW; + if (curval < curslider->maxval) + flags |= MENU_FLAG_RIGHT_ARROW; + ui_menu_item_append(menu, curslider->description, tempstring, flags, (void *)curslider); + } + + ui_menu_set_custom_render(menu, menu_sliders_custom_render, 0.0f, 2.0f * ui_get_line_height(machine) + 2.0f * UI_BOX_TB_BORDER); +} + + +/*------------------------------------------------- + menu_sliders_custom_render - perform our special + rendering +-------------------------------------------------*/ + +static void menu_sliders_custom_render(running_machine &machine, ui_menu *menu, void *state, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ + const slider_state *curslider = (const slider_state *)selectedref; + if (curslider != NULL) + { + float bar_left, bar_area_top, bar_width, bar_area_height, bar_top, bar_bottom, default_x, current_x; + float line_height = ui_get_line_height(machine); + float percentage, default_percentage; + astring tempstring; + float text_height; + INT32 curval; + + /* determine the current value and text */ + curval = (*curslider->update)(machine, curslider->arg, &tempstring, SLIDER_NOCHANGE); + + /* compute the current and default percentages */ + percentage = (float)(curval - curslider->minval) / (float)(curslider->maxval - curslider->minval); + default_percentage = (float)(curslider->defval - curslider->minval) / (float)(curslider->maxval - curslider->minval); + + /* assemble the the text */ + tempstring.ins(0, " ").ins(0, curslider->description); + + /* move us to the bottom of the screen, and expand to full width */ + y2 = 1.0f - UI_BOX_TB_BORDER; + y1 = y2 - bottom; + x1 = UI_BOX_LR_BORDER; + x2 = 1.0f - UI_BOX_LR_BORDER; + + /* draw extra menu area */ + ui_draw_outlined_box(menu->container, x1, y1, x2, y2, UI_BACKGROUND_COLOR); + y1 += UI_BOX_TB_BORDER; + + /* determine the text height */ + ui_draw_text_full(menu->container, tempstring, 0, 0, x2 - x1 - 2.0f * UI_BOX_LR_BORDER, + JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NONE, ARGB_WHITE, ARGB_BLACK, NULL, &text_height); + + /* draw the thermometer */ + bar_left = x1 + UI_BOX_LR_BORDER; + bar_area_top = y1; + bar_width = x2 - x1 - 2.0f * UI_BOX_LR_BORDER; + bar_area_height = line_height; + + /* compute positions */ + bar_top = bar_area_top + 0.125f * bar_area_height; + bar_bottom = bar_area_top + 0.875f * bar_area_height; + default_x = bar_left + bar_width * default_percentage; + current_x = bar_left + bar_width * percentage; + + /* fill in the percentage */ + menu->container->add_rect(bar_left, bar_top, current_x, bar_bottom, UI_SLIDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + /* draw the top and bottom lines */ + menu->container->add_line(bar_left, bar_top, bar_left + bar_width, bar_top, UI_LINE_WIDTH, UI_BORDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + menu->container->add_line(bar_left, bar_bottom, bar_left + bar_width, bar_bottom, UI_LINE_WIDTH, UI_BORDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + /* draw default marker */ + menu->container->add_line(default_x, bar_area_top, default_x, bar_top, UI_LINE_WIDTH, UI_BORDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + menu->container->add_line(default_x, bar_bottom, default_x, bar_area_top + bar_area_height, UI_LINE_WIDTH, UI_BORDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + /* draw the actual text */ + ui_draw_text_full(menu->container, tempstring, x1 + UI_BOX_LR_BORDER, y1 + line_height, x2 - x1 - 2.0f * UI_BOX_LR_BORDER, + JUSTIFY_CENTER, WRAP_WORD, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, NULL, &text_height); + } +} + + +/*------------------------------------------------- + menu_video_targets - handle the video targets + menu +-------------------------------------------------*/ + +static void menu_video_targets(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_video_targets_populate(machine, menu); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + if (menu_event != NULL && menu_event->iptkey == IPT_UI_SELECT) + ui_menu_stack_push(ui_menu_alloc(machine, menu->container, menu_video_options, menu_event->itemref)); +} + + +/*------------------------------------------------- + menu_video_targets_populate - populate the + video targets menu +-------------------------------------------------*/ + +static void menu_video_targets_populate(running_machine &machine, ui_menu *menu) +{ + int targetnum; + + /* find the targets */ + for (targetnum = 0; ; targetnum++) + { + render_target *target = machine.render().target_by_index(targetnum); + char buffer[40]; + + /* stop when we run out */ + if (target == NULL) + break; + + /* add a menu item */ + sprintf(buffer, "Screen #%d", targetnum); + ui_menu_item_append(menu, buffer, NULL, 0, target); + } +} + + +/*------------------------------------------------- + menu_video_options - handle the video options + menu +-------------------------------------------------*/ + +static void menu_video_options(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + render_target *target = (parameter != NULL) ? (render_target *)parameter : machine.render().first_target(); + const ui_menu_event *menu_event; + int changed = FALSE; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_video_options_populate(machine, menu, target); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + if (menu_event != NULL && menu_event->itemref != NULL) + { + switch ((FPTR)menu_event->itemref) + { + /* rotate adds rotation depending on the direction */ + case VIDEO_ITEM_ROTATE: + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + { + int delta = (menu_event->iptkey == IPT_UI_LEFT) ? ROT270 : ROT90; + target->set_orientation(orientation_add(delta, target->orientation())); + if (target->is_ui_target()) + { + render_container::user_settings settings; + menu->container->get_user_settings(settings); + settings.m_orientation = orientation_add(delta ^ ROT180, settings.m_orientation); + menu->container->set_user_settings(settings); + } + changed = TRUE; + } + break; + + /* layer config bitmasks handle left/right keys the same (toggle) */ + case VIDEO_ITEM_BACKDROPS: + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + { + target->set_backdrops_enabled(!target->backdrops_enabled()); + changed = TRUE; + } + break; + + case VIDEO_ITEM_OVERLAYS: + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + { + target->set_overlays_enabled(!target->overlays_enabled()); + changed = TRUE; + } + break; + + case VIDEO_ITEM_BEZELS: + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + { + target->set_bezels_enabled(!target->bezels_enabled()); + changed = TRUE; + } + break; + + case VIDEO_ITEM_CPANELS: + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + { + target->set_cpanels_enabled(!target->cpanels_enabled()); + changed = TRUE; + } + break; + + case VIDEO_ITEM_MARQUEES: + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + { + target->set_marquees_enabled(!target->marquees_enabled()); + changed = TRUE; + } + break; + + case VIDEO_ITEM_ZOOM: + if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + { + target->set_zoom_to_screen(!target->zoom_to_screen()); + changed = TRUE; + } + break; + + /* anything else is a view item */ + default: + if (menu_event->iptkey == IPT_UI_SELECT && (int)(FPTR)menu_event->itemref >= VIDEO_ITEM_VIEW) + { + target->set_view((FPTR)menu_event->itemref - VIDEO_ITEM_VIEW); + changed = TRUE; + } + break; + } + } + + /* if something changed, rebuild the menu */ + if (changed) + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); +} + + +/*------------------------------------------------- + menu_video_options_populate - populate the + video options menu +-------------------------------------------------*/ + +static void menu_video_options_populate(running_machine &machine, ui_menu *menu, render_target *target) +{ + const char *subtext = ""; + astring tempstring; + int viewnum; + int enabled; + + /* add items for each view */ + for (viewnum = 0; ; viewnum++) + { + const char *name = target->view_name(viewnum); + if (name == NULL) + break; + + /* create a string for the item, replacing underscores with spaces */ + tempstring.cpy(name).replace(0, "_", " "); + ui_menu_item_append(menu, tempstring, NULL, 0, (void *)(FPTR)(VIDEO_ITEM_VIEW + viewnum)); + } + + /* add a separator */ + ui_menu_item_append(menu, MENU_SEPARATOR_ITEM, NULL, 0, NULL); + + /* add a rotate item */ + switch (target->orientation()) + { + case ROT0: subtext = "None"; break; + case ROT90: subtext = "CW 90" UTF8_DEGREES; break; + case ROT180: subtext = "180" UTF8_DEGREES; break; + case ROT270: subtext = "CCW 90" UTF8_DEGREES; break; + } + ui_menu_item_append(menu, "Rotate", subtext, MENU_FLAG_LEFT_ARROW | MENU_FLAG_RIGHT_ARROW, (void *)VIDEO_ITEM_ROTATE); + + /* backdrop item */ + enabled = target->backdrops_enabled(); + ui_menu_item_append(menu, "Backdrops", enabled ? "Enabled" : "Disabled", enabled ? MENU_FLAG_LEFT_ARROW : MENU_FLAG_RIGHT_ARROW, (void *)VIDEO_ITEM_BACKDROPS); + + /* overlay item */ + enabled = target->overlays_enabled(); + ui_menu_item_append(menu, "Overlays", enabled ? "Enabled" : "Disabled", enabled ? MENU_FLAG_LEFT_ARROW : MENU_FLAG_RIGHT_ARROW, (void *)VIDEO_ITEM_OVERLAYS); + + /* bezel item */ + enabled = target->bezels_enabled(); + ui_menu_item_append(menu, "Bezels", enabled ? "Enabled" : "Disabled", enabled ? MENU_FLAG_LEFT_ARROW : MENU_FLAG_RIGHT_ARROW, (void *)VIDEO_ITEM_BEZELS); + + /* cpanel item */ + enabled = target->cpanels_enabled(); + ui_menu_item_append(menu, "CPanels", enabled ? "Enabled" : "Disabled", enabled ? MENU_FLAG_LEFT_ARROW : MENU_FLAG_RIGHT_ARROW, (void *)VIDEO_ITEM_CPANELS); + + /* marquee item */ + enabled = target->marquees_enabled(); + ui_menu_item_append(menu, "Marquees", enabled ? "Enabled" : "Disabled", enabled ? MENU_FLAG_LEFT_ARROW : MENU_FLAG_RIGHT_ARROW, (void *)VIDEO_ITEM_MARQUEES); + + /* cropping */ + enabled = target->zoom_to_screen(); + ui_menu_item_append(menu, "View", enabled ? "Cropped" : "Full", enabled ? MENU_FLAG_RIGHT_ARROW : MENU_FLAG_LEFT_ARROW, (void *)VIDEO_ITEM_ZOOM); +} + + +/*------------------------------------------------- + menu_crosshair - handle the crosshair settings + menu +-------------------------------------------------*/ + +static void menu_crosshair(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + const ui_menu_event *menu_event; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_crosshair_populate(machine, menu); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, UI_MENU_PROCESS_LR_REPEAT); + + /* handle events */ + if (menu_event != NULL && menu_event->itemref != NULL) + { + crosshair_user_settings settings; + crosshair_item_data *data = (crosshair_item_data *)menu_event->itemref; + int changed = FALSE; + //int set_def = FALSE; + int newval = data->cur; + + /* retreive the user settings */ + crosshair_get_user_settings(machine, data->player, &settings); + + switch (menu_event->iptkey) + { + /* if selected, reset to default value */ + case IPT_UI_SELECT: + newval = data->defvalue; + //set_def = TRUE; + break; + + /* left decrements */ + case IPT_UI_LEFT: + newval -= machine.input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; + break; + + /* right increments */ + case IPT_UI_RIGHT: + newval += machine.input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; + break; + } + + /* clamp to range */ + if (newval < data->min) + newval = data->min; + if (newval > data->max) + newval = data->max; + + /* if things changed, update */ + if (newval != data->cur) + { + switch (data->type) + { + /* visibility state */ + case CROSSHAIR_ITEM_VIS: + settings.mode = newval; + changed = TRUE; + break; + + /* auto time */ + case CROSSHAIR_ITEM_AUTO_TIME: + settings.auto_time = newval; + changed = TRUE; + break; + } + } + + /* crosshair graphic name */ + if (data->type == CROSSHAIR_ITEM_PIC) + { + if (menu_event->iptkey == IPT_UI_SELECT) + { + /* clear the name string to reset to default crosshair */ + settings.name[0] = 0; + changed = TRUE; + } + else if (menu_event->iptkey == IPT_UI_LEFT) + { + strcpy(settings.name, data->last_name); + changed = TRUE; + } + else if (menu_event->iptkey == IPT_UI_RIGHT) + { + strcpy(settings.name, data->next_name); + changed = TRUE; + } + } + + if (changed) + { + /* save the user settings */ + crosshair_set_user_settings(machine, data->player, &settings); + + /* rebuild the menu */ + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_POSITION); + } + } +} + + +/*------------------------------------------------- + menu_crosshair_populate - populate the + crosshair settings menu +-------------------------------------------------*/ + +static void menu_crosshair_populate(running_machine &machine, ui_menu *menu) +{ + crosshair_user_settings settings; + crosshair_item_data *data; + char temp_text[16]; + int player; + UINT8 use_auto = FALSE; + UINT32 flags = 0; + + /* loop over player and add the manual items */ + for (player = 0; player < MAX_PLAYERS; player++) + { + /* get the user settings */ + crosshair_get_user_settings(machine, player, &settings); + + /* add menu items for usable crosshairs */ + if (settings.used) + { + /* Make sure to keep these matched to the CROSSHAIR_VISIBILITY_xxx types */ + static const char *const vis_text[] = { "Off", "On", "Auto" }; + + /* track if we need the auto time menu */ + if (settings.mode == CROSSHAIR_VISIBILITY_AUTO) use_auto = TRUE; + + /* CROSSHAIR_ITEM_VIS - allocate a data item and fill it */ + data = (crosshair_item_data *)ui_menu_pool_alloc(menu, sizeof(*data)); + data->type = CROSSHAIR_ITEM_VIS; + data->player = player; + data->min = CROSSHAIR_VISIBILITY_OFF; + data->max = CROSSHAIR_VISIBILITY_AUTO; + data->defvalue = CROSSHAIR_VISIBILITY_DEFAULT; + data->cur = settings.mode; + + /* put on arrows */ + if (data->cur > data->min) + flags |= MENU_FLAG_LEFT_ARROW; + if (data->cur < data->max) + flags |= MENU_FLAG_RIGHT_ARROW; + + /* add CROSSHAIR_ITEM_VIS menu */ + sprintf(temp_text, "P%d Visibility", player + 1); + ui_menu_item_append(menu, temp_text, vis_text[settings.mode], flags, data); + + /* CROSSHAIR_ITEM_PIC - allocate a data item and fill it */ + data = (crosshair_item_data *)ui_menu_pool_alloc(menu, sizeof(*data)); + data->type = CROSSHAIR_ITEM_PIC; + data->player = player; + data->last_name[0] = 0; + /* other data item not used by this menu */ + + /* search for crosshair graphics */ + + /* open a path to the crosshairs */ + file_enumerator path(machine.options().crosshair_path()); + const osd_directory_entry *dir; + /* reset search flags */ + int using_default = FALSE; + int finished = FALSE; + int found = FALSE; + + /* if we are using the default, then we just need to find the first in the list */ + if (strlen(settings.name) == 0) + using_default = TRUE; + + /* look for the current name, then remember the name before */ + /* and find the next name */ + while (((dir = path.next()) != NULL) && !finished) + { + int length = strlen(dir->name); + + /* look for files ending in .png with a name not larger then 9 chars*/ + if ((length > 4) && (length <= CROSSHAIR_PIC_NAME_LENGTH + 4) && + dir->name[length - 4] == '.' && + tolower((UINT8)dir->name[length - 3]) == 'p' && + tolower((UINT8)dir->name[length - 2]) == 'n' && + tolower((UINT8)dir->name[length - 1]) == 'g') + + { + /* remove .png from length */ + length -= 4; + + if (found || using_default) + { + /* get the next name */ + strncpy(data->next_name, dir->name, length); + data->next_name[length] = 0; + finished = TRUE; + } + else if (!strncmp(dir->name, settings.name, length)) + { + /* we found the current name */ + /* so loop once more to find the next name */ + found = TRUE; + } + else + /* remember last name */ + /* we will do it here in case files get added to the directory */ + { + strncpy(data->last_name, dir->name, length); + data->last_name[length] = 0; + } + } + } + /* if name not found then next item is DEFAULT */ + if (!found && !using_default) + { + data->next_name[0] = 0; + finished = TRUE; + } + /* setup the selection flags */ + flags = 0; + if (finished) + flags |= MENU_FLAG_RIGHT_ARROW; + if (found) + flags |= MENU_FLAG_LEFT_ARROW; + + /* add CROSSHAIR_ITEM_PIC menu */ + sprintf(temp_text, "P%d Crosshair", player + 1); + ui_menu_item_append(menu, temp_text, using_default ? "DEFAULT" : settings.name, flags, data); + } + } + if (use_auto) + { + /* any player can be used to get the autotime */ + crosshair_get_user_settings(machine, 0, &settings); + + /* CROSSHAIR_ITEM_AUTO_TIME - allocate a data item and fill it */ + data = (crosshair_item_data *)ui_menu_pool_alloc(menu, sizeof(*data)); + data->type = CROSSHAIR_ITEM_AUTO_TIME; + data->min = CROSSHAIR_VISIBILITY_AUTOTIME_MIN; + data->max = CROSSHAIR_VISIBILITY_AUTOTIME_MAX; + data->defvalue = CROSSHAIR_VISIBILITY_AUTOTIME_DEFAULT; + data->cur = settings.auto_time; + + /* put on arrows in visible menu */ + if (data->cur > data->min) + flags |= MENU_FLAG_LEFT_ARROW; + if (data->cur < data->max) + flags |= MENU_FLAG_RIGHT_ARROW; + + /* add CROSSHAIR_ITEM_AUTO_TIME menu */ + sprintf(temp_text, "%d", settings.auto_time); + ui_menu_item_append(menu, "Visible Delay", temp_text, flags, data); + } +// else +// /* leave a blank filler line when not in auto time so size does not rescale */ +// ui_menu_item_append(menu, "", "", NULL, NULL); +} + + +/*------------------------------------------------- + menu_quit_game - handle the "menu" for + quitting the game +-------------------------------------------------*/ + +static void menu_quit_game(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + /* request a reset */ + machine.schedule_exit(); + + /* reset the menu stack */ + ui_menu_stack_reset(machine); +} + + +/*------------------------------------------------- + menu_select_game - handle the game select + menu +-------------------------------------------------*/ + +static void menu_select_game(running_machine &machine, ui_menu *menu, void *parameter, void *state) +{ + select_game_state *menustate; + const ui_menu_event *menu_event; + + /* if no state, allocate some */ + if (state == NULL) + { + state = ui_menu_alloc_state(menu, sizeof(*menustate) + sizeof(menustate->driverlist) * driver_list::total(), NULL); + if (parameter != NULL) + strcpy(((select_game_state *)state)->search, (const char *)parameter); + ((select_game_state *)state)->matchlist[0] = -1; + } + menustate = (select_game_state *)state; + + /* if the menu isn't built, populate now */ + if (!ui_menu_populated(menu)) + menu_select_game_populate(machine, menu, menustate); + + /* ignore pause keys by swallowing them before we process the menu */ + ui_input_pressed(machine, IPT_UI_PAUSE); + + /* process the menu */ + menu_event = ui_menu_process(machine, menu, 0); + if (menu_event != NULL && menu_event->itemref != NULL) + { + /* reset the error on any future menu_event */ + if (menustate->error) + menustate->error = FALSE; + + /* handle selections */ + else if (menu_event->iptkey == IPT_UI_SELECT) + { + const game_driver *driver = (const game_driver *)menu_event->itemref; + + /* special case for configure inputs */ + if ((FPTR)driver == 1) + ui_menu_stack_push(ui_menu_alloc(menu->machine(), menu->container, menu_input_groups, NULL)); + + /* anything else is a driver */ + else + { + // audit the game first to see if we're going to work + driver_enumerator enumerator(machine.options(), *driver); + enumerator.next(); + media_auditor auditor(enumerator); + media_auditor::summary summary = auditor.audit_media(AUDIT_VALIDATE_FAST); + + // if everything looks good, schedule the new driver + if (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE) + { + machine.schedule_new_driver(*driver); + ui_menu_stack_reset(machine); + } + + // otherwise, display an error + else + { + ui_menu_reset(menu, UI_MENU_RESET_REMEMBER_REF); + menustate->error = TRUE; + } + } + } + + /* escape pressed with non-empty text clears the text */ + else if (menu_event->iptkey == IPT_UI_CANCEL && menustate->search[0] != 0) + { + /* since we have already been popped, we must recreate ourself from scratch */ + ui_menu_stack_push(ui_menu_alloc(menu->machine(), menu->container, menu_select_game, NULL)); + } + + /* typed characters append to the buffer */ + else if (menu_event->iptkey == IPT_SPECIAL) + { + int buflen = strlen(menustate->search); + + /* if it's a backspace and we can handle it, do so */ + if ((menu_event->unichar == 8 || menu_event->unichar == 0x7f) && buflen > 0) + { + *(char *)utf8_previous_char(&menustate->search[buflen]) = 0; + menustate->rerandomize = TRUE; + ui_menu_reset(menu, UI_MENU_RESET_SELECT_FIRST); + } + + /* if it's any other key and we're not maxed out, update */ + else if (menu_event->unichar >= ' ' && menu_event->unichar < 0x7f) + { + buflen += utf8_from_uchar(&menustate->search[buflen], ARRAY_LENGTH(menustate->search) - buflen, menu_event->unichar); + menustate->search[buflen] = 0; + ui_menu_reset(menu, UI_MENU_RESET_SELECT_FIRST); + } + } + } + + /* if we're in an error state, overlay an error message */ + if (menustate->error) + ui_draw_text_box(menu->container, + "The selected game is missing one or more required ROM or CHD images. " + "Please select a different game.\n\nPress any key to continue.", + JUSTIFY_CENTER, 0.5f, 0.5f, UI_RED_COLOR); +} + + +/*------------------------------------------------- + menu_select_game_populate - populate the game + select menu +-------------------------------------------------*/ + +static void menu_select_game_populate(running_machine &machine, ui_menu *menu, select_game_state *menustate) +{ + int matchcount; + int curitem; + + /* update our driver list if necessary */ + if (menustate->driverlist[0] == NULL) + menu_select_game_build_driver_list(menu, menustate); + for (curitem = matchcount = 0; menustate->driverlist[curitem] != NULL && matchcount < VISIBLE_GAMES_IN_LIST; curitem++) + if (!(menustate->driverlist[curitem]->flags & GAME_NO_STANDALONE)) + matchcount++; + + /* if nothing there, add a single multiline item and return */ + if (matchcount == 0) + { + ui_menu_item_append(menu, "No "GAMESNOUN" found. Please check the rompath specified in the "CONFIGNAME".ini file.\n\n" + "If this is your first time using "APPNAME", please see the config.txt file in " + "the docs directory for information on configuring "APPNAME".", NULL, MENU_FLAG_MULTILINE | MENU_FLAG_REDTEXT, NULL); + return; + } + + /* otherwise, rebuild the match list */ + assert(drivlist != NULL); + if (menustate->search[0] != 0 || menustate->matchlist[0] == -1 || menustate->rerandomize) + drivlist->find_approximate_matches(menustate->search, matchcount, menustate->matchlist); + menustate->rerandomize = FALSE; + + /* iterate over entries */ + for (curitem = 0; curitem < matchcount; curitem++) + { + int curmatch = menustate->matchlist[curitem]; + if (curmatch != -1) + { + int cloneof = drivlist->non_bios_clone(curmatch); + ui_menu_item_append(menu, drivlist->driver(curmatch).name, drivlist->driver(curmatch).description, (cloneof == -1) ? 0 : MENU_FLAG_INVERT, (void *)&drivlist->driver(curmatch)); + } + } + + /* if we're forced into this, allow general input configuration as well */ + if (ui_menu_is_force_game_select()) + { + ui_menu_item_append(menu, MENU_SEPARATOR_ITEM, NULL, 0, NULL); + ui_menu_item_append(menu, "Configure General Inputs", NULL, 0, (void *)1); + } + + /* configure the custom rendering */ + ui_menu_set_custom_render(menu, menu_select_game_custom_render, ui_get_line_height(machine) + 3.0f * UI_BOX_TB_BORDER, 4.0f * ui_get_line_height(machine) + 3.0f * UI_BOX_TB_BORDER); +} + + +/*------------------------------------------------- + menu_select_game_build_driver_list - build a + list of available drivers +-------------------------------------------------*/ + +static void menu_select_game_build_driver_list(ui_menu *menu, select_game_state *menustate) +{ + // start with an empty list + // hack alert: use new directly here to avoid reporting this one-time static memory as unfreed + if (drivlist == NULL) + drivlist = global_alloc(driver_enumerator(menu->machine().options())); + drivlist->exclude_all(); + + /* open a path to the ROMs and find them in the array */ + file_enumerator path(menu->machine().options().media_path()); + const osd_directory_entry *dir; + + /* iterate while we get new objects */ + while ((dir = path.next()) != NULL) + { + char drivername[50]; + char *dst = drivername; + const char *src; + + /* build a name for it */ + for (src = dir->name; *src != 0 && *src != '.' && dst < &drivername[ARRAY_LENGTH(drivername) - 1]; src++) + *dst++ = tolower((UINT8)*src); + *dst = 0; + + int drivnum = drivlist->find(drivername); + if (drivnum != -1) + drivlist->include(drivnum); + } + + /* now build the final list */ + drivlist->reset(); + int listnum = 0; + while (drivlist->next()) + menustate->driverlist[listnum++] = &drivlist->driver(); + + /* NULL-terminate */ + menustate->driverlist[listnum] = NULL; +} + + +/*------------------------------------------------- + menu_select_game_custom_render - perform our + special rendering +-------------------------------------------------*/ + +static void menu_select_game_custom_render(running_machine &machine, ui_menu *menu, void *state, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + select_game_state *menustate = (select_game_state *)state; + const game_driver *driver; + float width, maxwidth; + float x1, y1, x2, y2; + char tempbuf[4][256]; + rgb_t color; + int line; + + /* display the current typeahead */ + if (menustate->search[0] != 0) + sprintf(&tempbuf[0][0], "Type name or select: %s_", menustate->search); + else + sprintf(&tempbuf[0][0], "Type name or select: (random)"); + + /* get the size of the text */ + ui_draw_text_full(menu->container, &tempbuf[0][0], 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE, + DRAW_NONE, ARGB_WHITE, ARGB_BLACK, &width, NULL); + width += 2 * UI_BOX_LR_BORDER; + maxwidth = MAX(width, origx2 - origx1); + + /* compute our bounds */ + x1 = 0.5f - 0.5f * maxwidth; + x2 = x1 + maxwidth; + y1 = origy1 - top; + y2 = origy1 - UI_BOX_TB_BORDER; + + /* draw a box */ + ui_draw_outlined_box(menu->container, x1, y1, x2, y2, UI_BACKGROUND_COLOR); + + /* take off the borders */ + x1 += UI_BOX_LR_BORDER; + x2 -= UI_BOX_LR_BORDER; + y1 += UI_BOX_TB_BORDER; + y2 -= UI_BOX_TB_BORDER; + + /* draw the text within it */ + ui_draw_text_full(menu->container, &tempbuf[0][0], x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE, + DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, NULL, NULL); + + /* determine the text to render below */ + driver = ((FPTR)selectedref > 1) ? (const game_driver *)selectedref : NULL; + if ((FPTR)driver > 1) + { + const char *gfxstat, *soundstat; + + /* first line is game name */ + sprintf(&tempbuf[0][0], "%-.100s", driver->description); + + /* next line is year, manufacturer */ + sprintf(&tempbuf[1][0], "%s, %-.100s", driver->year, driver->manufacturer); + + /* next line is overall driver status */ + if (driver->flags & GAME_NOT_WORKING) + strcpy(&tempbuf[2][0], "Overall: NOT WORKING"); + else if (driver->flags & GAME_UNEMULATED_PROTECTION) + strcpy(&tempbuf[2][0], "Overall: Unemulated Protection"); + else + strcpy(&tempbuf[2][0], "Overall: Working"); + + /* next line is graphics, sound status */ + if (driver->flags & (GAME_IMPERFECT_GRAPHICS | GAME_WRONG_COLORS | GAME_IMPERFECT_COLORS)) + gfxstat = "Imperfect"; + else + gfxstat = "OK"; + + if (driver->flags & GAME_NO_SOUND) + soundstat = "Unimplemented"; + else if (driver->flags & GAME_IMPERFECT_SOUND) + soundstat = "Imperfect"; + else + soundstat = "OK"; + + sprintf(&tempbuf[3][0], "Gfx: %s, Sound: %s", gfxstat, soundstat); + } + else + { + const char *s = COPYRIGHT; + line = 0; + int col = 0; + + /* first line is version string */ + sprintf(&tempbuf[line++][0], "%s %s", APPLONGNAME, build_version); + + /* output message */ + while (line < ARRAY_LENGTH(tempbuf)) + { + if (*s == 0 || *s == '\n') + { + tempbuf[line++][col] = 0; + col = 0; + } + else + tempbuf[line][col++] = *s; + + if (*s != 0) + s++; + } + } + + /* get the size of the text */ + maxwidth = origx2 - origx1; + for (line = 0; line < 4; line++) + { + ui_draw_text_full(menu->container, &tempbuf[line][0], 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE, + DRAW_NONE, ARGB_WHITE, ARGB_BLACK, &width, NULL); + width += 2 * UI_BOX_LR_BORDER; + maxwidth = MAX(maxwidth, width); + } + + /* compute our bounds */ + x1 = 0.5f - 0.5f * maxwidth; + x2 = x1 + maxwidth; + y1 = origy2 + UI_BOX_TB_BORDER; + y2 = origy2 + bottom; + + /* draw a box */ + color = UI_BACKGROUND_COLOR; + if (driver != NULL) + color = UI_GREEN_COLOR; + if (driver != NULL && (driver->flags & (GAME_IMPERFECT_GRAPHICS | GAME_WRONG_COLORS | GAME_IMPERFECT_COLORS | GAME_NO_SOUND | GAME_IMPERFECT_SOUND)) != 0) + color = UI_YELLOW_COLOR; + if (driver != NULL && (driver->flags & (GAME_NOT_WORKING | GAME_UNEMULATED_PROTECTION)) != 0) + color = UI_RED_COLOR; + ui_draw_outlined_box(menu->container, x1, y1, x2, y2, color); + + /* take off the borders */ + x1 += UI_BOX_LR_BORDER; + x2 -= UI_BOX_LR_BORDER; + y1 += UI_BOX_TB_BORDER; + y2 -= UI_BOX_TB_BORDER; + + /* draw all lines */ + for (line = 0; line < 4; line++) + { + ui_draw_text_full(menu->container, &tempbuf[line][0], x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE, + DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, NULL, NULL); + y1 += ui_get_line_height(machine); + } +} + + + +/*************************************************************************** + MENU HELPERS +***************************************************************************/ + +/*------------------------------------------------- + menu_render_triangle - render a triangle that + is used for up/down arrows and left/right + indicators +-------------------------------------------------*/ + +static void menu_render_triangle(bitmap_t &dest, const bitmap_t &source, const rectangle &sbounds, void *param) +{ + int halfwidth = dest.width / 2; + int height = dest.height; + int x, y; + + /* start with all-transparent */ + bitmap_fill(&dest, NULL, MAKE_ARGB(0x00,0x00,0x00,0x00)); + + /* render from the tip to the bottom */ + for (y = 0; y < height; y++) + { + int linewidth = (y * (halfwidth - 1) + (height / 2)) * 255 * 2 / height; + UINT32 *target = BITMAP_ADDR32(&dest, y, halfwidth); + + /* don't antialias if height < 12 */ + if (dest.height < 12) + { + int pixels = (linewidth + 254) / 255; + if (pixels % 2 == 0) pixels++; + linewidth = pixels * 255; + } + + /* loop while we still have data to generate */ + for (x = 0; linewidth > 0; x++) + { + int dalpha; + + /* first column we only consume one pixel */ + if (x == 0) + { + dalpha = MIN(0xff, linewidth); + target[x] = MAKE_ARGB(dalpha,0xff,0xff,0xff); + } + + /* remaining columns consume two pixels, one on each side */ + else + { + dalpha = MIN(0x1fe, linewidth); + target[x] = target[-x] = MAKE_ARGB(dalpha/2,0xff,0xff,0xff); + } + + /* account for the weight we consumed */ + linewidth -= dalpha; + } + } +} |