From 22bc3486c3ef8f2230e99daf49785a89ac8a1182 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Fri, 15 Oct 2021 09:42:35 +1100 Subject: More user experience improvements: frontend: Made it possible to cancel a media audit while it's in progress. Also made the media audit multi-threaded so it's faster. frontend: Made the DIP switches in the DIP switch preview clickable. frontend: Made the system and software selection menus leave focus on the same system when clearing the search rather than jumping to the first item. Also fixed a couple of bugs in the logic for keeping the selected item visible. frontend: Fixed a few places that weren't showing localised system names. frontend: Made UI Cancel clear a search in the file manager the same way it does on the system and sofware selection menus. frontend: Made it possible for plugin menus to handle UI Cancel more naturally, backing up to the previous plugin menu rather than dropping straight back to the list of plugins. Updated the autofire, cheat and cheatfind plugins, and fixed a few other issues in the cheatfind plugin. debugger: Made the mount and unmount commands accept instance names as well as brief instance names. Also updated another page of debugger documentation. --- docs/source/debugger/image.rst | 97 ++++--- docs/source/debugger/index.rst | 93 ++++--- plugins/autofire/autofire_menu.lua | 52 ++-- plugins/cheat/init.lua | 7 +- plugins/cheatfind/init.lua | 23 +- scripts/src/mame/frontend.lua | 2 + src/devices/cpu/m6805/m68hc05.cpp | 2 +- src/emu/debug/debugcmd.cpp | 53 ++-- src/emu/debug/debughlp.cpp | 36 ++- src/emu/softlist.cpp | 71 ++--- src/frontend/mame/ui/auditmenu.cpp | 146 +++++++---- src/frontend/mame/ui/auditmenu.h | 19 +- src/frontend/mame/ui/confswitch.cpp | 83 ++++-- src/frontend/mame/ui/confswitch.h | 13 + src/frontend/mame/ui/custui.cpp | 11 +- src/frontend/mame/ui/datmenu.cpp | 20 +- src/frontend/mame/ui/datmenu.h | 8 +- src/frontend/mame/ui/dirmenu.cpp | 1 + src/frontend/mame/ui/dirmenu.h | 2 +- src/frontend/mame/ui/filesel.cpp | 26 +- src/frontend/mame/ui/filesel.h | 1 + src/frontend/mame/ui/info.cpp | 8 +- src/frontend/mame/ui/inputmap.cpp | 8 +- src/frontend/mame/ui/menu.cpp | 6 +- src/frontend/mame/ui/menu.h | 4 +- src/frontend/mame/ui/miscmenu.cpp | 48 +--- src/frontend/mame/ui/miscmenu.h | 17 +- src/frontend/mame/ui/pluginopt.cpp | 18 +- src/frontend/mame/ui/pluginopt.h | 13 +- src/frontend/mame/ui/selector.cpp | 2 +- src/frontend/mame/ui/selector.h | 2 +- src/frontend/mame/ui/selgame.cpp | 493 +++-------------------------------- src/frontend/mame/ui/selgame.h | 9 +- src/frontend/mame/ui/selmenu.cpp | 25 +- src/frontend/mame/ui/selmenu.h | 9 +- src/frontend/mame/ui/selsoft.cpp | 39 +-- src/frontend/mame/ui/selsoft.h | 8 +- src/frontend/mame/ui/simpleselgame.h | 2 +- src/frontend/mame/ui/state.cpp | 4 +- src/frontend/mame/ui/swlist.cpp | 44 ++-- src/frontend/mame/ui/swlist.h | 8 +- src/frontend/mame/ui/systemlist.cpp | 414 +++++++++++++++++++++++++++++ src/frontend/mame/ui/systemlist.h | 122 +++++++++ src/frontend/mame/ui/ui.cpp | 26 +- src/frontend/mame/ui/ui.h | 3 + src/frontend/mame/ui/utils.cpp | 2 + 46 files changed, 1209 insertions(+), 891 deletions(-) create mode 100644 src/frontend/mame/ui/systemlist.cpp create mode 100644 src/frontend/mame/ui/systemlist.h diff --git a/docs/source/debugger/image.rst b/docs/source/debugger/image.rst index d52c3ed3f43..f02388062a7 100644 --- a/docs/source/debugger/image.rst +++ b/docs/source/debugger/image.rst @@ -1,63 +1,80 @@ .. _debugger-image-list: -Image Debugger Commands -======================= +Media Image Debugger Commands +============================= +:ref:`debugger-command-images` + lists all image devices and mounted media images +:ref:`debugger-command-mount` + mounts a media image file to an image device +:ref:`debugger-command-unmount` + unmounts the media image from a device -You can also type **help ** for further details on each command in the MAME Debugger interface. -| :ref:`debugger-command-images` -- lists all image devices and mounted files -| :ref:`debugger-command-mount` -- mounts file to named device -| :ref:`debugger-command-unmount` -- unmounts file from named device - - - .. _debugger-command-images: +.. _debugger-command-images: images ------ -| **images** -| -| Used to display list of available image devices. -| -| Examples: -| -| images -| -| Show list of devices and mounted files for current driver. +**images** + +Lists the instance names for media images devices in the system and the +currently mounted media images, if any. Brief instance names, as +allowed for command line media options, are listed. Mounted software +list items are displayed as the list name, software item short name, and +part name, separated by colons; other mounted images are displayed as +file names. + +Example: +``images`` + Lists image device instance names and mounted media. - .. _debugger-command-mount: +Back to :ref:`debugger-image-list` + + +.. _debugger-command-mount: mount ----- -| **mount ,** -| -| Mount to image . -| -| can be softlist item or full path to file. -| -| Examples: -| -| mount cart,aladdin -| -| Mounts softlist item aladdin on cart device. +**mount ,** + +Mounts a file on a media device. The device may be specified by its +instance name or brief instance name, as allowed for command line media +options. +Some media devices allow software list items to be mounted using this +command by supplying the short name of the software list item in place +of a filename for the **** parameter. - .. _debugger-command-unmount: +Examples: + +``mount flop1,os1xutls.td0`` + Mount the file **os1xutls.td0** on the media device with instance + name **flop1**. +``mount cart,10yard`` + Mount the software list item with short name **10yard** on the media + device with instance name **cart**. + +Back to :ref:`debugger-image-list` + + +.. _debugger-command-unmount: unmount ------- -| **unmount ** -| -| Unmounts file from image . -| -| Examples: -| -| unmount cart -| -| Unmounts any file mounted on device named cart. +**unmount ** + +Unmounts the mounted media image (if any) from a device. The device may +be specified by its instance name or brief instance name, as allowed for +command line media options. + +Examples: +unmount cart + Unmounts any media image mounted on the device with instance name + **cart**. +Back to :ref:`debugger-image-list` diff --git a/docs/source/debugger/index.rst b/docs/source/debugger/index.rst index c7503e8d678..0730a607688 100644 --- a/docs/source/debugger/index.rst +++ b/docs/source/debugger/index.rst @@ -11,10 +11,10 @@ MAME Debugger Introduction ------------ -MAME includes an interactive low-level debugger that target the emulated -system. This can be a useful tool for diagnosing emulation issues, -developing software to run on vintage systems, creating cheats, ROM -hacking, or just looking at how software works. +MAME includes an interactive low-level debugger that targets the +emulated system. This can be a useful tool for diagnosing emulation +issues, developing software to run on vintage systems, creating cheats, +ROM hacking, or just investigating how software works. Use the ``-debug`` command line option to start MAME with the debugger activated. By default, pressing the tilde (**~**) during emulation @@ -176,36 +176,36 @@ and a few new ones (memory accessors). The table below lists all the operators, ordered from highest to lowest precedence: -( ) +``(`` ``)`` Standard parentheses -++ -- +``++`` ``--`` Postfix increment/decrement -++ -- ~ ! - + b@ w@ d@ q@ b! w! d! q! +``++`` ``--`` ``~`` ``!`` ``-`` ``+`` ``b@`` ``w@`` ``d@`` ``q@`` ``b!`` ``w!`` ``d!`` ``q!`` Prefix increment/decrement, binary complement, logical complement, unary identity/negation, memory access -\* / % +``*`` ``/`` ``%`` Multiplication, division, modulo -\+ - +``+`` ``-`` Addition, subtraction -<< >> +``<<`` ``>>`` Bitwise left/right shift -< <= > >= +``< ``<=`` ``>`` ``>=`` Less than, less than or equal, greater than, greater than or equal -== != +``==`` ``!=`` Equal, not equal -\& +``&`` Bitwise intersection (and) -\^ +``^`` Bitwise exclusive or -\| +``|`` Bitwise union (or) -\&& +``&&`` Logical conjunction (and) -\|| +``||`` Logical disjunction (or) -= \*= /= %= += -= <<= >>= &= \|= ^= +``=`` ``*=`` ``/=`` ``%=`` ``+=`` ``-=`` ``<<=`` ``>>=`` ``&=`` ``|=`` ``^=`` Assignment and modifying assignment -\, +``,`` Separate terms, function parameters Major differences from C expression semantics: @@ -268,6 +268,38 @@ and side effect modes are as follows: * ``@`` suppress side effects * ``!`` do not suppress side effects +Suppressing side effects of a read access yields the value reading from +address would, with no further effects. For example reading a mailbox +with side effects disabled will not clear the pending flag, and reading +a FIFO with side effects disabled will not remove an item. + +For write accesses, suppressing side effects doesn’t change behaviour in +most cases – you want to see the effects of writing to a location. +However, there are some exceptions where it is useful to separate +multiple effects of a write access. For example: + +* Some registers need to be written in sequence to avoid race + conditions. The debugger can issue multiple writes at the same point + in emulated time, so these race conditions cannot be avoided + trivially. For example writing to the MC68HC05 output compare + register high byte (OCRH) inhibits compare until the output compare + register low byte (OCRL) is written to prevent race conditions. + Since the debugger can write to both locations at the same instant + from the emulated machine’s point of view, the race condition is not + usually relevant. It’s more error-prone if you can accidentally set + hidden state when all you really want to do is change the value, so + writing to OCRH with side effects suppressed does not inhibit compare, + it just changes the value in the output compare register. +* Writing to some registers has multiple effects that may be useful to + separate for debugging purposes. Using the MC68HC05 as an example + again, writing to OCRL changes the value in the output compare + register, and also clears the output compare flag (OCF) and enables + compare if it was previously inhibited by writing to OCRH. Writing to + OCRL with side effects disable just changes the value in the register + without clearing OCF or enabling compare, since it’s useful for + debugging purposes. Writing to OCRL with side effects enabled has the + additional effects. + The size may optionally be preceded by an access type specification: * ``p`` or ``lp`` specifies a logical address defaulting to space 0 @@ -295,40 +327,40 @@ That may seem like a lot to digest, so let’s look at the simplest examples: ``b@`` - Refers to the byte at **** in the program space of the visible + Refers to the byte at **** in the program space of the current CPU while suppressing side effects ``b!`` - Refers to the byte at **** in the program space of the visible + Refers to the byte at **** in the program space of the current CPU, *not* suppressing side effects such as reading a mailbox clearing the pending flag, or reading a FIFO removing an item ``w@`` and ``w!`` - Refer to the word at **** in the program space of the visible + Refer to the word at **** in the program space of the current CPU, suppressing or not suppressing side effects, respectively. ``d@`` and ``d!`` Refer to the double word at **** in the program space of the - visible CPU, suppressing or not suppressing side effects, + current CPU, suppressing or not suppressing side effects, respectively. ``q@`` and ``q!`` Refer to the quadruple word at **** in the program space of - the visible CPU, suppressing or not suppressing side effects, + the current CPU, suppressing or not suppressing side effects, respectively. Adding access types gives additional possibilities: ``dw@300`` - Refers to the word at 300 in the data space of the visible CPU while + Refers to the word at 300 in the data space of the current CPU while suppressing side effects ``id@400`` - Refers to the double word at 400 in the I/O space of the visible CPU + Refers to the double word at 400 in the I/O space of the current CPU CPU while suppressing side effects ``ppd!`` Refers to the double word at physical address **** in the - program space of the visible CPU while not suppressing side effects + program space of the current CPU while not suppressing side effects ``rw@`` Refers to the word at address **** in the program space of the - visible CPU using direct read/write pointer access + current CPU using direct read/write pointer access -If we want to access an address space of a device other than the visible +If we want to access an address space of a device other than the current CPU, an address space beyond the first four indices, or a memory region, we need to include a tag or name: @@ -344,6 +376,9 @@ we need to include a tag or name: ``monitor.mb@78`` Refers to the byte at 78 in the memory region with the absolute tag ``:monitor`` +``..md@202`` + Refers to the double word at address 202 in the memory region with + the same tag path as the current CPU. Some combinations are not useful. For example physical and logical addresses are equivalent for some CPUs, and direct read/write pointer diff --git a/plugins/autofire/autofire_menu.lua b/plugins/autofire/autofire_menu.lua index c4a76e39f64..3536800767f 100644 --- a/plugins/autofire/autofire_menu.lua +++ b/plugins/autofire/autofire_menu.lua @@ -140,8 +140,6 @@ local function handle_configure_menu(index, event) if event == 'select' then table.insert(menu_stack, MENU_TYPES.BUTTON) return true - else - return false end elseif index == 2 then -- Hotkey @@ -150,30 +148,30 @@ local function handle_configure_menu(index, event) if keycode then current_button.key = keycode return true - else - return false end - else - return false end elseif index == 3 then -- On frames manager.machine:popmessage(_('Number of frames button will be pressed')) if event == 'left' then current_button.on_frames = current_button.on_frames - 1 + return true elseif event == 'right' then current_button.on_frames = current_button.on_frames + 1 + return true end elseif index == 4 then -- Off frames manager.machine:popmessage(_('Number of frames button will be released')) if event == 'left' then current_button.off_frames = current_button.off_frames - 1 + return true elseif event == 'right' then current_button.off_frames = current_button.off_frames + 1 + return true end end - return true + return false end local function populate_edit_menu() @@ -192,13 +190,11 @@ end local function handle_edit_menu(index, event, buttons) local section, adjusted_index = menu_section(index) - if section == MENU_SECTIONS.CONTENT then + if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then + table.remove(menu_stack) + return true + elseif section == MENU_SECTIONS.CONTENT then return handle_configure_menu(adjusted_index, event) - elseif section == MENU_SECTIONS.FOOTER then - if event == 'select' then - table.remove(menu_stack) - return true - end end return false end @@ -223,16 +219,14 @@ end local function handle_add_menu(index, event, buttons) local section, adjusted_index = menu_section(index) - if section == MENU_SECTIONS.CONTENT then - return handle_configure_menu(adjusted_index, event) - elseif section == MENU_SECTIONS.FOOTER then - if event == 'select' then - table.remove(menu_stack) - if is_button_complete(current_button) then - buttons[#buttons + 1] = current_button - end - return true + if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then + table.remove(menu_stack) + if is_button_complete(current_button) and (event == 'select') then + buttons[#buttons + 1] = current_button end + return true + elseif section == MENU_SECTIONS.CONTENT then + return handle_configure_menu(adjusted_index, event) end return false end @@ -249,7 +243,6 @@ local function populate_button_menu() for port_key, port in pairs(manager.machine.ioport.ports) do for field_key, field in pairs(port.fields) do if is_supported_input(field) then - menu[#menu + 1] = { _p("input-name", field.name), '', '' } inputs[#inputs + 1] = { port_name = port_key, field_name = field_key, @@ -258,13 +251,24 @@ local function populate_button_menu() end end end + -- TODO: group by device so we can sort table.sort(inputs, function(x, y) return x.ioport_field.name < y.ioport_field.name end) + for i, input in pairs(inputs) do + menu[header_height + i] = { _p("input-name", input.ioport_field.name), '', '' } + end content_height = #menu + + menu[#menu + 1] = {'---', '', ''} + menu[#menu + 1] = {_('Cancel'), '', ''} + return menu end local function handle_button_menu(index, event) local section, adjusted_index = menu_section(index) - if section == MENU_SECTIONS.CONTENT and event == 'select' then + if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then + table.remove(menu_stack) + return true + elseif (section == MENU_SECTIONS.CONTENT) and (event == 'select') then local selected_input = inputs[adjusted_index] current_button.port = selected_input.port_name current_button.field = selected_input.field_name diff --git a/plugins/cheat/init.lua b/plugins/cheat/init.lua index 208efc3585e..e0ae0c058ab 100644 --- a/plugins/cheat/init.lua +++ b/plugins/cheat/init.lua @@ -712,6 +712,9 @@ function cheat.startplugin() hotkeymenu = false return true end + elseif event == "cancel" then + hotkeymenu = false + return true end return false end @@ -722,7 +725,9 @@ function cheat.startplugin() elseif index == 3 then for num, cheat in pairs(cheats) do cheat:set_enabled(false) - cheat:set_index(0) + if cheat.parameter then + cheat:set_index(0) + end end elseif index == 4 then for num, cheat in pairs(cheats) do diff --git a/plugins/cheatfind/init.lua b/plugins/cheatfind/init.lua index a1fff20150a..b9ff9ce3403 100644 --- a/plugins/cheatfind/init.lua +++ b/plugins/cheatfind/init.lua @@ -371,7 +371,7 @@ function cheatfind.startplugin() end local function incdec(event, val, min, max) - local ret + local ret = false if event == "left" and val ~= min then val = val - 1 ret = true @@ -500,8 +500,9 @@ function cheatfind.startplugin() if (event == "left" or event == "right") and #menu_blocks ~= 0 then manager.machine:popmessage(_("Changes to this only take effect when \"Start new search\" is selected")) end - devsel = incdec(event, devsel, 1, #devtable) - return true + local r + devsel, r = incdec(event, devsel, 1, #devtable) + return r end return m, f end @@ -521,9 +522,9 @@ function cheatfind.startplugin() manager.machine:popmessage(_("Automatically toggle pause with on-screen menus")) emu.pause() end + return true end - - return true + return false end return m, f end @@ -550,7 +551,7 @@ function cheatfind.startplugin() return true end end - local opsel = 1 + local opsel = 1 return { _("Start new search"), "", 0 }, f end @@ -577,6 +578,7 @@ function cheatfind.startplugin() devsel = devcur return true end + return false end return { string.format(_("Save current memory state to Slot %d"), #menu_blocks[1] + 1), "", 0 }, f end @@ -615,6 +617,7 @@ function cheatfind.startplugin() devsel = devcur return true end + return false end local slot_slot_comp = _("Perform Compare : Slot %d %s Slot %d") @@ -659,7 +662,7 @@ function cheatfind.startplugin() end menu[#menu + 1] = function() return { "---", "", "off" }, nil end menu[#menu + 1] = function() - local m = { _(leftop), "", 0 } + local m = { string.format("%d", leftop), "", 0 } menu_lim(leftop, 1, #menu_blocks[1], m) m[1] = string.format(_("Slot %d"), leftop) return m, function(event) local r leftop, r = incdec(event, leftop, 1, #menu_blocks[1]) return r end @@ -701,7 +704,7 @@ function cheatfind.startplugin() if optable[opsel]:sub(3, 3) == "v" then return nil end - local m = { _(rightop), "", 0 } + local m = { string.format("%d", rightop), "", 0 } menu_lim(rightop, 1, #menu_blocks[1], m) m[1] = string.format(_("Slot %d"), rightop) return m, function(event) local r rightop, r = incdec(event, rightop, 1, #menu_blocks[1]) return r end @@ -783,6 +786,7 @@ function cheatfind.startplugin() matchpg = 0 return true end + return false end return { _("Undo last search -- #") .. #matches, "", 0 }, f end @@ -1022,6 +1026,7 @@ function cheatfind.startplugin() end menu_lim(matchpg, 0, max, m) local function f(event) + local r matchpg, r = incdec(event, matchpg, 0, max) return r end @@ -1042,7 +1047,7 @@ function cheatfind.startplugin() if event == "cancel" and pausesel == 1 then emu.unpause() menu_is_showing = false - return {0,0,0} + return false -- return false so menu will be popped off the stack end return menu_func[index](event) end diff --git a/scripts/src/mame/frontend.lua b/scripts/src/mame/frontend.lua index 814d4f9423c..91d60cf6587 100644 --- a/scripts/src/mame/frontend.lua +++ b/scripts/src/mame/frontend.lua @@ -165,6 +165,8 @@ files { MAME_DIR .. "src/frontend/mame/ui/submenu.h", MAME_DIR .. "src/frontend/mame/ui/swlist.cpp", MAME_DIR .. "src/frontend/mame/ui/swlist.h", + MAME_DIR .. "src/frontend/mame/ui/systemlist.cpp", + MAME_DIR .. "src/frontend/mame/ui/systemlist.h", MAME_DIR .. "src/frontend/mame/ui/tapectrl.cpp", MAME_DIR .. "src/frontend/mame/ui/tapectrl.h", MAME_DIR .. "src/frontend/mame/ui/text.cpp", diff --git a/src/devices/cpu/m6805/m68hc05.cpp b/src/devices/cpu/m6805/m68hc05.cpp index 38cf06a2f03..df7c590d3a3 100644 --- a/src/devices/cpu/m6805/m68hc05.cpp +++ b/src/devices/cpu/m6805/m68hc05.cpp @@ -347,7 +347,7 @@ u8 m68hc05_device::ocr_r(offs_t offset) void m68hc05_device::ocr_w(offs_t offset, u8 data) { - // writing ORCH inhibits compare until OCRL is written + // writing OCRH inhibits compare until OCRL is written // writing OCRL after reading TCR with OCF set clears OCF u8 const low(BIT(offset, 0)); diff --git a/src/emu/debug/debugcmd.cpp b/src/emu/debug/debugcmd.cpp index e7ca864988d..fe5bce41841 100644 --- a/src/emu/debug/debugcmd.cpp +++ b/src/emu/debug/debugcmd.cpp @@ -23,6 +23,7 @@ #include "emuopts.h" #include "natkeyboard.h" #include "render.h" +#include "softlist.h" #include "corestr.h" @@ -4126,9 +4127,26 @@ void debugger_commands::execute_images(const std::vector ¶ms) { image_interface_enumerator iter(m_machine.root_device()); for (device_image_interface &img : iter) - m_console.printf("%s: %s\n", img.brief_instance_name(), img.exists() ? img.filename() : "[empty slot]"); - if (iter.first() == nullptr) - m_console.printf("No image devices in this driver\n"); + { + if (!img.exists()) + { + m_console.printf("%s: [no media]\n", img.brief_instance_name()); + } + else if (img.loaded_through_softlist()) + { + m_console.printf("%s: %s:%s:%s\n", + img.brief_instance_name(), + img.software_list_name(), + img.software_entry()->shortname(), + img.part_entry()->name()); + } + else + { + m_console.printf("%s: %s\n", img.brief_instance_name(), img.filename()); + } + } + if (!iter.first()) + m_console.printf("No image devices present\n"); } /*------------------------------------------------- @@ -4137,21 +4155,18 @@ void debugger_commands::execute_images(const std::vector ¶ms) void debugger_commands::execute_mount(const std::vector ¶ms) { - bool done = false; for (device_image_interface &img : image_interface_enumerator(m_machine.root_device())) { - if (img.brief_instance_name() == params[0]) + if ((img.instance_name() == params[0]) || (img.brief_instance_name() == params[0])) { if (img.load(params[1]) != image_init_result::PASS) m_console.printf("Unable to mount file %s on %s\n", params[1], params[0]); else m_console.printf("File %s mounted on %s\n", params[1], params[0]); - done = true; - break; + return; } } - if (!done) - m_console.printf("There is no image device :%s\n", params[0]); + m_console.printf("No image instance %s\n", params[0]); } /*------------------------------------------------- @@ -4160,19 +4175,23 @@ void debugger_commands::execute_mount(const std::vector ¶ms) void debugger_commands::execute_unmount(const std::vector ¶ms) { - bool done = false; for (device_image_interface &img : image_interface_enumerator(m_machine.root_device())) { - if (img.brief_instance_name() == params[0]) + if ((img.instance_name() == params[0]) || (img.brief_instance_name() == params[0])) { - img.unload(); - m_console.printf("Unmounted file from : %s\n", params[0]); - done = true; - break; + if (img.exists()) + { + img.unload(); + m_console.printf("Unmounted media from %s\n", params[0]); + } + else + { + m_console.printf("No media mounted on %s\n", params[0]); + } + return; } } - if (!done) - m_console.printf("There is no image device :%s\n", params[0]); + m_console.printf("No image instance %s\n", params[0]); } diff --git a/src/emu/debug/debughlp.cpp b/src/emu/debug/debughlp.cpp index 13a0b2d1216..ef19f91c3a9 100644 --- a/src/emu/debug/debughlp.cpp +++ b/src/emu/debug/debughlp.cpp @@ -259,9 +259,9 @@ const help_item f_static_help_list[] = "Image Commands\n" "Type help for further details on each command\n" "\n" - " images -- lists all image devices and mounted files\n" - " mount , -- mounts file to named device\n" - " unmount -- unmounts file from named device\n" + " images -- lists all image devices and mounted mounted media\n" + " mount , -- mounts file to specified device\n" + " unmount -- unmounts media from specified device\n" }, { "do", @@ -1767,37 +1767,47 @@ const help_item f_static_help_list[] = "\n" " images\n" "\n" - "Used to display list of available image devices.\n" + "Lists the instance names for media images devices in the system and the currently mounted " + "media images, if any. Brief instance names, as allowed for command line media options, " + "are listed.\n" "\n" "Examples:\n" "\n" "images\n" - " Show list of devices and mounted files for current driver.\n" + " Lists image device instance names and mounted media.\n" }, { "mount", "\n" - " mount ,\n" + " mount ,\n" "\n" - "Mount to image .\n" - " can be softlist item or full path to file.\n" + "Mounts a file on a media device. The device may be specified by its instance name or " + "brief instance name, as allowed for command line media options.\n" + "\n" + "Some media devices allow software list items to be mounted using this command by supplying " + "the short name of the software list item in place of a filename for the " + "parameter.\n" "\n" "Examples:\n" "\n" - "mount cart,aladdin\n" - " Mounts softlist item aladdin on cart device.\n" + "mount flop1,os1xutls.td0\n" + " Mount the file 'os1xutls.td0' on the media device with instance name 'flop1'.\n" + "mount cart,10yard\n" + " Mount the software list item with short name '10yard' on the media device with instance " + "name 'cart'.\n" }, { "unmount", "\n" - " unmount \n" + " unmount \n" "\n" - "Unmounts file from image .\n" + "Unmounts the mounted media image (if any) from a device. The device may be specified by " + "its instance name or brief instance name, as allowed for command line media options.\n" "\n" "Examples:\n" "\n" "unmount cart\n" - " Unmounts any file mounted on device named cart.\n" + " Unmounts any media image mounted on the device with instance name 'cart'.\n" } }; diff --git a/src/emu/softlist.cpp b/src/emu/softlist.cpp index 72f299c4c29..4892a5ca5d8 100644 --- a/src/emu/softlist.cpp +++ b/src/emu/softlist.cpp @@ -15,6 +15,7 @@ #include "expat.h" +#include #include #include @@ -215,8 +216,8 @@ private: void unknown_attribute(const char *attrname) { parse_error("Unknown attribute: %s", attrname); } // internal helpers - template std::vector parse_attributes(const char **attributes, const T &attrlist); - bool parse_name_and_value(const char **attributes, std::string &name, std::string &value); + template std::array parse_attributes(const char **attributes, const char *const (&attrlist)[N]); + bool parse_name_and_value(const char **attributes, std::string_view &name, std::string_view &value); void add_rom_entry(std::string &&name, std::string &&hashdata, u32 offset, u32 length, u32 flags); // expat callbacks @@ -325,10 +326,10 @@ inline void softlist_parser::parse_error(Format &&fmt, Params &&... args) // attributes into a list of strings //------------------------------------------------- -template -std::vector softlist_parser::parse_attributes(const char **attributes, const T &attrlist) +template +std::array softlist_parser::parse_attributes(const char **attributes, const char *const (&attrlist)[N]) { - std::vector outlist(std::distance(std::begin(attrlist), std::end(attrlist))); + std::array outlist; // iterate over attribute/value pairs for( ; attributes[0]; attributes += 2) @@ -361,7 +362,7 @@ std::vector softlist_parser::parse_attributes(const char **attribut // latter to be defined as an empty string) //------------------------------------------------- -bool softlist_parser::parse_name_and_value(const char **attributes, std::string &name, std::string &value) +bool softlist_parser::parse_name_and_value(const char **attributes, std::string_view &name, std::string_view &value) { bool found_value = false; @@ -559,7 +560,7 @@ void softlist_parser::parse_main_start(const char *tagname, const char **attribu if (!attrvalues[0].empty()) { - m_infolist.emplace_back(std::move(attrvalues[0]), std::move(attrvalues[1]), attrvalues[2].c_str()); + m_infolist.emplace_back(std::string(attrvalues[0]), std::string(attrvalues[1]), attrvalues[2]); m_current_info = &m_infolist.back(); } else @@ -613,10 +614,10 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu // else if (strcmp(tagname, "info") == 0) { - std::string infoname, infovalue; + std::string_view infoname, infovalue; if (parse_name_and_value(attributes, infoname, infovalue)) - m_current_info->m_info.emplace_back(std::move(infoname), std::move(infovalue)); + m_current_info->m_info.emplace_back(std::string(infoname), std::string(infovalue)); else parse_error("Incomplete other_info definition"); } @@ -624,11 +625,11 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu // else if (strcmp(tagname, "sharedfeat") == 0) { - std::string featname, featvalue; + std::string_view featname, featvalue; if (parse_name_and_value(attributes, featname, featvalue)) { - if (!m_current_info->m_shared_features.emplace(std::move(featname), std::move(featvalue)).second) + if (!m_current_info->m_shared_features.emplace(std::string(featname), std::string(featvalue)).second) parse_error("Duplicate sharedfeat name"); } else @@ -645,7 +646,7 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu if (!attrvalues[0].empty() && !attrvalues[1].empty()) { - m_current_info->m_partdata.emplace_back(*m_current_info, std::move(attrvalues[0]), std::move(attrvalues[1])); + m_current_info->m_partdata.emplace_back(*m_current_info, std::string(attrvalues[0]), std::string(attrvalues[1])); m_current_part = &m_current_info->m_partdata.back(); } else @@ -679,8 +680,8 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu if (!attrvalues[0].empty() && !attrvalues[1].empty()) { // handle region attributes - const std::string &width = attrvalues[2]; - const std::string &endianness = attrvalues[3]; + const auto &width = attrvalues[2]; + const auto &endianness = attrvalues[3]; u32 regionflags = ROMENTRYTYPE_REGION; if (!width.empty()) @@ -706,7 +707,7 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu parse_error("Invalid dataarea endianness"); } - add_rom_entry(std::move(attrvalues[0]), "", 0, strtol(attrvalues[1].c_str(), nullptr, 0), regionflags); + add_rom_entry(std::string(attrvalues[0]), "", 0, strtol(attrvalues[1].data(), nullptr, 0), regionflags); } else parse_error("Incomplete dataarea definition"); @@ -719,7 +720,7 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu auto attrvalues = parse_attributes(attributes, attrnames); if (!attrvalues[0].empty()) - add_rom_entry(std::move(attrvalues[0]), "", 0, 1, ROMENTRYTYPE_REGION | ROMREGION_DATATYPEDISK); + add_rom_entry(std::string(attrvalues[0]), "", 0, 1, ROMENTRYTYPE_REGION | ROMREGION_DATATYPEDISK); else parse_error("Incomplete diskarea definition"); } @@ -727,11 +728,11 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu // else if (strcmp(tagname, "feature") == 0) { - std::string featname, featvalue; + std::string_view featname, featvalue; if (parse_name_and_value(attributes, featname, featvalue)) { - if (!m_current_part->m_features.emplace(std::move(featname), std::move(featvalue)).second) + if (!m_current_part->m_features.emplace(std::string(featname), std::string(featvalue)).second) parse_error("Duplicate feature name"); } else @@ -768,18 +769,18 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu static char const *const attrnames[] = { "name", "size", "crc", "sha1", "offset", "value", "status", "loadflag" }; auto attrvalues = parse_attributes(attributes, attrnames); - std::string &name = attrvalues[0]; - const std::string &sizestr = attrvalues[1]; - const std::string &crc = attrvalues[2]; - const std::string &sha1 = attrvalues[3]; - const std::string &offsetstr = attrvalues[4]; - std::string &value = attrvalues[5]; - const std::string &status = attrvalues[6]; - const std::string &loadflag = attrvalues[7]; + const std::string_view &name = attrvalues[0]; + const std::string_view &sizestr = attrvalues[1]; + const std::string_view &crc = attrvalues[2]; + const std::string_view &sha1 = attrvalues[3]; + const std::string_view &offsetstr = attrvalues[4]; + const std::string_view &value = attrvalues[5]; + const std::string_view &status = attrvalues[6]; + const std::string_view &loadflag = attrvalues[7]; if (!sizestr.empty()) { - u32 length = strtol(sizestr.c_str(), nullptr, 0); - u32 offset = offsetstr.empty() ? 0 : strtol(offsetstr.c_str(), nullptr, 0); + u32 length = strtol(sizestr.data(), nullptr, 0); + u32 offset = offsetstr.empty() ? 0 : strtol(offsetstr.data(), nullptr, 0); if (loadflag == "reload") add_rom_entry("", "", offset, length, ROMENTRYTYPE_RELOAD | ROM_INHERITFLAGS); @@ -788,7 +789,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu else if (loadflag == "continue") add_rom_entry("", "", offset, length, ROMENTRYTYPE_CONTINUE | ROM_INHERITFLAGS); else if (loadflag == "fill") - add_rom_entry("", std::move(value), offset, length, ROMENTRYTYPE_FILL); + add_rom_entry("", std::string(value), offset, length, ROMENTRYTYPE_FILL); else if (loadflag == "ignore") add_rom_entry("", "", 0, length, ROMENTRYTYPE_IGNORE | ROM_INHERITFLAGS); else if (!name.empty()) @@ -824,7 +825,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu else if (loadflag == "load32_byte") romflags = ROM_SKIP(3); - add_rom_entry(std::move(name), std::move(hashdata), offset, length, ROMENTRYTYPE_ROM | romflags); + add_rom_entry(std::string(name), std::move(hashdata), offset, length, ROMENTRYTYPE_ROM | romflags); } else parse_error("Rom name missing"); @@ -839,10 +840,10 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu static char const *const attrnames[] = { "name", "sha1", "status", "writeable" }; auto attrvalues = parse_attributes(attributes, attrnames); - std::string &name = attrvalues[0]; - const std::string &sha1 = attrvalues[1]; - const std::string &status = attrvalues[2]; - const std::string &writeablestr = attrvalues[3]; + const std::string_view &name = attrvalues[0]; + const std::string_view &sha1 = attrvalues[1]; + const std::string_view &status = attrvalues[2]; + const std::string_view &writeablestr = attrvalues[3]; if (!name.empty() && !sha1.empty()) { const bool baddump = (status == "baddump"); @@ -850,7 +851,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu const bool writeable = (writeablestr == "yes"); std::string hashdata = string_format("%c%s%s", util::hash_collection::HASH_SHA1, sha1, (nodump ? NO_DUMP : (baddump ? BAD_DUMP : ""))); - add_rom_entry(std::move(name), std::move(hashdata), 0, 0, ROMENTRYTYPE_ROM | (writeable ? DISK_READWRITE : DISK_READONLY)); + add_rom_entry(std::string(name), std::move(hashdata), 0, 0, ROMENTRYTYPE_ROM | (writeable ? DISK_READWRITE : DISK_READONLY)); } else if (status != "nodump") // a no_dump chd is not an incomplete entry parse_error("Incomplete disk definition"); diff --git a/src/frontend/mame/ui/auditmenu.cpp b/src/frontend/mame/ui/auditmenu.cpp index aa6ffed6a7b..7fda02764a0 100644 --- a/src/frontend/mame/ui/auditmenu.cpp +++ b/src/frontend/mame/ui/auditmenu.cpp @@ -11,13 +11,19 @@ #include "emu.h" #include "ui/auditmenu.h" +#include "ui/systemlist.h" #include "ui/ui.h" #include "audit.h" -#include "corestr.h" + #include "drivenum.h" +#include "uiinput.h" + +#include "util/corestr.h" #include +#include +#include extern const char UI_VERSION_TAG[]; @@ -32,19 +38,20 @@ void *const ITEMREF_START_FAST = reinterpret_cast(std::uintptr_t(2)); } // anonymous namespace -menu_audit::menu_audit(mame_ui_manager &mui, render_container &container, std::vector &availablesorted) +menu_audit::menu_audit(mame_ui_manager &mui, render_container &container) : menu(mui, container) - , m_worker_thread() + , m_availablesorted(system_list::instance().sorted_list()) , m_unavailable( std::accumulate( - availablesorted.begin(), - availablesorted.end(), + m_availablesorted.begin(), + m_availablesorted.end(), std::size_t(0), [] (std::size_t n, ui_system_info const &info) { return n + (info.available ? 0 : 1); })) - , m_availablesorted(availablesorted) + , m_future() , m_audited(0) , m_current(nullptr) - , m_phase(phase::CONSENT) + , m_cancel(false) + , m_phase(phase::CONFIRMATION) { std::string filename(emulator_info::get_configname()); filename += "_avail.ini"; @@ -53,13 +60,20 @@ menu_audit::menu_audit(mame_ui_manager &mui, render_container &container, std::v menu_audit::~menu_audit() { + m_cancel.store(true); + for (auto &future : m_future) + { + if (future.valid()) + future.wait(); + } } + void menu_audit::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) { switch (m_phase) { - case phase::CONSENT: + case phase::CONFIRMATION: if ((ITEMREF_START_FAST == selectedref) || (ITEMREF_START_FULL == selectedref)) { draw_text_box( @@ -74,21 +88,46 @@ void menu_audit::custom_render(void *selectedref, float top, float bottom, float { // there's a race here between the total audited being updated and the next driver pointer being loaded // it doesn't matter because we redraw on every frame anyway so it sorts itself out very quickly - game_driver const *const driver(m_current.load()); + ui_system_info const *const system(m_current.load()); std::size_t const audited(m_audited.load()); std::size_t const total(m_fast ? m_unavailable : m_availablesorted.size()); - std::string const text( - util::string_format( - _("Auditing ROMs for machine %2$u of %3$u...\n%1$s"), - driver ? driver->type.fullname() : "", - (std::min)(audited + 1, total), - total)); - ui().draw_text_box(container(), text, ui::text_layout::CENTER, 0.5f, 0.5f, UI_GREEN_COLOR); + std::ostringstream text; + util::stream_format(text, + _("Auditing ROMs for machine %2$u of %3$u...\n%1$s"), + system ? std::string_view(system->description) : std::string_view(), + (std::min)(audited + 1, total), + total); + text << '\n' << m_prompt; + ui().draw_text_box( + container(), + std::move(text).str(), + ui::text_layout::CENTER, + 0.5f, 0.5f, + ui().colors().background_color()); } break; + + case phase::CANCELLATION: + ui().draw_text_box( + container(), + util::string_format( + _("Cancel audit?\n\nPress %1$s to cancel\nPress %2$s to continue"), + ui().get_general_input_setting(IPT_UI_SELECT), + ui().get_general_input_setting(IPT_UI_CANCEL)), + ui::text_layout::CENTER, + 0.5f, 0.5f, + UI_RED_COLOR); + break; } } + +bool menu_audit::custom_ui_cancel() +{ + return m_phase != phase::CONFIRMATION; +} + + void menu_audit::populate(float &customtop, float &custombottom) { if (m_unavailable && (m_availablesorted.size() != m_unavailable)) @@ -102,7 +141,7 @@ void menu_audit::handle() { switch (m_phase) { - case phase::CONSENT: + case phase::CONFIRMATION: { event const *const menu_event(process(0)); if (menu_event && (IPT_UI_SELECT == menu_event->iptkey)) @@ -111,40 +150,62 @@ void menu_audit::handle() { m_phase = phase::AUDIT; m_fast = ITEMREF_START_FAST == menu_event->itemref; - m_worker_thread = std::thread( - [this] () - { - if (m_fast) - audit_fast(); - else - audit_all(); - }); + m_prompt = util::string_format(_("Press %1$s to cancel\n"), ui().get_general_input_setting(IPT_UI_CANCEL)); + m_future.resize(std::thread::hardware_concurrency()); + for (auto &future : m_future) + future = std::async(std::launch::async, [this] () { return do_audit(); }); } } } break; case phase::AUDIT: + case phase::CANCELLATION: process(PROCESS_CUSTOM_ONLY | PROCESS_NOINPUT); - if (m_audited.load() >= (m_fast ? m_unavailable : m_availablesorted.size())) + if ((m_audited.load() >= (m_fast ? m_unavailable : m_availablesorted.size())) || m_cancel.load()) { - m_worker_thread.join(); - save_available_machines(); - reset_parent(reset_options::SELECT_FIRST); + bool done(true); + for (auto &future : m_future) + done = future.get() && done; + m_future.clear(); + if (done) + { + save_available_machines(); + reset_parent(reset_options::SELECT_FIRST); + } stack_pop(); } + else if (machine().ui_input().pressed(IPT_UI_CANCEL)) + { + if (phase::AUDIT == m_phase) + m_phase = phase::CANCELLATION; + else + m_phase = phase::AUDIT; + } + else if ((phase::CANCELLATION == m_phase) && machine().ui_input().pressed(IPT_UI_SELECT)) + { + m_cancel.store(true); + } break; } } -void menu_audit::audit_fast() +bool menu_audit::do_audit() { - for (ui_system_info &info : m_availablesorted) + while (true) { - if (!info.available) + std::size_t const i(m_audited.fetch_add(1)); + if (m_availablesorted.size() <= i) + return true; + + ui_system_info &info(m_availablesorted[i]); + if (!m_fast || !info.available) { - m_current.store(info.driver); + if (m_cancel.load()) + return false; + + m_current.store(&info); driver_enumerator enumerator(machine().options(), info.driver->name); enumerator.next(); media_auditor auditor(enumerator); @@ -158,25 +219,6 @@ void menu_audit::audit_fast() } } -void menu_audit::audit_all() -{ - driver_enumerator enumerator(machine().options()); - media_auditor auditor(enumerator); - std::vector available(driver_list::total(), false); - while (enumerator.next()) - { - m_current.store(&enumerator.driver()); - media_auditor::summary const summary(auditor.audit_media(AUDIT_VALIDATE_FAST)); - - // if everything looks good, include the driver - available[enumerator.current()] = (summary == media_auditor::CORRECT) || (summary == media_auditor::BEST_AVAILABLE) || (summary == media_auditor::NONE_NEEDED); - ++m_audited; - } - - for (ui_system_info &info : m_availablesorted) - info.available = available[info.index]; -} - void menu_audit::save_available_machines() { // attempt to open the output file diff --git a/src/frontend/mame/ui/auditmenu.h b/src/frontend/mame/ui/auditmenu.h index a9734e71709..6536c3a19aa 100644 --- a/src/frontend/mame/ui/auditmenu.h +++ b/src/frontend/mame/ui/auditmenu.h @@ -16,7 +16,7 @@ #include "ui/utils.h" #include -#include +#include #include @@ -25,28 +25,29 @@ namespace ui { class menu_audit : public menu { public: - menu_audit(mame_ui_manager &mui, render_container &container, std::vector &availablesorted); + menu_audit(mame_ui_manager &mui, render_container &container); virtual ~menu_audit() override; protected: virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual bool custom_ui_cancel() override; private: - enum class phase { CONSENT, AUDIT }; + enum class phase { CONFIRMATION, AUDIT, CANCELLATION }; virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; - void audit_fast(); - void audit_all(); + bool do_audit(); void save_available_machines(); - std::thread m_worker_thread; - std::size_t const m_unavailable; std::string m_prompt; - std::vector &m_availablesorted; + std::vector > const &m_availablesorted; + std::size_t const m_unavailable; + std::vector > m_future; std::atomic m_audited; - std::atomic m_current; + std::atomic m_current; + std::atomic m_cancel; phase m_phase; bool m_fast; }; diff --git a/src/frontend/mame/ui/confswitch.cpp b/src/frontend/mame/ui/confswitch.cpp index ee15d6d5ad5..b162b57efe5 100644 --- a/src/frontend/mame/ui/confswitch.cpp +++ b/src/frontend/mame/ui/confswitch.cpp @@ -51,6 +51,7 @@ menu_confswitch::switch_group_descriptor::switch_group_descriptor(ioport_field c , mask(0U) , state(0U) { + std::fill(std::begin(toggles), std::end(toggles), toggle{ nullptr, 0U }); } @@ -146,8 +147,10 @@ void menu_confswitch::populate(float &customtop, float &custombottom) ++m_active_switch_groups; // apply the bits - group.mask |= uint32_t(1) << (loc.number() - 1); ioport_value const mask(accummask & ~(accummask - 1)); + group.toggles[loc.number() - 1].field = &field; + group.toggles[loc.number() - 1].mask = mask; + group.mask |= uint32_t(1) << (loc.number() - 1); if (((settings.value & mask) && !loc.inverted()) || (!(settings.value & mask) && loc.inverted())) group.state |= uint32_t(1) << (loc.number() - 1); @@ -254,7 +257,12 @@ void menu_confswitch::find_fields() menu_settings_dip_switches::menu_settings_dip_switches(mame_ui_manager &mui, render_container &container) : menu_confswitch(mui, container, IPT_DIPSWITCH) + , m_switch_group_y() , m_visible_switch_groups(0U) + , m_single_width(0.0f) + , m_nub_width(0.0f) + , m_first_nub(0.0f) + , m_clickable_height(0.0f) { } @@ -266,15 +274,15 @@ menu_settings_dip_switches::~menu_settings_dip_switches() void menu_settings_dip_switches::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) { - // catch if no diploc has to be drawn + // catch if no DIP locations have to be drawn if (!m_visible_switch_groups) return; // calculate optimal width float const aspect(machine().render().ui_aspect(&container())); float const lineheight(ui().get_line_height()); - float const singlewidth(lineheight * SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * aspect); float const maxwidth(1.0f - (ui().box_lr_border() * 2.0f * aspect)); + m_single_width = (lineheight * SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * aspect); float width(0.0f); unsigned maxswitches(0U); for (switch_group_descriptor const &group : switch_groups()) @@ -283,30 +291,33 @@ void menu_settings_dip_switches::custom_render(void *selectedref, float top, flo { maxswitches = (std::max)(group.switch_count(), maxswitches); float const namewidth(ui().get_string_width(group.name)); - float const switchwidth(singlewidth * maxswitches); + float const switchwidth(m_single_width * maxswitches); width = (std::min)((std::max)(namewidth + switchwidth + (lineheight * aspect), width), maxwidth); } } // draw extra menu area float const boxwidth((std::max)(width + (ui().box_lr_border() * 2.0f * aspect), x2 - x1)); - float const boxleft((1.0f - boxwidth) / 2.0f); + float const boxleft((1.0f - boxwidth) * 0.5f); ui().draw_outlined_box(container(), boxleft, y2 + ui().box_tb_border(), boxleft + boxwidth, y2 + bottom, ui().colors().background_color()); // calculate centred layout - float const nameleft((1.0f - width) / 2.0f); - float const switchleft(nameleft + width - (singlewidth * maxswitches)); - float const namewidth(width - (singlewidth * maxswitches) - (lineheight * aspect)); + float const nameleft((1.0f - width) * 0.5f); + float const switchleft(nameleft + width - (m_single_width * maxswitches)); + float const namewidth(width - (m_single_width * maxswitches) - (lineheight * aspect)); // iterate over switch groups ioport_field *const field((uintptr_t(selectedref) != 1U) ? reinterpret_cast(selectedref) : nullptr); float const nubheight(lineheight * SINGLE_TOGGLE_SWITCH_HEIGHT); - float const nubwidth(lineheight * SINGLE_TOGGLE_SWITCH_WIDTH * aspect); - float const ygap(lineheight * ((DIP_SWITCH_HEIGHT / 2) - SINGLE_TOGGLE_SWITCH_HEIGHT) / 2); - float const xgap((singlewidth + (UI_LINE_WIDTH / 2) - nubwidth) / 2); + m_nub_width = lineheight * SINGLE_TOGGLE_SWITCH_WIDTH * aspect; + float const ygap(lineheight * ((DIP_SWITCH_HEIGHT * 0.5f) - SINGLE_TOGGLE_SWITCH_HEIGHT) * 0.5f); + float const xgap((m_single_width + (UI_LINE_WIDTH * 0.5f) - m_nub_width) * 0.5f); + m_first_nub = switchleft + xgap; + m_clickable_height = (lineheight * DIP_SWITCH_HEIGHT) - (ygap * 2.0f); unsigned line(0U); - for (switch_group_descriptor const &group : switch_groups()) + for (unsigned n = 0; switch_groups().size() > n; ++n) { + switch_group_descriptor const &group(switch_groups()[n]); if (group.mask) { // determine the mask of selected bits @@ -332,34 +343,35 @@ void menu_settings_dip_switches::custom_render(void *selectedref, float top, flo unsigned const cnt(group.switch_count()); ui().draw_outlined_box( container(), - switchleft, liney, switchleft + (singlewidth * cnt), switchbottom, + switchleft, liney, switchleft + (m_single_width * cnt), switchbottom, ui().colors().background_color()); for (unsigned i = 1; cnt > i; ++i) { container().add_line( - switchleft + (singlewidth * i), liney, switchleft + (singlewidth * i), switchbottom, + switchleft + (m_single_width * i), liney, switchleft + (m_single_width * i), switchbottom, UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } // compute top and bottom for on and off positions float const yoff(liney + UI_LINE_WIDTH + ygap); float const yon(switchbottom - UI_LINE_WIDTH - ygap - nubheight); + m_switch_group_y[n] = yoff; // draw the switch nubs for (unsigned toggle = 0; cnt > toggle; ++toggle) { - float const nubleft(switchleft + (singlewidth * toggle) + xgap); + float const nubleft(switchleft + (m_single_width * toggle) + xgap); if (BIT(group.mask, toggle)) { float const nubtop(BIT(group.state, toggle) ? yon : yoff); container().add_rect( - nubleft, nubtop, nubleft + nubwidth, nubtop + nubheight, + nubleft, nubtop, nubleft + m_nub_width, nubtop + nubheight, BIT(selectedmask, toggle) ? ui().colors().dipsw_color() : ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } else { container().add_rect( - nubleft, yoff, nubleft + nubwidth, yon + nubheight, + nubleft, yoff, nubleft + m_nub_width, yon + nubheight, ui().colors().unavailable_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } @@ -372,6 +384,42 @@ void menu_settings_dip_switches::custom_render(void *selectedref, float top, flo } +bool menu_settings_dip_switches::custom_mouse_down() +{ + if (!m_visible_switch_groups || (get_mouse_x() < m_first_nub)) + return false; + + float const x(get_mouse_x() - m_first_nub); + float const y(get_mouse_y()); + for (unsigned n = 0U; switch_groups().size() > n; ++n) + { + switch_group_descriptor const &group(switch_groups()[n]); + if (group.mask && (y >= m_switch_group_y[n]) && (y < (m_switch_group_y[n] + m_clickable_height))) + { + unsigned const cnt(group.switch_count()); + for (unsigned i = 0U; cnt > i; ++i) + { + if (BIT(group.mask, i)) + { + float const xstart(float(i) * m_single_width); + if ((x >= xstart) && (x < (xstart + m_nub_width))) + { + ioport_field::user_settings settings; + group.toggles[i].field->get_user_settings(settings); + settings.value ^= group.toggles[i].mask; + group.toggles[i].field->set_user_settings(settings); + reset(reset_options::REMEMBER_REF); + return true; + } + } + } + } + } + + return false; +} + + void menu_settings_dip_switches::populate(float &customtop, float &custombottom) { // let the base class add items @@ -380,6 +428,7 @@ void menu_settings_dip_switches::populate(float &customtop, float &custombottom) // use up to about 70% of height for DIP switch display if (active_switch_groups()) { + m_switch_group_y.resize(switch_groups().size()); float const lineheight(ui().get_line_height()); float const groupheight(DIP_SWITCH_HEIGHT * lineheight); float const groupspacing(DIP_SWITCH_SPACING * lineheight); diff --git a/src/frontend/mame/ui/confswitch.h b/src/frontend/mame/ui/confswitch.h index 0c4506abbf2..5ece14ae4c1 100644 --- a/src/frontend/mame/ui/confswitch.h +++ b/src/frontend/mame/ui/confswitch.h @@ -33,6 +33,12 @@ protected: struct switch_group_descriptor { + struct toggle + { + ioport_field *field; + ioport_value mask; + }; + switch_group_descriptor(ioport_field const &f, ioport_diplocation const &loc) noexcept; bool matches(ioport_field const &f, ioport_diplocation const &loc) const noexcept; @@ -40,6 +46,7 @@ protected: char const *name; std::reference_wrapper owner; + toggle toggles[32]; uint32_t mask; uint32_t state; }; @@ -75,11 +82,17 @@ public: protected: virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual bool custom_mouse_down() override; private: virtual void populate(float &customtop, float &custombottom) override; + std::vector m_switch_group_y; unsigned m_visible_switch_groups; + float m_single_width; + float m_nub_width; + float m_first_nub; + float m_clickable_height; }; diff --git a/src/frontend/mame/ui/custui.cpp b/src/frontend/mame/ui/custui.cpp index e100067bb46..94dbc8a898d 100644 --- a/src/frontend/mame/ui/custui.cpp +++ b/src/frontend/mame/ui/custui.cpp @@ -618,7 +618,7 @@ void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, f // bottom text // get the text for 'UI Select' - std::string const bottomtext[] = { util::string_format(_("Double-click or press %1$s to change the color value"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD))) }; + std::string const bottomtext[] = { util::string_format(_("Double-click or press %1$s to change the color value"), ui().get_general_input_setting(IPT_UI_SELECT)) }; draw_text_box( std::begin(bottomtext), std::end(bottomtext), origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, @@ -839,13 +839,12 @@ void menu_rgb_ui::handle() void menu_rgb_ui::populate(float &customtop, float &custombottom) { // set filter arrow - uint32_t arrow_flags = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; std::string s_text = std::string(m_search).append("_"); item_append(_("ARGB Settings"), FLAG_DISABLE | FLAG_UI_HEADING, nullptr); if (m_lock_ref != RGB_ALPHA) { - arrow_flags = get_arrow_flags(0, 255, m_color->a()); + uint32_t arrow_flags = get_arrow_flags(0, 255, m_color->a()); item_append(_("color-channel", "Alpha"), string_format("%3u", m_color->a()), arrow_flags, (void *)(uintptr_t)RGB_ALPHA); } else @@ -853,7 +852,7 @@ void menu_rgb_ui::populate(float &customtop, float &custombottom) if (m_lock_ref != RGB_RED) { - arrow_flags = get_arrow_flags(0, 255, m_color->r()); + uint32_t arrow_flags = get_arrow_flags(0, 255, m_color->r()); item_append(_("color-channel", "Red"), string_format("%3u", m_color->r()), arrow_flags, (void *)(uintptr_t)RGB_RED); } else @@ -861,7 +860,7 @@ void menu_rgb_ui::populate(float &customtop, float &custombottom) if (m_lock_ref != RGB_GREEN) { - arrow_flags = get_arrow_flags(0, 255, m_color->g()); + uint32_t arrow_flags = get_arrow_flags(0, 255, m_color->g()); item_append(_("color-channel", "Green"), string_format("%3u", m_color->g()), arrow_flags, (void *)(uintptr_t)RGB_GREEN); } else @@ -869,7 +868,7 @@ void menu_rgb_ui::populate(float &customtop, float &custombottom) if (m_lock_ref != RGB_BLUE) { - arrow_flags = get_arrow_flags(0, 255, m_color->b()); + uint32_t arrow_flags = get_arrow_flags(0, 255, m_color->b()); item_append(_("color-channel", "Blue"), string_format("%3u", m_color->b()), arrow_flags, (void *)(uintptr_t)RGB_BLUE); } else diff --git a/src/frontend/mame/ui/datmenu.cpp b/src/frontend/mame/ui/datmenu.cpp index f28651afdb5..a707c76459e 100644 --- a/src/frontend/mame/ui/datmenu.cpp +++ b/src/frontend/mame/ui/datmenu.cpp @@ -9,18 +9,22 @@ *********************************************************************/ #include "emu.h" +#include "ui/datmenu.h" +#include "ui/systemlist.h" #include "ui/ui.h" -#include "ui/datmenu.h" #include "ui/utils.h" +#include "luaengine.h" #include "mame.h" + +#include "drivenum.h" #include "rendfont.h" #include "softlist.h" #include "uiinput.h" -#include "luaengine.h" #include +#include namespace ui { @@ -29,10 +33,10 @@ namespace ui { // ctor / dtor //------------------------------------------------- -menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const game_driver *driver) +menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system) : menu(mui, container) , m_actual(0) - , m_driver((driver == nullptr) ? &mui.machine().system() : driver) + , m_system(!system ? &system_list::instance().systems()[driver_list::find(mui.machine().system().name)] : system) , m_swinfo(nullptr) , m_issoft(false) @@ -48,7 +52,7 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container } } std::vector lua_list; - if (mame_machine_manager::instance()->lua()->call_plugin("data_list", driver ? driver->name : "", lua_list)) + if (mame_machine_manager::instance()->lua()->call_plugin("data_list", system ? system->driver->name : "", lua_list)) { int count = 0; for (std::string& item : lua_list) @@ -65,10 +69,10 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container // ctor //------------------------------------------------- -menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info *swinfo, const game_driver *driver) +menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info *swinfo, const ui_system_info *system) : menu(mui, container) , m_actual(0) - , m_driver((driver == nullptr) ? &mui.machine().system() : driver) + , m_system(!system ? &system_list::instance().systems()[driver_list::find(mui.machine().system().name)] : system) , m_swinfo(swinfo) , m_list(swinfo->listname) , m_short(swinfo->shortname) @@ -295,7 +299,7 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f { float maxwidth = origx2 - origx1; float width; - std::string driver = (m_issoft == true) ? m_swinfo->longname : m_driver->type.fullname(); + std::string_view driver = m_issoft ? m_swinfo->longname : m_system->description; float const lr_border = ui().box_lr_border() * machine().render().ui_aspect(&container()); ui().draw_text_full(container(), driver, 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, diff --git a/src/frontend/mame/ui/datmenu.h b/src/frontend/mame/ui/datmenu.h index 0c7fd9c22e7..45de4f423a5 100644 --- a/src/frontend/mame/ui/datmenu.h +++ b/src/frontend/mame/ui/datmenu.h @@ -21,6 +21,8 @@ struct ui_software_info; +struct ui_system_info; + namespace ui { @@ -31,8 +33,8 @@ namespace ui { class menu_dats_view : public menu { public: - menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info *swinfo, const game_driver *driver = nullptr); - menu_dats_view(mame_ui_manager &mui, render_container &container, const game_driver *driver = nullptr); + menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info *swinfo, const ui_system_info *system = nullptr); + menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system = nullptr); virtual ~menu_dats_view() override; protected: @@ -47,7 +49,7 @@ private: virtual void handle() override; int m_actual; - const game_driver *m_driver; + const ui_system_info *m_system; const ui_software_info *m_swinfo; std::string m_list, m_short, m_long, m_parent; void get_data(); diff --git a/src/frontend/mame/ui/dirmenu.cpp b/src/frontend/mame/ui/dirmenu.cpp index 1fe6682b123..a361bfd325f 100644 --- a/src/frontend/mame/ui/dirmenu.cpp +++ b/src/frontend/mame/ui/dirmenu.cpp @@ -22,6 +22,7 @@ namespace ui { + static int ADDING = 1; static int CHANGE = 2; diff --git a/src/frontend/mame/ui/dirmenu.h b/src/frontend/mame/ui/dirmenu.h index 7280d4ef571..5d794319b7c 100644 --- a/src/frontend/mame/ui/dirmenu.h +++ b/src/frontend/mame/ui/dirmenu.h @@ -100,7 +100,7 @@ public: protected: virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool menu_has_search_active() override { return !m_search.empty(); } + virtual bool custom_ui_cancel() override { return !m_search.empty(); } private: virtual void populate(float &customtop, float &custombottom) override; diff --git a/src/frontend/mame/ui/filesel.cpp b/src/frontend/mame/ui/filesel.cpp index 41ecac49c16..1ddabffbb23 100644 --- a/src/frontend/mame/ui/filesel.cpp +++ b/src/frontend/mame/ui/filesel.cpp @@ -347,7 +347,7 @@ void menu_file_selector::type_search_char(char32_t ch) std::string const current(m_filename); if (input_character(m_filename, ch, uchar_is_printable)) { - ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename.c_str()); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename); file_selector_entry const *const cur_selected(reinterpret_cast(get_selection_ref())); @@ -482,17 +482,9 @@ void menu_file_selector::handle() { // process the menu event const *const event = process(0); - if (event && event->itemref) + if (event) { - // handle selections - if (event->iptkey == IPT_UI_SELECT) - { - select_item(*reinterpret_cast(event->itemref)); - - // reset the char buffer when pressing IPT_UI_SELECT - m_filename.clear(); - } - else if (event->iptkey == IPT_SPECIAL) + if (event->iptkey == IPT_SPECIAL) { // if it's any other key and we're not maxed out, update type_search_char(event->unichar); @@ -500,6 +492,18 @@ void menu_file_selector::handle() else if (event->iptkey == IPT_UI_CANCEL) { // reset the char buffer also in this case + if (!m_filename.empty()) + { + m_filename.clear(); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename); + } + } + else if (event->itemref && (event->iptkey == IPT_UI_SELECT)) + { + // handle selections + select_item(*reinterpret_cast(event->itemref)); + + // reset the char buffer when pressing IPT_UI_SELECT m_filename.clear(); } } diff --git a/src/frontend/mame/ui/filesel.h b/src/frontend/mame/ui/filesel.h index 768cf1ba55e..8eefce9462a 100644 --- a/src/frontend/mame/ui/filesel.h +++ b/src/frontend/mame/ui/filesel.h @@ -45,6 +45,7 @@ public: protected: virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual bool custom_ui_cancel() override { return !m_filename.empty(); } virtual bool custom_mouse_down() override; private: diff --git a/src/frontend/mame/ui/info.cpp b/src/frontend/mame/ui/info.cpp index e3f557ed54e..0dc8b8979e9 100644 --- a/src/frontend/mame/ui/info.cpp +++ b/src/frontend/mame/ui/info.cpp @@ -9,8 +9,9 @@ ***************************************************************************/ #include "emu.h" - #include "ui/info.h" + +#include "ui/systemlist.h" #include "ui/ui.h" #include "drivenum.h" @@ -337,7 +338,7 @@ std::string machine_info::game_info_string() const // print description, manufacturer, and CPU: util::stream_format(buf, _("%1$s\n%2$s %3$s\nDriver: %4$s\n\nCPU:\n"), - m_machine.system().type.fullname(), + system_list::instance().systems()[driver_list::find(m_machine.system().name)].description, m_machine.system().year, m_machine.system().manufacturer, core_filename_extract_base(m_machine.system().type.source())); @@ -538,7 +539,8 @@ menu_image_info::~menu_image_info() void menu_image_info::populate(float &customtop, float &custombottom) { - item_append(machine().system().type.fullname(), FLAG_DISABLE, nullptr); + ui_system_info const &system(system_list::instance().systems()[driver_list::find(machine().system().name)]); + item_append(system.description, FLAG_DISABLE, nullptr); item_append(std::string(), FLAG_DISABLE, nullptr); for (device_image_interface &image : image_interface_enumerator(machine().root_device())) diff --git a/src/frontend/mame/ui/inputmap.cpp b/src/frontend/mame/ui/inputmap.cpp index 0f4c269b92b..4d2c2c9bb8d 100644 --- a/src/frontend/mame/ui/inputmap.cpp +++ b/src/frontend/mame/ui/inputmap.cpp @@ -510,10 +510,10 @@ void menu_input::populate_sorted(float &customtop, float &custombottom) } // pre-format messages - assignprompt = util::string_format(_("Press %1$s to set\n"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT))); - appendprompt = util::string_format(_("Press %1$s to append\n"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT))); - clearprompt = util::string_format(_("Press %1$s to clear\n"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_CLEAR))); - defaultprompt = util::string_format(_("Press %1$s to restore default\n"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_CLEAR))); + assignprompt = util::string_format(_("Press %1$s to set\n"), ui().get_general_input_setting(IPT_UI_SELECT)); + appendprompt = util::string_format(_("Press %1$s to append\n"), ui().get_general_input_setting(IPT_UI_SELECT)); + clearprompt = util::string_format(_("Press %1$s to clear\n"), ui().get_general_input_setting(IPT_UI_CLEAR)); + defaultprompt = util::string_format(_("Press %1$s to restore default\n"), ui().get_general_input_setting(IPT_UI_CLEAR)); // leave space for showing the input sequence below the menu custombottom = 2.0f * ui().get_line_height() + 3.0f * ui().box_tb_border(); diff --git a/src/frontend/mame/ui/menu.cpp b/src/frontend/mame/ui/menu.cpp index d3caec1b2c2..5b2bb73b7ad 100644 --- a/src/frontend/mame/ui/menu.cpp +++ b/src/frontend/mame/ui/menu.cpp @@ -515,10 +515,12 @@ void menu::draw(uint32_t flags) if (top_line < 0 || is_first_selected()) top_line = 0; - if (m_selected >= (top_line + m_visible_lines)) + else if (m_selected >= (top_line + m_visible_lines)) top_line = m_selected - (m_visible_lines / 2); if ((top_line > (m_items.size() - m_visible_lines)) || is_last_selected()) top_line = m_items.size() - m_visible_lines; + else if (m_selected >= (top_line + m_visible_lines - 2)) + top_line = m_selected - m_visible_lines + ((m_selected == (m_items.size() - 1)) ? 1: 2); // if scrolling, show arrows bool const show_top_arrow((m_items.size() > m_visible_lines) && !first_item_visible()); @@ -1013,7 +1015,7 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // hitting cancel also pops the stack if (exclusive_input_pressed(iptkey, IPT_UI_CANCEL, 0)) { - if (!menu_has_search_active()) + if (!custom_ui_cancel()) stack_pop(); return; } diff --git a/src/frontend/mame/ui/menu.h b/src/frontend/mame/ui/menu.h index b509bff49ed..e0f5cbf9f0d 100644 --- a/src/frontend/mame/ui/menu.h +++ b/src/frontend/mame/ui/menu.h @@ -276,11 +276,9 @@ protected: // overridable event handling virtual void handle_events(uint32_t flags, event &ev); virtual void handle_keys(uint32_t flags, int &iptkey); + virtual bool custom_ui_cancel() { return false; } virtual bool custom_mouse_down() { return false; } - // test if search is active - virtual bool menu_has_search_active() { return false; } - static bool is_selectable(menu_item const &item) { return ((item.flags & (menu::FLAG_MULTILINE | menu::FLAG_DISABLE)) == 0 && item.type != menu_item_type::SEPARATOR); diff --git a/src/frontend/mame/ui/miscmenu.cpp b/src/frontend/mame/ui/miscmenu.cpp index b7b26f62ee0..09c63d591c8 100644 --- a/src/frontend/mame/ui/miscmenu.cpp +++ b/src/frontend/mame/ui/miscmenu.cpp @@ -706,41 +706,19 @@ menu_machine_configure::menu_machine_configure( ui_system_info const &info, std::function &&handler, float x0, float y0) - : menu_machine_configure(mui, container, info.description.c_str(), *info.driver, std::move(handler), x0, y0) -{ -} - -menu_machine_configure::menu_machine_configure( - mame_ui_manager &mui, - render_container &container, - game_driver const &drv, - std::function &&handler, - float x0, float y0) - : menu_machine_configure(mui, container, drv.type.fullname(), drv, std::move(handler), x0, y0) -{ -} - -menu_machine_configure::menu_machine_configure( - mame_ui_manager &mui, - render_container &container, - char const *description, - game_driver const &drv, - std::function &&handler, - float x0, float y0) : menu(mui, container) , m_handler(std::move(handler)) - , m_description(description) - , m_drv(drv) + , m_sys(info) , m_x0(x0) , m_y0(y0) , m_curbios(0) - , m_was_favorite(mame_machine_manager::instance()->favorite().is_favorite_system(drv)) + , m_was_favorite(mame_machine_manager::instance()->favorite().is_favorite_system(*info.driver)) , m_want_favorite(m_was_favorite) { // parse the INI file std::ostringstream error; osd_setup_osd_specific_emu_options(m_opts); - mame_options::parse_standard_inis(m_opts, error, &m_drv); + mame_options::parse_standard_inis(m_opts, error, m_sys.driver); setup_bios(); } @@ -749,9 +727,9 @@ menu_machine_configure::~menu_machine_configure() if (m_was_favorite != m_want_favorite) { if (m_want_favorite) - mame_machine_manager::instance()->favorite().add_favorite_system(m_drv); + mame_machine_manager::instance()->favorite().add_favorite_system(*m_sys.driver); else - mame_machine_manager::instance()->favorite().remove_favorite_system(m_drv); + mame_machine_manager::instance()->favorite().remove_favorite_system(*m_sys.driver); } if (m_handler) @@ -775,7 +753,7 @@ void menu_machine_configure::handle() { case SAVE: { - const std::string filename(m_drv.name); + const std::string filename(m_sys.driver->name); emu_file file(machine().options().ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE); std::error_condition const filerr = file.open(filename + ".ini"); if (!filerr) @@ -796,15 +774,15 @@ void menu_machine_configure::handle() break; case CONTROLLER: if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container(), submenu::control_options(), &m_drv, &m_opts); + menu::stack_push(ui(), container(), submenu::control_options(), m_sys.driver, &m_opts); break; case VIDEO: if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container(), submenu::video_options(), &m_drv, &m_opts); + menu::stack_push(ui(), container(), submenu::video_options(), m_sys.driver, &m_opts); break; case ADVANCED: if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container(), submenu::advanced_options(), &m_drv, &m_opts); + menu::stack_push(ui(), container(), submenu::advanced_options(), m_sys.driver, &m_opts); break; default: break; @@ -858,7 +836,7 @@ void menu_machine_configure::populate(float &customtop, float &custombottom) void menu_machine_configure::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - char const *const text[] = { _("Configure Machine:"), m_description }; + char const *const text[] = { _("Configure Machine:"), m_sys.description.c_str() }; draw_text_box( std::begin(text), std::end(text), origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(), @@ -868,19 +846,19 @@ void menu_machine_configure::custom_render(void *selectedref, float top, float b void menu_machine_configure::setup_bios() { - if (!m_drv.rom) + if (!m_sys.driver->rom) return; std::string specbios(m_opts.bios()); char const *default_name(nullptr); - for (tiny_rom_entry const *rom = m_drv.rom; !ROMENTRY_ISEND(rom); ++rom) + for (tiny_rom_entry const *rom = m_sys.driver->rom; !ROMENTRY_ISEND(rom); ++rom) { if (ROMENTRY_ISDEFAULT_BIOS(rom)) default_name = rom->name; } std::size_t bios_count = 0; - for (romload::system_bios const &bios : romload::entries(m_drv.rom).get_system_bioses()) + for (romload::system_bios const &bios : romload::entries(m_sys.driver->rom).get_system_bioses()) { std::string name(bios.get_description()); u32 const bios_flags(bios.get_value()); diff --git a/src/frontend/mame/ui/miscmenu.h b/src/frontend/mame/ui/miscmenu.h index 5fdf7fb8a8e..78ed68cc35a 100644 --- a/src/frontend/mame/ui/miscmenu.h +++ b/src/frontend/mame/ui/miscmenu.h @@ -136,12 +136,6 @@ public: ui_system_info const &info, std::function &&handler = nullptr, float x0 = 0.0f, float y0 = 0.0f); - menu_machine_configure( - mame_ui_manager &mui, - render_container &container, - game_driver const &drv, - std::function &&handler = nullptr, - float x0 = 0.0f, float y0 = 0.0f); virtual ~menu_machine_configure(); protected: @@ -162,22 +156,13 @@ private: LAST = ADVANCED }; - menu_machine_configure( - mame_ui_manager &mui, - render_container &container, - char const *description, - game_driver const &drv, - std::function &&handler, - float x0, float y0); - virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; void setup_bios(); std::function const m_handler; - char const *const m_description; - game_driver const &m_drv; + ui_system_info const &m_sys; emu_options m_opts; float const m_x0; float const m_y0; diff --git a/src/frontend/mame/ui/pluginopt.cpp b/src/frontend/mame/ui/pluginopt.cpp index 90522bc48ae..83faaa43603 100644 --- a/src/frontend/mame/ui/pluginopt.cpp +++ b/src/frontend/mame/ui/pluginopt.cpp @@ -9,12 +9,12 @@ *********************************************************************/ #include "emu.h" +#include "pluginopt.h" -#include "ui/pluginopt.h" #include "ui/utils.h" -#include "mame.h" #include "luaengine.h" +#include "mame.h" namespace ui { @@ -23,7 +23,7 @@ void menu_plugin::handle() { const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (menu_event && menu_event->itemref) { if (menu_event->iptkey == IPT_UI_SELECT) menu::stack_push(ui(), container(), (char *)menu_event->itemref); @@ -31,8 +31,8 @@ void menu_plugin::handle() } menu_plugin::menu_plugin(mame_ui_manager &mui, render_container &container) : - menu(mui, container), - m_plugins(mame_machine_manager::instance()->lua()->get_menu()) + menu(mui, container), + m_plugins(mame_machine_manager::instance()->lua()->get_menu()) { } @@ -72,10 +72,10 @@ void menu_plugin_opt::handle() { const event *menu_event = process(0); - if (menu_event != nullptr && (uintptr_t)menu_event->itemref) + if (menu_event && uintptr_t(menu_event->itemref)) { std::string key; - switch(menu_event->iptkey) + switch (menu_event->iptkey) { case IPT_UI_UP: key = "up"; @@ -107,8 +107,10 @@ void menu_plugin_opt::handle() default: return; } - if(mame_machine_manager::instance()->lua()->menu_callback(m_menu, (uintptr_t)menu_event->itemref, key)) + if (mame_machine_manager::instance()->lua()->menu_callback(m_menu, uintptr_t(menu_event->itemref), key)) reset(reset_options::REMEMBER_REF); + else if (menu_event->iptkey == IPT_UI_CANCEL) + stack_pop(); } } diff --git a/src/frontend/mame/ui/pluginopt.h b/src/frontend/mame/ui/pluginopt.h index 5a4b457654b..da2da131792 100644 --- a/src/frontend/mame/ui/pluginopt.h +++ b/src/frontend/mame/ui/pluginopt.h @@ -7,20 +7,20 @@ Internal menu for the plugin interface. ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_UI_PLUGINOPT_H #define MAME_FRONTEND_UI_PLUGINOPT_H -#include "ui/ui.h" +#pragma once + #include "ui/menu.h" +#include "ui/ui.h" #include #include namespace ui { + class menu_plugin : public menu { public: @@ -43,6 +43,9 @@ public: menu_plugin_opt(mame_ui_manager &mui, render_container &container, char *menu); virtual ~menu_plugin_opt(); +protected: + virtual bool custom_ui_cancel() override { return true; } + private: virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; @@ -52,4 +55,4 @@ private: } // namespace ui -#endif /* MAME_FRONTEND_UI_PLUGINOPT_H */ +#endif // MAME_FRONTEND_UI_PLUGINOPT_H diff --git a/src/frontend/mame/ui/selector.cpp b/src/frontend/mame/ui/selector.cpp index 58db5db28ae..4f7751713c3 100644 --- a/src/frontend/mame/ui/selector.cpp +++ b/src/frontend/mame/ui/selector.cpp @@ -123,7 +123,7 @@ void menu_selector::custom_render(void *selectedref, float top, float bottom, fl ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); // get the text for 'UI Select' - tempbuf[0] = string_format(_("Double-click or press %1$s to select"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD))); + tempbuf[0] = string_format(_("Double-click or press %1$s to select"), ui().get_general_input_setting(IPT_UI_SELECT)); draw_text_box( std::begin(tempbuf), std::end(tempbuf), origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, diff --git a/src/frontend/mame/ui/selector.h b/src/frontend/mame/ui/selector.h index f3921536267..1f53a244bfb 100644 --- a/src/frontend/mame/ui/selector.h +++ b/src/frontend/mame/ui/selector.h @@ -35,7 +35,7 @@ public: protected: virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool menu_has_search_active() override { return !m_search.empty(); } + virtual bool custom_ui_cancel() override { return !m_search.empty(); } private: enum { VISIBLE_GAMES_IN_SEARCH = 200 }; diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp index 348a1874a8b..0589c7fd20c 100644 --- a/src/frontend/mame/ui/selgame.cpp +++ b/src/frontend/mame/ui/selgame.cpp @@ -18,6 +18,7 @@ #include "ui/optsmenu.h" #include "ui/selector.h" #include "ui/selsoft.h" +#include "ui/systemlist.h" #include "ui/ui.h" #include "infoxml.h" @@ -33,15 +34,9 @@ #include "uiinput.h" #include "unicode.h" -#include -#include #include #include -#include #include -#include -#include -#include extern const char UI_VERSION_TAG[]; @@ -54,429 +49,6 @@ constexpr uint32_t FLAGS_UI = ui::menu::FLAG_LEFT_ARROW | ui::menu::FLAG_RIGHT_A } // anonymous namespace -class menu_select_game::persistent_data -{ -public: - enum available : unsigned - { - AVAIL_NONE = 0U, - AVAIL_SORTED_LIST = 1U << 0, - AVAIL_BIOS_COUNT = 1U << 1, - AVAIL_UCS_SHORTNAME = 1U << 2, - AVAIL_UCS_DESCRIPTION = 1U << 3, - AVAIL_UCS_MANUF_DESC = 1U << 4, - AVAIL_UCS_DFLT_DESC = 1U << 5, - AVAIL_UCS_MANUF_DFLT_DESC = 1U << 6, - AVAIL_FILTER_DATA = 1U << 7 - }; - - ~persistent_data() - { - if (m_thread) - m_thread->join(); - } - - void cache_data(ui_options const &options) - { - std::unique_lock lock(m_mutex); - if (!m_started) - { - m_started = true; - m_thread = std::make_unique( - [this, datpath = std::string(options.history_path()), titles = std::string(options.system_names())] - { - do_cache_data(datpath, titles); - }); - } - } - - void reset_cache() - { - if (m_thread) - m_thread->join(); - std::unique_lock lock(m_mutex); - m_thread.reset(); - m_started = false; - m_available = AVAIL_NONE; - m_sorted_list.clear(); - m_filter_data = machine_filter_data(); - m_bios_count = 0U; - } - - bool is_available(available desired) - { - return (m_available.load(std::memory_order_acquire) & desired) == desired; - } - - void wait_available(available desired) - { - if (!is_available(desired)) - { - assert(m_started); - std::unique_lock lock(m_mutex); - m_condition.wait(lock, [this, desired] () { return is_available(desired); }); - } - } - - std::vector &sorted_list() - { - wait_available(AVAIL_SORTED_LIST); - return m_sorted_list; - } - - int bios_count() - { - wait_available(AVAIL_BIOS_COUNT); - return m_bios_count; - } - - bool unavailable_systems() - { - wait_available(AVAIL_SORTED_LIST); - return std::find_if(m_sorted_list.begin(), m_sorted_list.end(), [] (ui_system_info const &info) { return !info.available; }) != m_sorted_list.end(); - } - - machine_filter_data &filter_data() - { - wait_available(AVAIL_FILTER_DATA); - return m_filter_data; - } - - static persistent_data &instance() - { - static persistent_data data; - return data; - } - -private: - persistent_data() - : m_started(false) - , m_available(AVAIL_NONE) - , m_bios_count(0) - { - } - - void notify_available(available value) - { - std::unique_lock lock(m_mutex); - m_available.fetch_or(value, std::memory_order_release); - m_condition.notify_all(); - } - - void do_cache_data(std::string const &datpath, std::string const &titles) - { - // try to open the titles file for optimisation reasons - emu_file titles_file(datpath, OPEN_FLAG_READ); - bool const try_titles(!titles.empty() && !titles_file.open(titles)); - - // generate full list - initially ordered by shortname - populate_list(!try_titles); - - // notify that BIOS count is valie - notify_available(AVAIL_BIOS_COUNT); - - // try to load localised descriptions - if (try_titles) - { - load_titles(titles_file); - - // populate parent descriptions while still ordered by shortname - // already done on the first pass if built-in titles are used - populate_parents(); - } - - // get rid of the "empty" driver - we don't need positions to line up any more - m_sorted_list.erase(m_sorted_list.begin() + driver_list::find(GAME_NAME(___empty))); - - // sort drivers and notify - std::collate const &coll = std::use_facet >(std::locale()); - auto const compare_names = - [&coll] (std::wstring const &wx, std::wstring const &wy) -> bool - { - return 0 > coll.compare(wx.data(), wx.data() + wx.size(), wy.data(), wy.data() + wy.size()); - }; - std::stable_sort( - m_sorted_list.begin(), - m_sorted_list.end(), - [&compare_names] (ui_system_info const &lhs, ui_system_info const &rhs) - { - game_driver const &x(*lhs.driver); - game_driver const &y(*rhs.driver); - - if (!lhs.is_clone && !rhs.is_clone) - { - return compare_names( - lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, - rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); - } - else if (lhs.is_clone && rhs.is_clone) - { - if (!std::strcmp(x.parent, y.parent)) - { - return compare_names( - lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, - rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); - } - else - { - return compare_names( - lhs.reading_parent.empty() ? wstring_from_utf8(lhs.parent) : lhs.reading_parent, - rhs.reading_parent.empty() ? wstring_from_utf8(rhs.parent) : rhs.reading_parent); - } - } - else if (!lhs.is_clone && rhs.is_clone) - { - if (!std::strcmp(x.name, y.parent)) - { - return true; - } - else - { - return compare_names( - lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, - rhs.reading_parent.empty() ? wstring_from_utf8(rhs.parent) : rhs.reading_parent); - } - } - else - { - if (!std::strcmp(x.parent, y.name)) - { - return false; - } - else - { - return compare_names( - lhs.reading_parent.empty() ? wstring_from_utf8(lhs.parent) : lhs.reading_parent, - rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); - } - } - }); - notify_available(AVAIL_SORTED_LIST); - - // sort manufacturers and years - m_filter_data.finalise(); - notify_available(AVAIL_FILTER_DATA); - - // convert shortnames to UCS-4 - for (ui_system_info &info : m_sorted_list) - info.ucs_shortname = ustr_from_utf8(normalize_unicode(info.driver->name, unicode_normalization_form::D, true)); - notify_available(AVAIL_UCS_SHORTNAME); - - // convert descriptions to UCS-4 - for (ui_system_info &info : m_sorted_list) - info.ucs_description = ustr_from_utf8(normalize_unicode(info.description, unicode_normalization_form::D, true)); - notify_available(AVAIL_UCS_DESCRIPTION); - - // convert " " to UCS-4 - std::string buf; - for (ui_system_info &info : m_sorted_list) - { - buf.assign(info.driver->manufacturer); - buf.append(1, ' '); - buf.append(info.description); - info.ucs_manufacturer_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); - } - notify_available(AVAIL_UCS_MANUF_DESC); - - // convert default descriptions to UCS-4 - if (try_titles) - { - for (ui_system_info &info : m_sorted_list) - { - std::string_view const fullname(info.driver->type.fullname()); - if (info.description != fullname) - info.ucs_default_description = ustr_from_utf8(normalize_unicode(fullname, unicode_normalization_form::D, true)); - } - } - notify_available(AVAIL_UCS_DFLT_DESC); - - // convert " " to UCS-4 - if (try_titles) - { - for (ui_system_info &info : m_sorted_list) - { - std::string_view const fullname(info.driver->type.fullname()); - if (info.description != fullname) - { - buf.assign(info.driver->manufacturer); - buf.append(1, ' '); - buf.append(fullname); - info.ucs_manufacturer_default_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); - } - } - } - notify_available(AVAIL_UCS_MANUF_DFLT_DESC); - } - - void populate_list(bool copydesc) - { - m_sorted_list.reserve(driver_list::total()); - std::unordered_set manufacturers, years; - for (int x = 0; x < driver_list::total(); ++x) - { - game_driver const &driver(driver_list::driver(x)); - ui_system_info &ins(m_sorted_list.emplace_back(driver, x, false)); - if (&driver != &GAME_NAME(___empty)) - { - if (driver.flags & machine_flags::IS_BIOS_ROOT) - ++m_bios_count; - - if ((driver.parent[0] != '0') || driver.parent[1]) - { - auto const parentindex(driver_list::find(driver.parent)); - if (copydesc) - { - if (0 <= parentindex) - { - game_driver const &parentdriver(driver_list::driver(parentindex)); - ins.is_clone = !(parentdriver.flags & machine_flags::IS_BIOS_ROOT); - ins.parent = parentdriver.type.fullname(); - } - else - { - ins.is_clone = false; - ins.parent = driver.parent; - } - } - else - { - ins.is_clone = (0 <= parentindex) && !(driver_list::driver(parentindex).flags & machine_flags::IS_BIOS_ROOT); - } - } - - if (copydesc) - ins.description = driver.type.fullname(); - - m_filter_data.add_manufacturer(driver.manufacturer); - m_filter_data.add_year(driver.year); - } - } - } - - void load_titles(util::core_file &file) - { - char readbuf[1024]; - std::string convbuf; - while (file.gets(readbuf, std::size(readbuf))) - { - // shortname, description, and description reading separated by tab - auto const eoln( - std::find_if( - std::begin(readbuf), - std::end(readbuf), - [] (char ch) { return !ch || ('\n' == ch) || ('\r' == ch); })); - auto const split(std::find(std::begin(readbuf), eoln, '\t')); - if (eoln == split) - continue; - std::string_view const shortname(readbuf, split - readbuf); - - // find matching system - still sorted by shortname at this point - auto const found( - std::lower_bound( - m_sorted_list.begin(), - m_sorted_list.end(), - shortname, - [] (ui_system_info const &a, std::string_view const &b) - { - return a.driver->name < b; - })); - if ((m_sorted_list.end() == found) || (shortname != found->driver->name)) - { - //osd_printf_verbose("System '%s' not found\n", shortname); very spammy for single-driver builds - continue; - } - - // find the end of the description - auto const descstart(std::next(split)); - auto const descend(std::find(descstart, eoln, '\t')); - auto const description(strtrimspace(std::string_view(descstart, descend - descstart))); - if (description.empty()) - { - osd_printf_warning("Empty translated description for system '%s'\n", shortname); - } - else if (!found->description.empty()) - { - osd_printf_warning( - "Multiple translated descriptions for system '%s' ('%s' and '%s')\n", - shortname, - found->description, - description); - } - else - { - found->description = description; - } - - // populate the reading if it's present - if (eoln == descend) - continue; - auto const readstart(std::next(descend)); - auto const readend(std::find(readstart, eoln, '\t')); - auto const reading(strtrimspace(std::string_view(readstart, readend - readstart))); - if (reading.empty()) - { - osd_printf_warning("Empty translated description reading for system '%s'\n", shortname); - } - else - { - found->reading_description = wstring_from_utf8(reading); - found->ucs_reading_description = ustr_from_utf8(normalize_unicode(reading, unicode_normalization_form::D, true)); - convbuf.assign(found->driver->manufacturer); - convbuf.append(1, ' '); - convbuf.append(reading); - found->ucs_manufacturer_reading_description = ustr_from_utf8(normalize_unicode(convbuf, unicode_normalization_form::D, true)); - } - } - - // fill in untranslated descriptions - for (ui_system_info &info : m_sorted_list) - { - if (info.description.empty()) - info.description = info.driver->type.fullname(); - } - } - - void populate_parents() - { - for (ui_system_info &info : m_sorted_list) - { - if ((info.driver->parent[0] != '0') || info.driver->parent[1]) - { - auto const found( - std::lower_bound( - m_sorted_list.begin(), - m_sorted_list.end(), - std::string_view(info.driver->parent), - [] (ui_system_info const &a, std::string_view const &b) - { - return a.driver->name < b; - })); - if (m_sorted_list.end() != found) - { - info.parent = found->description; - info.reading_parent = found->reading_description; - } - else - { - info.parent = info.driver->parent; - } - } - } - } - - // synchronisation - std::mutex m_mutex; - std::condition_variable m_condition; - std::unique_ptr m_thread; - std::atomic m_started; - std::atomic m_available; - - // data - std::vector m_sorted_list; - machine_filter_data m_filter_data; - int m_bios_count; -}; - bool menu_select_game::s_first_start = true; @@ -486,12 +58,12 @@ bool menu_select_game::s_first_start = true; menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &container, const char *gamename) : menu_select_launch(mui, container, false) - , m_persistent_data(persistent_data::instance()) + , m_persistent_data(system_list::instance()) , m_icons(MAX_ICONS_RENDER) , m_icon_paths() , m_displaylist() , m_searchlist() - , m_searched_fields(persistent_data::AVAIL_NONE) + , m_searched_fields(system_list::AVAIL_NONE) , m_populated_favorites(false) { std::string error_string, last_filter, sub_filter; @@ -587,7 +159,7 @@ void menu_select_game::handle() if (ui_globals::reset) { // dumb workaround for not being able to add an exit notifier - struct cache_reset { ~cache_reset() { persistent_data::instance().reset_cache(); } }; + struct cache_reset { ~cache_reset() { system_list::instance().reset_cache(); } }; ui().get_session_data(); ui_globals::reset = false; @@ -667,7 +239,8 @@ void menu_select_game::handle() if (!m_populated_favorites) { menu::stack_push( - ui(), container(), + ui(), + container(), *reinterpret_cast(m_prev_selected), nullptr, menu_event->mouse.x0, menu_event->mouse.y0); @@ -675,9 +248,11 @@ void menu_select_game::handle() else { ui_software_info *sw = reinterpret_cast(m_prev_selected); + ui_system_info const &sys = m_persistent_data.systems()[driver_list::find(sw->driver->name)]; menu::stack_push( - ui(), container(), - *sw->driver, + ui(), + container(), + sys, [this, empty = sw->startempty] (bool fav, bool changed) { if (changed) @@ -743,7 +318,7 @@ void menu_select_game::handle() break; case IPT_UI_AUDIT: - menu::stack_push(ui(), container(), m_persistent_data.sorted_list()); + menu::stack_push(ui(), container()); break; } } @@ -796,7 +371,7 @@ void menu_select_game::populate(float &customtop, float &custombottom) else { // if filter is set on category, build category list - std::vector const &sorted(m_persistent_data.sorted_list()); + auto const &sorted(m_persistent_data.sorted_list()); if (!flt) std::copy(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist)); else @@ -1083,10 +658,11 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) if (m_prev_selected) { ui_software_info *swinfo = reinterpret_cast(m_prev_selected); + ui_system_info const &sysinfo = m_persistent_data.systems()[driver_list::find(swinfo->driver->name)]; menu::stack_push( ui(), container(), - *swinfo->driver, + sysinfo, [this, empty = swinfo->startempty] (bool fav, bool changed) { if (changed) @@ -1105,7 +681,8 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) { if (!swlistdev.get_info().empty()) { - menu::stack_push(ui(), container(), *ui_swinfo->driver); + ui_system_info const &system(m_persistent_data.systems()[driver_list::find(ui_swinfo->driver->name)]); + menu::stack_push(ui(), container(), system); return; } } @@ -1217,7 +794,7 @@ void menu_select_game::populate_search() // ensure search list is populated if (m_searchlist.empty()) { - std::vector const &sorted(m_persistent_data.sorted_list()); + auto const &sorted(m_persistent_data.sorted_list()); m_searchlist.reserve(sorted.size()); for (ui_system_info const &info : sorted) m_searchlist.emplace_back(1.0, std::ref(info)); @@ -1227,16 +804,16 @@ void menu_select_game::populate_search() const std::u32string ucs_search(ustr_from_utf8(normalize_unicode(m_search, unicode_normalization_form::D, true))); // check available search data - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_SHORTNAME)) - m_searched_fields |= persistent_data::AVAIL_UCS_SHORTNAME; - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_DESCRIPTION)) - m_searched_fields |= persistent_data::AVAIL_UCS_DESCRIPTION; - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_MANUF_DESC)) - m_searched_fields |= persistent_data::AVAIL_UCS_MANUF_DESC; - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_DFLT_DESC)) - m_searched_fields |= persistent_data::AVAIL_UCS_DFLT_DESC; - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_MANUF_DFLT_DESC)) - m_searched_fields |= persistent_data::AVAIL_UCS_MANUF_DFLT_DESC; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_SHORTNAME)) + m_searched_fields |= system_list::AVAIL_UCS_SHORTNAME; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_DESCRIPTION)) + m_searched_fields |= system_list::AVAIL_UCS_DESCRIPTION; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_MANUF_DESC)) + m_searched_fields |= system_list::AVAIL_UCS_MANUF_DESC; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_DFLT_DESC)) + m_searched_fields |= system_list::AVAIL_UCS_DFLT_DESC; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_MANUF_DFLT_DESC)) + m_searched_fields |= system_list::AVAIL_UCS_MANUF_DFLT_DESC; for (std::pair > &info : m_searchlist) { @@ -1244,7 +821,7 @@ void menu_select_game::populate_search() ui_system_info const &sys(info.second); // match shortnames - if (m_searched_fields & persistent_data::AVAIL_UCS_SHORTNAME) + if (m_searched_fields & system_list::AVAIL_UCS_SHORTNAME) info.first = util::edit_distance(ucs_search, sys.ucs_shortname); // match reading @@ -1258,20 +835,20 @@ void menu_select_game::populate_search() } // match descriptions - if (info.first && (m_searched_fields & persistent_data::AVAIL_UCS_DESCRIPTION)) + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_DESCRIPTION)) info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_description), info.first); // match " " - if (info.first && (m_searched_fields & persistent_data::AVAIL_UCS_MANUF_DESC)) + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_MANUF_DESC)) info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_manufacturer_description), info.first); // match default description - if (info.first && (m_searched_fields & persistent_data::AVAIL_UCS_DFLT_DESC) && !sys.ucs_default_description.empty()) + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_DFLT_DESC) && !sys.ucs_default_description.empty()) { info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_default_description), info.first); // match " " - if (info.first && (m_searched_fields & persistent_data::AVAIL_UCS_MANUF_DFLT_DESC)) + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_MANUF_DFLT_DESC)) info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_manufacturer_default_description), info.first); } } @@ -1456,7 +1033,7 @@ void menu_select_game::get_selection(ui_software_info const *&software, ui_syste if (m_populated_favorites) { software = reinterpret_cast(get_selection_ptr()); - system = nullptr; + system = &m_persistent_data.systems()[driver_list::find(software->driver->name)]; } else { @@ -1492,10 +1069,10 @@ void menu_select_game::make_topbox_text(std::string &line0, std::string &line1, } -std::string menu_select_game::make_software_description(ui_software_info const &software) const +std::string menu_select_game::make_software_description(ui_software_info const &software, ui_system_info const *system) const { // first line is system - return string_format(_("System: %1$-.100s"), software.driver->type.fullname()); // TODO: localise description + return string_format(_("System: %1$-.100s"), system->description); } diff --git a/src/frontend/mame/ui/selgame.h b/src/frontend/mame/ui/selgame.h index 23127fa8c14..8b54024ece1 100644 --- a/src/frontend/mame/ui/selgame.h +++ b/src/frontend/mame/ui/selgame.h @@ -20,6 +20,9 @@ namespace ui { +class system_list; + + class menu_select_game : public menu_select_launch { public: @@ -38,9 +41,7 @@ private: using icon_cache = texture_lru; - class persistent_data; - - persistent_data &m_persistent_data; + system_list &m_persistent_data; icon_cache m_icons; std::string m_icon_paths; std::vector > m_displaylist; @@ -64,7 +65,7 @@ private: // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const override; - virtual std::string make_software_description(ui_software_info const &software) const override; + virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const override; // filter navigation virtual void filter_selected() override; diff --git a/src/frontend/mame/ui/selmenu.cpp b/src/frontend/mame/ui/selmenu.cpp index f755fd1d4e2..ba9facc7d25 100644 --- a/src/frontend/mame/ui/selmenu.cpp +++ b/src/frontend/mame/ui/selmenu.cpp @@ -671,7 +671,7 @@ void menu_select_launch::custom_render(void *selectedref, float top, float botto isstar = mame_machine_manager::instance()->favorite().is_favorite_system_software(*swinfo); // first line is long name or system - tempbuf[0] = make_software_description(*swinfo); + tempbuf[0] = make_software_description(*swinfo, system); // next line is year, publisher tempbuf[1] = string_format(_("%1$s, %2$-.100s"), swinfo->year, swinfo->publisher); @@ -857,14 +857,14 @@ void menu_select_launch::inkey_dats() if (software) { if (software->startempty && mame_machine_manager::instance()->lua()->call_plugin_check("data_list", software->driver->name, true)) - menu::stack_push(ui(), container(), software->driver); + menu::stack_push(ui(), container(), system); else if (mame_machine_manager::instance()->lua()->call_plugin_check("data_list", std::string(software->shortname).append(1, ',').append(software->listname).c_str()) || !software->infotext.empty()) menu::stack_push(ui(), container(), software); } else if (system) { if (mame_machine_manager::instance()->lua()->call_plugin_check("data_list", system->driver->name, true)) - menu::stack_push(ui(), container(), system->driver); + menu::stack_push(ui(), container(), system); } } @@ -1435,11 +1435,11 @@ void menu_select_launch::handle_keys(uint32_t flags, int &iptkey) { // dismiss error } - else if (menu_has_search_active()) + else if (!m_search.empty()) { // escape pressed with non-empty search text clears it m_search.clear(); - reset(reset_options::SELECT_FIRST); + reset(reset_options::REMEMBER_REF); } else { @@ -1960,12 +1960,22 @@ void menu_select_launch::draw(uint32_t flags) float line = visible_top + (float(m_visible_lines) * line_height); ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + // make sure the selection if (m_available_items < m_visible_lines) m_visible_lines = m_available_items; if (top_line < 0 || is_first_selected()) + { top_line = 0; - if (selected_index() < m_available_items && top_line + m_visible_lines >= m_available_items) - top_line = m_available_items - m_visible_lines; + } + else if (selected_index() < m_available_items) + { + if (selected_index() >= (top_line + m_visible_lines)) + top_line = selected_index() - (m_visible_lines / 2); + if ((top_line + m_visible_lines) >= m_available_items) + top_line = m_available_items - m_visible_lines; + else if (selected_index() >= (top_line + m_visible_lines - 2)) + top_line = selected_index() - m_visible_lines + ((selected_index() == (m_available_items - 1)) ? 1: 2); + } // determine effective positions taking into account the hilighting arrows float effective_width = visible_width - 2.0f * gutter_width; @@ -2934,7 +2944,6 @@ void menu_select_launch::general_info(ui_system_info const *system, game_driver int cloneof = driver_list::non_bios_clone(driver); if (0 <= cloneof) { - // TODO: this shows the non-localised parent name in some situations util::stream_format( str, _("Driver is Clone of\t%1$-.100s\n"), diff --git a/src/frontend/mame/ui/selmenu.h b/src/frontend/mame/ui/selmenu.h index e6576711ab7..9a32b33b6db 100644 --- a/src/frontend/mame/ui/selmenu.h +++ b/src/frontend/mame/ui/selmenu.h @@ -273,15 +273,10 @@ private: void get_title_search(std::string &title, std::string &search); - // handle keys + // event handling virtual void handle_keys(uint32_t flags, int &iptkey) override; - - // handle mouse virtual void handle_events(uint32_t flags, event &ev) override; - // live search active? - virtual bool menu_has_search_active() override { return !m_search.empty(); } - // draw game list virtual void draw(uint32_t flags) override; @@ -297,7 +292,7 @@ private: // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const = 0; - virtual std::string make_software_description(ui_software_info const &software) const = 0; + virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const = 0; // filter navigation virtual void filter_selected() = 0; diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp index 98d28b535e5..05ce1302a70 100644 --- a/src/frontend/mame/ui/selsoft.cpp +++ b/src/frontend/mame/ui/selsoft.cpp @@ -86,9 +86,9 @@ public: , m_searchlist() { // add start empty item - m_swinfo.emplace_back(menu.m_driver); + m_swinfo.emplace_back(*menu.m_system.driver); - machine_config config(menu.m_driver, menu.machine().options()); + machine_config config(*menu.m_system.driver, menu.machine().options()); // see if any media devices require an image to be loaded m_has_empty_start = true; @@ -156,7 +156,7 @@ public: if (instance_name && type_name) { // add to collection and try to resolve parent if applicable - auto const ins(m_swinfo.emplace(m_swinfo.end(), swinfo, part, menu.m_driver, swlist.list_name(), instance_name, type_name)); + auto const ins(m_swinfo.emplace(m_swinfo.end(), swinfo, part, *menu.m_system.driver, swlist.list_name(), instance_name, type_name)); if (!swinfo.parentname().empty()) { if ((parentnames.end() == prevparent) || (swinfo.parentname() != prevparent->first)) @@ -279,7 +279,7 @@ public: // load custom filters info from file emu_file file(menu.ui().options().ui_path(), OPEN_FLAG_READ); - if (!file.open(util::string_format("custom_%s_filter.ini", menu.m_driver.name))) + if (!file.open(util::string_format("custom_%s_filter.ini", menu.m_system.driver->name))) { software_filter::ptr flt(software_filter::create(file, m_filter_data)); if (flt) @@ -361,31 +361,16 @@ private: // ctor //------------------------------------------------- -menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver) - : menu_select_software(mui, container, driver.type.fullname(), driver) -{ -} - menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, ui_system_info const &system) - : menu_select_software(mui, container, system.description.c_str(), *system.driver) -{ -} - -menu_select_software::menu_select_software( - mame_ui_manager &mui, - render_container &container, - char const *description, - game_driver const &driver) : menu_select_launch(mui, container, true) , m_icon_paths() - , m_description(description) - , m_driver(driver) + , m_system(system) , m_displaylist() { reselect_last::reselect(false); using machine_data_cache = util::lru_cache_map >; - auto &cached(mui.get_session_data(8)[&driver]); + auto &cached(mui.get_session_data(8)[system.driver]); if (!cached) cached = std::make_shared(*this); m_data = cached; @@ -712,7 +697,7 @@ render_texture *menu_select_software::get_icon_texture(int linenum, void *select void menu_select_software::get_selection(ui_software_info const *&software, ui_system_info const *&system) const { software = reinterpret_cast(get_selection_ptr()); - system = nullptr; + system = &m_system; } @@ -721,7 +706,7 @@ void menu_select_software::make_topbox_text(std::string &line0, std::string &lin // determine the text for the header int vis_item = !m_search.empty() ? m_available_items : (m_available_items - 1); line0 = string_format(_("%1$s %2$s ( %3$d / %4$d software packages )"), emulator_info::get_appname(), bare_build_version, vis_item, m_data->swinfo().size() - 1); - line1 = string_format(_("Driver: \"%1$s\" software list "), m_description); + line1 = string_format(_("Driver: \"%1$s\" software list "), m_system.description); software_filter const *const it(m_data->current_filter()); char const *const filter(it ? it->filter_text() : nullptr); @@ -732,10 +717,10 @@ void menu_select_software::make_topbox_text(std::string &line0, std::string &lin } -std::string menu_select_software::make_software_description(ui_software_info const &software) const +std::string menu_select_software::make_software_description(ui_software_info const &software, ui_system_info const *system) const { - // first line is long name - return string_format(_("%1$-.100s"), software.longname); + // show list/item to make it less confusing when there are multiple lists mixed + return string_format(_("Software list/item: %1$s:%2$s"), software.listname, software.shortname); } @@ -752,7 +737,7 @@ void menu_select_software::filter_selected() if (software_filter::CUSTOM == new_type) { emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (!file.open(util::string_format("custom_%s_filter.ini", m_driver.name))) + if (!file.open(util::string_format("custom_%s_filter.ini", m_system.driver->name))) { filter.save_ini(file, 0); file.close(); diff --git a/src/frontend/mame/ui/selsoft.h b/src/frontend/mame/ui/selsoft.h index 370bcd0c6f1..d3b2d88f71b 100644 --- a/src/frontend/mame/ui/selsoft.h +++ b/src/frontend/mame/ui/selsoft.h @@ -29,7 +29,6 @@ namespace ui { class menu_select_software : public menu_select_launch { public: - menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver); menu_select_software(mame_ui_manager &mui, render_container &container, ui_system_info const &system); virtual ~menu_select_software() override; @@ -40,8 +39,6 @@ private: struct search_item; class machine_data; - menu_select_software(mame_ui_manager &mui, render_container &container, char const *description, game_driver const &driver); - virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; @@ -54,7 +51,7 @@ private: // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const override; - virtual std::string make_software_description(ui_software_info const &software) const override; + virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const override; // filter navigation virtual void filter_selected() override; @@ -66,8 +63,7 @@ private: void inkey_select(const event *menu_event); std::map m_icon_paths; - char const *const m_description; - game_driver const &m_driver; + ui_system_info const &m_system; std::shared_ptr m_data; std::vector > m_displaylist; diff --git a/src/frontend/mame/ui/simpleselgame.h b/src/frontend/mame/ui/simpleselgame.h index e56dac8185b..7a581b5ea32 100644 --- a/src/frontend/mame/ui/simpleselgame.h +++ b/src/frontend/mame/ui/simpleselgame.h @@ -30,7 +30,7 @@ public: protected: virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool menu_has_search_active() override { return !m_search.empty(); } + virtual bool custom_ui_cancel() override { return !m_search.empty(); } private: enum { VISIBLE_GAMES_IN_LIST = 15 }; diff --git a/src/frontend/mame/ui/state.cpp b/src/frontend/mame/ui/state.cpp index 6bb0b9ccdc5..98655ffa23e 100644 --- a/src/frontend/mame/ui/state.cpp +++ b/src/frontend/mame/ui/state.cpp @@ -264,8 +264,8 @@ void menu_load_save_state_base::handle() m_confirm_prompt = util::string_format( _("Delete saved state %1$s?\nPress %2$s to delete\nPress %3$s to cancel"), m_confirm_delete->visible_name(), - machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT)), - machine().input().seq_name(machine().ioport().type_seq(IPT_UI_CANCEL))); + ui().get_general_input_setting(IPT_UI_SELECT), + ui().get_general_input_setting(IPT_UI_CANCEL)); } } else if (!m_confirm_delete) diff --git a/src/frontend/mame/ui/swlist.cpp b/src/frontend/mame/ui/swlist.cpp index c1650654e5a..ac7235c1b1c 100644 --- a/src/frontend/mame/ui/swlist.cpp +++ b/src/frontend/mame/ui/swlist.cpp @@ -244,25 +244,31 @@ void menu_software_list::handle() // process the menu const event *event = process(0); - if (event && event->itemref) + if (event) { - if (event->itemref == ITEMREF_SWITCH_ITEM_ORDERING && event->iptkey == IPT_UI_SELECT) + if (event->iptkey == IPT_UI_SELECT) { - m_ordered_by_shortname = !m_ordered_by_shortname; + if (event->itemref == ITEMREF_SWITCH_ITEM_ORDERING) + { + m_ordered_by_shortname = !m_ordered_by_shortname; - // reset the char buffer if we change ordering criterion - m_search.clear(); + // reset the char buffer if we change ordering criterion + m_search.clear(); - // reload the menu with the new order - reset(reset_options::REMEMBER_REF); - machine().popmessage(_("Switched Order: entries now ordered by %s"), m_ordered_by_shortname ? _("shortname") : _("description")); - } - // handle selections - else if (event->iptkey == IPT_UI_SELECT) - { - entry_info *info = (entry_info *) event->itemref; - m_result = info->short_name; - stack_pop(); + // reload the menu with the new order + reset(reset_options::REMEMBER_REF); + machine().popmessage( + m_ordered_by_shortname + ? _("Switched Order: entries now ordered by shortname") + : _("Switched Order: entries now ordered by description")); + } + else if (event->itemref) + { + // handle selections + entry_info *info = (entry_info *) event->itemref; + m_result = info->short_name; + stack_pop(); + } } else if (event->iptkey == IPT_SPECIAL) { @@ -312,9 +318,11 @@ void menu_software_list::handle() else if (event->iptkey == IPT_UI_CANCEL) { // reset the char buffer also in this case - m_search.clear(); - m_result = m_search; - stack_pop(); + if (!m_search.empty()) + { + m_search.clear(); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_search); + } } } } diff --git a/src/frontend/mame/ui/swlist.h b/src/frontend/mame/ui/swlist.h index 3a4ade44c4e..b62ec167b02 100644 --- a/src/frontend/mame/ui/swlist.h +++ b/src/frontend/mame/ui/swlist.h @@ -63,8 +63,9 @@ class menu_software_list : public menu public: menu_software_list(mame_ui_manager &mui, render_container &container, software_list_device *swlist, const char *interface, std::string &result); virtual ~menu_software_list() override; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + +protected: + virtual bool custom_ui_cancel() override { return !m_search.empty(); } private: struct entry_info @@ -87,6 +88,9 @@ private: std::string m_search; bool m_ordered_by_shortname; + virtual void populate(float &customtop, float &custombottom) override; + virtual void handle() override; + // functions void append_software_entry(const software_info &swinfo); }; diff --git a/src/frontend/mame/ui/systemlist.cpp b/src/frontend/mame/ui/systemlist.cpp new file mode 100644 index 00000000000..258df48b0b2 --- /dev/null +++ b/src/frontend/mame/ui/systemlist.cpp @@ -0,0 +1,414 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/systemlist.h + + Persistent system list data. + +***************************************************************************/ + +#include "emu.h" +#include "ui/systemlist.h" + +#include "ui/moptions.h" + +#include "drivenum.h" + +#include "util/corestr.h" +#include "util/unicode.h" + +#include +#include +#include +#include + + +namespace ui { + +void system_list::cache_data(ui_options const &options) +{ + std::unique_lock lock(m_mutex); + if (!m_started) + { + m_started = true; + m_thread = std::make_unique( + [this, datpath = std::string(options.history_path()), titles = std::string(options.system_names())] + { + do_cache_data(datpath, titles); + }); + } +} + + +void system_list::reset_cache() +{ + std::unique_lock lock(m_mutex); + if (m_thread) + m_thread->join(); + m_thread.reset(); + m_started = false; + m_available = AVAIL_NONE; + m_systems.clear(); + m_sorted_list.clear(); + m_filter_data = machine_filter_data(); + m_bios_count = 0; +} + + +void system_list::wait_available(available desired) +{ + if (!is_available(desired)) + { + assert(m_started); + std::unique_lock lock(m_mutex); + if (!is_available(desired)) + m_condition.wait(lock, [this, desired] () { return is_available(desired); }); + } +} + + +system_list &system_list::instance() +{ + static system_list data; + return data; +} + + +system_list::system_list() + : m_started(false) + , m_available(AVAIL_NONE) + , m_bios_count(0) +{ +} + + +system_list::~system_list() +{ + if (m_thread) + m_thread->join(); +} + + +void system_list::notify_available(available value) +{ + std::unique_lock lock(m_mutex); + m_available.fetch_or(value, std::memory_order_release); + m_condition.notify_all(); +} + + +void system_list::do_cache_data(std::string const &datpath, std::string const &titles) +{ + // try to open the titles file for optimisation reasons + emu_file titles_file(datpath, OPEN_FLAG_READ); + bool const try_titles(!titles.empty() && !titles_file.open(titles)); + + // generate full list - initially ordered by shortname + populate_list(!try_titles); + + // notify that BIOS count is valie + notify_available(AVAIL_BIOS_COUNT); + + // try to load localised descriptions + if (try_titles) + { + load_titles(titles_file); + + // populate parent descriptions while still ordered by shortname + // already done on the first pass if built-in titles are used + populate_parents(); + } + + // system names are finalised now + notify_available(AVAIL_SYSTEM_NAMES); + + // get rid of the "empty" driver - we don't need positions to line up any more + m_sorted_list.reserve(m_systems.size() - 1); + auto const empty(driver_list::find(GAME_NAME(___empty))); + for (ui_system_info &info : m_systems) + { + if (info.index != empty) + m_sorted_list.emplace_back(info); + } + + // sort drivers and notify + std::collate const &coll = std::use_facet >(std::locale()); + auto const compare_names = + [&coll] (std::wstring const &wx, std::wstring const &wy) -> bool + { + return 0 > coll.compare(wx.data(), wx.data() + wx.size(), wy.data(), wy.data() + wy.size()); + }; + std::stable_sort( + m_sorted_list.begin(), + m_sorted_list.end(), + [&compare_names] (ui_system_info const &lhs, ui_system_info const &rhs) + { + game_driver const &x(*lhs.driver); + game_driver const &y(*rhs.driver); + + if (!lhs.is_clone && !rhs.is_clone) + { + return compare_names( + lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, + rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); + } + else if (lhs.is_clone && rhs.is_clone) + { + if (!std::strcmp(x.parent, y.parent)) + { + return compare_names( + lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, + rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); + } + else + { + return compare_names( + lhs.reading_parent.empty() ? wstring_from_utf8(lhs.parent) : lhs.reading_parent, + rhs.reading_parent.empty() ? wstring_from_utf8(rhs.parent) : rhs.reading_parent); + } + } + else if (!lhs.is_clone && rhs.is_clone) + { + if (!std::strcmp(x.name, y.parent)) + { + return true; + } + else + { + return compare_names( + lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, + rhs.reading_parent.empty() ? wstring_from_utf8(rhs.parent) : rhs.reading_parent); + } + } + else + { + if (!std::strcmp(x.parent, y.name)) + { + return false; + } + else + { + return compare_names( + lhs.reading_parent.empty() ? wstring_from_utf8(lhs.parent) : lhs.reading_parent, + rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); + } + } + }); + notify_available(AVAIL_SORTED_LIST); + + // sort manufacturers and years + m_filter_data.finalise(); + notify_available(AVAIL_FILTER_DATA); + + // convert shortnames to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_shortname = ustr_from_utf8(normalize_unicode(info.driver->name, unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_SHORTNAME); + + // convert descriptions to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_description = ustr_from_utf8(normalize_unicode(info.description, unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_DESCRIPTION); + + // convert " " to UCS-4 + std::string buf; + for (ui_system_info &info : m_sorted_list) + { + buf.assign(info.driver->manufacturer); + buf.append(1, ' '); + buf.append(info.description); + info.ucs_manufacturer_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); + } + notify_available(AVAIL_UCS_MANUF_DESC); + + // convert default descriptions to UCS-4 + if (try_titles) + { + for (ui_system_info &info : m_sorted_list) + { + std::string_view const fullname(info.driver->type.fullname()); + if (info.description != fullname) + info.ucs_default_description = ustr_from_utf8(normalize_unicode(fullname, unicode_normalization_form::D, true)); + } + } + notify_available(AVAIL_UCS_DFLT_DESC); + + // convert " " to UCS-4 + if (try_titles) + { + for (ui_system_info &info : m_sorted_list) + { + std::string_view const fullname(info.driver->type.fullname()); + if (info.description != fullname) + { + buf.assign(info.driver->manufacturer); + buf.append(1, ' '); + buf.append(fullname); + info.ucs_manufacturer_default_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); + } + } + } + notify_available(AVAIL_UCS_MANUF_DFLT_DESC); +} + + +void system_list::populate_list(bool copydesc) +{ + m_systems.reserve(driver_list::total()); + std::unordered_set manufacturers, years; + for (int x = 0; x < driver_list::total(); ++x) + { + game_driver const &driver(driver_list::driver(x)); + ui_system_info &ins(m_systems.emplace_back(driver, x, false)); + if (&driver != &GAME_NAME(___empty)) + { + if (driver.flags & machine_flags::IS_BIOS_ROOT) + ++m_bios_count; + + if ((driver.parent[0] != '0') || driver.parent[1]) + { + auto const parentindex(driver_list::find(driver.parent)); + if (copydesc) + { + if (0 <= parentindex) + { + game_driver const &parentdriver(driver_list::driver(parentindex)); + ins.is_clone = !(parentdriver.flags & machine_flags::IS_BIOS_ROOT); + ins.parent = parentdriver.type.fullname(); + } + else + { + ins.is_clone = false; + ins.parent = driver.parent; + } + } + else + { + ins.is_clone = (0 <= parentindex) && !(driver_list::driver(parentindex).flags & machine_flags::IS_BIOS_ROOT); + } + } + + if (copydesc) + ins.description = driver.type.fullname(); + + m_filter_data.add_manufacturer(driver.manufacturer); + m_filter_data.add_year(driver.year); + } + } +} + + +void system_list::load_titles(util::core_file &file) +{ + char readbuf[1024]; + std::string convbuf; + while (file.gets(readbuf, std::size(readbuf))) + { + // shortname, description, and description reading separated by tab + auto const eoln( + std::find_if( + std::begin(readbuf), + std::end(readbuf), + [] (char ch) { return !ch || ('\n' == ch) || ('\r' == ch); })); + auto const split(std::find(std::begin(readbuf), eoln, '\t')); + if (eoln == split) + continue; + std::string_view const shortname(readbuf, split - readbuf); + + // find matching system - still sorted by shortname at this point + auto const found( + std::lower_bound( + m_systems.begin(), + m_systems.end(), + shortname, + [] (ui_system_info const &a, std::string_view const &b) + { + return a.driver->name < b; + })); + if ((m_systems.end() == found) || (shortname != found->driver->name)) + { + //osd_printf_verbose("System '%s' not found\n", shortname); very spammy for single-driver builds + continue; + } + + // find the end of the description + auto const descstart(std::next(split)); + auto const descend(std::find(descstart, eoln, '\t')); + auto const description(strtrimspace(std::string_view(descstart, descend - descstart))); + if (description.empty()) + { + osd_printf_warning("Empty translated description for system '%s'\n", shortname); + } + else if (!found->description.empty()) + { + osd_printf_warning( + "Multiple translated descriptions for system '%s' ('%s' and '%s')\n", + shortname, + found->description, + description); + } + else + { + found->description = description; + } + + // populate the reading if it's present + if (eoln == descend) + continue; + auto const readstart(std::next(descend)); + auto const readend(std::find(readstart, eoln, '\t')); + auto const reading(strtrimspace(std::string_view(readstart, readend - readstart))); + if (reading.empty()) + { + osd_printf_warning("Empty translated description reading for system '%s'\n", shortname); + } + else + { + found->reading_description = wstring_from_utf8(reading); + found->ucs_reading_description = ustr_from_utf8(normalize_unicode(reading, unicode_normalization_form::D, true)); + convbuf.assign(found->driver->manufacturer); + convbuf.append(1, ' '); + convbuf.append(reading); + found->ucs_manufacturer_reading_description = ustr_from_utf8(normalize_unicode(convbuf, unicode_normalization_form::D, true)); + } + } + + // fill in untranslated descriptions + for (ui_system_info &info : m_systems) + { + if (info.description.empty()) + info.description = info.driver->type.fullname(); + } +} + + +void system_list::populate_parents() +{ + for (ui_system_info &info : m_systems) + { + if ((info.driver->parent[0] != '0') || info.driver->parent[1]) + { + auto const found( + std::lower_bound( + m_systems.begin(), + m_systems.end(), + std::string_view(info.driver->parent), + [] (ui_system_info const &a, std::string_view const &b) + { + return a.driver->name < b; + })); + if (m_systems.end() != found) + { + info.parent = found->description; + info.reading_parent = found->reading_description; + } + else + { + info.parent = info.driver->parent; + } + } + } +} + +} // namespace ui diff --git a/src/frontend/mame/ui/systemlist.h b/src/frontend/mame/ui/systemlist.h new file mode 100644 index 00000000000..6c3ee17dad7 --- /dev/null +++ b/src/frontend/mame/ui/systemlist.h @@ -0,0 +1,122 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/systemlist.h + + Persistent system list data. + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_SYSTEMLIST_H +#define MAME_FRONTEND_UI_SYSTEMLIST_H + +#pragma once + +#include "ui/utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +class ui_options; + + +namespace ui { + +class system_list +{ +public: + enum available : unsigned + { + AVAIL_NONE = 0U, + AVAIL_SYSTEM_NAMES = 1U << 0, + AVAIL_SORTED_LIST = 1U << 1, + AVAIL_BIOS_COUNT = 1U << 2, + AVAIL_UCS_SHORTNAME = 1U << 3, + AVAIL_UCS_DESCRIPTION = 1U << 4, + AVAIL_UCS_MANUF_DESC = 1U << 5, + AVAIL_UCS_DFLT_DESC = 1U << 6, + AVAIL_UCS_MANUF_DFLT_DESC = 1U << 7, + AVAIL_FILTER_DATA = 1U << 8 + }; + + using system_vector = std::vector; + using system_reference = std::reference_wrapper; + using system_reference_vector = std::vector; + + void cache_data(ui_options const &options); + + void reset_cache(); + + bool is_available(available desired) const + { + return (m_available.load(std::memory_order_acquire) & desired) == desired; + } + + void wait_available(available desired); + + system_vector const &systems() + { + wait_available(AVAIL_SYSTEM_NAMES); + return m_systems; + } + + system_reference_vector const &sorted_list() + { + wait_available(AVAIL_SORTED_LIST); + return m_sorted_list; + } + + int bios_count() + { + wait_available(AVAIL_BIOS_COUNT); + return m_bios_count; + } + + bool unavailable_systems() + { + wait_available(AVAIL_SORTED_LIST); + return std::find_if(m_sorted_list.begin(), m_sorted_list.end(), [] (ui_system_info const &info) { return !info.available; }) != m_sorted_list.end(); + } + + machine_filter_data &filter_data() + { + wait_available(AVAIL_FILTER_DATA); + return m_filter_data; + } + + static system_list &instance(); + +private: + system_list(); + ~system_list(); + + void notify_available(available value); + void do_cache_data(std::string const &datpath, std::string const &titles); + void populate_list(bool copydesc); + void load_titles(util::core_file &file); + void populate_parents(); + + // synchronisation + std::mutex m_mutex; + std::condition_variable m_condition; + std::unique_ptr m_thread; + std::atomic m_started; + std::atomic m_available; + + // data + system_vector m_systems; + system_reference_vector m_sorted_list; + machine_filter_data m_filter_data; + int m_bios_count; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_SYSTEMLIST_H diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index 2e5f999da08..c2f50d8c33a 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -21,6 +21,7 @@ #include "ui/menu.h" #include "ui/sliders.h" #include "ui/state.h" +#include "ui/systemlist.h" #include "ui/viewgfx.h" #include "imagedev/cassette.h" @@ -194,6 +195,9 @@ void mame_ui_manager::init() { load_ui_options(); + // start loading system names as early as possible + ui::system_list::instance().cache_data(options()); + // initialize the other UI bits ui::menu::init(machine(), options()); ui_gfx_init(machine()); @@ -1259,7 +1263,7 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) machine().set_ui_active(!machine().ui_active()); // display a popup indicating the new status - std::string const name = machine().input().seq_name(machine().ioport().type_seq(IPT_UI_TOGGLE_UI)); + std::string const name = get_general_input_setting(IPT_UI_TOGGLE_UI); if (machine().ui_active()) popup_time(2, _("UI controls enabled\nUse %1$s to toggle"), name); else @@ -1450,13 +1454,14 @@ uint32_t mame_ui_manager::handler_confirm_quit(render_container &container) uint32_t state = 0; // get the text for 'UI Select' - std::string ui_select_text = machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD)); + std::string ui_select_text = get_general_input_setting(IPT_UI_SELECT); // get the text for 'UI Cancel' - std::string ui_cancel_text = machine().input().seq_name(machine().ioport().type_seq(IPT_UI_CANCEL, 0, SEQ_TYPE_STANDARD)); + std::string ui_cancel_text = get_general_input_setting(IPT_UI_CANCEL); // assemble the quit message - std::string quit_message = string_format(_("Are you sure you want to quit?\n\n" + std::string quit_message = string_format( + _("Are you sure you want to quit?\n\n" "Press ''%1$s'' to quit,\n" "Press ''%2$s'' to return to emulation."), ui_select_text, @@ -2252,6 +2257,19 @@ void mame_ui_manager::menu_reset() ui::menu::stack_reset(machine()); } + +//------------------------------------------------- +// get_general_input_setting - get the current +// default setting for an input type (useful for +// prompting the user) +//------------------------------------------------- + +std::string mame_ui_manager::get_general_input_setting(ioport_type type, int player, input_seq_type seqtype) +{ + return machine().input().seq_name(machine().ioport().type_seq(type, player, seqtype)); +} + + void ui_colors::refresh(const ui_options &options) { m_border_color = options.border_color(); diff --git a/src/frontend/mame/ui/ui.h b/src/frontend/mame/ui/ui.h index 44727703a20..4cd6292a764 100644 --- a/src/frontend/mame/ui/ui.h +++ b/src/frontend/mame/ui/ui.h @@ -230,6 +230,9 @@ public: return *result; } + // helper for getting a general input setting - used for instruction text + std::string get_general_input_setting(ioport_type type, int player = 0, input_seq_type seqtype = SEQ_TYPE_STANDARD); + private: using handler_callback_func = std::function; using device_feature_set = std::set >; diff --git a/src/frontend/mame/ui/utils.cpp b/src/frontend/mame/ui/utils.cpp index c8d65fbe665..95002ce6482 100644 --- a/src/frontend/mame/ui/utils.cpp +++ b/src/frontend/mame/ui/utils.cpp @@ -2043,6 +2043,8 @@ ui_software_info::ui_software_info( , available(false) { // show the list/item here + infotext.append(longname); + infotext.append("\t\n\n"); infotext.append(_("swlist-info", "Software list/item")); infotext.append(1, '\n'); infotext.append(listname); -- cgit v1.2.3