-frontend: Refactored menu event handling and fixed a number of issues. (#8777)

* Moved common code for drawing about box, info viewer, and other text box menus to a base class; removed the last of the info viewer logic and the multi-line item hack from the base menu class.
* Added previous/next group navigation for general inputs and plugin input selection menus.
* Moved message catalog logic to lib/util, allowing osd and emu to use localised messages.
* Made the base menu class use the UI manager’s feature for holding session state rather than a static map and mutex.
* Improved menu event handling model, and fixed many issues, particularly with menus behaving badly when hidden/shown.
* Added better support for menus that don’t participate in the usual menu stack, like the menuless sliders and the save/load state menus.
* Made a number of menus refresh state when being shown after being hidden (fixes MT08121 among other issues).
* Fixed indication of mounted slot option in the slot option details menu.
* Improved appearance of background menus when emulation isn't running - draw all menus in the stack, and darken the background menus to make the edges of the active menu clearer.
* Fixed locale issues in -listxml.

-debugger: Made GUI debuggers more uniform.
* Added new memory view features to Win32 debugger.
* Fixed spelling of hexadecimal in Cocoa debugger and added decimal address option.
* Fixed duplicate keyboard shortcut in Cocoa debugger (Shift-Cmd-D was both new device window and 64-bit float format).
* Made keyboard shortcuts slightly more consistent across debuggers.

-plugins: Moved input selection menu and sequence polling code to a common library.  Fixed the issue that prevented keyboard inputs being mapped with -steadykey on.

-docs: Started adding some documentation for MAME's internal UI, and updated the list of example front-ends.

-Regenerated message catalog sources.  For translators, the new strings are mostly:
* The names of the inputs provided by the OS-dependent layer for things like fullscreen and video features. These show up in the user interface inputs menu.
* The names for automatically generated views. These show up in the video options menu - test with a system with a lot of screens to see more variants.
* The input macro plugin UI.
* A few format strings for analog input assignments.
* A few strings for the about box header.
This commit is contained in:
Vas Crabb 2021-10-31 12:31:16 +11:00 committed by GitHub
parent cfffc54b61
commit d64ea5331b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
174 changed files with 84805 additions and 63693 deletions

View File

@ -16,6 +16,10 @@ To set the folders where the data plugin looks for supported files, choose
**Configure Directories**, and then choose **DATs**. You can also set the **Configure Directories**, and then choose **DATs**. You can also set the
``historypath`` option in your **ui.ini** file. ``historypath`` option in your **ui.ini** file.
Loading large data files like **history.xml** can take quite a while, so please
be patient the first time you start MAME after updating or adding new data
files.
The following files are supported: The following files are supported:
history.xml history.xml

View File

@ -436,6 +436,11 @@ ui:get_string_width(str)
ui:set_aggressive_input_focus(enable) ui:set_aggressive_input_focus(enable)
On some platforms, this controls whether MAME should accept input focus in On some platforms, this controls whether MAME should accept input focus in
more situations than when its windows have UI focus. more situations than when its windows have UI focus.
ui:get_general_input_setting(type, [player])
Gets a description of the configured input sequence for the specified input
type and player suitable for using in prompts. The input type is an
enumerated value. The player number is a zero-based index. If the player
number is not supplied, it is assumed to be zero.
Properties Properties
^^^^^^^^^^ ^^^^^^^^^^

View File

@ -59,10 +59,11 @@ and saving/loading save states.
Change current UI option setting when an arrow is present on it. Change current UI option setting when an arrow is present on it.
**Right Arrow** **Right Arrow**
Change current UI option setting when an arrow is present on it. Change current UI option setting when an arrow is present on it.
**Home** **Home**/**End**
Highlight first UI menu option. Highlight first or last UI menu option.
**End** **[** **]**
Select last UI menu option. Move to previous or next group in UI menus that support it (e.g. move to the
inputs for the previous or next device in the Input (this Machine) menu).
**Enter**/**Joystick 1 Button 1** **Enter**/**Joystick 1 Button 1**
Select currently highlighted UI menu option. Select currently highlighted UI menu option.
**Space** **Space**

View File

@ -1,20 +1,45 @@
.. _frontends: .. _frontends:
Frontends Front-ends
========= ==========
There are a number of third party tools for MAME to make system and software selection simpler. These tools are called "Frontends", and there are far too many to list conclusively here. Some are free, some are commercial-- caveat emptor. Some older frontends predate the merging of MAME and MESS and do not support the additional console, handheld, etc functionality that MAME inherited from MESS. A number of third party tools for MAME to make system and software selection
simpler are available. These tools are called “front-ends”, and there are far
too many to list exhaustively here. Some are free, some are commercial
caveat emptor. Some older front-ends predate the merging of MAME and MESS and
do not support the additional console, hand-held, and computer functionality
inherited from MESS.
This following list is not an endorsement of any of these frontends by the MAME team, but simply showing a number of commonly used free frontends as a good starting point to begin from. This following list is not an endorsement of any of these front-ends by the MAME
team. It simply shows a number of commonly used free front-ends to provide a
starting point.
| QMC2 (multiple platforms) `QMC2 <http://qmc2.batcom-it.net/>`__ (multiple platforms)
| Download: http://qmc2.batcom-it.net/ Provides a graphical interface for configuring many of MAMEs settings and
| features. Also includes ROM management and media auditing features.
| IV/Play (Microsoft Windows) Written in C++ using the Qt toolkit, the
| Download: http://www.mameui.info/ `source code is on SourceForge <https://sourceforge.net/projects/qmc2/>`__.
| `Negatron <http://negatron.net/>`__ (multiple platforms)
| EmuLoader (Microsoft Windows) Negatron emphasises features for configuring emulated computers and
| Download: http://emuloader.mameworld.info/ consoles. Written in Java, the
| `source code is on GitHub <https://github.com/xinyingho/Negatron>`__.
`BletchMAME <https://www.bletchmame.org/>`__ (multiple platforms)
BletchMAME takes advantage of MAMEs Lua scripting interface to integrate
tightly and effectively replace MAMEs internal user interface. It has
many useful features for home computer emulation. Written in C++, the
`source code is on GitHub <https://github.com/npwoods/bletchmame>`__.
`IV/Play <http://www.mameui.info/>`__ (Microsoft Windows)
A simple Windows program for launching systems in MAME. Written in C#, the
`souce code is on GitHub <https://github.com/Mataniko/IV-Play>`__.
`EmuLoader <http://emuloader.mameworld.info/>`__ (Microsoft Windows)
EmuLoader provides a Windows interface for launching systems in multiple
emulators, including MAME, Supermodel and DEMUL. Written in Delphi Pascal,
the source code is available
`on the download page <https://emuloader.mameworld.info/downloads.htm>`__.
`Retrofire <https://e2j.net/downloads/>`__ (Japanese, Microsoft Windows)
Provides a Japanese-language graphical interface for launching systems or
software in MAME.
The MAME team will not provide support for issues with frontends. For support, we suggest contacting the frontend author or trying any of the popular MAME-friendly forums on the internet. The MAME team will not provide support for issues with front-ends. For support,
we suggest contacting the front-end author or asking on one of the popular
MAME-friendly forums on the Internet.

View File

@ -1,16 +1,18 @@
Basic MAME Usage and Configuration Basic MAME Usage and Configuration
---------------------------------- ----------------------------------
This section describes general usage information about MAME. It is intended to cover aspects of using and configuring MAME that are common across all operating systems. For additional OS-specific options, please see the separate documentation for your platform of choice. This section describes general usage information about MAME. It is intended to
cover aspects of using and configuring MAME that are common across all operating
systems. For additional OS-specific options, please see the separate
documentation for your platform of choice.
.. toctree:: .. toctree::
:titlesonly: :titlesonly:
usingmame usingmame
ui
defaultkeys defaultkeys
mamemenus mamemenus
frontends frontends
aboutromsets aboutromsets
commonissues commonissues

View File

@ -0,0 +1,137 @@
.. _ui:
MAMEs User Interface
=====================
.. contents:: :local:
.. _ui-intro:
Introduction
------------
MAME provides a simple user interface for selecting a system and software to
run and changing settings while running an emulated system. MAMEs user
interface is designed to be usable with a keyboard, game controller, or pointing
device, but will require a keyboard for initial configuration.
The default settings for the most important controls to know when running an
emulated system, and the settings they correspond to in case you want to change
them, are as follows:
Scroll Lock, or Forward Delete on macOS (UI Toggle)
For emulated systems with keyboard inputs, enable or disable UI controls.
(MAME starts with UI controls disabled for systems with keyboard inputs
unless the :ref:`ui_active option <mame-commandline-uiactive>` is on.)
Tab (Config Menu)
Show or hide the menu during emulation.
Escape (UI Cancel)
Return to the system selection menu, or exit if MAME was started with a
system specified (from the command line or using an
:ref:`external front-end <frontends>`).
.. _ui-menus:
Navigating menus
----------------
By default, MAME menus can be navigated using the keyboard cursor keys. All
the UI controls can be changed by going to the **General Inputs** menu and then
selecting **User Interface**. The default keyboard controls on a US ANSI QWERTY
layout keyboard, and the settings they correspond to, are as follows:
Up Arrow (UI Up)
Highlight the previous menu item, or the last item if the first item is
highlighted.
Down Arrow (UI Down)
Highlight the next menu item, or the first item if the last item is
highlighted.
Left Arrow (UI Left)
For menu items that are adjustable settings, reduce the value or select the
previous setting (these menu items show left- and right-facing triangles
beside the value).
Right Arrow (UI Left)
For menu items that are adjustable settings, increase the value or select
the next setting (these menu items show left- and right-facing triangles
beside the value).
Return/Enter keypad Enter (UI Select)
Select the highlighted menu item.
Forward Delete, or Fn+Delete on some compact keyboards (UI Clear)
Clear setting or reset to default value.
Escape (UI Cancel)
Close the menu, returning to the previous menu, or returning to the
emulated machine for the main menu (theres usually an item at the bottom
of the menu for the same purpose).
Home (UI Home)
Highlight the first menu item and scroll to the top of the menu.
End (UI End)
Highlight the last menu item and scroll to the bottom of the menu.
Page Up (UI Page Up)
Scroll the menu up by one screen.
Page Down (UI Page Down)
Scroll the menu down by one screen.
[ (UI Previous Group)
Move to the previous group of items (not used by all menus).
] (UI Next Group)
Move to the next group of items (not used by all menus).
.. _ui-menus-gamectrl:
Using a game controller
~~~~~~~~~~~~~~~~~~~~~~~
MAME supports navigating menus with a game controller or joystick, but only the
most important UI controls have joystick assignments by default:
* Move the first joystick up or down in the Y axis to highlight the previous or
next menu item.
* Move the first joystick left or right in the X axis to adjust settings.
* Press the first button on the first joystick to select the highlighted menu
item.
If you want to be able to use MAME with a game controller without needing a
keyboard, youll need to assign joystick buttons (or combinations of buttons) to
these controls as well:
* **Config Menu** to show or dismiss the menu during emulation
* **UI Cancel** to close menus, return to the system selection menu, or exit
MAME
* **UI Clear** isnt essential for basic emulation, but its used to clear or
reset some settings to defaults
* **UI Home**, **UI End**, **UI Page Up**, **UI Page Down**, **UI Previous
Group** and **UI Next Group** are not essential, but make navigating some
menus easier
If youre not using an external front-end to launch systems in MAME, you should
assign joystick buttons (or combinations of buttons) to these controls to make
full use of the system and software selection menus:
* **UI Focus Next**/**UI Focus Previous** to navigate between panes
* **UI Add Remove favorite**, **UI Export List** and **UI Audit Media** if you
want access to these features without using a keyboard or pointing device
.. _ui-menus-mouse:
Using a mouse or trackball
~~~~~~~~~~~~~~~~~~~~~~~~~~
MAME supports navigating menus using a mouse or trackball that works as a system
pointing device:
* Click menu items to highlight them.
* Double-click menu items to select them.
* Click the left- or right-pointing triangle to adjust settings.
* For menus with too many items to fit on the screen, click the upward- or
downward-pointing triangle at the top or bottom to scroll up or down by one
screen at a time.
* Use vertical scrolling gestures to scroll menus or text boxes with too many
items or lines to fit on the screen.
* Click toolbar items to select them, or hover over them to see a description.
If you have enough additional mouse buttons, you may want to assign button
combinations to the **Config Menu**, **Pause** and/or **UI Cancel** inputs to
make it possible to use MAME without a keyboard.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
local lib = {} local lib = {}
-- Common UI helper library
local commonui
-- Set of all menus -- Set of all menus
local MENU_TYPES = { MAIN = 0, EDIT = 1, ADD = 2, BUTTON = 3 } local MENU_TYPES = { MAIN = 0, EDIT = 1, ADD = 2, BUTTON = 3 }
@ -27,14 +30,17 @@ local configure_menu_active = false
-- Saved selection on configure menu (to restore after button menu is dismissed) -- Saved selection on configure menu (to restore after button menu is dismissed)
local configure_selection_save local configure_selection_save
-- Helper for polling for hotkeys
local hotkey_poller
-- Button being created/edited -- Button being created/edited
local current_button = {} local current_button = {}
-- Initial button to select when opening buttons menu -- Initial button to select when opening buttons menu
local initial_input local initial_input
-- Inputs that can be autofired (to list in BUTTON menu) -- Handler for BUTTON menu
local inputs local input_menu
-- Returns the section (from MENU_SECTIONS) and the index within that section -- Returns the section (from MENU_SECTIONS) and the index within that section
local function menu_section(index) local function menu_section(index)
@ -59,19 +65,14 @@ local function is_button_complete(button)
return button.port and button.field and button.key and button.on_frames and button.off_frames and button.button and button.counter return button.port and button.field and button.key and button.on_frames and button.off_frames and button.button and button.counter
end end
local function is_supported_input(ioport_field)
-- IPT_BUTTON1 through IPT_BUTTON16 in ioport_type enum (ioport.h)
return ioport_field.type >= 64 and ioport_field.type <= 79
end
-- Main menu -- Main menu
local function populate_main_menu(buttons) local function populate_main_menu(buttons)
local ioport = manager.machine.ioport local ioport = manager.machine.ioport
local input = manager.machine.input local input = manager.machine.input
local menu = {} local menu = {}
menu[#menu + 1] = {_('Autofire buttons'), '', 'off'} menu[#menu + 1] = {_p('plugin-autofire', 'Autofire buttons'), '', 'off'}
menu[#menu + 1] = {string.format(_('Press %s to delete'), input:seq_name(ioport:type_seq(ioport:token_to_input_type('UI_CLEAR')))), '', 'off'} menu[#menu + 1] = {string.format(_p('plugin-autofire', 'Press %s to delete'), manager.ui:get_general_input_setting(ioport:token_to_input_type('UI_CLEAR'))), '', 'off'}
menu[#menu + 1] = {'---', '', ''} menu[#menu + 1] = {'---', '', ''}
header_height = #menu header_height = #menu
@ -82,22 +83,26 @@ local function populate_main_menu(buttons)
freq = 1 / screen.frame_period freq = 1 / screen.frame_period
end end
if #buttons > 0 then
for index, button in ipairs(buttons) do for index, button in ipairs(buttons) do
-- Round rate to two decimal places -- Round rate to two decimal places
local rate = freq / (button.on_frames + button.off_frames) local rate = freq / (button.on_frames + button.off_frames)
rate = math.floor(rate * 100) / 100 rate = math.floor(rate * 100) / 100
local text = string.format(_('%s [%g Hz]'), _p('input-name', button.button.name), rate) local text = string.format(_p('plugin-autofire', '%s [%g Hz]'), _p('input-name', button.button.name), rate)
local subtext = input:seq_name(button.key) local subtext = input:seq_name(button.key)
menu[#menu + 1] = {text, subtext, ''} menu[#menu + 1] = {text, subtext, ''}
if index == initial_button then if index == initial_button then
main_selection_save = #menu main_selection_save = #menu
end end
end end
else
menu[#menu + 1] = {_p('plugin-autofire', '[no autofire buttons]'), '', 'off'}
end
initial_button = nil initial_button = nil
content_height = #menu content_height = #menu
menu[#menu + 1] = {'---', '', ''} menu[#menu + 1] = {'---', '', ''}
menu[#menu + 1] = {_('Add autofire button'), '', ''} menu[#menu + 1] = {_p('plugin-autofire', 'Add autofire button'), '', ''}
local selection = main_selection_save local selection = main_selection_save
main_selection_save = nil main_selection_save = nil
@ -134,67 +139,53 @@ end
-- Add/edit menus (mostly identical) -- Add/edit menus (mostly identical)
local function populate_configure_menu(menu) local function populate_configure_menu(menu)
local button_name = current_button.button and _p('input-name', current_button.button.name) or _('NOT SET') local button_name = current_button.button and _p('input-name', current_button.button.name) or _p('plugin-autofire', '[not set]')
local key_name = current_button.key and manager.machine.input:seq_name(current_button.key) or _('NOT SET') local key_name = current_button.key and manager.machine.input:seq_name(current_button.key) or _p('plugin-autofire', '[not set]')
menu[#menu + 1] = {_('Input'), button_name, ''} menu[#menu + 1] = {_p('plugin-autofire', 'Input'), button_name, ''}
if not (configure_menu_active or configure_selection_save) then if not (configure_menu_active or configure_selection_save) then
configure_selection_save = #menu configure_selection_save = #menu
end end
menu[#menu + 1] = {_('Hotkey'), key_name, ''} menu[#menu + 1] = {_p('plugin-autofire', 'Hotkey'), key_name, hotkey_poller and 'lr' or ''}
menu[#menu + 1] = {_('On frames'), current_button.on_frames, current_button.on_frames > 1 and 'lr' or 'r'} menu[#menu + 1] = {_p('plugin-autofire', 'On frames'), current_button.on_frames, current_button.on_frames > 1 and 'lr' or 'r'}
menu[#menu + 1] = {_('Off frames'), current_button.off_frames, current_button.off_frames > 1 and 'lr' or 'r'} menu[#menu + 1] = {_p('plugin-autofire', 'Off frames'), current_button.off_frames, current_button.off_frames > 1 and 'lr' or 'r'}
configure_menu_active = true configure_menu_active = true
end end
-- Borrowed from the cheat plugin
local function poll_for_hotkey()
local input = manager.machine.input
local poller = input:switch_sequence_poller()
manager.machine:popmessage(_('Press button for hotkey or wait to leave unchanged'))
manager.machine.video:frame_update()
poller:start()
local time = os.clock()
local clearmsg = true
while (not poller:poll()) and (poller.modified or (os.clock() < time + 1)) do
if poller.modified then
if not poller.valid then
manager.machine:popmessage(_('Invalid sequence entered'))
clearmsg = false
break
end
manager.machine:popmessage(input:seq_name(poller.sequence))
manager.machine.video:frame_update()
end
end
if clearmsg then
manager.machine:popmessage()
end
return poller.valid and poller.sequence or nil
end
local function handle_configure_menu(index, event) local function handle_configure_menu(index, event)
if hotkey_poller then
-- special handling for polling for hotkey
if hotkey_poller:poll() then
if hotkey_poller.sequence then
current_button.key = hotkey_poller.sequence
end
hotkey_poller = nil
return true
end
return false
end
if index == 1 then if index == 1 then
-- Input -- Input
if event == 'select' then if event == 'select' then
configure_selection_save = header_height + index configure_selection_save = header_height + index
table.insert(menu_stack, MENU_TYPES.BUTTON) table.insert(menu_stack, MENU_TYPES.BUTTON)
if current_button.port and current_button.field then if current_button.port and current_button.field then
initial_input = {port_name = current_button.port, ioport_field = current_button.button} initial_input = current_button.button
end end
return true return true
end end
elseif index == 2 then elseif index == 2 then
-- Hotkey -- Hotkey
if event == 'select' then if event == 'select' then
local keycode = poll_for_hotkey() if not commonui then
if keycode then commonui = require('commonui')
current_button.key = keycode
return true
end end
hotkey_poller = commonui.switch_polling_helper()
return true
end end
elseif index == 3 then elseif index == 3 then
-- On frames -- On frames
manager.machine:popmessage(_('Number of frames button will be pressed')) manager.machine:popmessage(_p('plugin-autofire', 'Number of frames button will be pressed'))
if event == 'left' then if event == 'left' then
current_button.on_frames = current_button.on_frames - 1 current_button.on_frames = current_button.on_frames - 1
return true return true
@ -207,7 +198,7 @@ local function handle_configure_menu(index, event)
end end
elseif index == 4 then elseif index == 4 then
-- Off frames -- Off frames
manager.machine:popmessage(_('Number of frames button will be released')) manager.machine:popmessage(_p('plugin-autofire', 'Number of frames button will be released'))
if event == 'left' then if event == 'left' then
current_button.off_frames = current_button.off_frames - 1 current_button.off_frames = current_button.off_frames - 1
return true return true
@ -224,7 +215,7 @@ end
local function populate_edit_menu() local function populate_edit_menu()
local menu = {} local menu = {}
menu[#menu + 1] = {_('Edit autofire button'), '', 'off'} menu[#menu + 1] = {_p('plugin-autofire', 'Edit autofire button'), '', 'off'}
menu[#menu + 1] = {'---', '', ''} menu[#menu + 1] = {'---', '', ''}
header_height = #menu header_height = #menu
@ -232,17 +223,20 @@ local function populate_edit_menu()
content_height = #menu content_height = #menu
menu[#menu + 1] = {'---', '', ''} menu[#menu + 1] = {'---', '', ''}
menu[#menu + 1] = {_('Done'), '', ''} menu[#menu + 1] = {_p('plugin-autofire', 'Done'), '', ''}
local selection = configure_selection_save local selection = configure_selection_save
configure_selection_save = nil configure_selection_save = nil
if hotkey_poller then
return hotkey_poller:overlay(menu, selection, 'lrrepeat')
else
return menu, selection, 'lrrepeat' return menu, selection, 'lrrepeat'
end
end end
local function handle_edit_menu(index, event, buttons) local function handle_edit_menu(index, event, buttons)
local section, adjusted_index = menu_section(index) local section, adjusted_index = menu_section(index)
if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then
inputs = nil
configure_menu_active = false configure_menu_active = false
table.remove(menu_stack) table.remove(menu_stack)
return true return true
@ -254,7 +248,7 @@ end
local function populate_add_menu() local function populate_add_menu()
local menu = {} local menu = {}
menu[#menu + 1] = {_('Add autofire button'), '', 'off'} menu[#menu + 1] = {_p('plugin-autofire', 'Add autofire button'), '', 'off'}
menu[#menu + 1] = {'---', '', ''} menu[#menu + 1] = {'---', '', ''}
header_height = #menu header_height = #menu
@ -263,20 +257,23 @@ local function populate_add_menu()
menu[#menu + 1] = {'---', '', ''} menu[#menu + 1] = {'---', '', ''}
if is_button_complete(current_button) then if is_button_complete(current_button) then
menu[#menu + 1] = {_('Create'), '', ''} menu[#menu + 1] = {_p('plugin-autofire', 'Create'), '', ''}
else else
menu[#menu + 1] = {_('Cancel'), '', ''} menu[#menu + 1] = {_p('plugin-autofire', 'Cancel'), '', ''}
end end
local selection = configure_selection_save local selection = configure_selection_save
configure_selection_save = nil configure_selection_save = nil
if hotkey_poller then
return hotkey_poller:overlay(menu, selection, 'lrrepeat')
else
return menu, selection, 'lrrepeat' return menu, selection, 'lrrepeat'
end
end end
local function handle_add_menu(index, event, buttons) local function handle_add_menu(index, event, buttons)
local section, adjusted_index = menu_section(index) local section, adjusted_index = menu_section(index)
if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then
inputs = nil
configure_menu_active = false configure_menu_active = false
table.remove(menu_stack) table.remove(menu_stack)
if is_button_complete(current_button) and (event == 'select') then if is_button_complete(current_button) and (event == 'select') then
@ -293,103 +290,31 @@ end
-- Button selection menu -- Button selection menu
local function populate_button_menu() local function populate_button_menu()
local ioport = manager.machine.ioport local function is_supported_input(ioport_field)
menu = {} -- IPT_BUTTON1 through IPT_BUTTON16 in ioport_type enum (ioport.h)
menu[#menu + 1] = {_('Select an input for autofire'), '', 'off'} return ioport_field.type >= 64 and ioport_field.type <= 79
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
if not inputs then
inputs = {}
for port_key, port in pairs(ioport.ports) do
for field_key, field in pairs(port.fields) do
if is_supported_input(field) then
inputs[#inputs + 1] = {
port_name = port_key,
field_name = field_key,
ioport_field = field
}
end
end
end end
local function compare(x, y) local function action(field)
if x.ioport_field.device.tag < y.ioport_field.device.tag then if field then
return true current_button.port = field.port.tag
elseif x.ioport_field.device.tag > y.ioport_field.device.tag then current_button.field = field.name
return false current_button.button = field
end end
groupx = ioport:type_group(x.ioport_field.type, x.ioport_field.player)
groupy = ioport:type_group(y.ioport_field.type, y.ioport_field.player)
if groupx < groupy then
return true
elseif groupx > groupy then
return false
elseif x.ioport_field.type < y.ioport_field.type then
return true
elseif x.ioport_field.type > y.ioport_field.type then
return false
else
return x.ioport_field.name < y.ioport_field.name
end
end
table.sort(inputs, compare)
local i = 1
local prev
while i <= #inputs do
local current = inputs[i]
if (not prev) or (prev.ioport_field.device.tag ~= current.ioport_field.device.tag) then
table.insert(inputs, i, false)
i = i + 2
else
i = i + 1
end
prev = current
end
end
local selection = header_height + 1
for i, input in ipairs(inputs) do
if input then
menu[header_height + i] = { _p('input-name', input.ioport_field.name), '', '' }
if initial_input and (initial_input.port_name == input.port_name) and (initial_input.ioport_field.mask == input.ioport_field.mask) and (initial_input.ioport_field.type == input.ioport_field.type) then
selection = header_height + i
initial_input = nil initial_input = nil
input_menu = nil
table.remove(menu_stack)
end end
else
local device = inputs[i + 1].ioport_field.device
if device.owner then
menu[header_height + i] = {string.format(_('%s [root%s]'), device.name, device.tag), '', 'heading'}
else
menu[header_height + i] = {string.format(_('[root%s]'), device.tag), '', 'heading'}
end
end
end
content_height = #menu
initial_input = nil
menu[#menu + 1] = {'---', '', ''} if not commonui then
menu[#menu + 1] = {_('Cancel'), '', ''} commonui = require('commonui')
end
return menu, selection input_menu = commonui.input_selection_menu(action, _p('plugin-autofire', 'Select an input for autofire'), is_supported_input)
return input_menu:populate(initial_input)
end end
local function handle_button_menu(index, event) local function handle_button_menu(index, event)
local section, adjusted_index = menu_section(index) return input_menu:handle(index, event)
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
current_button.button = selected_input.ioport_field
table.remove(menu_stack)
return true
end
return false
end end
function lib:init_menu(buttons) function lib:init_menu(buttons)
@ -397,7 +322,7 @@ function lib:init_menu(buttons)
content_height = 0 content_height = 0
menu_stack = { MENU_TYPES.MAIN } menu_stack = { MENU_TYPES.MAIN }
current_button = {} current_button = {}
inputs = nil input_menu = nil
end end
function lib:populate_menu(buttons) function lib:populate_menu(buttons)

View File

@ -100,14 +100,14 @@ function autofire.startplugin()
if menu_handler then if menu_handler then
return menu_handler:populate_menu(buttons) return menu_handler:populate_menu(buttons)
else else
return {{_('Failed to load autofire menu'), '', ''}} return {{_p('plugin-autofire', 'Failed to load autofire menu'), '', 'off'}}
end end
end end
emu.register_frame_done(process_frame) emu.register_frame_done(process_frame)
emu.register_start(load_settings) emu.register_start(load_settings)
emu.register_stop(save_settings) emu.register_stop(save_settings)
emu.register_menu(menu_callback, menu_populate, _('Autofire')) emu.register_menu(menu_callback, menu_populate, _p('plugin-autofire', 'Autofire'))
end end
return exports return exports

View File

@ -597,59 +597,57 @@ function cheat.startplugin()
local hotkeymenu = false local hotkeymenu = false
local hotkeylist = {} local hotkeylist = {}
local commonui
local poller
local function menu_populate() local function menu_populate()
local menu = {} local menu = {}
if hotkeymenu then if hotkeymenu then
local ioport = manager.machine.ioport local ioport = manager.machine.ioport
local input = manager.machine.input local input = manager.machine.input
menu[1] = {_("Select cheat to set hotkey"), "", "off"} menu[1] = {_("Select cheat to set hotkey"), "", "off"}
menu[2] = {string.format(_("Press %s to clear hotkey"), input:seq_name(ioport:type_seq(ioport:token_to_input_type("UI_CLEAR")))), "", "off"} menu[2] = {string.format(_("Press %s to clear hotkey"), manager.ui:get_general_input_setting(ioport:token_to_input_type("UI_CLEAR"))), "", "off"}
menu[3] = {"---", "", "off"} menu[3] = {"---", "", "off"}
hotkeylist = {} hotkeylist = {}
local function hkcbfunc(cheat, event) local function hkcbfunc(cheat, event)
if event == "clear" then if poller then
cheat.hotkeys = nil if poller:poll() then
return if poller.sequence then
end
local poller = input:switch_sequence_poller()
manager.machine:popmessage(_("Press button for hotkey or wait to leave unchanged"))
manager.machine.video:frame_update()
poller:start()
local time = os.clock()
local clearmsg = true
while (not poller:poll()) and (poller.modified or (os.clock() < time + 1)) do
if poller.modified then
if not poller.valid then
manager.machine:popmessage(_("Invalid sequence entered"))
clearmsg = false
break
end
manager.machine:popmessage(input:seq_name(poller.sequence))
manager.machine.video:frame_update()
end
end
if poller.modified and poller.valid then
cheat.hotkeys = { pressed = false, keys = poller.sequence } cheat.hotkeys = { pressed = false, keys = poller.sequence }
end end
if clearmsg then poller = nil
manager.machine:popmessage() return true
end end
manager.machine.video:frame_update() elseif event == "clear" then
cheat.hotkeys = nil
return true
elseif event == "select" then
if not commonui then
commonui = require('commonui')
end
poller = commonui.switch_polling_helper()
return true
end
return false
end end
for num, cheat in ipairs(cheats) do for num, cheat in ipairs(cheats) do
if cheat.script then if cheat.script then
menu[#menu + 1] = {cheat.desc, cheat.hotkeys and input:seq_name(cheat.hotkeys.keys) or _("None"), ""} local setting = cheat.hotkeys and input:seq_name(cheat.hotkeys.keys) or _("None")
menu[#menu + 1] = {cheat.desc, setting, ""}
hotkeylist[#hotkeylist + 1] = function(event) return hkcbfunc(cheat, event) end hotkeylist[#hotkeylist + 1] = function(event) return hkcbfunc(cheat, event) end
end end
end end
menu[#menu + 1] = {"---", "", ""} menu[#menu + 1] = {"---", "", ""}
menu[#menu + 1] = {_("Done"), "", ""} menu[#menu + 1] = {_("Done"), "", ""}
if poller then
return poller:overlay(menu)
else
return menu return menu
end end
end
for num, cheat in ipairs(cheats) do for num, cheat in ipairs(cheats) do
menu[num] = {} menu[num] = {}
menu[num][1] = cheat.desc menu[num][1] = cheat.desc
@ -703,7 +701,10 @@ function cheat.startplugin()
local function menu_callback(index, event) local function menu_callback(index, event)
manager.machine:popmessage() manager.machine:popmessage()
if hotkeymenu then if hotkeymenu then
if event == "select" or event == "clear" then if event == "cancel" then
hotkeymenu = false
return true
else
index = index - 3 index = index - 3
if index >= 1 and index <= #hotkeylist then if index >= 1 and index <= #hotkeylist then
hotkeylist[index](event) hotkeylist[index](event)
@ -712,9 +713,6 @@ function cheat.startplugin()
hotkeymenu = false hotkeymenu = false
return true return true
end end
elseif event == "cancel" then
hotkeymenu = false
return true
end end
return false return false
end end

214
plugins/commonui/init.lua Normal file
View File

@ -0,0 +1,214 @@
-- license:BSD-3-Clause
-- copyright-holders:Vas Crabb
local exports = {
name = 'commonui',
version = '0.0.1',
description = 'Common plugin UI helpers',
license = 'BSD-3-Clause',
author = { name = 'Vas Crabb' } }
local commonui = exports
function commonui.input_selection_menu(action, title, filter)
menu = { }
local choices
local index_first_choice
local index_cancel
local function populate_choices()
local ioport = manager.machine.ioport
local function compare(a, b)
if a.device.tag < b.device.tag then
return true
elseif a.device.tag > b.device.tag then
return false
end
groupa = ioport:type_group(a.type, a.player)
groupb = ioport:type_group(b.type, b.player)
if groupa < groupb then
return true
elseif groupa > groupb then
return false
elseif a.type < b.type then
return true
elseif a.type > b.type then
return false
else
return a.name < b.name
end
end
choices = { }
for tag, port in pairs(manager.machine.ioport.ports) do
for name, field in pairs(port.fields) do
if (not filter) or filter(field) then
table.insert(choices, field)
end
end
end
table.sort(choices, compare)
local index = 1
local prev
while index <= #choices do
local current = choices[index]
if (not prev) or (prev.device.tag ~= current.device.tag) then
table.insert(choices, index, false)
index = index + 2
else
index = index + 1
end
prev = current
end
end
function menu:populate(initial_selection)
if not choices then
populate_choices()
end
local items = { }
if title then
table.insert(items, { title, '', 'off' })
table.insert(items, { '---', '', '' })
end
index_first_choice = #items + 1
local selection = index_first_choice
for index, field in ipairs(choices) do
if field then
table.insert(items, { _p('input-name', field.name), '', '' })
if initial_selection and (field.port.tag == initial_selection.port.tag) and (field.mask == initial_selection.mask) and (field.type == initial_selection.type) then
selection = #items
initial_selection = nil
end
else
local device = choices[index + 1].device
if device.owner then
table.insert(items, { string.format(_p('plugin-commonui', '%s [root%s]'), device.name, device.tag), '', 'heading' })
else
table.insert(items, { string.format(_p('plugin-commonui', '[root%s]'), device.tag), '', 'heading' })
end
end
end
table.insert(items, { '---', '', '' })
table.insert(items, { _p('plugin-commonui', 'Cancel'), '', '' })
index_cancel = #items
return items, selection
end
function menu:handle(index, event)
local selection
if (event == 'cancel') or ((index == input_item_cancel) and (event == 'select')) then
action(nil)
return true
elseif event == 'select' then
local field = choices[index - index_first_choice + 1]
if field then
action(field)
return true
end
elseif event == 'prevgroup' then
local found_break = false
while (index > index_first_choice) and (not selection) do
index = index - 1
if not choices[index - index_first_choice + 1] then
if found_break then
selection = index + 1
else
found_break = true
end
end
end
elseif event == 'nextgroup' then
while ((index - index_first_choice + 2) < #choices) and (not selection) do
index = index + 1
if not choices[index - index_first_choice + 1] then
selection = index + 1
end
end
end
return false, selection
end
return menu
end
function commonui.switch_polling_helper(starting_sequence)
helper = { }
local machine = manager.machine
local cancel = machine.ioport:token_to_input_type('UI_CANCEL')
local cancel_prompt = manager.ui:get_general_input_setting(cancel)
local input = machine.input
local uiinput = machine.uiinput
local poller = input:switch_sequence_poller()
local modified_ticks = 0
if starting_sequence then
poller:start(starting_sequence)
else
poller:start()
end
function helper:overlay(items, selection, flags)
if flags then
flags = flags .. " nokeys"
else
flags = "nokeys"
end
return items, selection, flags
end
function helper:poll()
-- prevent race condition between uiinput:pressed() and poll()
if (modified_ticks == 0) and poller.modified then
modified_ticks = emu.osd_ticks()
end
if uiinput:pressed(cancel) then
-- UI_CANCEL pressed, abort
machine:popmessage()
if (not poller.modified) or (modified_ticks == emu.osd_ticks()) then
-- cancelled immediately
self.sequence = nil
return true -- TODO: communicate this better?
else
-- entered something before cancelling
self.sequence = nil
return true
end
elseif poller:poll() then
if poller.valid then
-- valid sequence entered
machine:popmessage()
self.sequence = poller.sequence
return true
else
-- invalid sequence entered
machine:popmessage(_p('plugin-commonui', 'Invalid sequence entered'))
self.sequence = nil
return true
end
else
machine:popmessage(string.format(
_p('plugin-commonui', 'Enter sequence or press %s to cancel\n%s'),
cancel_prompt,
input:seq_name(poller.sequence)))
return false
end
end
return helper
end
return exports

View File

@ -0,0 +1,9 @@
{
"plugin": {
"name": "commonui",
"description": "Common plugin UI helpers",
"version": "0.0.1",
"author": "Vas Crabb",
"type": "library"
}
}

View File

@ -19,7 +19,7 @@ function dat.check(set, softlist)
if not status or not info then if not status or not info then
return nil return nil
end end
return _("Command") return _p("plugin-data", "Command")
end end
function dat.get() function dat.get()

View File

@ -13,7 +13,7 @@ function dat.check(set, softlist)
if not status or not info then if not status or not info then
return nil return nil
end end
return _("Gameinit") return _p("plugin-data", "Gameinit")
end end
function dat.get() function dat.get()

View File

@ -1217,7 +1217,7 @@ function dat.check(set, softlist)
if curset == set then if curset == set then
if output then if output then
return _("High Scores") return _p("plugin-data", "High Scores")
else else
return nil return nil
end end
@ -1265,7 +1265,7 @@ function dat.check(set, softlist)
end end
end end
if output then if output then
return _("High Scores") return _p("plugin-data", "High Scores")
else else
return nil return nil
end end

View File

@ -169,7 +169,7 @@ function dat.check(set, softlist)
info = stmt:get_value(0) info = stmt:get_value(0)
end end
stmt:finalize() stmt:finalize()
return info and _("History") or nil return info and _p("plugin-data", "History") or nil
end end
function dat.get() function dat.get()

View File

@ -15,9 +15,9 @@ function dat.check(set, softlist)
local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$") local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$")
status, drvinfo = pcall(datread, "drv", "info", sourcefile) status, drvinfo = pcall(datread, "drv", "info", sourcefile)
if drvinfo then if drvinfo then
info = info .. _("\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo info = info .. _p("plugin-data", "\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
end end
return _("MAMEinfo") return _p("plugin-data", "MAMEinfo")
end end
function dat.get() function dat.get()

View File

@ -133,7 +133,7 @@ function dat.check(set, softlist)
info = "#j2\n" .. stmt:get_value(0) info = "#j2\n" .. stmt:get_value(0)
end end
stmt:finalize() stmt:finalize()
return info and _("MARPScore") or nil return info and _p("plugin-data", "MARPScore") or nil
end end
function dat.get() function dat.get()

View File

@ -16,9 +16,9 @@ function dat.check(set, softlist)
local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$") local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$")
status, drvinfo = pcall(datread, "drv", "info", sourcefile) status, drvinfo = pcall(datread, "drv", "info", sourcefile)
if drvinfo then if drvinfo then
info = info .. _("\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo info = info .. _p("plugin-data", "\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
end end
return _("MESSinfo") return _p("plugin-data", "MESSinfo")
end end
function dat.get() function dat.get()

View File

@ -19,7 +19,7 @@ function dat.check(set, softlist)
end end
end end
info = "#j2\n" .. table.concat(lines, "\n") info = "#j2\n" .. table.concat(lines, "\n")
return _("Mamescore") return _p("plugin-data", "Mamescore")
end end
function dat.get() function dat.get()

View File

@ -12,7 +12,7 @@ function dat.check(set, softlist)
if not status or not info then if not status or not info then
return nil return nil
end end
return _("Sysinfo") return _p("plugin-data", "Sysinfo")
end end
function dat.get() function dat.get()

View File

@ -127,7 +127,7 @@ function inputmacro.startplugin()
emu.register_frame_done(process_frame) emu.register_frame_done(process_frame)
emu.register_start(start) emu.register_start(start)
emu.register_stop(stop) emu.register_stop(stop)
emu.register_menu(menu_callback, menu_populate, _('Input Macros')) emu.register_menu(menu_callback, menu_populate, _p('plugin-inputmacro', 'Input Macros'))
end end
return exports return exports

View File

@ -9,6 +9,7 @@ local MENU_TYPES = { MACROS = 0, ADD = 1, EDIT = 2, INPUT = 3 }
-- Globals -- Globals
local commonui
local macros local macros
local menu_stack local menu_stack
@ -51,38 +52,10 @@ end
-- Input menu -- Input menu
local input_action local input_menu
local input_start_field local input_start_field
local input_choices
local input_item_first_choice
local input_item_cancel
function handle_input(index, action)
if (action == 'cancel') or ((index == input_item_cancel) and (action == 'select')) then
table.remove(menu_stack)
input_action = nil
return true
elseif action == 'select' then
field = input_choices[index - input_item_first_choice + 1]
if field then
table.remove(menu_stack)
input_action(field)
input_action = nil
return true
end
end
return false
end
function populate_input()
local items = { }
items[#items + 1] = { _p('plugin-inputmacro', 'Set Input'), '', 'off' }
items[#items + 1] = { '---', '', '' }
if not input_choices then
local ioport = manager.machine.ioport
function start_input_menu(handler, start_field)
local function supported(f) local function supported(f)
if f.is_analog or f.is_toggle then if f.is_analog or f.is_toggle then
return false return false
@ -93,76 +66,29 @@ function populate_input()
end end
end end
local function compare(a, b) local function action(field)
if a.device.tag < b.device.tag then
return true
elseif a.device.tag > b.device.tag then
return false
end
groupa = ioport:type_group(a.type, a.player)
groupb = ioport:type_group(b.type, b.player)
if groupa < groupb then
return true
elseif groupa > groupb then
return false
elseif a.type < b.type then
return true
elseif a.type > b.type then
return false
else
return a.name < b.name
end
end
input_choices = { }
for tag, port in pairs(manager.machine.ioport.ports) do
for name, field in pairs(port.fields) do
if supported(field) then
table.insert(input_choices, field)
end
end
end
table.sort(input_choices, compare)
local index = 1
local prev
while index <= #input_choices do
local current = input_choices[index]
if (not prev) or (prev.device.tag ~= current.device.tag) then
table.insert(input_choices, index, false)
index = index + 2
else
index = index + 1
end
prev = current
end
end
input_item_first_choice = #items + 1
local selection = input_item_first_choice
for index, field in ipairs(input_choices) do
if field then if field then
items[#items + 1] = { _p('input-name', field.name), '', '' } handler(field)
if input_start_field and (field.port.tag == input_start_field.port.tag) and (field.mask == input_start_field.mask) and (field.type == input_start_field.type) then end
selection = #items table.remove(menu_stack)
input_menu = nil
input_start_field = nil input_start_field = nil
end end
else
local device = input_choices[index + 1].device
if device.owner then
items[#items + 1] = { string.format(_('plugin-inputmacro', '%s [root%s]'), device.name, device.tag), '', 'heading' }
else
items[#items + 1] = { string.format(_('plugin-inputmacro', '[root%s]'), device.tag), '', 'heading' }
end
end
end
input_start_field = nil
items[#items + 1] = { '---', '', '' } if not commonui then
items[#items + 1] = { _p('plugin-inputmacro', 'Cancel'), '', '' } commonui = require('commonui')
input_item_cancel = #items end
input_menu = commonui.input_selection_menu(action, _p('plugin-inputmacro', 'Set Input'), supported)
input_start_field = start_field
table.insert(menu_stack, MENU_TYPES.INPUT)
end
return items, selection function handle_input(index, action)
return input_menu:handle(index, action)
end
function populate_input()
return input_menu:populate(input_start_field)
end end
@ -176,6 +102,7 @@ local edit_insert_position
local edit_name_buffer local edit_name_buffer
local edit_items local edit_items
local edit_item_exit local edit_item_exit
local edit_switch_poller
local function current_macro_complete() local function current_macro_complete()
if not edit_current_macro.binding then if not edit_current_macro.binding then
@ -188,36 +115,18 @@ local function current_macro_complete()
return true return true
end end
local function set_binding() local function handle_edit_items(index, event)
local input = manager.machine.input if edit_switch_poller then
local poller = input:switch_sequence_poller() if edit_switch_poller:poll() then
manager.machine:popmessage(_p('plugin-inputmacro', 'Enter activation sequence or wait to leave unchanged')) if edit_switch_poller.sequence then
manager.machine.video:frame_update() edit_current_macro.binding = edit_switch_poller.sequence
poller:start()
local time = os.clock()
local clearmsg = true
while (not poller:poll()) and (poller.modified or (os.clock() < (time + 1))) do
if poller.modified then
if not poller.valid then
manager.machine:popmessage(_p('plugin-inputmacro', 'Invalid sequence entered'))
clearmsg = false
break
end end
manager.machine:popmessage(input:seq_name(poller.sequence)) edit_switch_poller = nil
manager.machine.video:frame_update()
end
end
if clearmsg then
manager.machine:popmessage()
end
if poller.valid then
edit_current_macro.binding = poller.sequence
return true return true
end end
return false return false
end end
local function handle_edit_items(index, event)
local command = edit_items[index] local command = edit_items[index]
local namecancel = false local namecancel = false
@ -275,7 +184,11 @@ local function handle_edit_items(index, event)
end end
elseif command.action == 'binding' then elseif command.action == 'binding' then
if event == 'select' then if event == 'select' then
return set_binding() if not commonui then
commonui = require('commonui')
end
edit_switch_poller = commonui.switch_polling_helper()
return true
end end
elseif command.action == 'releaseaction' then elseif command.action == 'releaseaction' then
if (event == 'select') or (event == 'left') or (event == 'right') then if (event == 'select') or (event == 'left') or (event == 'right') then
@ -320,14 +233,13 @@ local function handle_edit_items(index, event)
elseif command.action == 'input' then elseif command.action == 'input' then
local inputs = edit_current_macro.steps[command.step].inputs local inputs = edit_current_macro.steps[command.step].inputs
if event == 'select' then if event == 'select' then
input_action = local hanlder =
function(field) function(field)
inputs[command.input].port = field.port inputs[command.input].port = field.port
inputs[command.input].field = field inputs[command.input].field = field
end end
input_start_field = inputs[command.input].field start_input_menu(hanlder, inputs[command.input].field)
edit_start_selection = index edit_start_selection = index
table.insert(menu_stack, MENU_TYPES.INPUT)
return true return true
elseif event == 'clear' then elseif event == 'clear' then
if #inputs > 1 then if #inputs > 1 then
@ -338,14 +250,14 @@ local function handle_edit_items(index, event)
elseif command.action == 'addinput' then elseif command.action == 'addinput' then
if event == 'select' then if event == 'select' then
local inputs = edit_current_macro.steps[command.step].inputs local inputs = edit_current_macro.steps[command.step].inputs
input_action = local handler =
function(field) function(field)
inputs[#inputs + 1] = { inputs[#inputs + 1] = {
port = field.port, port = field.port,
field = field } field = field }
end end
start_input_menu(handler)
edit_start_selection = index edit_start_selection = index
table.insert(menu_stack, MENU_TYPES.INPUT)
return true return true
end end
elseif command.action == 'deletestep' then elseif command.action == 'deletestep' then
@ -368,7 +280,7 @@ local function handle_edit_items(index, event)
elseif command.action == 'addstep' then elseif command.action == 'addstep' then
if event == 'select' then if event == 'select' then
local steps = edit_current_macro.steps local steps = edit_current_macro.steps
input_action = local handler =
function(field) function(field)
local newstep = { local newstep = {
inputs = { inputs = {
@ -384,8 +296,8 @@ local function handle_edit_items(index, event)
edit_start_step = edit_insert_position edit_start_step = edit_insert_position
edit_insert_position = edit_insert_position + 1 edit_insert_position = edit_insert_position + 1
end end
start_input_menu(handler)
edit_start_selection = index edit_start_selection = index
table.insert(menu_stack, MENU_TYPES.INPUT)
return true return true
elseif event == 'left' then elseif event == 'left' then
edit_insert_position = edit_insert_position - 1 edit_insert_position = edit_insert_position - 1
@ -413,7 +325,7 @@ local function add_edit_items(items)
local binding = edit_current_macro.binding local binding = edit_current_macro.binding
local activation = binding and input:seq_name(binding) or _p('plugin-inputmacro', '[not set]') local activation = binding and input:seq_name(binding) or _p('plugin-inputmacro', '[not set]')
items[#items + 1] = { _p('plugin-inputmacro', 'Activation sequence'), activation, '' } items[#items + 1] = { _p('plugin-inputmacro', 'Activation sequence'), activation, edit_switch_poller and 'lr' or '' }
edit_items[#items] = { action = 'binding' } edit_items[#items] = { action = 'binding' }
local releaseaction = edit_current_macro.earlycancel and _p('plugin-inputmacro', 'Stop immediately') or _p('plugin-inputmacro', 'Complete macro') local releaseaction = edit_current_macro.earlycancel and _p('plugin-inputmacro', 'Stop immediately') or _p('plugin-inputmacro', 'Complete macro')
@ -484,7 +396,6 @@ local function handle_add(index, event)
if handle_edit_items(index, event) then if handle_edit_items(index, event) then
return true return true
elseif event == 'cancel' then elseif event == 'cancel' then
input_choices = nil
edit_current_macro = nil edit_current_macro = nil
edit_menu_active = false edit_menu_active = false
edit_items = nil edit_items = nil
@ -495,7 +406,6 @@ local function handle_add(index, event)
table.insert(macros, edit_current_macro) table.insert(macros, edit_current_macro)
macros_start_macro = #macros macros_start_macro = #macros
end end
input_choices = nil
edit_menu_active = false edit_menu_active = false
edit_current_macro = nil edit_current_macro = nil
edit_items = nil edit_items = nil
@ -509,7 +419,6 @@ local function handle_edit(index, event)
if handle_edit_items(index, event) then if handle_edit_items(index, event) then
return true return true
elseif (event == 'cancel') or ((index == edit_item_exit) and (event == 'select')) then elseif (event == 'cancel') or ((index == edit_item_exit) and (event == 'select')) then
input_choices = nil
edit_current_macro = nil edit_current_macro = nil
edit_menu_active = false edit_menu_active = false
edit_items = nil edit_items = nil
@ -537,7 +446,11 @@ local function populate_add()
local selection = edit_start_selection local selection = edit_start_selection
edit_start_selection = nil edit_start_selection = nil
if edit_switch_poller then
return edit_switch_poller:overlay(items, selection, 'lrrepeat')
else
return items, selection, 'lrrepeat' return items, selection, 'lrrepeat'
end
end end
local function populate_edit() local function populate_edit()
@ -554,7 +467,11 @@ local function populate_edit()
local selection = edit_start_selection local selection = edit_start_selection
edit_start_selection = nil edit_start_selection = nil
if edit_switch_poller then
return edit_switch_poller:overlay(items, selection, 'lrrepeat')
else
return items, selection, 'lrrepeat' return items, selection, 'lrrepeat'
end
end end
@ -601,7 +518,7 @@ function populate_macros()
local items = { } local items = { }
items[#items + 1] = { _p('plugin-inputmacro', 'Input Macros'), '', 'off' } items[#items + 1] = { _p('plugin-inputmacro', 'Input Macros'), '', 'off' }
items[#items + 1] = { string.format(_p('plugin-inputmacro', 'Press %s to delete'), input:seq_name(ioport:type_seq(ioport:token_to_input_type('UI_CLEAR')))), '', 'off' } items[#items + 1] = { string.format(_p('plugin-inputmacro', 'Press %s to delete'), manager.ui:get_general_input_setting(ioport:token_to_input_type('UI_CLEAR'))), '', 'off' }
items[#items + 1] = { '---', '', '' } items[#items + 1] = { '---', '', '' }
macros_item_first_macro = #items + 1 macros_item_first_macro = #items + 1

View File

@ -86,7 +86,7 @@ function timer.startplugin()
local hrs = math.floor(time / 3600) local hrs = math.floor(time / 3600)
local min = math.floor((time % 3600) / 60) local min = math.floor((time % 3600) / 60)
local sec = time % 60 local sec = time % 60
return string.format("%03d:%02d:%02d", hrs, min, sec) return string.format(_p("plugin-timer", "%03d:%02d:%02d"), hrs, min, sec)
end end
local lastupdate local lastupdate
@ -95,9 +95,9 @@ function timer.startplugin()
lastupdate = os.time() lastupdate = os.time()
local time = lastupdate - start_time local time = lastupdate - start_time
return return
{{ _("Current time"), sectohms(time), "off" }, {{ _p("plugin-timer", "Current time"), sectohms(time), "off" },
{ _("Total time"), sectohms(total_time + time), "off" }, { _p("plugin-timer", "Total time"), sectohms(total_time + time), "off" },
{ _("Play Count"), play_count, "off" }}, { _p("plugin-timer", "Play Count"), play_count, "off" }},
nil, nil,
"idle" "idle"
end end
@ -106,7 +106,7 @@ function timer.startplugin()
return os.time() > lastupdate return os.time() > lastupdate
end end
emu.register_menu(menu_callback, menu_populate, _("Timer")) emu.register_menu(menu_callback, menu_populate, _p("plugin-timer", "Timer"))
end end
return exports return exports

View File

@ -89,6 +89,8 @@ end
MAME_DIR .. "src/lib/util/ioprocsvec.h", MAME_DIR .. "src/lib/util/ioprocsvec.h",
MAME_DIR .. "src/lib/util/jedparse.cpp", MAME_DIR .. "src/lib/util/jedparse.cpp",
MAME_DIR .. "src/lib/util/jedparse.h", MAME_DIR .. "src/lib/util/jedparse.h",
MAME_DIR .. "src/lib/util/language.cpp",
MAME_DIR .. "src/lib/util/language.h",
MAME_DIR .. "src/lib/util/lrucache.h", MAME_DIR .. "src/lib/util/lrucache.h",
MAME_DIR .. "src/lib/util/md5.cpp", MAME_DIR .. "src/lib/util/md5.cpp",
MAME_DIR .. "src/lib/util/md5.h", MAME_DIR .. "src/lib/util/md5.h",

View File

@ -171,6 +171,8 @@ files {
MAME_DIR .. "src/frontend/mame/ui/tapectrl.h", MAME_DIR .. "src/frontend/mame/ui/tapectrl.h",
MAME_DIR .. "src/frontend/mame/ui/text.cpp", MAME_DIR .. "src/frontend/mame/ui/text.cpp",
MAME_DIR .. "src/frontend/mame/ui/text.h", MAME_DIR .. "src/frontend/mame/ui/text.h",
MAME_DIR .. "src/frontend/mame/ui/textbox.cpp",
MAME_DIR .. "src/frontend/mame/ui/textbox.h",
MAME_DIR .. "src/frontend/mame/ui/toolbar.ipp", MAME_DIR .. "src/frontend/mame/ui/toolbar.ipp",
MAME_DIR .. "src/frontend/mame/ui/ui.cpp", MAME_DIR .. "src/frontend/mame/ui/ui.cpp",
MAME_DIR .. "src/frontend/mame/ui/ui.h", MAME_DIR .. "src/frontend/mame/ui/ui.h",

View File

@ -8,6 +8,8 @@
***************************************************************************/ ***************************************************************************/
#include "util/language.h"
/*************************************************************************** /***************************************************************************
BUILT-IN CORE MAPPINGS BUILT-IN CORE MAPPINGS
@ -15,12 +17,6 @@
namespace { namespace {
// can't get frontend/mame/language.h from here
#ifndef N_p
#define N_p(ctx, msg) (msg)
#endif
#define CORE_INPUT_TYPES_P1 \ #define CORE_INPUT_TYPES_P1 \
CORE_INPUT_TYPES_BEGIN(p1) \ CORE_INPUT_TYPES_BEGIN(p1) \
INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, JOYSTICK_UP, N_p("input-name", "P1 Up"), input_seq(KEYCODE_UP, input_seq::or_code, JOYCODE_Y_UP_SWITCH_INDEXED(0)) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, JOYSTICK_UP, N_p("input-name", "P1 Up"), input_seq(KEYCODE_UP, input_seq::or_code, JOYCODE_Y_UP_SWITCH_INDEXED(0)) ) \

View File

@ -51,6 +51,7 @@
#include "ui/uimain.h" #include "ui/uimain.h"
#include "util/ioprocsfilter.h" #include "util/ioprocsfilter.h"
#include "util/language.h"
#include "util/path.h" #include "util/path.h"
#include "util/xmlfile.h" #include "util/xmlfile.h"
@ -1693,6 +1694,8 @@ void render_target::load_layout_files(util::xml::data_node const &rootnode, bool
void render_target::load_additional_layout_files(const char *basename, bool have_artwork) void render_target::load_additional_layout_files(const char *basename, bool have_artwork)
{ {
using util::lang_translate;
m_external_artwork = false; m_external_artwork = false;
// if override_artwork defined, load that and skip artwork other than default // if override_artwork defined, load that and skip artwork other than default
@ -1841,7 +1844,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
viewnode->set_attribute( viewnode->set_attribute(
"name", "name",
util::string_format( util::string_format(
"Screen %1$u Standard (%2$u:%3$u)", _("view-name", "Screen %1$u Standard (%2$u:%3$u)"),
i, screens[i].physical_x(), screens[i].physical_y()).c_str()); i, screens[i].physical_x(), screens[i].physical_y()).c_str());
util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr)); util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr));
if (!screennode) if (!screennode)
@ -1867,7 +1870,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
viewnode->set_attribute( viewnode->set_attribute(
"name", "name",
util::string_format( util::string_format(
"Screen %1$u Pixel Aspect (%2$u:%3$u)", _("view-name", "Screen %1$u Pixel Aspect (%2$u:%3$u)"),
i, screens[i].native_x(), screens[i].native_y()).c_str()); i, screens[i].native_x(), screens[i].native_y()).c_str());
util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr)); util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr));
if (!screennode) if (!screennode)
@ -1889,7 +1892,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr)); util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr));
if (!viewnode) if (!viewnode)
throw emu_fatalerror("Couldn't create XML node??"); throw emu_fatalerror("Couldn't create XML node??");
viewnode->set_attribute("name", "Cocktail"); viewnode->set_attribute("name", _("view-name", "Cocktail"));
util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr)); util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr));
if (!mirrornode) if (!mirrornode)
@ -2009,10 +2012,10 @@ void render_target::load_additional_layout_files(const char *basename, bool have
}; };
// generate linear views // generate linear views
generate_view("Left-to-Right", screens.size(), false, [] (unsigned x, unsigned y) { return x; }); generate_view(_("view-name", "Left-to-Right"), screens.size(), false, [] (unsigned x, unsigned y) { return x; });
generate_view("Left-to-Right (Gapless)", screens.size(), true, [] (unsigned x, unsigned y) { return x; }); generate_view(_("view-name", "Left-to-Right (Gapless)"), screens.size(), true, [] (unsigned x, unsigned y) { return x; });
generate_view("Top-to-Bottom", 1U, false, [] (unsigned x, unsigned y) { return y; }); generate_view(_("view-name", "Top-to-Bottom"), 1U, false, [] (unsigned x, unsigned y) { return y; });
generate_view("Top-to-Bottom (Gapless)", 1U, true, [] (unsigned x, unsigned y) { return y; }); generate_view(_("view-name", "Top-to-Bottom (Gapless)"), 1U, true, [] (unsigned x, unsigned y) { return y; });
// generate fake cocktail view for systems with two screens // generate fake cocktail view for systems with two screens
if (screens.size() == 2U) if (screens.size() == 2U)
@ -2024,7 +2027,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr)); util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr));
if (!viewnode) if (!viewnode)
throw emu_fatalerror("Couldn't create XML node??"); throw emu_fatalerror("Couldn't create XML node??");
viewnode->set_attribute("name", "Cocktail"); viewnode->set_attribute("name", _("view-name", "Cocktail"));
util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr)); util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr));
if (!mirrornode) if (!mirrornode)
@ -2063,7 +2066,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
if (!remainder || (((majdim + 1) / 2) <= remainder)) if (!remainder || (((majdim + 1) / 2) <= remainder))
{ {
generate_view( generate_view(
util::string_format("%1$u\xC3\x97%2$u Left-to-Right, Top-to-Bottom", majdim, mindim).c_str(), util::string_format(_("view-name", u8"%1$u×%2$u Left-to-Right, Top-to-Bottom"), majdim, mindim).c_str(),
majdim, majdim,
false, false,
[&screens, majdim] (unsigned x, unsigned y) [&screens, majdim] (unsigned x, unsigned y)
@ -2072,7 +2075,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
return (screens.size() > i) ? int(i) : -1; return (screens.size() > i) ? int(i) : -1;
}); });
generate_view( generate_view(
util::string_format("%1$u\xC3\x97%2$u Left-to-Right, Top-to-Bottom (Gapless)", majdim, mindim).c_str(), util::string_format(_("view-name", u8"%1$u×%2$u Left-to-Right, Top-to-Bottom (Gapless)"), majdim, mindim).c_str(),
majdim, majdim,
true, true,
[&screens, majdim] (unsigned x, unsigned y) [&screens, majdim] (unsigned x, unsigned y)
@ -2081,7 +2084,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
return (screens.size() > i) ? int(i) : -1; return (screens.size() > i) ? int(i) : -1;
}); });
generate_view( generate_view(
util::string_format("%1$u\xC3\x97%2$u Top-to-Bottom, Left-to-Right", mindim, majdim).c_str(), util::string_format(_("view-name", u8"%1$u×%2$u Top-to-Bottom, Left-to-Right"), mindim, majdim).c_str(),
mindim, mindim,
false, false,
[&screens, majdim] (unsigned x, unsigned y) [&screens, majdim] (unsigned x, unsigned y)
@ -2090,7 +2093,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
return (screens.size() > i) ? int(i) : -1; return (screens.size() > i) ? int(i) : -1;
}); });
generate_view( generate_view(
util::string_format("%1$u\xC3\x97%2$u Top-to-Bottom, Left-to-Right (Gapless)", mindim, majdim).c_str(), util::string_format(_("view-name", u8"%1$u×%2$u Top-to-Bottom, Left-to-Right (Gapless)"), mindim, majdim).c_str(),
mindim, mindim,
true, true,
[&screens, majdim] (unsigned x, unsigned y) [&screens, majdim] (unsigned x, unsigned y)

View File

@ -81,6 +81,7 @@
namespace { namespace {
//************************************************************************** //**************************************************************************
// COMMAND-LINE OPTIONS // COMMAND-LINE OPTIONS
//************************************************************************** //**************************************************************************

View File

@ -30,6 +30,7 @@
#include <cctype> #include <cctype>
#include <cstring> #include <cstring>
#include <future> #include <future>
#include <locale>
#include <queue> #include <queue>
#include <type_traits> #include <type_traits>
#include <unordered_set> #include <unordered_set>
@ -100,6 +101,7 @@ private:
typedef std::set<std::add_pointer_t<device_type>, device_type_compare> device_type_set; typedef std::set<std::add_pointer_t<device_type>, device_type_compare> device_type_set;
std::string normalize_string(const char *string); std::string normalize_string(const char *string);
std::string normalize_string(std::string_view string);
// internal helper // internal helper
void output_header(std::ostream &out, bool dtd); void output_header(std::ostream &out, bool dtd);
@ -440,6 +442,11 @@ void info_xml_creator::output(std::ostream &out, const std::function<bool(const
device_type_set m_dev_set; device_type_set m_dev_set;
}; };
// TODO: maybe not the best place for this as it affects the stream passed in
// if the device part is threaded, the local streams used by the tasks can be
// imbued and the stream passed in can be left alone
out.imbue(std::locale::classic());
// prepare a driver enumerator and the queue // prepare a driver enumerator and the queue
driver_enumerator drivlist(m_lookup_options); driver_enumerator drivlist(m_lookup_options);
device_filter devfilter(filter); device_filter devfilter(filter);
@ -491,6 +498,7 @@ void info_xml_creator::output(std::ostream &out, const std::function<bool(const
{ {
prepared_info result; prepared_info result;
std::ostringstream stream; std::ostringstream stream;
stream.imbue(std::locale::classic());
// output each of the drivers // output each of the drivers
for (const game_driver &driver : drivers) for (const game_driver &driver : drivers)
@ -568,26 +576,30 @@ namespace
std::string normalize_string(const char *string) std::string normalize_string(const char *string)
{ {
std::ostringstream stream; if (string)
return normalize_string(std::string_view(string));
else
return std::string();
}
if (string != nullptr) std::string normalize_string(std::string_view string)
{
std::string result;
result.reserve(string.length());
for (char ch : string)
{ {
while (*string) switch (ch)
{ {
switch (*string) case '\"': result.append("&quot;"); break;
{ case '&': result.append("&amp;"); break;
case '\"': stream << "&quot;"; break; case '<': result.append("&lt;"); break;
case '&': stream << "&amp;"; break; case '>': result.append("&gt;"); break;
case '<': stream << "&lt;"; break; default: result.append(1, ch); break;
case '>': stream << "&gt;"; break;
default:
stream << *string;
break;
}
++string;
} }
} }
return stream.str();
return result;
} }
@ -647,7 +659,8 @@ void output_header(std::ostream &out, bool dtd)
} }
// top-level tag // top-level tag
out << util::string_format("<%s build=\"%s\" debug=\"" util::stream_format(out,
"<%s build=\"%s\" debug=\""
#ifdef MAME_DEBUG #ifdef MAME_DEBUG
"yes" "yes"
#else #else
@ -667,7 +680,7 @@ void output_header(std::ostream &out, bool dtd)
void output_footer(std::ostream &out) void output_footer(std::ostream &out)
{ {
// close the top level tag // close the top level tag
out << util::string_format("</%s>\n", XML_ROOT); util::stream_format(out, "</%s>\n", XML_ROOT);
} }
@ -726,14 +739,14 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
} }
// print the header and the machine name // print the header and the machine name
out << util::string_format("\t<%s name=\"%s\"", XML_TOP, normalize_string(driver.name)); util::stream_format(out, "\t<%s name=\"%s\"", XML_TOP, normalize_string(driver.name));
// strip away any path information from the source_file and output it // strip away any path information from the source_file and output it
const char *start = strrchr(driver.type.source(), '/'); const char *start = strrchr(driver.type.source(), '/');
if (!start) if (!start)
start = strrchr(driver.type.source(), '\\'); start = strrchr(driver.type.source(), '\\');
start = start ? (start + 1) : driver.type.source(); start = start ? (start + 1) : driver.type.source();
out << util::string_format(" sourcefile=\"%s\"", normalize_string(start)); util::stream_format(out, " sourcefile=\"%s\"", normalize_string(start));
// append bios and runnable flags // append bios and runnable flags
if (driver.flags & machine_flags::IS_BIOS_ROOT) if (driver.flags & machine_flags::IS_BIOS_ROOT)
@ -744,9 +757,9 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
// display clone information // display clone information
int clone_of = drivlist.find(driver.parent); int clone_of = drivlist.find(driver.parent);
if (clone_of != -1 && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT)) if (clone_of != -1 && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT))
out << util::string_format(" cloneof=\"%s\"", normalize_string(drivlist.driver(clone_of).name)); util::stream_format(out, " cloneof=\"%s\"", normalize_string(drivlist.driver(clone_of).name));
if (clone_of != -1) if (clone_of != -1)
out << util::string_format(" romof=\"%s\"", normalize_string(drivlist.driver(clone_of).name)); util::stream_format(out, " romof=\"%s\"", normalize_string(drivlist.driver(clone_of).name));
// display sample information and close the game tag // display sample information and close the game tag
output_sampleof(out, config.root_device()); output_sampleof(out, config.root_device());
@ -754,15 +767,15 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
// output game description // output game description
if (driver.type.fullname() != nullptr) if (driver.type.fullname() != nullptr)
out << util::string_format("\t\t<description>%s</description>\n", normalize_string(driver.type.fullname())); util::stream_format(out, "\t\t<description>%s</description>\n", normalize_string(driver.type.fullname()));
// print the year only if is a number or another allowed character (? or +) // print the year only if is a number or another allowed character (? or +)
if (driver.year != nullptr && strspn(driver.year, "0123456789?+") == strlen(driver.year)) if (driver.year && strspn(driver.year, "0123456789?+") == strlen(driver.year))
out << util::string_format("\t\t<year>%s</year>\n", normalize_string(driver.year)); util::stream_format(out, "\t\t<year>%s</year>\n", normalize_string(driver.year));
// print the manufacturer information // print the manufacturer information
if (driver.manufacturer != nullptr) if (driver.manufacturer != nullptr)
out << util::string_format("\t\t<manufacturer>%s</manufacturer>\n", normalize_string(driver.manufacturer)); util::stream_format(out, "\t\t<manufacturer>%s</manufacturer>\n", normalize_string(driver.manufacturer));
// now print various additional information // now print various additional information
output_bios(out, config.root_device()); output_bios(out, config.root_device());
@ -785,7 +798,7 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
output_ramoptions(out, config.root_device()); output_ramoptions(out, config.root_device());
// close the topmost tag // close the topmost tag
out << util::string_format("\t</%s>\n", XML_TOP); util::stream_format(out, "\t</%s>\n", XML_TOP);
} }
@ -824,15 +837,16 @@ void output_one_device(std::ostream &out, machine_config &config, device_t &devi
} }
// start to output info // start to output info
out << util::string_format("\t<%s name=\"%s\"", XML_TOP, normalize_string(device.shortname())); util::stream_format(out, "\t<%s name=\"%s\"", XML_TOP, normalize_string(device.shortname()));
std::string src(device.source()); std::string src(device.source());
strreplace(src,"../", ""); strreplace(src,"../", "");
out << util::string_format(" sourcefile=\"%s\" isdevice=\"yes\" runnable=\"no\"", normalize_string(src.c_str())); util::stream_format(out, " sourcefile=\"%s\" isdevice=\"yes\" runnable=\"no\"", normalize_string(src));
auto const parent(device.type().parent_rom_device_type()); auto const parent(device.type().parent_rom_device_type());
if (parent) if (parent)
out << util::string_format(" romof=\"%s\"", normalize_string(parent->shortname())); util::stream_format(out, " romof=\"%s\"", normalize_string(parent->shortname()));
output_sampleof(out, device); output_sampleof(out, device);
out << ">\n" << util::string_format("\t\t<description>%s</description>\n", normalize_string(device.name())); out << ">\n";
util::stream_format(out, "\t\t<description>%s</description>\n", normalize_string(device.name()));
output_bios(out, device); output_bios(out, device);
output_rom(out, config, nullptr, nullptr, device); output_rom(out, config, nullptr, nullptr, device);
@ -854,7 +868,7 @@ void output_one_device(std::ostream &out, machine_config &config, device_t &devi
output_images(out, device, devtag); output_images(out, device, devtag);
output_slots(out, config, device, devtag, nullptr); output_slots(out, config, device, devtag, nullptr);
output_software_lists(out, device, devtag); output_software_lists(out, device, devtag);
out << util::string_format("\t</%s>\n", XML_TOP); util::stream_format(out, "\t</%s>\n", XML_TOP);
} }
@ -909,7 +923,7 @@ void output_device_refs(std::ostream &out, device_t &root)
{ {
for (device_t &device : device_enumerator(root)) for (device_t &device : device_enumerator(root))
if (&device != &root) if (&device != &root)
out << util::string_format("\t\t<device_ref name=\"%s\"/>\n", normalize_string(device.shortname())); util::stream_format(out, "\t\t<device_ref name=\"%s\"/>\n", normalize_string(device.shortname()));
} }
@ -926,7 +940,7 @@ void output_sampleof(std::ostream &out, device_t &device)
samples_iterator sampiter(samples); samples_iterator sampiter(samples);
if (sampiter.altbasename() != nullptr) if (sampiter.altbasename() != nullptr)
{ {
out << util::string_format(" sampleof=\"%s\"", normalize_string(sampiter.altbasename())); util::stream_format(out, " sampleof=\"%s\"", normalize_string(sampiter.altbasename()));
// must stop here, as there can only be one attribute of the same name // must stop here, as there can only be one attribute of the same name
return; return;
@ -954,8 +968,8 @@ void output_bios(std::ostream &out, device_t const &device)
{ {
// output extracted name and descriptions' // output extracted name and descriptions'
out << "\t\t<biosset"; out << "\t\t<biosset";
out << util::string_format(" name=\"%s\"", normalize_string(bios.get_name())); util::stream_format(out, " name=\"%s\"", normalize_string(bios.get_name()));
out << util::string_format(" description=\"%s\"", normalize_string(bios.get_description())); util::stream_format(out, " description=\"%s\"", normalize_string(bios.get_description()));
if (defaultname && !std::strcmp(defaultname, bios.get_name())) if (defaultname && !std::strcmp(defaultname, bios.get_name()))
out << " default=\"yes\""; out << " default=\"yes\"";
out << "/>\n"; out << "/>\n";
@ -1062,13 +1076,13 @@ void output_rom(std::ostream &out, machine_config &config, driver_enumerator *dr
// add name, merge, bios, and size tags // add name, merge, bios, and size tags
char const *const name(rom->name); char const *const name(rom->name);
if (name && name[0]) if (name && name[0])
out << util::string_format(" name=\"%s\"", normalize_string(name)); util::stream_format(out, " name=\"%s\"", normalize_string(name));
if (merge_name) if (merge_name)
out << util::string_format(" merge=\"%s\"", normalize_string(merge_name)); util::stream_format(out, " merge=\"%s\"", normalize_string(merge_name));
if (bios_name) if (bios_name)
out << util::string_format(" bios=\"%s\"", normalize_string(bios_name)); util::stream_format(out, " bios=\"%s\"", normalize_string(bios_name));
if (!is_disk) if (!is_disk)
out << util::string_format(" size=\"%u\"", rom_file_size(rom)); util::stream_format(out, " size=\"%u\"", rom_file_size(rom));
// dump checksum information only if there is a known dump // dump checksum information only if there is a known dump
if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP))
@ -1077,17 +1091,17 @@ void output_rom(std::ostream &out, machine_config &config, driver_enumerator *dr
out << " status=\"nodump\""; out << " status=\"nodump\"";
// append a region name // append a region name
out << util::string_format(" region=\"%s\"", region->name); util::stream_format(out, " region=\"%s\"", region->name);
if (!is_disk) if (!is_disk)
{ {
// for non-disk entries, print offset // for non-disk entries, print offset
out << util::string_format(" offset=\"%x\"", ROM_GETOFFSET(rom)); util::stream_format(out, " offset=\"%x\"", ROM_GETOFFSET(rom));
} }
else else
{ {
// for disk entries, add the disk index // for disk entries, add the disk index
out << util::string_format(" index=\"%x\" writable=\"%s\"", DISK_GETINDEX(rom), DISK_ISREADONLY(rom) ? "no" : "yes"); util::stream_format(out, " index=\"%x\" writable=\"%s\"", DISK_GETINDEX(rom), DISK_ISREADONLY(rom) ? "no" : "yes");
} }
// add optional flag // add optional flag
@ -1120,7 +1134,7 @@ void output_sample(std::ostream &out, device_t &device)
continue; continue;
// output the sample name // output the sample name
out << util::string_format("\t\t<sample name=\"%s\"/>\n", normalize_string(samplename)); util::stream_format(out, "\t\t<sample name=\"%s\"/>\n", normalize_string(samplename));
} }
} }
} }
@ -1143,9 +1157,9 @@ void output_chips(std::ostream &out, device_t &device, const char *root_tag)
out << "\t\t<chip"; out << "\t\t<chip";
out << " type=\"cpu\""; out << " type=\"cpu\"";
out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str())); util::stream_format(out, " tag=\"%s\"", normalize_string(newtag));
out << util::string_format(" name=\"%s\"", normalize_string(exec.device().name())); util::stream_format(out, " name=\"%s\"", normalize_string(exec.device().name()));
out << util::string_format(" clock=\"%d\"", exec.device().clock()); util::stream_format(out, " clock=\"%d\"", exec.device().clock());
out << "/>\n"; out << "/>\n";
} }
} }
@ -1160,10 +1174,10 @@ void output_chips(std::ostream &out, device_t &device, const char *root_tag)
out << "\t\t<chip"; out << "\t\t<chip";
out << " type=\"audio\""; out << " type=\"audio\"";
out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str())); util::stream_format(out, " tag=\"%s\"", normalize_string(newtag));
out << util::string_format(" name=\"%s\"", normalize_string(sound.device().name())); util::stream_format(out, " name=\"%s\"", normalize_string(sound.device().name()));
if (sound.device().clock() != 0) if (sound.device().clock() != 0)
out << util::string_format(" clock=\"%d\"", sound.device().clock()); util::stream_format(out, " clock=\"%d\"", sound.device().clock());
out << "/>\n"; out << "/>\n";
} }
} }
@ -1185,7 +1199,7 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con
std::string newtag(screendev.tag()), oldtag(":"); std::string newtag(screendev.tag()), oldtag(":");
newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
out << util::string_format("\t\t<display tag=\"%s\"", normalize_string(newtag.c_str())); util::stream_format(out, "\t\t<display tag=\"%s\"", normalize_string(newtag));
switch (screendev.screen_type()) switch (screendev.screen_type())
{ {
@ -1229,12 +1243,12 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con
if (screendev.screen_type() != SCREEN_TYPE_VECTOR) if (screendev.screen_type() != SCREEN_TYPE_VECTOR)
{ {
const rectangle &visarea = screendev.visible_area(); const rectangle &visarea = screendev.visible_area();
out << util::string_format(" width=\"%d\"", visarea.width()); util::stream_format(out, " width=\"%d\"", visarea.width());
out << util::string_format(" height=\"%d\"", visarea.height()); util::stream_format(out, " height=\"%d\"", visarea.height());
} }
// output refresh rate // output refresh rate
out << util::string_format(" refresh=\"%f\"", ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds())); util::stream_format(out, " refresh=\"%f\"", ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds()));
// output raw video parameters only for games that are not vector // output raw video parameters only for games that are not vector
// and had raw parameters specified // and had raw parameters specified
@ -1242,13 +1256,13 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con
{ {
int pixclock = screendev.width() * screendev.height() * ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds()); int pixclock = screendev.width() * screendev.height() * ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds());
out << util::string_format(" pixclock=\"%d\"", pixclock); util::stream_format(out, " pixclock=\"%d\"", pixclock);
out << util::string_format(" htotal=\"%d\"", screendev.width()); util::stream_format(out, " htotal=\"%d\"", screendev.width());
out << util::string_format(" hbend=\"%d\"", screendev.visible_area().min_x); util::stream_format(out, " hbend=\"%d\"", screendev.visible_area().min_x);
out << util::string_format(" hbstart=\"%d\"", screendev.visible_area().max_x+1); util::stream_format(out, " hbstart=\"%d\"", screendev.visible_area().max_x+1);
out << util::string_format(" vtotal=\"%d\"", screendev.height()); util::stream_format(out, " vtotal=\"%d\"", screendev.height());
out << util::string_format(" vbend=\"%d\"", screendev.visible_area().min_y); util::stream_format(out, " vbend=\"%d\"", screendev.visible_area().min_y);
out << util::string_format(" vbstart=\"%d\"", screendev.visible_area().max_y+1); util::stream_format(out, " vbstart=\"%d\"", screendev.visible_area().max_y+1);
} }
out << " />\n"; out << " />\n";
} }
@ -1271,7 +1285,7 @@ void output_sound(std::ostream &out, device_t &device)
if (snditer.first() == nullptr) if (snditer.first() == nullptr)
speakers = 0; speakers = 0;
out << util::string_format("\t\t<sound channels=\"%d\"/>\n", speakers); util::stream_format(out, "\t\t<sound channels=\"%d\"/>\n", speakers);
} }
@ -1297,7 +1311,7 @@ void output_ioport_condition(std::ostream &out, const ioport_condition &conditio
case ioport_condition::NOTLESSTHAN: rel = "ge"; break; case ioport_condition::NOTLESSTHAN: rel = "ge"; break;
} }
out << util::string_format("<condition tag=\"%s\" mask=\"%u\" relation=\"%s\" value=\"%u\"/>\n", normalize_string(condition.tag()), condition.mask(), rel, condition.value()); util::stream_format(out, "<condition tag=\"%s\" mask=\"%u\" relation=\"%s\" value=\"%u\"/>\n", normalize_string(condition.tag()), condition.mask(), rel, condition.value());
} }
//------------------------------------------------- //-------------------------------------------------
@ -1700,13 +1714,13 @@ void output_input(std::ostream &out, const ioport_list &portlist)
// Output the input info // Output the input info
// First basic info // First basic info
out << "\t\t<input"; out << "\t\t<input";
out << util::string_format(" players=\"%d\"", nplayer); util::stream_format(out, " players=\"%d\"", nplayer);
if (ncoin != 0) if (ncoin != 0)
out << util::string_format(" coins=\"%d\"", ncoin); util::stream_format(out, " coins=\"%d\"", ncoin);
if (service) if (service)
out << util::string_format(" service=\"yes\""); util::stream_format(out, " service=\"yes\"");
if (tilt) if (tilt)
out << util::string_format(" tilt=\"yes\""); util::stream_format(out, " tilt=\"yes\"");
out << ">\n"; out << ">\n";
// Then controller specific ones // Then controller specific ones
@ -1716,21 +1730,21 @@ void output_input(std::ostream &out, const ioport_list &portlist)
//printf("type %s - player %d - buttons %d\n", elem.type, elem.player, elem.nbuttons); //printf("type %s - player %d - buttons %d\n", elem.type, elem.player, elem.nbuttons);
if (elem.analog) if (elem.analog)
{ {
out << util::string_format("\t\t\t<control type=\"%s\"", normalize_string(elem.type)); util::stream_format(out, "\t\t\t<control type=\"%s\"", normalize_string(elem.type));
if (nplayer > 1) if (nplayer > 1)
out << util::string_format(" player=\"%d\"", elem.player); util::stream_format(out, " player=\"%d\"", elem.player);
if (elem.nbuttons > 0) if (elem.nbuttons > 0)
{ {
out << util::string_format(" buttons=\"%d\"", strcmp(elem.type, "stick") ? elem.nbuttons : elem.maxbuttons); util::stream_format(out, " buttons=\"%d\"", strcmp(elem.type, "stick") ? elem.nbuttons : elem.maxbuttons);
if (elem.reqbuttons < elem.nbuttons) if (elem.reqbuttons < elem.nbuttons)
out << util::string_format(" reqbuttons=\"%d\"", elem.reqbuttons); util::stream_format(out, " reqbuttons=\"%d\"", elem.reqbuttons);
} }
if (elem.min != 0 || elem.max != 0) if (elem.min != 0 || elem.max != 0)
out << util::string_format(" minimum=\"%d\" maximum=\"%d\"", elem.min, elem.max); util::stream_format(out, " minimum=\"%d\" maximum=\"%d\"", elem.min, elem.max);
if (elem.sensitivity != 0) if (elem.sensitivity != 0)
out << util::string_format(" sensitivity=\"%d\"", elem.sensitivity); util::stream_format(out, " sensitivity=\"%d\"", elem.sensitivity);
if (elem.keydelta != 0) if (elem.keydelta != 0)
out << util::string_format(" keydelta=\"%d\"", elem.keydelta); util::stream_format(out, " keydelta=\"%d\"", elem.keydelta);
if (elem.reverse) if (elem.reverse)
out << " reverse=\"yes\""; out << " reverse=\"yes\"";
@ -1742,14 +1756,14 @@ void output_input(std::ostream &out, const ioport_list &portlist)
if (elem.helper[0] == 0 && elem.helper[1] != 0) { elem.helper[0] = elem.helper[1]; elem.helper[1] = 0; } if (elem.helper[0] == 0 && elem.helper[1] != 0) { elem.helper[0] = elem.helper[1]; elem.helper[1] = 0; }
if (elem.helper[1] == 0 && elem.helper[2] != 0) { elem.helper[1] = elem.helper[2]; elem.helper[2] = 0; } if (elem.helper[1] == 0 && elem.helper[2] != 0) { elem.helper[1] = elem.helper[2]; elem.helper[2] = 0; }
const char *joys = (elem.helper[2] != 0) ? "triple" : (elem.helper[1] != 0) ? "double" : ""; const char *joys = (elem.helper[2] != 0) ? "triple" : (elem.helper[1] != 0) ? "double" : "";
out << util::string_format("\t\t\t<control type=\"%s%s\"", joys, normalize_string(elem.type)); util::stream_format(out, "\t\t\t<control type=\"%s%s\"", joys, normalize_string(elem.type));
if (nplayer > 1) if (nplayer > 1)
out << util::string_format(" player=\"%d\"", elem.player); util::stream_format(out, " player=\"%d\"", elem.player);
if (elem.nbuttons > 0) if (elem.nbuttons > 0)
{ {
out << util::string_format(" buttons=\"%d\"", strcmp(elem.type, "joy") ? elem.nbuttons : elem.maxbuttons); util::stream_format(out, " buttons=\"%d\"", strcmp(elem.type, "joy") ? elem.nbuttons : elem.maxbuttons);
if (elem.reqbuttons < elem.nbuttons) if (elem.reqbuttons < elem.nbuttons)
out << util::string_format(" reqbuttons=\"%d\"", elem.reqbuttons); util::stream_format(out, " reqbuttons=\"%d\"", elem.reqbuttons);
} }
for (int lp = 0; lp < 3 && elem.helper[lp] != 0; lp++) for (int lp = 0; lp < 3 && elem.helper[lp] != 0; lp++)
{ {
@ -1759,7 +1773,7 @@ void output_input(std::ostream &out, const ioport_list &portlist)
switch (elem.helper[lp] & (DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT)) switch (elem.helper[lp] & (DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT))
{ {
case DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT: case DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT:
helper = string_format("%d", (elem.ways == 0) ? 8 : elem.ways); helper = util::string_format(std::locale::classic(), "%d", (elem.ways == 0) ? 8 : elem.ways);
ways = helper.c_str(); ways = helper.c_str();
break; break;
case DIR_LEFT | DIR_RIGHT: case DIR_LEFT | DIR_RIGHT:
@ -1784,7 +1798,7 @@ void output_input(std::ostream &out, const ioport_list &portlist)
ways = "strange2"; ways = "strange2";
break; break;
} }
out << util::string_format(" ways%s=\"%s\"", plural, ways); util::stream_format(out, " ways%s=\"%s\"", plural, ways);
} }
out << "/>\n"; out << "/>\n";
} }
@ -1811,15 +1825,15 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char
// output the switch name information // output the switch name information
std::string const normalized_field_name(normalize_string(field.name())); std::string const normalized_field_name(normalize_string(field.name()));
std::string const normalized_newtag(normalize_string(newtag.c_str())); std::string const normalized_newtag(normalize_string(newtag));
out << util::string_format("\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name.c_str(), normalized_newtag.c_str(), field.mask()); util::stream_format(out, "\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name, normalized_newtag, field.mask());
if (!field.condition().none()) if (!field.condition().none())
output_ioport_condition(out, field.condition(), 3); output_ioport_condition(out, field.condition(), 3);
// loop over locations // loop over locations
for (ioport_diplocation const &diploc : field.diplocations()) for (ioport_diplocation const &diploc : field.diplocations())
{ {
out << util::string_format("\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, normalize_string(diploc.name()), diploc.number()); util::stream_format(out, "\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, normalize_string(diploc.name()), diploc.number());
if (diploc.inverted()) if (diploc.inverted())
out << " inverted=\"yes\""; out << " inverted=\"yes\"";
out << "/>\n"; out << "/>\n";
@ -1828,7 +1842,7 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char
// loop over settings // loop over settings
for (ioport_setting const &setting : field.settings()) for (ioport_setting const &setting : field.settings())
{ {
out << util::string_format("\t\t\t<%s name=\"%s\" value=\"%u\"", innertag, normalize_string(setting.name()), setting.value()); util::stream_format(out, "\t\t\t<%s name=\"%s\" value=\"%u\"", innertag, normalize_string(setting.name()), setting.value());
if (setting.value() == field.defvalue()) if (setting.value() == field.defvalue())
out << " default=\"yes\""; out << " default=\"yes\"";
if (setting.condition().none()) if (setting.condition().none())
@ -1839,12 +1853,12 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char
{ {
out << ">\n"; out << ">\n";
output_ioport_condition(out, setting.condition(), 4); output_ioport_condition(out, setting.condition(), 4);
out << util::string_format("\t\t\t</%s>\n", innertag); util::stream_format(out, "\t\t\t</%s>\n", innertag);
} }
} }
// terminate the switch entry // terminate the switch entry
out << util::string_format("\t\t</%s>\n", outertag); util::stream_format(out, "\t\t</%s>\n", outertag);
} }
} }
@ -1857,13 +1871,13 @@ void output_ports(std::ostream &out, const ioport_list &portlist)
// cycle through ports // cycle through ports
for (auto &port : portlist) for (auto &port : portlist)
{ {
out << util::string_format("\t\t<port tag=\"%s\">\n", normalize_string(port.second->tag())); util::stream_format(out, "\t\t<port tag=\"%s\">\n", normalize_string(port.second->tag()));
for (ioport_field const &field : port.second->fields()) for (ioport_field const &field : port.second->fields())
{ {
if (field.is_analog()) if (field.is_analog())
out << util::string_format("\t\t\t<analog mask=\"%u\"/>\n", field.mask()); util::stream_format(out, "\t\t\t<analog mask=\"%u\"/>\n", field.mask());
} }
out << util::string_format("\t\t</port>\n"); util::stream_format(out, "\t\t</port>\n");
} }
} }
@ -1880,7 +1894,7 @@ void output_adjusters(std::ostream &out, const ioport_list &portlist)
for (ioport_field const &field : port.second->fields()) for (ioport_field const &field : port.second->fields())
if (field.type() == IPT_ADJUSTER) if (field.type() == IPT_ADJUSTER)
{ {
out << util::string_format("\t\t<adjuster name=\"%s\" default=\"%d\"/>\n", normalize_string(field.name()), field.defvalue()); util::stream_format(out, "\t\t<adjuster name=\"%s\" default=\"%d\"/>\n", normalize_string(field.name()), field.defvalue());
} }
} }
@ -1955,7 +1969,7 @@ void output_features(std::ostream &out, device_type type, device_t::feature_type
{ {
if (flags & feature.first) if (flags & feature.first)
{ {
out << util::string_format("\t\t<feature type=\"%s\"", feature.second); util::stream_format(out, "\t\t<feature type=\"%s\"", feature.second);
if (type.unemulated_features() & feature.first) if (type.unemulated_features() & feature.first)
{ {
out << " status=\"unemulated\""; out << " status=\"unemulated\"";
@ -1991,11 +2005,11 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
// print m_output device type // print m_output device type
out << util::string_format("\t\t<device type=\"%s\"", normalize_string(imagedev.image_type_name())); util::stream_format(out, "\t\t<device type=\"%s\"", normalize_string(imagedev.image_type_name()));
// does this device have a tag? // does this device have a tag?
if (imagedev.device().tag()) if (imagedev.device().tag())
out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str())); util::stream_format(out, " tag=\"%s\"", normalize_string(newtag));
// is this device available as media switch? // is this device available as media switch?
if (!loadable) if (!loadable)
@ -2006,7 +2020,7 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
out << " mandatory=\"1\""; out << " mandatory=\"1\"";
if (imagedev.image_interface() && imagedev.image_interface()[0]) if (imagedev.image_interface() && imagedev.image_interface()[0])
out << util::string_format(" interface=\"%s\"", normalize_string(imagedev.image_interface())); util::stream_format(out, " interface=\"%s\"", normalize_string(imagedev.image_interface()));
// close the XML tag // close the XML tag
out << ">\n"; out << ">\n";
@ -2017,8 +2031,8 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
char const *const shortname = imagedev.brief_instance_name().c_str(); char const *const shortname = imagedev.brief_instance_name().c_str();
out << "\t\t\t<instance"; out << "\t\t\t<instance";
out << util::string_format(" name=\"%s\"", normalize_string(name)); util::stream_format(out, " name=\"%s\"", normalize_string(name));
out << util::string_format(" briefname=\"%s\"", normalize_string(shortname)); util::stream_format(out, " briefname=\"%s\"", normalize_string(shortname));
out << "/>\n"; out << "/>\n";
char const *extensions(imagedev.file_extensions()); char const *extensions(imagedev.file_extensions());
@ -2027,7 +2041,7 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
char const *end(extensions); char const *end(extensions);
while (*end && (',' != *end)) while (*end && (',' != *end))
++end; ++end;
out << util::string_format("\t\t\t<extension name=\"%s\"/>\n", normalize_string(std::string(extensions, end).c_str())); util::stream_format(out, "\t\t\t<extension name=\"%s\"/>\n", normalize_string(std::string_view(extensions, end - extensions)));
extensions = *end ? (end + 1) : nullptr; extensions = *end ? (end + 1) : nullptr;
} }
} }
@ -2056,7 +2070,7 @@ void output_slots(std::ostream &out, machine_config &config, device_t &device, c
// print m_output device type // print m_output device type
if (listed) if (listed)
out << util::string_format("\t\t<slot name=\"%s\">\n", normalize_string(newtag.c_str())); util::stream_format(out, "\t\t<slot name=\"%s\">\n", normalize_string(newtag));
for (auto &option : slot.option_list()) for (auto &option : slot.option_list())
{ {
@ -2071,9 +2085,9 @@ void output_slots(std::ostream &out, machine_config &config, device_t &device, c
if (listed && option.second->selectable()) if (listed && option.second->selectable())
{ {
out << util::string_format("\t\t\t<slotoption name=\"%s\"", normalize_string(option.second->name())); util::stream_format(out, "\t\t\t<slotoption name=\"%s\"", normalize_string(option.second->name()));
out << util::string_format(" devname=\"%s\"", normalize_string(dev->shortname())); util::stream_format(out, " devname=\"%s\"", normalize_string(dev->shortname()));
if (slot.default_option() != nullptr && strcmp(slot.default_option(), option.second->name())==0) if (slot.default_option() && !strcmp(slot.default_option(), option.second->name()))
out << " default=\"yes\""; out << " default=\"yes\"";
out << "/>\n"; out << "/>\n";
} }
@ -2106,10 +2120,10 @@ void output_software_lists(std::ostream &out, device_t &root, const char *root_t
std::string newtag(swlist.tag()), oldtag(":"); std::string newtag(swlist.tag()), oldtag(":");
newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
out << util::string_format("\t\t<softwarelist tag=\"%s\" name=\"%s\" status=\"%s\"", normalize_string(newtag.c_str()), normalize_string(swlist.list_name().c_str()), swlist.is_original() ? "original" : "compatible"); util::stream_format(out, "\t\t<softwarelist tag=\"%s\" name=\"%s\" status=\"%s\"", normalize_string(newtag), normalize_string(swlist.list_name()), swlist.is_original() ? "original" : "compatible");
if (swlist.filter()) if (swlist.filter())
out << util::string_format(" filter=\"%s\"", normalize_string(swlist.filter())); util::stream_format(out, " filter=\"%s\"", normalize_string(swlist.filter()));
out << "/>\n"; out << "/>\n";
} }
} }
@ -2135,15 +2149,15 @@ void output_ramoptions(std::ostream &out, device_t &root)
{ {
assert(!havedefault); assert(!havedefault);
havedefault = true; havedefault = true;
out << util::string_format("\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", normalize_string(option.first.c_str()), option.second); util::stream_format(out, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", normalize_string(option.first), option.second);
} }
else else
{ {
out << util::string_format("\t\t<ramoption name=\"%s\">%u</ramoption>\n", normalize_string(option.first.c_str()), option.second); util::stream_format(out, "\t\t<ramoption name=\"%s\">%u</ramoption>\n", normalize_string(option.first), option.second);
} }
} }
if (!havedefault) if (!havedefault)
out << util::string_format("\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", ram.default_size_string(), defsize); util::stream_format(out, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", ram.default_size_string(), defsize);
break; break;
} }
} }

View File

@ -15,28 +15,12 @@
#include "corestr.h" #include "corestr.h"
#include <cstring> #include <string>
#include <memory>
#include <new>
#include <unordered_map>
#include <utility>
namespace {
constexpr u32 MO_MAGIC = 0x950412de;
constexpr u32 MO_MAGIC_REVERSED = 0xde120495;
std::unique_ptr<u32 []> f_translation_data;
std::unordered_map<std::string_view, std::pair<char const *, u32> > f_translation_map;
} // anonymous namespace
void load_translation(emu_options &m_options) void load_translation(emu_options &m_options)
{ {
f_translation_data.reset(); util::unload_translation();
f_translation_map.clear();
std::string name = m_options.language(); std::string name = m_options.language();
if (name.empty()) if (name.empty())
@ -52,174 +36,6 @@ void load_translation(emu_options &m_options)
return; return;
} }
u64 const size = file.size(); osd_printf_verbose("Loading translation file %s\n", file.fullpath());
if (20 > size) util::load_translation(file);
{
file.close();
osd_printf_error("Error reading translation file %s: %u-byte file is too small to contain translation data\n", name, size);
return;
}
f_translation_data.reset(new (std::nothrow) u32 [(size + 3) / 4]);
if (!f_translation_data)
{
file.close();
osd_printf_error("Failed to allocate %u bytes to load translation data file %s\n", size, name);
return;
}
auto const read = file.read(f_translation_data.get(), size);
file.close();
if (read != size)
{
osd_printf_error("Error reading translation file %s: requested %u bytes but got %u bytes\n", name, size, read);
f_translation_data.reset();
return;
}
if ((f_translation_data[0] != MO_MAGIC) && (f_translation_data[0] != MO_MAGIC_REVERSED))
{
osd_printf_error("Error reading translation file %s: unrecognized magic number 0x%08X\n", name, f_translation_data[0]);
f_translation_data.reset();
return;
}
auto fetch_word =
[reversed = f_translation_data[0] == MO_MAGIC_REVERSED, words = f_translation_data.get()] (size_t offset)
{
return reversed ? swapendian_int32(words[offset]) : words[offset];
};
// FIXME: check major/minor version number
if ((fetch_word(3) % 4) || (fetch_word(4) % 4))
{
osd_printf_error("Error reading translation file %s: table offsets %u and %u are not word-aligned\n", name, fetch_word(3), fetch_word(4));
f_translation_data.reset();
return;
}
u32 const number_of_strings = fetch_word(2);
u32 const original_table_offset = fetch_word(3) >> 2;
u32 const translation_table_offset = fetch_word(4) >> 2;
if ((4 * (original_table_offset + (u64(number_of_strings) * 2))) > size)
{
osd_printf_error("Error reading translation file %s: %u-entry original string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(3), size);
f_translation_data.reset();
return;
}
if ((4 * (translation_table_offset + (u64(number_of_strings) * 2))) > size)
{
osd_printf_error("Error reading translation file %s: %u-entry translated string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(4), size);
f_translation_data.reset();
return;
}
osd_printf_verbose("Reading translation file %s: %u strings, original table at word offset %u, translated table at word offset %u\n", name, number_of_strings, original_table_offset, translation_table_offset);
char const *const data = reinterpret_cast<char const *>(f_translation_data.get());
for (u32 i = 1; number_of_strings > i; ++i)
{
u32 const original_length = fetch_word(original_table_offset + (2 * i));
u32 const original_offset = fetch_word(original_table_offset + (2 * i) + 1);
if ((original_length + original_offset) >= size)
{
osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u extends past end of %u-byte file\n", name, original_length, i, original_offset, size);
continue;
}
if (data[original_length + original_offset])
{
osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u is not correctly NUL-terminated\n", name, original_length, i, original_offset);
continue;
}
u32 const translation_length = fetch_word(translation_table_offset + (2 * i));
u32 const translation_offset = fetch_word(translation_table_offset + (2 * i) + 1);
if ((translation_length + translation_offset) >= size)
{
osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u extends past end of %u-byte file\n", name, translation_length, i, translation_offset, size);
continue;
}
if (data[translation_length + translation_offset])
{
osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u is not correctly NUL-terminated\n", name, translation_length, i, translation_offset);
continue;
}
std::string_view const original(&data[original_offset], original_length);
char const *const translation(&data[translation_offset]);
auto const ins = f_translation_map.emplace(original, std::make_pair(translation, translation_length));
if (!ins.second)
{
osd_printf_warning(
"Loading translation file %s: translation %u '%s'='%s' conflicts with previous translation '%s'='%s'\n",
name,
i,
original,
translation,
ins.first->first,
ins.first->second.first);
}
}
osd_printf_verbose("Loaded %u translations from file %s\n", f_translation_map.size(), name);
}
char const *lang_translate(char const *message)
{
auto const found = f_translation_map.find(message);
if (f_translation_map.end() != found)
return found->second.first;
return message;
}
std::string_view lang_translate(std::string_view message)
{
auto const found = f_translation_map.find(message);
if (f_translation_map.end() != found)
return std::string_view(found->second.first, found->second.second);
return message;
}
char const *lang_translate(char const *context, char const *message)
{
if (!f_translation_map.empty())
{
auto const ctxlen(std::strlen(context));
auto const msglen(std::strlen(message));
std::string key;
key.reserve(ctxlen + 1 + msglen);
key.append(context, ctxlen);
key.append(1, '\004');
key.append(message, msglen);
auto const found = f_translation_map.find(key);
if (f_translation_map.end() != found)
return found->second.first;
}
return message;
}
std::string_view lang_translate(char const *context, std::string_view message)
{
return lang_translate(std::string_view(context), message);
}
std::string_view lang_translate(std::string_view context, std::string_view message)
{
if (!f_translation_map.empty())
{
std::string key;
key.reserve(context.length() + 1 + message.length());
key.append(context);
key.append(1, '\004');
key.append(message);
auto const found = f_translation_map.find(key);
if (f_translation_map.end() != found)
return std::string_view(found->second.first, found->second.second);
}
return message;
} }

View File

@ -12,25 +12,11 @@
#pragma once #pragma once
#include <string_view> #include "util/language.h"
//************************************************************************** void load_translation(emu_options &options);
// LOCALIZATION SUPPORT
//**************************************************************************
#define _(...) lang_translate(__VA_ARGS__) using util::lang_translate;
#define N_(msg) (msg)
#define N_p(ctx, msg) (msg)
void load_translation(emu_options &option);
char const *lang_translate(char const *message);
std::string_view lang_translate(std::string_view message);
char const *lang_translate(char const *context, char const *message);
std::string_view lang_translate(char const *context, std::string_view message);
std::string_view lang_translate(std::string_view context, std::string_view message);
#endif // MAME_FRONTEND_MAME_LANGUAGE_H #endif // MAME_FRONTEND_MAME_LANGUAGE_H

View File

@ -747,6 +747,8 @@ void lua_engine::initialize()
emu["print_error"] = [] (const char *str) { osd_printf_error("%s\n", str); }; emu["print_error"] = [] (const char *str) { osd_printf_error("%s\n", str); };
emu["print_info"] = [] (const char *str) { osd_printf_info("%s\n", str); }; emu["print_info"] = [] (const char *str) { osd_printf_info("%s\n", str); };
emu["print_debug"] = [] (const char *str) { osd_printf_debug("%s\n", str); }; emu["print_debug"] = [] (const char *str) { osd_printf_debug("%s\n", str); };
emu["osd_ticks"] = &osd_ticks;
emu["osd_ticks_per_second"] = &osd_ticks_per_second;
emu["driver_find"] = emu["driver_find"] =
[] (sol::this_state s, const char *driver) -> sol::object [] (sol::this_state s, const char *driver) -> sol::object
{ {
@ -1737,6 +1739,10 @@ void lua_engine::initialize()
ui_type["get_char_width"] = [] (mame_ui_manager &m, uint32_t utf8char) { return m.get_char_width(utf8char); }; ui_type["get_char_width"] = [] (mame_ui_manager &m, uint32_t utf8char) { return m.get_char_width(utf8char); };
ui_type["get_string_width"] = &mame_ui_manager::get_string_width; ui_type["get_string_width"] = &mame_ui_manager::get_string_width;
ui_type["set_aggressive_input_focus"] = [](mame_ui_manager &m, bool aggressive_focus) { osd_set_aggressive_input_focus(aggressive_focus); }; ui_type["set_aggressive_input_focus"] = [](mame_ui_manager &m, bool aggressive_focus) { osd_set_aggressive_input_focus(aggressive_focus); };
ui_type["get_general_input_setting"] = sol::overload(
// TODO: overload with sequence type string - parser isn't available here
[] (mame_ui_manager &ui, ioport_type type, int player) { return ui.get_general_input_setting(type, player, SEQ_TYPE_STANDARD); },
[] (mame_ui_manager &ui, ioport_type type) { return ui.get_general_input_setting(type, 0, SEQ_TYPE_STANDARD); });
ui_type["options"] = sol::property([] (mame_ui_manager &m) { return static_cast<core_options *>(&m.options()); }); ui_type["options"] = sol::property([] (mame_ui_manager &m) { return static_cast<core_options *>(&m.options()); });
ui_type["line_height"] = sol::property(&mame_ui_manager::get_line_height); ui_type["line_height"] = sol::property(&mame_ui_manager::get_line_height);
ui_type["menu_active"] = sol::property(&mame_ui_manager::is_menu_active); ui_type["menu_active"] = sol::property(&mame_ui_manager::is_menu_active);

View File

@ -12,12 +12,9 @@
#include "ui/about.h" #include "ui/about.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "ui/utils.h"
#include "mame.h" #include "mame.h"
#include <string_view>
namespace ui { namespace ui {
@ -38,21 +35,22 @@ namespace {
//------------------------------------------------- //-------------------------------------------------
menu_about::menu_about(mame_ui_manager &mui, render_container &container) menu_about::menu_about(mame_ui_manager &mui, render_container &container)
: menu(mui, container) : menu_textbox(mui, container)
, m_header{ , m_header{
util::string_format( util::string_format(
#ifdef MAME_DEBUG #ifdef MAME_DEBUG
_("%1$s %2$s (%3$s%4$sP%5$s, debug)"), _("about-header", "%1$s %2$s (%3$s%4$sP%5$s, debug)"),
#else #else
_("%1$s %2$s (%3$s%4$sP%5$s)"), _("about-header", "%1$s %2$s (%3$s%4$sP%5$s)"),
#endif #endif
emulator_info::get_appname(), emulator_info::get_appname(),
bare_build_version, bare_build_version,
(sizeof(int) == sizeof(void *)) ? "I" : "", (sizeof(int) == sizeof(void *)) ? "I" : "",
(sizeof(long) == sizeof(void *)) ? "L" : (sizeof(long long) == sizeof(void *)) ? "LL" : "", (sizeof(long) == sizeof(void *)) ? "L" : (sizeof(long long) == sizeof(void *)) ? "LL" : "",
sizeof(void *) * 8), sizeof(void *) * 8),
util::string_format(_("Revision: %1$s"), bare_vcs_revision) } util::string_format(_("about-header", "Revision: %1$s"), bare_vcs_revision) }
{ {
set_process_flags(PROCESS_CUSTOM_NAV);
} }
@ -81,136 +79,23 @@ void menu_about::custom_render(void *selectedref, float top, float bottom, float
//------------------------------------------------- //-------------------------------------------------
// draw - draw about // populate_text - populate the about box text
//------------------------------------------------- //-------------------------------------------------
void menu_about::draw(uint32_t flags) void menu_about::populate_text(std::optional<text_layout> &layout, float &width, int &lines)
{ {
rgb_t const color = ui().colors().text_color(); if (!layout || (layout->width() != width))
float const aspect = machine().render().ui_aspect(&container());
float const line_height = ui().get_line_height();
float const ud_arrow_width = line_height * aspect;
float const gutter_width = 0.52f * line_height * aspect;
float const visible_width = 1.0f - (2.0f * ui().box_lr_border() * aspect);
float const visible_left = (1.0f - visible_width) * 0.5f;
float const extra_height = 2.0f * line_height;
float const visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height;
// determine effective positions taking into account the hilighting arrows
float const maximum_width = visible_width - 2.0f * gutter_width;
draw_background();
map_mouse();
// account for extra space at the top and bottom
float visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height;
m_visible_lines = int(std::trunc(visible_main_menu_height / line_height));
visible_main_menu_height = float(m_visible_lines) * line_height;
// compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust
float const visible_top = ((1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f) + get_customtop();
// lay out the text if necessary
if (!m_layout || (m_layout->width() != maximum_width))
{ {
m_layout.emplace(ui().create_layout(container(), maximum_width)); rgb_t const color = ui().colors().text_color();
layout.emplace(ui().create_layout(container(), width));
for (char const *const *line = copying_text; *line; ++line) for (char const *const *line = copying_text; *line; ++line)
{ {
m_layout->add_text(*line, color); layout->add_text(*line, color);
m_layout->add_text("\n", color); layout->add_text("\n", color);
} }
lines = layout->lines();
} }
float const actual_width = m_layout->actual_width(); width = layout->actual_width();
// compute text box size
float const x1 = visible_left + ((maximum_width - actual_width) * 0.5f);
float const y1 = visible_top - ui().box_tb_border();
float const x2 = visible_left + visible_width - ((maximum_width - actual_width) * 0.5f);
float const y2 = visible_top + visible_main_menu_height + ui().box_tb_border() + extra_height;
float const effective_left = x1 + gutter_width;
float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH;
float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH;
float const separator = visible_top + float(m_visible_lines) * line_height;
ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color());
int const visible_items = m_layout->lines();
m_visible_lines = (std::min)(visible_items, m_visible_lines);
top_line = (std::max)(0, top_line);
if (top_line + m_visible_lines >= visible_items)
top_line = visible_items - m_visible_lines;
clear_hover();
if (top_line)
{
// if we're on the top line, display the up arrow
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, visible_top, line_x1, visible_top + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, visible_top,
line_x1, visible_top + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_UP);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, visible_top + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, visible_top + 0.75f * line_height,
fgcolor, ROT0);
}
if ((top_line + m_visible_lines) < visible_items)
{
// if we're on the bottom line, display the down arrow
float const line_y = visible_top + float(m_visible_lines - 1) * line_height;
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, line_y, line_x1, line_y + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, line_y,
line_x1, line_y + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_DOWN);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, line_y + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, line_y + 0.75f * line_height,
fgcolor, ROT0 ^ ORIENTATION_FLIP_Y);
}
// return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow
m_visible_items = m_visible_lines - (top_line ? 1 : 0) - (top_line + m_visible_lines != visible_items);
m_layout->emit(
container(),
top_line ? (top_line + 1) : 0, m_visible_items,
effective_left, visible_top + (top_line ? line_height : 0.0f));
// add visual separator before the "return to prevous menu" item
container().add_line(
x1, separator + (0.5f * line_height),
x2, separator + (0.5f * line_height),
UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
menu_item const &pitem = item(0);
std::string_view const itemtext = pitem.text;
float const line_y0 = separator + line_height;
float const line_y1 = line_y0 + line_height;
if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem))
set_hover(0);
highlight(line_x0, line_y0, line_x1, line_y1, ui().colors().selected_bg_color());
ui().draw_text_full(
container(), itemtext,
effective_left, line_y0, actual_width,
text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE,
mame_ui_manager::NORMAL,
ui().colors().selected_color(), ui().colors().selected_bg_color(),
nullptr, nullptr);
// if there is something special to add, do it by calling the virtual method
custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2);
} }
@ -229,38 +114,10 @@ void menu_about::populate(float &customtop, float &custombottom)
// handle - manages inputs in the about modal // handle - manages inputs in the about modal
//------------------------------------------------- //-------------------------------------------------
void menu_about::handle() void menu_about::handle(event const *ev)
{ {
const event *event = process(PROCESS_CUSTOM_NAV); if (ev)
if (event) handle_key(ev->iptkey);
{
switch (event->iptkey)
{
case IPT_UI_UP:
--top_line;
break;
case IPT_UI_DOWN:
++top_line;
break;
case IPT_UI_PAGE_UP:
top_line -= m_visible_lines - 3;
break;
case IPT_UI_PAGE_DOWN:
top_line += m_visible_lines - 3;
break;
case IPT_UI_HOME:
top_line = 0;
break;
case IPT_UI_END:
top_line = m_layout->lines() - m_visible_lines;
break;
}
}
} }
}; } // namespace ui

View File

@ -12,7 +12,8 @@
#pragma once #pragma once
#include "ui/menu.h" #include "ui/text.h"
#include "ui/textbox.h"
#include <optional> #include <optional>
#include <string> #include <string>
@ -21,7 +22,7 @@
namespace ui { namespace ui {
class menu_about : public menu class menu_about : public menu_textbox
{ {
public: public:
menu_about(mame_ui_manager &mui, render_container &container); menu_about(mame_ui_manager &mui, render_container &container);
@ -30,13 +31,13 @@ public:
protected: protected:
virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override;
virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override;
private: private:
virtual void draw(uint32_t flags) override;
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
std::vector<std::string> const m_header; std::vector<std::string> const m_header;
std::optional<text_layout> m_layout;
}; };
} // namespace ui } // namespace ui

View File

@ -52,6 +52,7 @@ menu_analog::menu_analog(mame_ui_manager &mui, render_container &container)
, m_field_data() , m_field_data()
, m_visible_fields(0U) , m_visible_fields(0U)
{ {
set_process_flags(PROCESS_LR_REPEAT);
} }
@ -121,18 +122,15 @@ void menu_analog::custom_render(void *selectedref, float top, float bottom, floa
} }
void menu_analog::handle() void menu_analog::handle(event const *ev)
{ {
// process the menu
event const *const menu_event(process(PROCESS_LR_REPEAT));
// handle events // handle events
if (menu_event && menu_event->itemref) if (ev && ev->itemref)
{ {
item_data &data(*reinterpret_cast<item_data *>(menu_event->itemref)); item_data &data(*reinterpret_cast<item_data *>(ev->itemref));
int newval(data.cur); int newval(data.cur);
switch (menu_event->iptkey) switch (ev->iptkey)
{ {
// if selected, reset to default value // if selected, reset to default value
case IPT_UI_SELECT: case IPT_UI_SELECT:

View File

@ -66,7 +66,7 @@ private:
using field_data_vector = std::vector<field_data>; using field_data_vector = std::vector<field_data>;
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
void find_fields(); void find_fields();

View File

@ -139,32 +139,28 @@ void menu_audit::populate(float &customtop, float &custombottom)
custombottom = (ui().get_line_height() * 1.0f) + (ui().box_tb_border() * 3.0f); custombottom = (ui().get_line_height() * 1.0f) + (ui().box_tb_border() * 3.0f);
} }
void menu_audit::handle() void menu_audit::handle(event const *ev)
{ {
switch (m_phase) switch (m_phase)
{ {
case phase::CONFIRMATION: case phase::CONFIRMATION:
if (ev && (IPT_UI_SELECT == ev->iptkey))
{ {
event const *const menu_event(process(0)); if ((ITEMREF_START_FULL == ev->itemref) || (ITEMREF_START_FAST == ev->itemref))
if (menu_event && (IPT_UI_SELECT == menu_event->iptkey))
{
if ((ITEMREF_START_FULL == menu_event->itemref) || (ITEMREF_START_FAST == menu_event->itemref))
{ {
set_process_flags(PROCESS_CUSTOM_ONLY | PROCESS_NOINPUT);
m_phase = phase::AUDIT; m_phase = phase::AUDIT;
m_fast = ITEMREF_START_FAST == menu_event->itemref; m_fast = ITEMREF_START_FAST == ev->itemref;
m_prompt = util::string_format(_("Press %1$s to cancel\n"), ui().get_general_input_setting(IPT_UI_CANCEL)); 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()); m_future.resize(std::thread::hardware_concurrency());
for (auto &future : m_future) for (auto &future : m_future)
future = std::async(std::launch::async, [this] () { return do_audit(); }); future = std::async(std::launch::async, [this] () { return do_audit(); });
} }
} }
}
break; break;
case phase::AUDIT: case phase::AUDIT:
case phase::CANCELLATION: case phase::CANCELLATION:
process(PROCESS_CUSTOM_ONLY | PROCESS_NOINPUT);
if ((m_next.load() >= m_availablesorted.size()) || m_cancel.load()) if ((m_next.load() >= m_availablesorted.size()) || m_cancel.load())
{ {
bool done(true); bool done(true);

View File

@ -36,7 +36,7 @@ private:
enum class phase { CONFIRMATION, AUDIT, CANCELLATION }; enum class phase { CONFIRMATION, AUDIT, CANCELLATION };
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
bool do_audit(); bool do_audit();
void save_available_machines(); void save_available_machines();

View File

@ -9,12 +9,14 @@
***************************************************************************/ ***************************************************************************/
#include "emu.h" #include "emu.h"
#include "ui/barcode.h" #include "ui/barcode.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "ui/utils.h" #include "ui/utils.h"
namespace ui { namespace ui {
// itemrefs for key menu items // itemrefs for key menu items
#define ITEMREF_NEW_BARCODE ((void *) 0x0001) #define ITEMREF_NEW_BARCODE ((void *) 0x0001)
#define ITEMREF_ENTER_BARCODE ((void *) 0x0002) #define ITEMREF_ENTER_BARCODE ((void *) 0x0002)
@ -35,6 +37,7 @@ namespace ui {
menu_barcode_reader::menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device) menu_barcode_reader::menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device)
: menu_device_control<barcode_reader_device>(mui, container, device) : menu_device_control<barcode_reader_device>(mui, container, device)
{ {
set_process_flags(PROCESS_LR_REPEAT);
} }
@ -86,29 +89,26 @@ void menu_barcode_reader::populate(float &customtop, float &custombottom)
// handle - manages inputs in the barcode input menu // handle - manages inputs in the barcode input menu
//------------------------------------------------- //-------------------------------------------------
void menu_barcode_reader::handle() void menu_barcode_reader::handle(event const *ev)
{ {
// process the menu
const event *event = process(PROCESS_LR_REPEAT);
// process the event // process the event
if (event) if (ev)
{ {
// handle selections // handle selections
switch (event->iptkey) switch (ev->iptkey)
{ {
case IPT_UI_LEFT: case IPT_UI_LEFT:
if (event->itemref == ITEMREF_SELECT_READER) if (ev->itemref == ITEMREF_SELECT_READER)
previous(); previous();
break; break;
case IPT_UI_RIGHT: case IPT_UI_RIGHT:
if (event->itemref == ITEMREF_SELECT_READER) if (ev->itemref == ITEMREF_SELECT_READER)
next(); next();
break; break;
case IPT_UI_SELECT: case IPT_UI_SELECT:
if (event->itemref == ITEMREF_ENTER_BARCODE) if (ev->itemref == ITEMREF_ENTER_BARCODE)
{ {
std::string tmp_file(m_barcode_buffer); std::string tmp_file(m_barcode_buffer);
//printf("code %s\n", m_barcode_buffer); //printf("code %s\n", m_barcode_buffer);
@ -135,7 +135,7 @@ void menu_barcode_reader::handle()
case IPT_SPECIAL: case IPT_SPECIAL:
if (get_selection_ref() == ITEMREF_NEW_BARCODE) if (get_selection_ref() == ITEMREF_NEW_BARCODE)
{ {
if (input_character(m_barcode_buffer, event->unichar, uchar_is_digit)) if (input_character(m_barcode_buffer, ev->unichar, uchar_is_digit))
reset(reset_options::REMEMBER_POSITION); reset(reset_options::REMEMBER_POSITION);
} }
break; break;

View File

@ -17,6 +17,7 @@
#include "ui/devctrl.h" #include "ui/devctrl.h"
namespace ui { namespace ui {
class menu_barcode_reader : public menu_device_control<barcode_reader_device> { class menu_barcode_reader : public menu_device_control<barcode_reader_device> {
public: public:
menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device); menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device);
@ -24,7 +25,7 @@ public:
private: private:
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
std::string m_barcode_buffer; std::string m_barcode_buffer;
}; };

View File

@ -9,12 +9,12 @@
*********************************************************************/ *********************************************************************/
#include "emu.h" #include "emu.h"
#include "cheat.h" #include "ui/cheatopt.h"
#include "mame.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "ui/menu.h"
#include "ui/cheatopt.h" #include "cheat.h"
#include "mame.h"
namespace ui { namespace ui {
@ -29,78 +29,73 @@ namespace ui {
menu_cheat - handle the cheat menu menu_cheat - handle the cheat menu
-------------------------------------------------*/ -------------------------------------------------*/
void menu_cheat::handle() void menu_cheat::handle(event const *ev)
{ {
/* process the menu */ // handle events
const event *menu_event = process(PROCESS_LR_REPEAT); if (ev && ev->itemref)
/* handle events */
if (menu_event != nullptr && menu_event->itemref != nullptr)
{ {
bool changed = false; bool changed = false;
/* clear cheat comment on any movement or keypress */ // clear cheat comment on any movement or keypress
machine().popmessage(); machine().popmessage();
/* handle reset all + reset all cheats for reload all option */ if ((ev->itemref == ITEMREF_CHEATS_RESET_ALL || ev->itemref == ITEMREF_CHEATS_RELOAD_ALL) && ev->iptkey == IPT_UI_SELECT)
if ((menu_event->itemref == ITEMREF_CHEATS_RESET_ALL || menu_event->itemref == ITEMREF_CHEATS_RELOAD_ALL) && menu_event->iptkey == IPT_UI_SELECT)
{ {
// handle reset all + reset all cheats for reload all option
for (auto &curcheat : mame_machine_manager::instance()->cheat().entries()) for (auto &curcheat : mame_machine_manager::instance()->cheat().entries())
if (curcheat->select_default_state()) if (curcheat->select_default_state())
changed = true; changed = true;
} }
else if (ev->itemref >= ITEMREF_CHEATS_FIRST_ITEM)
/* handle individual cheats */
else if (menu_event->itemref >= ITEMREF_CHEATS_FIRST_ITEM)
{ {
cheat_entry *curcheat = reinterpret_cast<cheat_entry *>(menu_event->itemref); // handle individual cheats
cheat_entry *curcheat = reinterpret_cast<cheat_entry *>(ev->itemref);
const char *string; const char *string;
switch (menu_event->iptkey) switch (ev->iptkey)
{ {
/* if selected, activate a oneshot */ // if selected, activate a oneshot
case IPT_UI_SELECT: case IPT_UI_SELECT:
changed = curcheat->activate(); changed = curcheat->activate();
break; break;
/* if cleared, reset to default value */ // if cleared, reset to default value
case IPT_UI_CLEAR: case IPT_UI_CLEAR:
changed = curcheat->select_default_state(); changed = curcheat->select_default_state();
break; break;
/* left decrements */ // left decrements
case IPT_UI_LEFT: case IPT_UI_LEFT:
changed = curcheat->select_previous_state(); changed = curcheat->select_previous_state();
break; break;
/* right increments */ // right increments
case IPT_UI_RIGHT: case IPT_UI_RIGHT:
changed = curcheat->select_next_state(); changed = curcheat->select_next_state();
break; break;
/* bring up display comment if one exists */ // bring up display comment if one exists
case IPT_UI_DISPLAY_COMMENT: case IPT_UI_DISPLAY_COMMENT:
case IPT_UI_UP: case IPT_UI_UP:
case IPT_UI_DOWN: case IPT_UI_DOWN:
string = curcheat->comment(); string = curcheat->comment();
if (string != nullptr && string[0] != 0) if (string && *string)
machine().popmessage(_("Cheat Comment:\n%s"), string); machine().popmessage(_("Cheat Comment:\n%s"), string);
break; break;
} }
} }
/* handle reload all */ // handle reload all
if (menu_event->itemref == ITEMREF_CHEATS_RELOAD_ALL && menu_event->iptkey == IPT_UI_SELECT) if (ev->itemref == ITEMREF_CHEATS_RELOAD_ALL && ev->iptkey == IPT_UI_SELECT)
{ {
/* re-init cheat engine and thus reload cheats/cheats have already been turned off by here */ // re-init cheat engine and thus reload cheats/cheats have already been turned off by here
mame_machine_manager::instance()->cheat().reload(); mame_machine_manager::instance()->cheat().reload();
/* display the reloaded cheats */ // display the reloaded cheats
reset(reset_options::REMEMBER_REF); reset(reset_options::REMEMBER_REF);
machine().popmessage(_("All cheats reloaded")); machine().popmessage(_("All cheats reloaded"));
} }
/* if things changed, update */ // if things changed, update
if (changed) if (changed)
reset(reset_options::REMEMBER_REF); reset(reset_options::REMEMBER_REF);
} }
@ -113,6 +108,7 @@ void menu_cheat::handle()
menu_cheat::menu_cheat(mame_ui_manager &mui, render_container &container) : menu(mui, container) menu_cheat::menu_cheat(mame_ui_manager &mui, render_container &container) : menu(mui, container)
{ {
set_process_flags(PROCESS_LR_REPEAT);
} }
void menu_cheat::populate(float &customtop, float &custombottom) void menu_cheat::populate(float &customtop, float &custombottom)

View File

@ -25,7 +25,7 @@ public:
private: private:
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
}; };
} // namespace ui } // namespace ui

View File

@ -82,6 +82,13 @@ menu_confswitch::~menu_confswitch()
} }
void menu_confswitch::menu_activated()
{
// switches can have input assignments, and scripts are a thing
reset(reset_options::REMEMBER_REF);
}
void menu_confswitch::populate(float &customtop, float &custombottom) void menu_confswitch::populate(float &customtop, float &custombottom)
{ {
// locate relevant fields if necessary // locate relevant fields if necessary
@ -162,31 +169,28 @@ void menu_confswitch::populate(float &customtop, float &custombottom)
} }
item_append(menu_item_type::SEPARATOR); item_append(menu_item_type::SEPARATOR);
item_append(_("Reset"), 0, (void *)1); item_append(_("Reset Machine"), 0, (void *)1);
} }
void menu_confswitch::handle() void menu_confswitch::handle(event const *ev)
{ {
// process the menu
event const *const menu_event(process(0));
// handle events // handle events
if (menu_event && menu_event->itemref) if (ev && ev->itemref)
{ {
if (uintptr_t(menu_event->itemref) == 1U) if (uintptr_t(ev->itemref) == 1U)
{ {
// reset // reset
if (menu_event->iptkey == IPT_UI_SELECT) if (ev->iptkey == IPT_UI_SELECT)
machine().schedule_hard_reset(); machine().schedule_hard_reset();
} }
else else
{ {
// actual settings // actual settings
ioport_field &field(*reinterpret_cast<ioport_field *>(menu_event->itemref)); ioport_field &field(*reinterpret_cast<ioport_field *>(ev->itemref));
bool changed(false); bool changed(false);
switch (menu_event->iptkey) switch (ev->iptkey)
{ {
// if selected, reset to default value // if selected, reset to default value
case IPT_UI_SELECT: case IPT_UI_SELECT:

View File

@ -56,6 +56,7 @@ protected:
menu_confswitch(mame_ui_manager &mui, render_container &container, uint32_t type); menu_confswitch(mame_ui_manager &mui, render_container &container, uint32_t type);
virtual void menu_activated() override;
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
field_vector const &fields() { return m_fields; } field_vector const &fields() { return m_fields; }
@ -63,7 +64,7 @@ protected:
unsigned active_switch_groups() const { return m_active_switch_groups; } unsigned active_switch_groups() const { return m_active_switch_groups; }
private: private:
virtual void handle() override; virtual void handle(event const *ev) override;
void find_fields(); void find_fields();

View File

@ -45,6 +45,7 @@ menu_custom_ui::menu_custom_ui(mame_ui_manager &mui, render_container &container
, m_currlang(0) , m_currlang(0)
, m_currsysnames(0) , m_currsysnames(0)
{ {
set_process_flags(PROCESS_LR_REPEAT);
find_languages(); find_languages();
find_sysnames(); find_sysnames();
} }
@ -72,19 +73,17 @@ menu_custom_ui::~menu_custom_ui()
// handle // handle
//------------------------------------------------- //-------------------------------------------------
void menu_custom_ui::handle() void menu_custom_ui::handle(event const *ev)
{ {
bool changed = false; bool changed = false;
// process the menu // process the menu
const event *menu_event = process(PROCESS_LR_REPEAT); if (ev && ev->itemref)
if (menu_event != nullptr && menu_event->itemref != nullptr)
{ {
switch ((uintptr_t)menu_event->itemref) switch ((uintptr_t)ev->itemref)
{ {
case FONT_MENU: case FONT_MENU:
if (menu_event->iptkey == IPT_UI_SELECT) if (ev->iptkey == IPT_UI_SELECT)
menu::stack_push<menu_font_ui>( menu::stack_push<menu_font_ui>(
ui(), ui(),
container(), container(),
@ -95,16 +94,16 @@ void menu_custom_ui::handle()
}); });
break; break;
case COLORS_MENU: case COLORS_MENU:
if (menu_event->iptkey == IPT_UI_SELECT) if (ev->iptkey == IPT_UI_SELECT)
menu::stack_push<menu_colors_ui>(ui(), container()); menu::stack_push<menu_colors_ui>(ui(), container());
break; break;
case HIDE_MENU: case HIDE_MENU:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{ {
changed = true; changed = true;
(menu_event->iptkey == IPT_UI_RIGHT) ? ui_globals::panels_status++ : ui_globals::panels_status--; (ev->iptkey == IPT_UI_RIGHT) ? ui_globals::panels_status++ : ui_globals::panels_status--;
} }
else if (menu_event->iptkey == IPT_UI_SELECT) else if (ev->iptkey == IPT_UI_SELECT)
{ {
std::vector<std::string> s_sel(std::size(HIDE_STATUS)); std::vector<std::string> s_sel(std::size(HIDE_STATUS));
std::transform(std::begin(HIDE_STATUS), std::end(HIDE_STATUS), s_sel.begin(), [](auto &s) { return _(s); }); std::transform(std::begin(HIDE_STATUS), std::end(HIDE_STATUS), s_sel.begin(), [](auto &s) { return _(s); });
@ -118,15 +117,15 @@ void menu_custom_ui::handle()
} }
break; break;
case LANGUAGE_MENU: case LANGUAGE_MENU:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{ {
changed = true; changed = true;
if (menu_event->iptkey == IPT_UI_LEFT) if (ev->iptkey == IPT_UI_LEFT)
m_currlang = (m_currlang ? m_currlang : m_languages.size())- 1; m_currlang = (m_currlang ? m_currlang : m_languages.size())- 1;
else if (++m_currlang >= m_languages.size()) else if (++m_currlang >= m_languages.size())
m_currlang = 0; m_currlang = 0;
} }
else if (menu_event->iptkey == IPT_UI_SELECT) else if (ev->iptkey == IPT_UI_SELECT)
{ {
// copying list of language names - expensive // copying list of language names - expensive
menu::stack_push<menu_selector>( menu::stack_push<menu_selector>(
@ -139,15 +138,15 @@ void menu_custom_ui::handle()
} }
break; break;
case SYSNAMES_MENU: case SYSNAMES_MENU:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{ {
changed = true; changed = true;
if (menu_event->iptkey == IPT_UI_LEFT) if (ev->iptkey == IPT_UI_LEFT)
m_currsysnames = (m_currsysnames ? m_currsysnames : m_sysnames.size())- 1; m_currsysnames = (m_currsysnames ? m_currsysnames : m_sysnames.size())- 1;
else if (++m_currsysnames >= m_sysnames.size()) else if (++m_currsysnames >= m_sysnames.size())
m_currsysnames = 0; m_currsysnames = 0;
} }
else if (menu_event->iptkey == IPT_UI_SELECT) else if (ev->iptkey == IPT_UI_SELECT)
{ {
// copying list of file names - expensive // copying list of file names - expensive
menu::stack_push<menu_selector>( menu::stack_push<menu_selector>(
@ -308,6 +307,8 @@ menu_font_ui::menu_font_ui(mame_ui_manager &mui, render_container &container, st
, m_changed(false) , m_changed(false)
, m_actual(0U) , m_actual(0U)
{ {
set_process_flags(PROCESS_LR_REPEAT);
std::string name(mui.machine().options().ui_font()); std::string name(mui.machine().options().ui_font());
list(); list();
@ -374,41 +375,39 @@ menu_font_ui::~menu_font_ui()
// handle // handle
//------------------------------------------------- //-------------------------------------------------
void menu_font_ui::handle() void menu_font_ui::handle(event const *ev)
{ {
bool changed = false; bool changed = false;
// process the menu // process the menu
const event *menu_event = process(PROCESS_LR_REPEAT); if (ev && ev->itemref)
if (menu_event && menu_event->itemref)
{ {
switch ((uintptr_t)menu_event->itemref) switch ((uintptr_t)ev->itemref)
{ {
case INFOS_SIZE: case INFOS_SIZE:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{ {
(menu_event->iptkey == IPT_UI_RIGHT) ? m_info_size += 0.05f : m_info_size -= 0.05f; (ev->iptkey == IPT_UI_RIGHT) ? m_info_size += 0.05f : m_info_size -= 0.05f;
changed = true; changed = true;
} }
break; break;
case FONT_SIZE: case FONT_SIZE:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{ {
(menu_event->iptkey == IPT_UI_RIGHT) ? m_font_size++ : m_font_size--; (ev->iptkey == IPT_UI_RIGHT) ? m_font_size++ : m_font_size--;
changed = true; changed = true;
} }
break; break;
case MUI_FNT: case MUI_FNT:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{ {
(menu_event->iptkey == IPT_UI_RIGHT) ? m_actual++ : m_actual--; (ev->iptkey == IPT_UI_RIGHT) ? m_actual++ : m_actual--;
changed = true; changed = true;
} }
else if (menu_event->iptkey == IPT_UI_SELECT) else if (ev->iptkey == IPT_UI_SELECT)
{ {
std::vector<std::string> display_names; std::vector<std::string> display_names;
display_names.reserve(m_fonts.size()); display_names.reserve(m_fonts.size());
@ -428,9 +427,9 @@ void menu_font_ui::handle()
#ifdef UI_WINDOWS #ifdef UI_WINDOWS
case MUI_BOLD: case MUI_BOLD:
case MUI_ITALIC: case MUI_ITALIC:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT) if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT || ev->iptkey == IPT_UI_SELECT)
{ {
((uintptr_t)menu_event->itemref == MUI_BOLD) ? m_bold = !m_bold : m_italic = !m_italic; ((uintptr_t)ev->itemref == MUI_BOLD) ? m_bold = !m_bold : m_italic = !m_italic;
changed = true; changed = true;
} }
break; break;
@ -551,17 +550,17 @@ menu_colors_ui::~menu_colors_ui()
// handle // handle
//------------------------------------------------- //-------------------------------------------------
void menu_colors_ui::handle() void menu_colors_ui::handle(event const *ev)
{ {
bool changed = false; bool changed = false;
// process the menu // process the menu
const event *menu_event = process(0); if (ev && ev->itemref && ev->iptkey == IPT_UI_SELECT)
if (menu_event != nullptr && menu_event->itemref != nullptr && menu_event->iptkey == IPT_UI_SELECT)
{ {
if ((uintptr_t)menu_event->itemref != MUI_RESTORE) if ((uintptr_t)ev->itemref != MUI_RESTORE)
menu::stack_push<menu_rgb_ui>(ui(), container(), &m_color_table[(uintptr_t)menu_event->itemref].color, selected_item().text); {
menu::stack_push<menu_rgb_ui>(ui(), container(), &m_color_table[(uintptr_t)ev->itemref].color, selected_item().text);
}
else else
{ {
changed = true; changed = true;
@ -767,6 +766,7 @@ menu_rgb_ui::menu_rgb_ui(mame_ui_manager &mui, render_container &container, rgb_
m_lock_ref(0), m_lock_ref(0),
m_title(_title) m_title(_title)
{ {
set_process_flags(PROCESS_LR_REPEAT);
} }
//------------------------------------------------- //-------------------------------------------------
@ -781,26 +781,19 @@ menu_rgb_ui::~menu_rgb_ui()
// handle // handle
//------------------------------------------------- //-------------------------------------------------
void menu_rgb_ui::handle() void menu_rgb_ui::handle(event const *ev)
{ {
// process the menu // process the menu
const event *menu_event; if (ev && ev->itemref)
if (!m_key_active)
menu_event = process(PROCESS_LR_REPEAT);
else
menu_event = process(PROCESS_ONLYCHAR);
if (menu_event && menu_event->itemref != nullptr)
{ {
bool changed = false; bool changed = false;
switch (menu_event->iptkey) switch (ev->iptkey)
{ {
case IPT_UI_LEFT: case IPT_UI_LEFT:
case IPT_UI_RIGHT: case IPT_UI_RIGHT:
{ {
int updated = (IPT_UI_LEFT == menu_event->iptkey) ? -1 : 1; int updated = (IPT_UI_LEFT == ev->iptkey) ? -1 : 1;
switch (uintptr_t(menu_event->itemref)) switch (uintptr_t(ev->itemref))
{ {
case RGB_ALPHA: case RGB_ALPHA:
updated += m_color->a(); updated += m_color->a();
@ -839,20 +832,20 @@ void menu_rgb_ui::handle()
break; break;
case IPT_UI_SELECT: case IPT_UI_SELECT:
if (uintptr_t(menu_event->itemref) == PALETTE_CHOOSE) if (uintptr_t(ev->itemref) == PALETTE_CHOOSE)
{ {
menu::stack_push<menu_palette_sel>(ui(), container(), *m_color); menu::stack_push<menu_palette_sel>(ui(), container(), *m_color);
break; break;
} }
[[fallthrough]]; [[fallthrough]];
case IPT_SPECIAL: case IPT_SPECIAL:
switch (uintptr_t(menu_event->itemref)) switch (uintptr_t(ev->itemref))
{ {
case RGB_ALPHA: case RGB_ALPHA:
case RGB_RED: case RGB_RED:
case RGB_GREEN: case RGB_GREEN:
case RGB_BLUE: case RGB_BLUE:
inkey_special(menu_event); inkey_special(ev);
changed = true; changed = true;
break; break;
} }
@ -1004,6 +997,7 @@ void menu_rgb_ui::inkey_special(const event *menu_event)
if (menu_event->iptkey == IPT_UI_SELECT) if (menu_event->iptkey == IPT_UI_SELECT)
{ {
m_key_active = !m_key_active; m_key_active = !m_key_active;
set_process_flags(m_key_active ? PROCESS_ONLYCHAR : PROCESS_LR_REPEAT);
m_lock_ref = (uintptr_t)menu_event->itemref; m_lock_ref = (uintptr_t)menu_event->itemref;
if (!m_key_active) if (!m_key_active)
@ -1079,13 +1073,12 @@ menu_palette_sel::~menu_palette_sel()
// handle // handle
//------------------------------------------------- //-------------------------------------------------
void menu_palette_sel::handle() void menu_palette_sel::handle(event const *ev)
{ {
// process the menu // process the menu
const event *menu_event = process(0); if (ev && ev->itemref)
if (menu_event != nullptr && menu_event->itemref != nullptr)
{ {
if (menu_event->iptkey == IPT_UI_SELECT) if (ev->iptkey == IPT_UI_SELECT)
{ {
m_original = rgb_t(uint32_t(strtoul(selected_item().subtext.c_str(), nullptr, 16))); m_original = rgb_t(uint32_t(strtoul(selected_item().subtext.c_str(), nullptr, 16)));
reset_parent(reset_options::SELECT_FIRST); reset_parent(reset_options::SELECT_FIRST);

View File

@ -44,7 +44,7 @@ private:
}; };
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
void find_languages(); void find_languages();
void find_sysnames(); void find_sysnames();
@ -82,7 +82,7 @@ private:
}; };
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
void list(); void list();
@ -143,7 +143,7 @@ private:
}; };
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
s_color_table m_color_table[MUI_RESTORE]; s_color_table m_color_table[MUI_RESTORE];
void restore_colors(); void restore_colors();
@ -173,7 +173,7 @@ private:
}; };
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
void inkey_special(const event *menu_event); void inkey_special(const event *menu_event);
@ -196,7 +196,7 @@ public:
private: private:
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
static std::pair<const char *, const char *> const s_palette[]; static std::pair<const char *, const char *> const s_palette[];
rgb_t &m_original; rgb_t &m_original;

View File

@ -24,6 +24,7 @@
#include "uiinput.h" #include "uiinput.h"
#include <cmath> #include <cmath>
#include <limits>
#include <string_view> #include <string_view>
@ -34,14 +35,14 @@ namespace ui {
//------------------------------------------------- //-------------------------------------------------
menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system) menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system)
: menu(mui, container) : menu_textbox(mui, container)
, m_system(!system ? &system_list::instance().systems()[driver_list::find(mui.machine().system().name)] : system) , m_system(!system ? &system_list::instance().systems()[driver_list::find(mui.machine().system().name)] : system)
, m_swinfo(nullptr) , m_swinfo(nullptr)
, m_issoft(false) , m_issoft(false)
, m_layout()
, m_actual(0) , m_actual(0)
{ {
set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV);
for (device_image_interface& image : image_interface_enumerator(mui.machine().root_device())) for (device_image_interface& image : image_interface_enumerator(mui.machine().root_device()))
{ {
if (image.filename()) if (image.filename())
@ -71,11 +72,10 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container
//------------------------------------------------- //-------------------------------------------------
menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo) menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo)
: menu(mui, container) : menu_textbox(mui, container)
, m_system(nullptr) , m_system(nullptr)
, m_swinfo(&swinfo) , m_swinfo(&swinfo)
, m_issoft(true) , m_issoft(true)
, m_layout()
, m_actual(0) , m_actual(0)
, m_list(swinfo.listname) , m_list(swinfo.listname)
, m_short(swinfo.shortname) , m_short(swinfo.shortname)
@ -83,6 +83,7 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container
, m_parent(swinfo.parentname) , m_parent(swinfo.parentname)
{ {
set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV);
if (!swinfo.infotext.empty()) if (!swinfo.infotext.empty())
m_items_list.emplace_back(_("Software List Info"), 0, ""); m_items_list.emplace_back(_("Software List Info"), 0, "");
std::vector<std::string> lua_list; std::vector<std::string> lua_list;
@ -165,18 +166,17 @@ void menu_dats_view::add_info_text(text_layout &layout, std::string_view text, r
// handle // handle
//------------------------------------------------- //-------------------------------------------------
void menu_dats_view::handle() void menu_dats_view::handle(event const *ev)
{ {
event const *const menu_event = process(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV); if (ev)
if (menu_event)
{ {
switch (menu_event->iptkey) switch (ev->iptkey)
{ {
case IPT_UI_LEFT: case IPT_UI_LEFT:
if (m_actual > 0) if (m_actual > 0)
{ {
m_actual--; m_actual--;
reset(reset_options::SELECT_FIRST); reset_layout();
} }
break; break;
@ -184,33 +184,12 @@ void menu_dats_view::handle()
if ((m_actual + 1) < m_items_list.size()) if ((m_actual + 1) < m_items_list.size())
{ {
m_actual++; m_actual++;
reset(reset_options::SELECT_FIRST); reset_layout();
} }
break; break;
case IPT_UI_UP: default:
--top_line; handle_key(ev->iptkey);
break;
case IPT_UI_DOWN:
++top_line;
break;
case IPT_UI_PAGE_UP:
top_line -= m_visible_lines - 3;
break;
case IPT_UI_PAGE_DOWN:
top_line += m_visible_lines - 3;
break;
case IPT_UI_HOME:
top_line = 0;
break;
case IPT_UI_END:
top_line = m_layout->lines() - m_visible_lines;
break;
} }
} }
} }
@ -221,150 +200,8 @@ void menu_dats_view::handle()
void menu_dats_view::populate(float &customtop, float &custombottom) void menu_dats_view::populate(float &customtop, float &custombottom)
{ {
bool paused = machine().paused();
if (!paused)
machine().pause();
m_layout = std::nullopt;
customtop = 2.0f * ui().get_line_height() + 4.0f * ui().box_tb_border(); customtop = 2.0f * ui().get_line_height() + 4.0f * ui().box_tb_border();
custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border(); custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border();
if (!paused)
machine().resume();
}
//-------------------------------------------------
// draw - draw dats menu
//-------------------------------------------------
void menu_dats_view::draw(uint32_t flags)
{
float const aspect = machine().render().ui_aspect(&container());
float const line_height = ui().get_line_height();
float const ud_arrow_width = line_height * aspect;
float const gutter_width = 0.52f * line_height * aspect;
float const visible_width = 1.0f - (2.0f * ui().box_lr_border() * aspect);
float const visible_left = (1.0f - visible_width) * 0.5f;
float const extra_height = 2.0f * line_height;
float const visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height;
// determine effective positions taking into account the hilighting arrows
float const effective_width = visible_width - 2.0f * gutter_width;
float const effective_left = visible_left + gutter_width;
draw_background();
map_mouse();
// account for extra space at the top and bottom
float visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height;
m_visible_lines = int(std::trunc(visible_main_menu_height / line_height));
visible_main_menu_height = float(m_visible_lines) * line_height;
// compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust
float const visible_top = ((1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f) + get_customtop();
// compute text box size
float const x1 = visible_left;
float const y1 = visible_top - ui().box_tb_border();
float const x2 = x1 + visible_width;
float const y2 = visible_top + visible_main_menu_height + ui().box_tb_border() + extra_height;
float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH;
float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH;
float const separator = visible_top + float(m_visible_lines) * line_height;
ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color());
if (!m_layout || (m_layout->width() != effective_width))
{
std::string buffer;
if (!m_items_list.empty())
{
if (m_issoft)
get_data_sw(buffer);
else
get_data(buffer);
}
m_layout.emplace(ui().create_layout(container(), effective_width));
add_info_text(*m_layout, buffer, ui().colors().text_color());
}
int const visible_items = m_layout->lines();
m_visible_lines = (std::min)(visible_items, m_visible_lines);
top_line = (std::max)(0, top_line);
if (top_line + m_visible_lines >= visible_items)
top_line = visible_items - m_visible_lines;
clear_hover();
if (top_line)
{
// if we're on the top line, display the up arrow
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, visible_top, line_x1, visible_top + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, visible_top,
line_x1, visible_top + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_UP);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, visible_top + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, visible_top + 0.75f * line_height,
fgcolor, ROT0);
}
if ((top_line + m_visible_lines) < visible_items)
{
// if we're on the bottom line, display the down arrow
float const line_y = visible_top + float(m_visible_lines - 1) * line_height;
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, line_y, line_x1, line_y + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, line_y,
line_x1, line_y + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_DOWN);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, line_y + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, line_y + 0.75f * line_height,
fgcolor, ROT0 ^ ORIENTATION_FLIP_Y);
}
// return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow
m_visible_items = m_visible_lines - (top_line ? 1 : 0) - (top_line + m_visible_lines != visible_items);
m_layout->emit(
container(),
top_line ? (top_line + 1) : 0, m_visible_items,
effective_left, visible_top + (top_line ? line_height : 0.0f));
// add visual separator before the "return to prevous menu" item
container().add_line(
x1, separator + (0.5f * line_height),
x2, separator + (0.5f * line_height),
UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
menu_item const &pitem = item(0);
std::string_view const itemtext = pitem.text;
float const line_y0 = separator + line_height;
float const line_y1 = line_y0 + line_height;
if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem))
set_hover(0);
highlight(line_x0, line_y0, line_x1, line_y1, ui().colors().selected_bg_color());
ui().draw_text_full(
container(), itemtext,
effective_left, line_y0, effective_width,
text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE,
mame_ui_manager::NORMAL,
ui().colors().selected_color(), ui().colors().selected_bg_color(),
nullptr, nullptr);
// if there is something special to add, do it by calling the virtual method
custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2);
} }
//------------------------------------------------- //-------------------------------------------------
@ -512,7 +349,7 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f
} }
//------------------------------------------------- //-------------------------------------------------
// load data from DATs // custom mouse click handling
//------------------------------------------------- //-------------------------------------------------
bool menu_dats_view::custom_mouse_down() bool menu_dats_view::custom_mouse_down()
@ -532,6 +369,28 @@ bool menu_dats_view::custom_mouse_down()
} }
} }
//-------------------------------------------------
// populate selected DAT text
//-------------------------------------------------
void menu_dats_view::populate_text(std::optional<text_layout> &layout, float &width, int &lines)
{
if (!layout || (layout->width() != width))
{
std::string buffer;
if (!m_items_list.empty())
{
if (m_issoft)
get_data_sw(buffer);
else
get_data(buffer);
}
layout.emplace(ui().create_layout(container(), width));
add_info_text(*layout, buffer, ui().colors().text_color());
lines = std::numeric_limits<int>::max();
}
}
//------------------------------------------------- //-------------------------------------------------
// load data from DATs // load data from DATs
//------------------------------------------------- //-------------------------------------------------

View File

@ -14,8 +14,8 @@
#pragma once #pragma once
#include "ui/menu.h"
#include "ui/text.h" #include "ui/text.h"
#include "ui/textbox.h"
#include <optional> #include <optional>
#include <string> #include <string>
@ -33,7 +33,7 @@ namespace ui {
// class dats menu // class dats menu
//------------------------------------------------- //-------------------------------------------------
class menu_dats_view : public menu class menu_dats_view : public menu_textbox
{ {
public: public:
menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo); menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo);
@ -46,6 +46,8 @@ protected:
virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; 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; virtual bool custom_mouse_down() override;
virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override;
private: private:
struct list_items struct list_items
{ {
@ -56,11 +58,8 @@ private:
std::string revision; std::string revision;
}; };
// draw dats menu
virtual void draw(uint32_t flags) override;
virtual void populate(float &customtop, float &custombottom) override; virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override; virtual void handle(event const *ev) override;
void get_data(std::string &buffer); void get_data(std::string &buffer);
void get_data_sw(std::string &buffer); void get_data_sw(std::string &buffer);
@ -68,7 +67,6 @@ private:
ui_system_info const *const m_system; ui_system_info const *const m_system;
ui_software_info const *const m_swinfo; ui_software_info const *const m_swinfo;
bool const m_issoft; bool const m_issoft;
std::optional<text_layout> m_layout;
int m_actual; int m_actual;
std::string m_list, m_short, m_long, m_parent; std::string m_list, m_short, m_long, m_parent;
std::vector<list_items> m_items_list; std::vector<list_items> m_items_list;

Some files were not shown because too many files have changed in this diff Show More