From 97b67170277437131adf6ed4d60139c172529e4f Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Tue, 26 Mar 2019 11:13:37 +1100 Subject: (nw) Clean up the mess on master MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This effectively reverts b380514764cf857469bae61c11143a19f79a74c5 and c24473ddff715ecec2e258a6eb38960cf8c8e98e, restoring the state at 598cd5227223c3b04ca31f0dbc1981256d9ea3ff. Before pushing, please check that what you're about to push is sane. Check your local commit log and ensure there isn't anything out-of-place before pushing to mainline. When things like this happen, it wastes everyone's time. I really don't need this in a week when real work™ is busting my balls and I'm behind where I want to be with preparing for MAME release. --- src/frontend/mame/clifront.cpp | 451 ++++++++++--------- src/frontend/mame/clifront.h | 2 + src/frontend/mame/info.cpp | 19 +- src/frontend/mame/language.cpp | 2 +- src/frontend/mame/mame.cpp | 34 +- src/frontend/mame/mame.h | 20 +- src/frontend/mame/mameopts.cpp | 4 +- src/frontend/mame/ui/auditmenu.cpp | 11 +- src/frontend/mame/ui/icorender.cpp | 781 +++++++++++++++++++++++++++++++++ src/frontend/mame/ui/icorender.h | 227 +--------- src/frontend/mame/ui/info.cpp | 35 +- src/frontend/mame/ui/inifile.cpp | 585 +++++++++++++++--------- src/frontend/mame/ui/inifile.h | 90 ++-- src/frontend/mame/ui/mainmenu.cpp | 6 +- src/frontend/mame/ui/menu.cpp | 16 +- src/frontend/mame/ui/menu.h | 25 +- src/frontend/mame/ui/miscmenu.cpp | 99 +++-- src/frontend/mame/ui/miscmenu.h | 22 +- src/frontend/mame/ui/optsmenu.cpp | 365 ++++++++------- src/frontend/mame/ui/optsmenu.h | 53 ++- src/frontend/mame/ui/selector.cpp | 14 +- src/frontend/mame/ui/selector.h | 16 +- src/frontend/mame/ui/selgame.cpp | 755 ++++++++++++++++++++----------- src/frontend/mame/ui/selgame.h | 27 +- src/frontend/mame/ui/selmenu.cpp | 439 +++++++++++------- src/frontend/mame/ui/selmenu.h | 40 +- src/frontend/mame/ui/selsoft.cpp | 383 +++++++++------- src/frontend/mame/ui/selsoft.h | 59 ++- src/frontend/mame/ui/simpleselgame.cpp | 10 +- src/frontend/mame/ui/utils.cpp | 287 ++++++------ src/frontend/mame/ui/utils.h | 166 +++---- src/frontend/mame/ui/videoopt.cpp | 2 +- src/frontend/mame/ui/widgets.cpp | 13 +- src/frontend/mame/ui/widgets.h | 12 +- 34 files changed, 3250 insertions(+), 1820 deletions(-) create mode 100644 src/frontend/mame/ui/icorender.cpp (limited to 'src/frontend/mame') diff --git a/src/frontend/mame/clifront.cpp b/src/frontend/mame/clifront.cpp index f2dbf4e1c55..28249e7bd3e 100644 --- a/src/frontend/mame/clifront.cpp +++ b/src/frontend/mame/clifront.cpp @@ -99,11 +99,11 @@ const options_entry cli_option_entries[] = { CLICOMMAND_LISTCLONES ";lc", "0", OPTION_COMMAND, "show clones" }, { CLICOMMAND_LISTBROTHERS ";lb", "0", OPTION_COMMAND, "show \"brothers\", or other drivers from same sourcefile" }, { CLICOMMAND_LISTCRC, "0", OPTION_COMMAND, "CRC-32s" }, - { CLICOMMAND_LISTROMS ";lr", "0", OPTION_COMMAND, "list required roms for a driver" }, + { CLICOMMAND_LISTROMS ";lr", "0", OPTION_COMMAND, "list required ROMs for a driver" }, { CLICOMMAND_LISTSAMPLES, "0", OPTION_COMMAND, "list optional samples for a driver" }, { CLICOMMAND_VERIFYROMS, "0", OPTION_COMMAND, "report romsets that have problems" }, { CLICOMMAND_VERIFYSAMPLES, "0", OPTION_COMMAND, "report samplesets that have problems" }, - { CLICOMMAND_ROMIDENT, "0", OPTION_COMMAND, "compare files with known MAME roms" }, + { CLICOMMAND_ROMIDENT, "0", OPTION_COMMAND, "compare files with known MAME ROMs" }, { CLICOMMAND_LISTDEVICES ";ld", "0", OPTION_COMMAND, "list available devices" }, { CLICOMMAND_LISTSLOTS ";lslot", "0", OPTION_COMMAND, "list available slots and slot devices" }, { CLICOMMAND_LISTMEDIA ";lm", "0", OPTION_COMMAND, "list available media for the system" }, @@ -183,9 +183,9 @@ void print_summary( //------------------------------------------------- cli_frontend::cli_frontend(emu_options &options, osd_interface &osd) - : m_options(options), - m_osd(osd), - m_result(EMU_ERR_NONE) + : m_options(options) + , m_osd(osd) + , m_result(EMU_ERR_NONE) { m_options.add_entries(cli_option_entries); } @@ -211,6 +211,7 @@ void cli_frontend::start_execution(mame_machine_manager *manager, const std::vec try { m_options.parse_command_line(args, OPTION_PRIORITY_CMDLINE); + m_osd.set_verbose(m_options.verbose()); } catch (options_exception &ex) { @@ -234,7 +235,10 @@ void cli_frontend::start_execution(mame_machine_manager *manager, const std::vec // read INI's, if appropriate if (m_options.read_config()) + { mame_options::parse_standard_inis(m_options, option_errors); + m_osd.set_verbose(m_options.verbose()); + } // otherwise, check for a valid system load_translation(m_options); @@ -291,14 +295,25 @@ int cli_frontend::execute(std::vector &args) // get the top 16 approximate matches driver_enumerator drivlist(m_options); int matches[16]; - drivlist.find_approximate_matches(m_options.attempted_system_name().c_str(), ARRAY_LENGTH(matches), matches); + drivlist.find_approximate_matches(m_options.attempted_system_name(), ARRAY_LENGTH(matches), matches); + + // work out how wide the titles need to be + int titlelen(0); + for (int match : matches) + if (0 <= match) + titlelen = (std::max)(titlelen, int(strlen(drivlist.driver(match).type.fullname()))); // print them out osd_printf_error("\n\"%s\" approximately matches the following\n" "supported machines (best match first):\n\n", m_options.attempted_system_name().c_str()); - for (auto & matche : matches) - if (matche != -1) - osd_printf_error("%-18s%s\n", drivlist.driver(matche).name, drivlist.driver(matche).type.fullname()); + for (int match : matches) + { + if (0 <= match) + { + game_driver const &drv(drivlist.driver(match)); + osd_printf_error("%s", util::string_format("%-18s%-*s(%s, %s)\n", drv.name, titlelen + 2, drv.type.fullname(), drv.manufacturer, drv.year).c_str()); + } + } } } catch (emu_exception &) @@ -349,77 +364,20 @@ void cli_frontend::listxml(const std::vector &args) void cli_frontend::listfull(const std::vector &args) { - bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); - std::vector matched(args.size(), false); - auto const included = [&args, &matched] (char const *name) -> bool - { - if (args.empty()) - return true; - - bool result = false; - auto it = matched.begin(); - for (std::string const &pat : args) - { - if (!core_strwildcmp(pat.c_str(), name)) - { - result = true; - *it = true; - } - ++it; - } - return result; - }; - - bool first = true; - auto const list_system_name = [&first] (device_type const &type) + auto const list_system_name = [] (device_type type, bool first) { // print the header if (first) osd_printf_info("Name: Description:\n"); - first = false; osd_printf_info("%-17s \"%s\"\n", type.shortname(), type.fullname()); }; - - // determine which drivers to output - driver_enumerator drivlist(m_options); - while (drivlist.next()) - { - if (included(drivlist.driver().name)) - { - list_system_name(drivlist.driver().type); - - // if it wasn't a wildcard, there can only be one - if (!iswild) - break; - } - } - - // try devices as well - if (iswild || first) - { - for (device_type type : registered_device_types) - { - if (included(type.shortname())) - { - list_system_name(type); - - // if it wasn't a wildcard, there can only be one - if (!iswild) - break; - } - } - } - - // return an error if none found - auto it = matched.begin(); - for (std::string const &pat : args) - { - if (!*it) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", pat.c_str()); - - ++it; - } + apply_action( + args, + [&list_system_name] (driver_enumerator &drivlist, bool first) + { list_system_name(drivlist.driver().type, first); }, + [&list_system_name] (device_type type, bool first) + { list_system_name(type, first); }); } @@ -430,16 +388,16 @@ void cli_frontend::listfull(const std::vector &args) void cli_frontend::listsource(const std::vector &args) { - const char *gamename = args.empty() ? nullptr : args[0].c_str(); - - // determine which drivers to output; return an error if none found - driver_enumerator drivlist(m_options, gamename); - if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); - - // iterate through drivers and output the info - while (drivlist.next()) - osd_printf_info("%-16s %s\n", drivlist.driver().name, core_filename_extract_base(drivlist.driver().type.source()).c_str()); + auto const list_system_source = [] (device_type type) + { + osd_printf_info("%-16s %s\n", type.shortname(), core_filename_extract_base(type.source()).c_str()); + }; + apply_action( + args, + [&list_system_source] (driver_enumerator &drivlist, bool first) + { list_system_source(drivlist.driver().type); }, + [&list_system_source] (device_type type, bool first) + { list_system_source(type); }); } @@ -547,164 +505,92 @@ void cli_frontend::listbrothers(const std::vector &args) void cli_frontend::listcrc(const std::vector &args) { - const char *gamename = args.empty() ? nullptr : args[0].c_str(); - - // determine which drivers to output; return an error if none found - driver_enumerator drivlist(m_options, gamename); - if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); - - // iterate through matches, and then through ROMs - while (drivlist.next()) - { - for (device_t const &device : device_iterator(drivlist.config()->root_device())) - { - for (tiny_rom_entry const *rom = device.rom_region(); rom && !ROMENTRY_ISEND(rom); ++rom) + apply_device_action( + args, + [] (device_t &root, char const *type, bool first) { - if (ROMENTRY_ISFILE(rom)) + for (device_t const &device : device_iterator(root)) { - // if we have a CRC, display it - uint32_t crc; - if (util::hash_collection(rom->hashdata).crc(crc)) - osd_printf_info("%08x %-32s\t%-16s\t%s\n", crc, rom->name, device.shortname(), device.name()); + for (tiny_rom_entry const *rom = device.rom_region(); rom && !ROMENTRY_ISEND(rom); ++rom) + { + if (ROMENTRY_ISFILE(rom)) + { + // if we have a CRC, display it + uint32_t crc; + if (util::hash_collection(rom->hashdata).crc(crc)) + osd_printf_info("%08x %-32s\t%-16s\t%s\n", crc, rom->name, device.shortname(), device.name()); + } + } } - } - } - } + }); } //------------------------------------------------- // listroms - output the list of ROMs referenced -// by a given game or set of games +// by matching systems/devices //------------------------------------------------- void cli_frontend::listroms(const std::vector &args) { - bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); - std::vector matched(args.size(), false); - auto const included = [&args, &matched] (char const *name) -> bool - { - if (args.empty()) - return true; - - bool result = false; - auto it = matched.begin(); - for (std::string const &pat : args) - { - if (!core_strwildcmp(pat.c_str(), name)) + apply_device_action( + args, + [] (device_t &root, char const *type, bool first) { - result = true; - *it = true; - } - ++it; - } - return result; - }; - - bool first = true; - auto const list_system_roms = [&first] (device_t &root, char const *type) - { - // print a header - if (!first) - osd_printf_info("\n"); - first = false; + // space between items + if (!first) + osd_printf_info("\n"); - // iterate through roms - bool hasroms = false; - for (device_t const &device : device_iterator(root)) - { - for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region)) - { - for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) + // iterate through ROMs + bool hasroms = false; + for (device_t const &device : device_iterator(root)) { - // print a header - if (!hasroms) - osd_printf_info( - "ROMs required for %s \"%s\".\n" - "%-32s %10s %s\n", - type, root.shortname(), "Name", "Size", "Checksum"); - hasroms = true; - - // accumulate the total length of all chunks - int64_t length = -1; - if (ROMREGION_ISROMDATA(region)) - length = rom_file_size(rom); - - // start with the name - const char *name = ROM_GETNAME(rom); - osd_printf_info("%-32s ", name); - - // output the length next - if (length >= 0) - osd_printf_info("%10u", unsigned(uint64_t(length))); - else - osd_printf_info("%10s", ""); - - // output the hash data - util::hash_collection hashes(ROM_GETHASHDATA(rom)); - if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) + for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region)) { - if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP)) - osd_printf_info(" BAD"); - osd_printf_info(" %s", hashes.macro_string().c_str()); - } - else - osd_printf_info(" NO GOOD DUMP KNOWN"); + for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) + { + // print a header + if (!hasroms) + osd_printf_info( + "ROMs required for %s \"%s\".\n" + "%-32s %10s %s\n", + type, root.shortname(), "Name", "Size", "Checksum"); + hasroms = true; + + // accumulate the total length of all chunks + int64_t length = -1; + if (ROMREGION_ISROMDATA(region)) + length = rom_file_size(rom); + + // start with the name + const char *name = ROM_GETNAME(rom); + osd_printf_info("%-32s ", name); + + // output the length next + if (length >= 0) + osd_printf_info("%10u", unsigned(uint64_t(length))); + else + osd_printf_info("%10s", ""); + + // output the hash data + util::hash_collection hashes(ROM_GETHASHDATA(rom)); + if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) + { + if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP)) + osd_printf_info(" BAD"); + osd_printf_info(" %s", hashes.macro_string().c_str()); + } + else + osd_printf_info(" NO GOOD DUMP KNOWN"); - // end with a CR - osd_printf_info("\n"); + // end with a CR + osd_printf_info("\n"); + } + } } - } - } - if (!hasroms) - osd_printf_info("No ROMs required for %s \"%s\".\n", type, root.shortname()); - }; - - // determine which drivers to output - driver_enumerator drivlist(m_options); - - // iterate through matches - while (drivlist.next()) - { - if (included(drivlist.driver().name)) - { - list_system_roms(drivlist.config()->root_device(), "driver"); - - // if it wasn't a wildcard, there can only be one - if (!iswild) - break; - } - } - - if (iswild || first) - { - machine_config config(GAME_NAME(___empty), m_options); - machine_config::token const tok(config.begin_configuration(config.root_device())); - for (device_type type : registered_device_types) - { - if (included(type.shortname())) - { - device_t *const dev = config.device_add("_tmp", type, 0); - list_system_roms(*dev, "device"); - config.device_remove("_tmp"); - - // if it wasn't a wildcard, there can only be one - if (!iswild) - break; - } - } - } - - // return an error if none found - auto it = matched.begin(); - for (std::string const &pat : args) - { - if (!*it) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", pat.c_str()); - - ++it; - } + if (!hasroms) + osd_printf_info("No ROMs required for %s \"%s\".\n", type, root.shortname()); + }); } @@ -1223,7 +1109,7 @@ void cli_frontend::output_single_softlist(FILE *out, software_list_device &swlis for (const feature_list_item &flist : part.featurelist()) fprintf(out, "\t\t\t\t\n", flist.name().c_str(), util::xml::normalize_string(flist.value().c_str())); - /* TODO: display rom region information */ + // TODO: display ROM region information for (const rom_entry *region = part.romdata().data(); region; region = rom_next_region(region)) { int is_disk = ROMREGION_ISDISKDATA(region); @@ -1354,7 +1240,7 @@ void cli_frontend::listsoftware(const std::vector &args) /*------------------------------------------------- - verifysoftware - verify roms from the software + verifysoftware - verify ROMs from the software list of the specified driver(s) -------------------------------------------------*/ void cli_frontend::verifysoftware(const std::vector &args) @@ -1427,6 +1313,7 @@ void cli_frontend::verifysoftware(const std::vector &args) } + /*------------------------------------------------- getsoftlist - retrieve software list by name -------------------------------------------------*/ @@ -1560,6 +1447,105 @@ void cli_frontend::romident(const std::vector &args) } +//------------------------------------------------- +// apply_action - apply action to matching +// systems/devices +//------------------------------------------------- + +template void cli_frontend::apply_action(const std::vector &args, T &&drvact, U &&devact) + +{ + bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); + std::vector matched(args.size(), false); + auto const included = [&args, &matched] (char const *name) -> bool + { + if (args.empty()) + return true; + + bool result = false; + auto it = matched.begin(); + for (std::string const &pat : args) + { + if (!core_strwildcmp(pat.c_str(), name)) + { + result = true; + *it = true; + } + ++it; + } + return result; + }; + + // determine which drivers to output + driver_enumerator drivlist(m_options); + + // iterate through matches + bool first(true); + while (drivlist.next()) + { + if (included(drivlist.driver().name)) + { + drvact(drivlist, first); + first = false; + + // if it wasn't a wildcard, there can only be one + if (!iswild) + break; + } + } + + if (iswild || first) + { + for (device_type type : registered_device_types) + { + if (included(type.shortname())) + { + devact(type, first); + first = false; + + // if it wasn't a wildcard, there can only be one + if (!iswild) + break; + } + } + } + + // return an error if none found + auto it = matched.begin(); + for (std::string const &pat : args) + { + if (!*it) + throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", pat.c_str()); + + ++it; + } +} + + +//------------------------------------------------- +// apply_device_action - apply action to matching +// systems/devices +//------------------------------------------------- + +template void cli_frontend::apply_device_action(const std::vector &args, T &&action) +{ + machine_config config(GAME_NAME(___empty), m_options); + machine_config::token const tok(config.begin_configuration(config.root_device())); + apply_action( + args, + [&action] (driver_enumerator &drivlist, bool first) + { + action(drivlist.config()->root_device(), "driver", first); + }, + [&action, &config] (device_type type, bool first) + { + device_t *const dev = config.device_add("_tmp", type, 0); + action(*dev, "device", first); + config.device_remove("_tmp"); + }); +} + + //------------------------------------------------- // find_command //------------------------------------------------- @@ -1570,10 +1556,10 @@ const cli_frontend::info_command_struct *cli_frontend::find_command(const std::s { { CLICOMMAND_LISTXML, 0, -1, &cli_frontend::listxml, "[pattern] ..." }, { CLICOMMAND_LISTFULL, 0, -1, &cli_frontend::listfull, "[pattern] ..." }, - { CLICOMMAND_LISTSOURCE, 0, 1, &cli_frontend::listsource, "[system name]" }, + { CLICOMMAND_LISTSOURCE, 0, -1, &cli_frontend::listsource, "[system name]" }, { CLICOMMAND_LISTCLONES, 0, 1, &cli_frontend::listclones, "[system name]" }, { CLICOMMAND_LISTBROTHERS, 0, 1, &cli_frontend::listbrothers, "[system name]" }, - { CLICOMMAND_LISTCRC, 0, 1, &cli_frontend::listcrc, "[system name]" }, + { CLICOMMAND_LISTCRC, 0, -1, &cli_frontend::listcrc, "[system name]" }, { CLICOMMAND_LISTDEVICES, 0, 1, &cli_frontend::listdevices, "[system name]" }, { CLICOMMAND_LISTSLOTS, 0, 1, &cli_frontend::listslots, "[system name]" }, { CLICOMMAND_LISTROMS, 0, -1, &cli_frontend::listroms, "[pattern] ..." }, @@ -1622,6 +1608,11 @@ void cli_frontend::execute_commands(const char *exename) // validate? if (m_options.command() == CLICOMMAND_VALIDATE) { + if (m_options.command_arguments().size() > 1) + { + osd_printf_error("Auxiliary verb -validate takes at most 1 argument\n"); + return; + } validity_checker valid(m_options); valid.set_validate_all(true); const char *sysname = m_options.command_arguments().empty() ? nullptr : m_options.command_arguments()[0].c_str(); @@ -1689,9 +1680,9 @@ void cli_frontend::execute_commands(const char *exename) // validate argument count const char *error_message = nullptr; if (m_options.command_arguments().size() < info_command->min_args) - error_message = "Auxillary verb -%s requires at least %d argument(s)\n"; + error_message = "Auxiliary verb -%s requires at least %d argument(s)\n"; if ((info_command->max_args >= 0) && (m_options.command_arguments().size() > info_command->max_args)) - error_message = "Auxillary verb -%s takes at most %d argument(s)\n"; + error_message = "Auxiliary verb -%s takes at most %d argument(s)\n"; if (error_message) { osd_printf_info(error_message, info_command->option, info_command->max_args); @@ -1700,7 +1691,7 @@ void cli_frontend::execute_commands(const char *exename) return; } - // invoke the auxillary command! + // invoke the auxiliary command! (this->*info_command->function)(m_options.command_arguments()); return; } diff --git a/src/frontend/mame/clifront.h b/src/frontend/mame/clifront.h index 15da370ee1f..e05467bc627 100644 --- a/src/frontend/mame/clifront.h +++ b/src/frontend/mame/clifront.h @@ -67,6 +67,8 @@ private: void verifysoftlist(const std::vector &args); // internal helpers + template void apply_action(const std::vector &args, T &&drvact, U &&devact); + template void apply_device_action(const std::vector &args, T &&action); void execute_commands(const char *exename); void display_help(const char *exename); void output_single_softlist(FILE *out, software_list_device &swlist); diff --git a/src/frontend/mame/info.cpp b/src/frontend/mame/info.cpp index 6110c5c5fa9..1fe65d7ea3c 100644 --- a/src/frontend/mame/info.cpp +++ b/src/frontend/mame/info.cpp @@ -168,7 +168,7 @@ const char info_xml_creator::s_dtd_string[] = "\t\t\t\n" "\t\t\t\n" "\t\t\n" -"\t\t\t\n" +"\t\t\t\n" "\t\t\t\n" "\t\t\t\n" "\t\t\n" @@ -1639,19 +1639,26 @@ void info_xml_creator::output_features(device_type type, device_t::feature_type { static constexpr std::pair features[] = { { device_t::feature::PROTECTION, "protection" }, - { device_t::feature::PALETTE, "palette" }, + { device_t::feature::TIMING, "timing" }, { device_t::feature::GRAPHICS, "graphics" }, + { device_t::feature::PALETTE, "palette" }, { device_t::feature::SOUND, "sound" }, + { device_t::feature::CAPTURE, "capture" }, + { device_t::feature::CAMERA, "camera" }, + { device_t::feature::MICROPHONE, "microphone" }, { device_t::feature::CONTROLS, "controls" }, { device_t::feature::KEYBOARD, "keyboard" }, { device_t::feature::MOUSE, "mouse" }, - { device_t::feature::MICROPHONE, "microphone" }, - { device_t::feature::CAMERA, "camera" }, + { device_t::feature::MEDIA, "media" }, { device_t::feature::DISK, "disk" }, { device_t::feature::PRINTER, "printer" }, + { device_t::feature::TAPE, "tape" }, + { device_t::feature::PUNCH, "punch" }, + { device_t::feature::DRUM, "drum" }, + { device_t::feature::ROM, "rom" }, + { device_t::feature::COMMS, "comms" }, { device_t::feature::LAN, "lan" }, - { device_t::feature::WAN, "wan" }, - { device_t::feature::TIMING, "timing" } }; + { device_t::feature::WAN, "wan" } }; device_t::feature_type const flags(type.unemulated_features() | type.imperfect_features() | unemulated | imperfect); for (auto const &feature : features) diff --git a/src/frontend/mame/language.cpp b/src/frontend/mame/language.cpp index 93d8e410c4a..016a32f1811 100644 --- a/src/frontend/mame/language.cpp +++ b/src/frontend/mame/language.cpp @@ -72,7 +72,7 @@ void load_translation(emu_options &m_options) { std::string original = (const char *)data + buffer[original_table_offset + 2 * i + 1]; std::string translation = (const char *)data + buffer[translation_table_offset + 2 * i + 1]; - g_translation.insert(std::pair(original, translation)); + g_translation.emplace(std::move(original), std::move(translation)); } global_free_array(buffer); } diff --git a/src/frontend/mame/mame.cpp b/src/frontend/mame/mame.cpp index 528a1717a4a..f3c08f661e9 100644 --- a/src/frontend/mame/mame.cpp +++ b/src/frontend/mame/mame.cpp @@ -31,12 +31,11 @@ mame_machine_manager* mame_machine_manager::m_manager = nullptr; -mame_machine_manager* mame_machine_manager::instance(emu_options &options,osd_interface &osd) +mame_machine_manager* mame_machine_manager::instance(emu_options &options, osd_interface &osd) { - if(!m_manager) - { - m_manager = global_alloc(mame_machine_manager(options,osd)); - } + if (!m_manager) + m_manager = global_alloc(mame_machine_manager(options, osd)); + return m_manager; } @@ -49,13 +48,13 @@ mame_machine_manager* mame_machine_manager::instance() // mame_machine_manager - constructor //------------------------------------------------- -mame_machine_manager::mame_machine_manager(emu_options &options,osd_interface &osd) - : machine_manager(options, osd), - m_plugins(std::make_unique()), - m_lua(global_alloc(lua_engine)), - m_new_driver_pending(nullptr), - m_firstrun(true), - m_autoboot_timer(nullptr) +mame_machine_manager::mame_machine_manager(emu_options &options,osd_interface &osd) : + machine_manager(options, osd), + m_plugins(std::make_unique()), + m_lua(global_alloc(lua_engine)), + m_new_driver_pending(nullptr), + m_firstrun(true), + m_autoboot_timer(nullptr) { } @@ -100,7 +99,8 @@ std::vector split(const std::string &text, char sep) { std::vector tokens; std::size_t start = 0, end = 0; - while ((end = text.find(sep, start)) != std::string::npos) { + while ((end = text.find(sep, start)) != std::string::npos) + { std::string temp = text.substr(start, end - start); if (temp != "") tokens.push_back(temp); start = end + 1; @@ -120,8 +120,8 @@ void mame_machine_manager::start_luaengine() { m_plugins->parse_json(pluginpath); } - std::vector include = split(options().plugin(),','); - std::vector exclude = split(options().no_plugin(),','); + std::vector include = split(options().plugin(), ','); + std::vector exclude = split(options().no_plugin(), ','); { // parse the file // attempt to open the output file @@ -304,13 +304,13 @@ void mame_machine_manager::ui_initialize(running_machine& machine) void mame_machine_manager::create_custom(running_machine& machine) { // start the inifile manager - m_inifile = std::make_unique(machine, m_ui->options()); + m_inifile = std::make_unique(m_ui->options()); // allocate autoboot timer m_autoboot_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(mame_machine_manager::autoboot_callback), this)); // start favorite manager - m_favorite = std::make_unique(machine, m_ui->options()); + m_favorite = std::make_unique(m_ui->options()); } void mame_machine_manager::load_cheatfiles(running_machine& machine) diff --git a/src/frontend/mame/mame.h b/src/frontend/mame/mame.h index 3700aaab81b..01a7235972b 100644 --- a/src/frontend/mame/mame.h +++ b/src/frontend/mame/mame.h @@ -15,26 +15,20 @@ class plugin_options; class osd_interface; -//************************************************************************** -// TYPE DEFINITIONS -//************************************************************************** class lua_engine; class cheat_manager; class inifile_manager; class favorite_manager; class mame_ui_manager; -namespace ui { -} // namespace ui +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** // ======================> machine_manager class mame_machine_manager : public machine_manager { - DISABLE_COPYING(mame_machine_manager); -private: - // construction/destruction - mame_machine_manager(emu_options &options, osd_interface &osd); public: static mame_machine_manager *instance(emu_options &options, osd_interface &osd); static mame_machine_manager *instance(); @@ -64,7 +58,15 @@ public: cheat_manager &cheat() const { assert(m_cheat != nullptr); return *m_cheat; } inifile_manager &inifile() const { assert(m_inifile != nullptr); return *m_inifile; } favorite_manager &favorite() const { assert(m_favorite != nullptr); return *m_favorite; } + private: + // construction + mame_machine_manager(emu_options &options, osd_interface &osd); + mame_machine_manager(mame_machine_manager const &) = delete; + mame_machine_manager(mame_machine_manager &&) = delete; + mame_machine_manager &operator=(mame_machine_manager const &) = delete; + mame_machine_manager &operator=(mame_machine_manager &&) = delete; + std::unique_ptr m_plugins; // pointer to plugin options lua_engine * m_lua; diff --git a/src/frontend/mame/mameopts.cpp b/src/frontend/mame/mameopts.cpp index d6c1e1044cf..47f1a71c595 100644 --- a/src/frontend/mame/mameopts.cpp +++ b/src/frontend/mame/mameopts.cpp @@ -39,8 +39,8 @@ void mame_options::parse_standard_inis(emu_options &options, std::ostream &error parse_one_ini(options, "debug", OPTION_PRIORITY_DEBUG_INI, &error_stream); // if we have a valid system driver, parse system-specific INI files - const game_driver *cursystem = (driver == nullptr) ? system(options) : driver; - if (cursystem == nullptr) + game_driver const *const cursystem = !driver ? system(options) : driver; + if (!cursystem) return; // parse "vertical.ini" or "horizont.ini" diff --git a/src/frontend/mame/ui/auditmenu.cpp b/src/frontend/mame/ui/auditmenu.cpp index 0349e1c2949..7d5cbfd7305 100644 --- a/src/frontend/mame/ui/auditmenu.cpp +++ b/src/frontend/mame/ui/auditmenu.cpp @@ -207,24 +207,21 @@ void menu_audit::audit_fast() void menu_audit::audit_all() { - m_availablesorted.clear(); driver_enumerator enumerator(machine().options()); media_auditor auditor(enumerator); + std::vector available(driver_list::total(), false); while (enumerator.next()) { m_current.store(&enumerator.driver()); media_auditor::summary const summary(auditor.audit_media(AUDIT_VALIDATE_FAST)); // if everything looks good, include the driver - m_availablesorted.emplace_back(enumerator.driver(), (summary == media_auditor::CORRECT) || (summary == media_auditor::BEST_AVAILABLE) || (summary == media_auditor::NONE_NEEDED)); + available[enumerator.current()] = (summary == media_auditor::CORRECT) || (summary == media_auditor::BEST_AVAILABLE) || (summary == media_auditor::NONE_NEEDED); ++m_audited; } - // sort - std::stable_sort( - m_availablesorted.begin(), - m_availablesorted.end(), - [] (ui_system_info const &a, ui_system_info const &b) { return sorted_game_list(a.driver, b.driver); }); + for (ui_system_info &info : m_availablesorted) + info.available = available[info.index]; } void menu_audit::save_available_machines() diff --git a/src/frontend/mame/ui/icorender.cpp b/src/frontend/mame/ui/icorender.cpp new file mode 100644 index 00000000000..bea85f9dd60 --- /dev/null +++ b/src/frontend/mame/ui/icorender.cpp @@ -0,0 +1,781 @@ +// license:BSD-3-Clause +// copyright-holders:Maurizio Petrarota, Victor Laskin +/*************************************************************************** + + ui/icorender.h + + Windows icon file parser. + + Previously based on code by Victor Laskin (victor.laskin@gmail.com) + http://vitiy.info/Code/ico.cpp + + TODO: + * Add variant that loads all images from the file + * Allow size hint for choosing best candidate + * Allow selecting amongst candidates based on colour depth + +***************************************************************************/ + +#include "emu.h" +#include "icorender.h" + +#include "util/png.h" + +#include +#include +#include +#include + +// need to set LOG_OUTPUT_STREAM because there's no logerror outside devices +#define LOG_OUTPUT_STREAM std::cerr + +#define LOG_GENERAL (1U << 0) +#define LOG_DIB (1U << 1) + +//#define VERBOSE (LOG_GENERAL | LOG_DIB) + +#include "logmacro.h" + + +namespace ui { + +namespace { + +// DIB compression schemes +enum : uint32_t +{ + DIB_COMP_NONE = 0, + DIB_COMP_RLE8 = 1, + DIB_COMP_RLE4 = 2, + DIB_COMP_BITFIELDS = 3 +}; + + +// ICO file header +struct icon_dir_t +{ + uint16_t reserved; // must be 0 + uint16_t type; // 1 for icon or 2 for cursor + uint16_t count; // number of images in the file +}; + +// ICO file directory entry +struct icon_dir_entry_t +{ + constexpr unsigned get_width() const { return width ? width : 256U; } + constexpr unsigned get_height() const { return height ? height : 256U; } + + void byteswap() + { + planes = little_endianize_int16(planes); + bpp = little_endianize_int16(bpp); + size = little_endianize_int32(size); + offset = little_endianize_int32(offset); + } + + uint8_t width; // 0 means 256 + uint8_t height; // 0 means 256 + uint8_t colors; // for indexed colour, or 0 for direct colour + uint8_t reserved; // documentation says this should be 0 but .NET writes 255 + uint16_t planes; // or hotspot X for cursor + uint16_t bpp; // 0 to infer from image data, or hotspot Y for cursor + uint32_t size; // image data size in bytes + uint32_t offset; // offset to image data from start of file +}; + +// old-style DIB header +struct bitmap_core_header_t +{ + uint32_t size; // size of the header (12, 16 or 64) + int16_t width; // width of bitmap in pixels + int16_t height; // height of the image in pixels + uint16_t planes; // number of colour planes (must be 1) + uint16_t bpp; // bits per pixel +}; + +// new-style DIB header +struct bitmap_info_header_t +{ + uint32_t size; // size of the header + int32_t width; // width of bitmap in pixels + int32_t height; // height of bitmap in pixels + uint16_t planes; // number of colour planes (must be 1) + uint16_t bpp; // bits per pixel + uint32_t comp; // compression method + uint32_t rawsize; // size of bitmap data after decompression or 0 if uncompressed + int32_t hres; // horizontal resolution in pixels/metre + int32_t vres; // horizontal resolution in pixels/metre + uint32_t colors; // number of colours or 0 for 1 << bpp + uint32_t important; // number of important colours or 0 if all important + uint32_t red; // red field mask - must be contiguous + uint32_t green; // green field mask - must be contiguous + uint32_t blue; // blue field mask - must be contiguous + uint32_t alpha; // alpha field mask - must be contiguous +}; + + +bool dib_parse_mask(uint32_t mask, unsigned &shift, unsigned &bits) +{ + shift = count_leading_zeros(mask); + mask <<= shift; + bits = count_leading_ones(mask); + mask <<= shift; + shift = 32 - shift - bits; + return !mask; +} + + +void dib_truncate_channel(unsigned &shift, unsigned &bits) +{ + if (8U < bits) + { + unsigned const excess(bits - 8); + shift += excess; + bits -= excess; + } +} + + +uint8_t dib_splat_sample(uint8_t val, unsigned bits) +{ + assert(8U >= bits); + for (val <<= (8U - bits); bits && (8U > bits); bits <<= 1) + val |= val >> bits; + return val; +} + + +bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) +{ + // skip out if the data isn't a reasonable size - PNG magic alone is eight bytes + if (9U >= dir.size) + return false; + fp.seek(dir.offset, SEEK_SET); + png_error const err(png_read_bitmap(fp, bitmap)); + switch (err) + { + case PNGERR_NONE: + // found valid PNG image + assert(bitmap.valid()); + if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height()))) + { + LOG("Loaded %d*%d pixel PNG image from ICO file\n", bitmap.width(), bitmap.height()); + } + else + { + LOG( + "Loaded %d*%d pixel PNG image from ICO file (directory indicated %u*%u)\n", + bitmap.width(), + bitmap.height(), + dir.get_width(), + dir.get_height()); + } + return true; + + case PNGERR_BAD_SIGNATURE: + // doesn't look like PNG data - just fall back to DIB without the file header + return false; + + default: + // invalid PNG data or I/O error + LOG( + "Error %u reading PNG image data from ICO file at offset %u (directory size %u)\n", + unsigned(err), + dir.offset, + dir.size); + return false; + } +} + + +bool load_ico_dib(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) +{ + // check that these things haven't been padded somehow + static_assert(sizeof(bitmap_core_header_t) == 12U, "compiler has applied padding to bitmap_core_header_t"); + static_assert(sizeof(bitmap_info_header_t) == 56U, "compiler has applied padding to bitmap_info_header_t"); + + // ensure the header fits in the space for the image data + union { bitmap_core_header_t core; bitmap_info_header_t info; } header; + assert(&header.core.size == &header.info.size); + if (sizeof(header.core) > dir.size) + return false; + std::memset(&header, 0, sizeof(header)); + fp.seek(dir.offset, SEEK_SET); + if (fp.read(&header.core.size, sizeof(header.core.size)) != sizeof(header.core.size)) + { + LOG( + "Error reading DIB header size from ICO file at offset %u (directory size %u)\n", + dir.offset, + dir.size); + return false; + } + header.core.size = little_endianize_int32(header.core.size); + if (dir.size < header.core.size) + { + LOG( + "ICO file image data at %u (%u bytes) is too small for DIB header (%u bytes)\n", + dir.offset, + dir.size, + header.core.size); + return false; + } + + // identify and read the header - convert OS/2 headers to Windows 3 format + unsigned palette_bytes(4U); + switch (header.core.size) + { + case 16U: + case 64U: + // extended OS/2 bitmap header with support for compression + LOG( + "ICO image data at %u (%u bytes) uses unsupported OS/2 DIB header (size %u)\n", + dir.offset, + dir.size, + header.core.size); + return false; + + case 12U: + // introduced in OS/2 and Windows 2.0 + { + palette_bytes = 3U; + uint32_t const header_read(std::min(header.core.size, sizeof(header.core)) - sizeof(header.core.size)); + if (fp.read(&header.core.width, header_read) != header_read) + { + LOG("Error reading DIB core header from ICO file image data at %u (%u bytes)\n", dir.offset, dir.size); + return false; + } + fp.seek(header.core.size - sizeof(header.core.size) - header_read, SEEK_CUR); + header.core.width = little_endianize_int16(header.core.width); + header.core.height = little_endianize_int16(header.core.height); + header.core.planes = little_endianize_int16(header.core.planes); + header.core.bpp = little_endianize_int16(header.core.bpp); + LOGMASKED( + LOG_DIB, + "Read DIB core header from ICO file image data at %u: %d*%d, %u planes, %u bpp\n", + dir.offset, + header.core.width, + header.core.height, + header.core.planes, + header.core.bpp); + + // this works because the core header only aliases the width/height of the info header + header.info.bpp = header.core.bpp; + header.info.planes = header.core.planes; + header.info.height = header.core.height; + header.info.width = header.core.width; + header.info.size = 40U; + } + break; + + default: + // the next version will be longer + if (124U >= header.core.size) + { + LOG( + "ICO image data at %u (%u bytes) uses unsupported DIB header format (size %u)\n", + dir.offset, + dir.size, + header.core.size); + return false; + } + // fall through + case 40U: + case 52U: + case 56U: + case 108U: + case 124U: + // the Windows 3 bitmap header with optional extensions + { + palette_bytes = 4U; + uint32_t const header_read(std::min(header.info.size, sizeof(header.info)) - sizeof(header.info.size)); + if (fp.read(&header.info.width, header_read) != header_read) + { + LOG("Error reading DIB info header from ICO file image data at %u (%u bytes)\n", dir.offset, dir.size); + return false; + } + fp.seek(header.info.size - sizeof(header.info.size) - header_read, SEEK_CUR); + header.info.width = little_endianize_int32(header.info.width); + header.info.height = little_endianize_int32(header.info.height); + header.info.planes = little_endianize_int16(header.info.planes); + header.info.bpp = little_endianize_int16(header.info.bpp); + header.info.comp = little_endianize_int32(header.info.comp); + header.info.rawsize = little_endianize_int32(header.info.rawsize); + header.info.hres = little_endianize_int32(header.info.hres); + header.info.vres = little_endianize_int32(header.info.vres); + header.info.colors = little_endianize_int32(header.info.colors); + header.info.important = little_endianize_int32(header.info.important); + header.info.red = little_endianize_int32(header.info.red); + header.info.green = little_endianize_int32(header.info.green); + header.info.blue = little_endianize_int32(header.info.blue); + header.info.alpha = little_endianize_int32(header.info.alpha); + LOGMASKED( + LOG_DIB, + "Read DIB info header from ICO file image data at %u: %d*%d (%d*%d ppm), %u planes, %u bpp %u/%s%u colors\n", + dir.offset, + header.info.width, + header.info.height, + header.info.hres, + header.info.vres, + header.info.planes, + header.info.bpp, + header.info.important, + header.info.colors ? "" : "2^", + header.info.colors ? header.info.colors : header.info.bpp); + } + break; + } + + // check for unsupported planes/bit depth + if ((1U != header.info.planes) || !header.info.bpp || (32U < header.info.bpp) || ((8U < header.info.bpp) ? (header.info.bpp % 8) : (8 % header.info.bpp))) + { + LOG( + "ICO file DIB image data at %u uses unsupported planes/bits per pixel %u*%u\n", + dir.offset, + header.info.planes, + header.info.bpp); + return false; + } + + // check dimensions + if ((0 >= header.info.width) || (0 == header.info.height)) + { + LOG( + "ICO file DIB image data at %u has invalid dimensions %u*%u\n", + dir.offset, + header.info.width, + header.info.height); + return false; + } + bool const top_down(0 > header.info.height); + if (top_down) + header.info.height = -header.info.height; + bool have_and_mask((2 * dir.get_height()) == header.info.height); + if (!have_and_mask && (dir.get_height() != header.info.height)) + { + osd_printf_verbose( + "ICO file DIB image data at %lu height %ld doesn't match directory height %u with or without AND mask\n", + (unsigned long)dir.offset, + (long)header.info.height, + dir.get_height()); + return false; + } + if (have_and_mask) + header.info.height >>= 1; + + // ensure compression scheme is supported + bool indexed(true), no_palette(false); + switch (header.info.comp) + { + case DIB_COMP_NONE: + // uncompressed - direct colour with implied bitfields if more than eight bits/pixel + indexed = 8U >= header.info.bpp; + if (indexed) + { + if ((1U << header.info.bpp) < header.info.colors) + { + osd_printf_verbose( + "ICO file DIB image data at %lu has oversized palette with %lu entries for %u bits per pixel\n", + (unsigned long)dir.offset, + (unsigned long)header.info.colors, + (unsigned)header.info.bpp); + } + } + if (!indexed) + { + no_palette = true; + switch(header.info.bpp) + { + case 16U: + header.info.red = 0x00007c00; + header.info.green = 0x000003e0; + header.info.blue = 0x0000001f; + header.info.alpha = 0x00000000; + break; + case 24U: + case 32U: + header.info.red = 0x00ff0000; + header.info.green = 0x0000ff00; + header.info.blue = 0x000000ff; + header.info.alpha = 0x00000000; + break; + } + } + break; + + case DIB_COMP_BITFIELDS: + // uncompressed direct colour with explicitly-specified bitfields + indexed = false; + if (offsetof(bitmap_info_header_t, alpha) > header.info.size) + { + osd_printf_verbose( + "ICO file DIB image data at %lu specifies bit masks but is too small (size %lu)\n", + (unsigned long)dir.offset, + (unsigned long)header.info.size); + return false; + } + break; + + default: + LOG("ICO file DIB image data at %u uses unsupported compression scheme %u\n", header.info.comp); + return false; + } + + // we can now calculate the size of the palette and row data + size_t const palette_entries( + indexed + ? ((1U == header.info.bpp) ? 2U : header.info.colors ? header.info.colors : (1U << header.info.bpp)) + : (no_palette ? 0U : header.info.colors)); + size_t const palette_size(palette_bytes * palette_entries); + size_t const row_bytes(((31 + (header.info.width * header.info.bpp)) >> 5) << 2); + size_t const mask_row_bytes(((31 + header.info.width) >> 5) << 2); + size_t const required_size( + header.info.size + + palette_size + + ((row_bytes + (have_and_mask ? mask_row_bytes : 0U)) * header.info.height)); + if (required_size > dir.size) + { + LOG( + "ICO file image data at %u (%u bytes) smaller than calculated DIB data size (%u bytes)\n", + dir.offset, + dir.size, + required_size); + return false; + } + + // load the palette for indexed colour formats or the shifts for direct colour formats + unsigned red_shift(0), green_shift(0), blue_shift(0), alpha_shift(0); + unsigned red_bits(0), green_bits(0), blue_bits(0), alpha_bits(0); + std::unique_ptr palette; + if (indexed) + { + // read palette and convert + std::unique_ptr palette_data(new uint8_t [palette_size]); + if (fp.read(palette_data.get(), palette_size) != palette_size) + { + LOG("Error reading palette from ICO file DIB image data at %u (%u bytes)\n", dir.offset, dir.size); + return false; + } + size_t const palette_usable(std::min(palette_entries, size_t(1) << header.info.bpp)); + palette.reset(new rgb_t [palette_usable]); + uint8_t const *ptr(palette_data.get()); + for (size_t i = 0; palette_usable > i; ++i, ptr += palette_bytes) + palette[i] = rgb_t(ptr[2], ptr[1], ptr[0]); + } + else + { + // skip over the palette if necessary + if (palette_entries) + fp.seek(palette_bytes * palette_entries, SEEK_CUR); + + // convert masks to shifts + bool const masks_contiguous( + dib_parse_mask(header.info.red, red_shift, red_bits) && + dib_parse_mask(header.info.green, green_shift, green_bits) && + dib_parse_mask(header.info.blue, blue_shift, blue_bits) && + dib_parse_mask(header.info.alpha, alpha_shift, alpha_bits)); + if (!masks_contiguous) + { + osd_printf_verbose( + "ICO file DIB image data at %lu specifies non-contiguous channel masks 0x%lx | 0x%lx | 0x%lx | 0x%lx\n", + (unsigned long)dir.offset, + (unsigned long)header.info.red, + (unsigned long)header.info.green, + (unsigned long)header.info.blue, + (unsigned long)header.info.alpha); + } + if ((32U != header.info.bpp) && ((header.info.red | header.info.green | header.info.blue | header.info.alpha) >> header.info.bpp)) + { + LOG( + "ICO file DIB image data at %lu specifies channel masks 0x%x | 0x%x | 0x%x | 0x%x that exceed %u bits per pixel\n", + dir.offset, + header.info.red, + header.info.green, + header.info.blue, + header.info.alpha, + header.info.bpp); + return false; + } + LOGMASKED( + LOG_DIB, + "DIB from ICO file image data at %1$u using channels: R((x >> %3$u) & 0x%4$0*2$x) G((x >> %5$u) & 0x%6$0*2$x) B((x >> %7$u) & 0x%8$0*2$x) A((x >> %9$u) & 0x%10$0*2$x)\n", + dir.offset, + (header.info.bpp + 3) >> 2, + red_shift, + (uint32_t(1) << red_bits) - 1, + green_shift, + (uint32_t(1) << green_bits) - 1, + blue_shift, + (uint32_t(1) << blue_bits) - 1, + alpha_shift, + (uint32_t(1) << alpha_bits) - 1); + + // the MAME bitmap only supports 8 bits/sample maximum + dib_truncate_channel(red_shift, red_bits); + dib_truncate_channel(green_shift, green_bits); + dib_truncate_channel(blue_shift, blue_bits); + dib_truncate_channel(alpha_shift, alpha_bits); + } + + // allocate the bitmap and process row data + std::unique_ptr row_data(new uint8_t [row_bytes]); + bitmap.allocate(header.info.width, header.info.height); + int const y_inc(top_down ? 1 : -1); + for (int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc) + { + if (fp.read(row_data.get(), row_bytes) != row_bytes) + { + LOG("Error reading DIB row %d data from ICO image data at %u\n", i, dir.offset); + return false; + } + uint8_t *src(row_data.get()); + uint32_t *dest(&bitmap.pix(y)); + unsigned shift(0U); + for (int32_t x = 0; header.info.width > x; ++x, ++dest) + { + // extract or compose a pixel + uint32_t pix(0U); + if (8U >= header.info.bpp) + { + assert(8U > shift); + pix = *src >> (8U - header.info.bpp); + *src <<= header.info.bpp; + shift += header.info.bpp; + if (8U <= shift) + { + shift = 0U; + ++src; + } + } + else for (shift = 0; header.info.bpp > shift; shift += 8U, ++src) + { + pix |= uint32_t(*src) << shift; + } + + // convert to RGB + if (indexed) + { + if (palette_entries > pix) + { + *dest = palette[pix]; + } + else + { + *dest = rgb_t::transparent(); + osd_printf_verbose( + "ICO file DIB image data at %lu has out-of-range color %lu at (%ld, %ld) with %lu palette entries\n", + (unsigned long)dir.offset, + (unsigned long)pix, + (long)x, + (long)y, + (unsigned long)palette_entries); + } + } + else + { + uint8_t r(dib_splat_sample((pix >> red_shift) & ((uint32_t(1) << red_bits) - 1), red_bits)); + uint8_t g(dib_splat_sample((pix >> green_shift) & ((uint32_t(1) << green_bits) - 1), green_bits)); + uint8_t b(dib_splat_sample((pix >> blue_shift) & ((uint32_t(1) << blue_bits) - 1), blue_bits)); + uint8_t a(dib_splat_sample((pix >> alpha_shift) & ((uint32_t(1) << alpha_bits) - 1), alpha_bits)); + *dest = rgb_t(alpha_bits ? a : 255, r, g, b); + } + } + } + + // process the AND mask if present + if (have_and_mask) + { + for (int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc) + { + if (fp.read(row_data.get(), mask_row_bytes) != mask_row_bytes) + { + LOG("Error reading DIB mask row %d data from ICO image data at %u\n", i, dir.offset); + return false; + } + uint8_t *src(row_data.get()); + uint32_t *dest(&bitmap.pix(y)); + unsigned shift(0U); + for (int32_t x = 0; header.info.width > x; ++x, ++dest) + { + assert(8U > shift); + rgb_t pix(*dest); + *dest = pix.set_a(BIT(*src, 7U - shift) ? 0U : pix.a()); + if (8U <= ++shift) + { + shift = 0U; + ++src; + } + } + } + } + + // we're done! + return true; +} + + +bool load_ico_image(util::core_file &fp, unsigned index, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) +{ + // try loading PNG image data (contains PNG file magic if used), and then fall back + if (load_ico_png(fp, dir, bitmap)) + { + LOG("Successfully loaded PNG image from ICO file entry %u\n", index); + return true; + } + else if (load_ico_dib(fp, dir, bitmap)) + { + LOG("Successfully loaded DIB image from ICO file entry %u\n", index); + return true; + } + + // no luck + return false; +} + + +bool load_ico_image(util::core_file &fp, unsigned count, unsigned index, bitmap_argb32 &bitmap) +{ + // read the directory entry + icon_dir_entry_t dir; + fp.seek(sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * index), SEEK_SET); + if (fp.read(&dir, sizeof(dir)) != sizeof(dir)) + { + LOG("Failed to read ICO file directory entry %u\n", index); + return false; + } + dir.byteswap(); + if ((sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * count)) > dir.offset) + { + LOG( + "ICO file image %u data starting at %u overlaps %u bytes of file header and directory\n", + index, + dir.offset, + sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * count)); + return false; + } + else + { + return load_ico_image(fp, index, dir, bitmap); + } +} + +} // anonymous namespace + + +int images_in_ico(util::core_file &fp) +{ + // read and check the icon file header + icon_dir_t header; + fp.seek(0, SEEK_SET); + if (fp.read(&header, sizeof(header)) != sizeof(header)) + { + LOG("Failed to read ICO file header\n"); + return -1; + } + header.reserved = little_endianize_int16(header.reserved); + header.type = little_endianize_int16(header.type); + header.count = little_endianize_int16(header.count); + if (0U != header.reserved) + { + LOG("Invalid ICO file header reserved field %u (expected 0)\n", header.reserved); + return -1; + } + if ((1U != header.type) && (2U != header.type)) + { + LOG("Invalid ICO file header type field %u (expected 1 or 2)\n", header.type); + return -1; + } + return int(unsigned(little_endianize_int16(header.count))); +} + + +void render_load_ico(util::core_file &fp, unsigned index, bitmap_argb32 &bitmap) +{ + // check that these things haven't been padded somehow + static_assert(sizeof(icon_dir_t) == 6U, "compiler has applied padding to icon_dir_t"); + static_assert(sizeof(icon_dir_entry_t) == 16U, "compiler has applied padding to icon_dir_entry_t"); + + // read and check the icon file header, then try to load the specified image + int const count(images_in_ico(fp)); + if (0 > count) + { + // images_in_ico already logged an error + } + else if (index >= count) + { + osd_printf_verbose("Requested image %u from ICO file containing %d images\n", index, count); + } + else if (load_ico_image(fp, count, index, bitmap)) + { + return; + } + bitmap.reset(); +} + + +void render_load_ico_first(util::core_file &fp, bitmap_argb32 &bitmap) +{ + int const count(images_in_ico(fp)); + for (int i = 0; count > i; ++i) + { + if (load_ico_image(fp, count, i, bitmap)) + return; + } + bitmap.reset(); +} + + +void render_load_ico_highest_detail(util::core_file &fp, bitmap_argb32 &bitmap) +{ + // read and check the icon file header - logs a message on error + int const count(images_in_ico(fp)); + if (0 <= count) + { + // now load all the directory entries + size_t const dir_bytes(sizeof(icon_dir_entry_t) * count); + std::unique_ptr dir(new icon_dir_entry_t [count]); + std::unique_ptr index(new unsigned [count]); + if (count && (fp.read(dir.get(), dir_bytes) != dir_bytes)) + { + LOG("Failed to read ICO file directory entries\n"); + } + else + { + // byteswap and sort by (pixels, depth) + for (int i = 0; count > i; ++i) + { + dir[i].byteswap(); + index[i] = i; + } + std::stable_sort( + index.get(), + index.get() + count, + [&dir] (unsigned x, unsigned y) + { + unsigned const x_pixels(dir[x].get_width() * dir[x].get_height()); + unsigned const y_pixels(dir[y].get_width() * dir[y].get_height()); + if (x_pixels > y_pixels) + return true; + else if (x_pixels < y_pixels) + return false; + else + return dir[x].bpp > dir[y].bpp; + }); + + // walk down until something works + for (int i = 0; count > i; ++i) + { + LOG( + "Try loading ICO file entry %u: %u*%u, %u bits per pixel\n", + index[i], + dir[index[i]].get_width(), + dir[index[i]].get_height(), + dir[index[i]].bpp); + if (load_ico_image(fp, index[i], dir[index[i]], bitmap)) + return; + } + } + } + bitmap.reset(); +} + +} // namespace ui diff --git a/src/frontend/mame/ui/icorender.h b/src/frontend/mame/ui/icorender.h index 17bc5a1d8ea..bb3f1a083be 100644 --- a/src/frontend/mame/ui/icorender.h +++ b/src/frontend/mame/ui/icorender.h @@ -1,13 +1,13 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota, Victor Laskin +// copyright-holders:Vas Crabb /*************************************************************************** ui/icorender.h - ICOns file loader. + Windows icon file parser. - Original code by Victor Laskin (victor.laskin@gmail.com) - http://vitiy.info/Code/ico.cpp + File handles passed to these functions must support read and seek + operations. ***************************************************************************/ #ifndef MAME_FRONTEND_MAME_UI_ICORENDER_H @@ -15,219 +15,20 @@ #pragma once -// These next two structs represent how the icon information is stored -// in an ICO file. -typedef struct -{ - uint8_t bWidth; // Width of the image - uint8_t bHeight; // Height of the image (times 2) - uint8_t bColorCount; // Number of colors in image (0 if >=8bpp) - uint8_t bReserved; // Reserved - uint16_t wPlanes; // Color Planes - uint16_t wBitCount; // Bits per pixel - uint32_t dwBytesInRes; // how many bytes in this resource? - uint32_t dwImageOffset; // where in the file is this image -} ICONDIRENTRY, *LPICONDIRENTRY; +namespace ui { -typedef struct -{ - uint16_t idReserved; // Reserved - uint16_t idType; // resource type (1 for icons) - uint16_t idCount; // how many images? - //ICONDIRENTRY idEntries[1]; // the entries for each image -} ICONDIR, *LPICONDIR; +// get number of images in icon file (-1 on error) +int images_in_ico(util::core_file &fp); -// size - 40 bytes -typedef struct { - uint32_t biSize; - uint32_t biWidth; - uint32_t biHeight; // Icon Height (added height of XOR-Bitmap and AND-Bitmap) - uint16_t biPlanes; - uint16_t biBitCount; - uint32_t biCompression; - int32_t biSizeImage; - uint32_t biXPelsPerMeter; - uint32_t biYPelsPerMeter; - uint32_t biClrUsed; - uint32_t biClrImportant; -} s_BITMAPINFOHEADER, *s_PBITMAPINFOHEADER; +// load specified icon from file (zero-based) +void render_load_ico(util::core_file &fp, unsigned index, bitmap_argb32 &bitmap); -// 46 bytes -typedef struct{ - s_BITMAPINFOHEADER icHeader; // DIB header - uint32_t icColors[1]; // Color table (short 4 bytes) //RGBQUAD - uint8_t icXOR[1]; // DIB bits for XOR mask - uint8_t icAND[1]; // DIB bits for AND mask -} ICONIMAGE, *LPICONIMAGE; +// load first supported icon from file +void render_load_ico_first(util::core_file &fp, bitmap_argb32 &bitmap); -//------------------------------------------------- -// load an ICO file into a bitmap -//------------------------------------------------- +// load highest detail supported icon from file +void render_load_ico_highest_detail(util::core_file &fp, bitmap_argb32 &bitmap); -inline void render_load_ico(bitmap_argb32 &bitmap, emu_file &file, const char *dirname, const char *filename) -{ - int32_t width = 0; - int32_t height = 0; - - // deallocate previous bitmap - bitmap.reset(); - - // define file's full name - std::string fname; - - if (!dirname) - fname = filename; - else - fname.assign(dirname).append(PATH_SEPARATOR).append(filename); - - osd_file::error filerr = file.open(fname.c_str()); - - if (filerr != osd_file::error::NONE) - return; - - // allocates a buffer for the image - uint64_t size = file.size(); - uint8_t *buffer = global_alloc_array(uint8_t, size + 1); - - // read data from the file and set them in the buffer - file.read(buffer, size); - - LPICONDIR icoDir = (LPICONDIR)buffer; - int iconsCount = icoDir->idCount; - - if (icoDir->idReserved != 0 || icoDir->idType != 1 || iconsCount == 0 || iconsCount > 20) - { - file.close(); - global_free_array(buffer); - return; - } - - uint8_t* cursor = buffer; - cursor += 6; - ICONDIRENTRY* dirEntry = (ICONDIRENTRY*)(cursor); - int maxSize = 0; - int offset = 0; - int maxBitCount = 0; - for (int i = 0; i < iconsCount; i++, ++dirEntry) - { - int w = dirEntry->bWidth; - int h = dirEntry->bHeight; - int bitCount = dirEntry->wBitCount; - if (w * h > maxSize || bitCount > maxBitCount) // we choose icon with max resolution - { - width = w; - height = h; - offset = dirEntry->dwImageOffset; - maxSize = w * h; - } - } - - if (offset == 0) return; - - cursor = buffer; - cursor += offset; - ICONIMAGE* icon = (ICONIMAGE*)(cursor); - int realBitsCount = (int)icon->icHeader.biBitCount; - bool hasAndMask = (realBitsCount < 32) && (height != icon->icHeader.biHeight); - - cursor += 40; - bitmap.allocate(width, height); - - // rgba + vertical swap - if (realBitsCount >= 32) - { - for (int x = 0; x < width; ++x) - for (int y = 0; y < height; ++y) - { - int shift2 = 4 * (x + (height - y - 1) * width); - bitmap.pix32(y, x) = rgb_t(cursor[shift2 + 3], cursor[shift2 + 2], cursor[shift2 + 1], cursor[shift2]); - } - } - else if (realBitsCount == 24) - for (int x = 0; x < width; ++x) - for (int y = 0; y < height; ++y) - { - int shift2 = 3 * (x + (height - y - 1) * width); - bitmap.pix32(y, x) = rgb_t(255, cursor[shift2 + 2], cursor[shift2 + 1], cursor[shift2]); - } - else if (realBitsCount == 8) // 256 colors - { - // 256 color table - uint8_t *colors = cursor; - cursor += 256 * 4; - for (int x = 0; x < width; ++x) - for (int y = 0; y < height; ++y) - { - int shift2 = (x + (height - y - 1) * width); - int index = 4 * cursor[shift2]; - bitmap.pix32(y, x) = rgb_t(255, colors[index + 2], colors[index + 1], colors[index]); - } - } - else if (realBitsCount == 4) // 16 colors - { - // 16 color table - uint8_t *colors = cursor; - cursor += 16 * 4; - for (int x = 0; x < width; ++x) - for (int y = 0; y < height; ++y) - { - int shift2 = (x + (height - y - 1) * width); - uint8_t index = cursor[shift2 / 2]; - if (shift2 % 2 == 0) - index = (index >> 4) & 0xF; - else - index = index & 0xF; - index *= 4; - bitmap.pix32(y, x) = rgb_t(255, colors[index + 2], colors[index + 1], colors[index]); - } - } - else if (realBitsCount == 1) // 2 colors - { - // 2 color table - uint8_t *colors = cursor; - cursor += 2 * 4; - int boundary = width; // !!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) - while (boundary % 32 != 0) boundary++; - - for (int x = 0; x < width; ++x) - for (int y = 0; y < height; ++y) - { - int shift2 = (x + (height - y - 1) * boundary); - uint8_t index = cursor[shift2 / 8]; - - // select 1 bit only - uint8_t bit = 7 - (x % 8); - index = (index >> bit) & 0x01; - index *= 4; - bitmap.pix32(y, x) = rgb_t(255, colors[index + 2], colors[index + 1], colors[index]); - } - } - - // Read AND mask after base color data - 1 BIT MASK - if (hasAndMask) - { - int boundary = width * realBitsCount; // !!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) - while (boundary % 32 != 0) boundary++; - cursor += boundary * height / 8; - - boundary = width; - while (boundary % 32 != 0) boundary++; - - for (int y = 0; y < height; ++y) - for (int x = 0; x < width; ++x) - { - uint8_t bit = 7 - (x % 8); - int shift2 = (x + (height - y - 1) * boundary) / 8; - int mask = (0x01 & ((uint8_t)cursor[shift2] >> bit)); - rgb_t colors = bitmap.pix32(y, x); - uint8_t alpha = colors.a(); - alpha *= 1 - mask; - colors.set_a(alpha); - bitmap.pix32(y, x) = colors; - } - } - file.close(); - global_free_array(buffer); -} +} // namespace ui #endif // MAME_FRONTEND_MAME_UI_ICORENDER_H diff --git a/src/frontend/mame/ui/info.cpp b/src/frontend/mame/ui/info.cpp index 963dfb5597c..e7235b89111 100644 --- a/src/frontend/mame/ui/info.cpp +++ b/src/frontend/mame/ui/info.cpp @@ -28,20 +28,27 @@ constexpr machine_flags::type MACHINE_WARNINGS = machine_flags::NO_COCKTAIL | m constexpr machine_flags::type MACHINE_BTANB = machine_flags::NO_SOUND_HW | machine_flags::IS_INCOMPLETE; constexpr std::pair FEATURE_NAMES[] = { - { device_t::feature::PROTECTION, __("protection") }, - { device_t::feature::PALETTE, __("color palette") }, - { device_t::feature::GRAPHICS, __("graphics") }, - { device_t::feature::SOUND, __("sound") }, - { device_t::feature::CONTROLS, __("controls") }, - { device_t::feature::KEYBOARD, __("keyboard") }, - { device_t::feature::MOUSE, __("mouse") }, - { device_t::feature::MICROPHONE, __("microphone") }, - { device_t::feature::CAMERA, __("camera") }, - { device_t::feature::DISK, __("disk") }, - { device_t::feature::PRINTER, __("printer") }, - { device_t::feature::LAN, __("LAN") }, - { device_t::feature::WAN, __("WAN") }, - { device_t::feature::TIMING, __("timing") } }; + { device_t::feature::PROTECTION, __("protection") }, + { device_t::feature::TIMING, __("timing") }, + { device_t::feature::GRAPHICS, __("graphics") }, + { device_t::feature::PALETTE, __("color palette") }, + { device_t::feature::SOUND, __("sound") }, + { device_t::feature::CAPTURE, __("capture hardware") }, + { device_t::feature::CAMERA, __("camera") }, + { device_t::feature::MICROPHONE, __("microphone") }, + { device_t::feature::CONTROLS, __("controls") }, + { device_t::feature::KEYBOARD, __("keyboard") }, + { device_t::feature::MOUSE, __("mouse") }, + { device_t::feature::MEDIA, __("media") }, + { device_t::feature::DISK, __("disk") }, + { device_t::feature::PRINTER, __("printer") }, + { device_t::feature::TAPE, __("magnetic tape") }, + { device_t::feature::PUNCH, __("punch tape") }, + { device_t::feature::DRUM, __("magnetic drum") }, + { device_t::feature::ROM, __("solid state storage") }, + { device_t::feature::COMMS, __("communications") }, + { device_t::feature::LAN, __("LAN") }, + { device_t::feature::WAN, __("WAN") } }; } // anonymous namespace diff --git a/src/frontend/mame/ui/inifile.cpp b/src/frontend/mame/ui/inifile.cpp index 852884fea6a..b68be00122b 100644 --- a/src/frontend/mame/ui/inifile.cpp +++ b/src/frontend/mame/ui/inifile.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /*************************************************************************** ui/inifile.cpp @@ -16,13 +16,24 @@ #include "drivenum.h" #include "softlist_dev.h" +#include +#include +#include + + +namespace { + +char const FAVORITE_FILENAME[] = "favorites.ini"; + +} // anonymous namespace + //------------------------------------------------- // ctor //------------------------------------------------- -inifile_manager::inifile_manager(running_machine &machine, ui_options &moptions) - : m_options(moptions) +inifile_manager::inifile_manager(ui_options &options) + : m_options(options) , m_ini_index() { // scan directories and create index @@ -108,204 +119,103 @@ void inifile_manager::init_category(std::string &&filename, emu_file &file) FAVORITE MANAGER **************************************************************************/ -//------------------------------------------------- -// ctor -//------------------------------------------------- - -favorite_manager::favorite_manager(running_machine &machine, ui_options &moptions) - : m_machine(machine) - , m_options(moptions) +bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, ui_software_info const &rhs) const { - parse_favorite(); -} + assert(lhs.driver); + assert(rhs.driver); -//------------------------------------------------- -// add a game -//------------------------------------------------- - -void favorite_manager::add_favorite_game(const game_driver *driver) -{ - m_list.emplace(driver->type.fullname(), *driver); - save_favorite_games(); -} - -//------------------------------------------------- -// add a system -//------------------------------------------------- - -void favorite_manager::add_favorite_game(ui_software_info &swinfo) -{ - m_list.emplace(swinfo.longname, swinfo); - save_favorite_games(); -} - -//------------------------------------------------- -// add a game / system -//------------------------------------------------- - -void favorite_manager::add_favorite_game() -{ - if ((machine().system().flags & machine_flags::MASK_TYPE) == machine_flags::TYPE_ARCADE) + if (!lhs.startempty) { - add_favorite_game(&machine().system()); - return; + if (rhs.startempty) + return false; + else if (lhs.listname < rhs.listname) + return true; + else if (lhs.listname > rhs.listname) + return false; + else if (lhs.shortname < rhs.shortname) + return true; + else if (lhs.shortname > rhs.shortname) + return false; } - - auto software_avail = false; - for (device_image_interface &image : image_interface_iterator(machine().root_device())) + else if (!rhs.startempty) { - if (image.exists() && image.loaded_through_softlist()) - { - const software_info *swinfo = image.software_entry(); - const software_part *part = image.part_entry(); - ui_software_info tmpmatches; - tmpmatches.shortname = swinfo->shortname(); - tmpmatches.longname = image.longname(); - tmpmatches.parentname = swinfo->parentname(); - tmpmatches.year = image.year(); - tmpmatches.publisher = image.manufacturer(); - tmpmatches.supported = image.supported(); - tmpmatches.part = part->name(); - tmpmatches.driver = &machine().system(); - tmpmatches.listname = strensure(image.software_list_name()); - tmpmatches.interface = part->interface(); - tmpmatches.instance = image.instance_name(); - tmpmatches.startempty = 0; - tmpmatches.parentlongname.clear(); - if (!swinfo->parentname().empty()) - { - auto swlist = software_list_device::find_by_name(machine().config(), image.software_list_name()); - for (const software_info &c_swinfo : swlist->get_info()) - { - std::string c_parent(c_swinfo.parentname()); - if (!c_parent.empty() && c_parent == swinfo->shortname()) - { - tmpmatches.parentlongname = c_swinfo.longname(); - break; - } - } - } - - tmpmatches.usage.clear(); - for (const feature_list_item &flist : swinfo->other_info()) - if (!strcmp(flist.name().c_str(), "usage")) - tmpmatches.usage = flist.value(); - - tmpmatches.devicetype = strensure(image.image_type_name()); - tmpmatches.available = true; - software_avail = true; - m_list.emplace(tmpmatches.longname, tmpmatches); - save_favorite_games(); - } + return true; } - if (!software_avail) - add_favorite_game(&machine().system()); + return 0 > std::strncmp(lhs.driver->name, rhs.driver->name, ARRAY_LENGTH(lhs.driver->name)); } -//------------------------------------------------- -// remove a favorite from list -//------------------------------------------------- - -void favorite_manager::remove_favorite_game(ui_software_info const &swinfo) +bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, game_driver const &rhs) const { - for (auto e = m_list.begin(); e != m_list.end(); ++e) - if (e->second == swinfo) - { - m_list.erase(e); - break; - } - m_current = m_list.begin(); - save_favorite_games(); -} - -//------------------------------------------------- -// remove a favorite from list -//------------------------------------------------- + assert(lhs.driver); -void favorite_manager::remove_favorite_game() -{ - m_list.erase(m_current); - m_current = m_list.begin(); - save_favorite_games(); + if (!lhs.startempty) + return false; + else + return 0 > std::strncmp(lhs.driver->name, rhs.name, ARRAY_LENGTH(rhs.name)); } -//------------------------------------------------- -// check if game is already in favorite list -//------------------------------------------------- - -bool favorite_manager::isgame_favorite() +bool favorite_manager::favorite_compare::operator()(game_driver const &lhs, ui_software_info const &rhs) const { - if ((machine().system().flags & machine_flags::MASK_TYPE) == machine_flags::TYPE_ARCADE) - return isgame_favorite(&machine().system()); - - auto image_loaded = false; + assert(rhs.driver); - for (device_image_interface &image : image_interface_iterator(machine().root_device())) - { - const software_info *swinfo = image.software_entry(); - if (image.exists() && swinfo != nullptr) - { - image_loaded = true; - for (auto current = m_list.begin(); current != m_list.end(); ++current) - if (current->second.shortname == swinfo->shortname() && - current->second.listname == image.software_list_name()) - { - m_current = current; - return true; - } - } - } - - if (!image_loaded) - return isgame_favorite(&machine().system()); - - m_current = m_list.begin(); - return false; + if (!rhs.startempty) + return true; + else + return 0 > std::strncmp(lhs.name, rhs.driver->name, ARRAY_LENGTH(lhs.name)); } -//------------------------------------------------- -// check if game is already in favorite list -//------------------------------------------------- - -bool favorite_manager::isgame_favorite(const game_driver *driver) +bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, running_software_key const &rhs) const { - for (auto current = m_list.begin(); current != m_list.end(); ++current) - if (current->second.driver == driver && current->second.shortname == driver->name) - { - m_current = current; - return true; - } - - m_current = m_list.begin(); - return false; + assert(lhs.driver); + assert(std::get<1>(rhs)); + + if (lhs.startempty) + return true; + else if (lhs.listname < std::get<1>(rhs)) + return true; + else if (lhs.listname > std::get<1>(rhs)) + return false; + else if (lhs.shortname < std::get<2>(rhs)) + return true; + else if (lhs.shortname > std::get<2>(rhs)) + return false; + else + return 0 > std::strncmp(lhs.driver->name, std::get<0>(rhs).name, ARRAY_LENGTH(lhs.driver->name)); } -//------------------------------------------------- -// check if game is already in favorite list -//------------------------------------------------- - -bool favorite_manager::isgame_favorite(ui_software_info const &swinfo) +bool favorite_manager::favorite_compare::operator()(running_software_key const &lhs, ui_software_info const &rhs) const { - for (auto current = m_list.begin(); current != m_list.end(); ++current) - if (current->second == swinfo) - { - m_current = current; - return true; - } - - m_current = m_list.begin(); - return false; + assert(std::get<1>(lhs)); + assert(rhs.driver); + + if (rhs.startempty) + return false; + else if (std::get<1>(lhs) < rhs.listname) + return true; + else if (std::get<1>(lhs) > rhs.listname) + return false; + else if (std::get<2>(lhs) < rhs.shortname) + return true; + else if (std::get<2>(lhs) > rhs.shortname) + return false; + else + return 0 > std::strncmp(std::get<0>(lhs).name, rhs.driver->name, ARRAY_LENGTH(rhs.driver->name)); } + //------------------------------------------------- -// parse favorite file +// construction/destruction //------------------------------------------------- -void favorite_manager::parse_favorite() +favorite_manager::favorite_manager(ui_options &options) + : m_options(options) + , m_favorites() + , m_sorted() + , m_need_sort(true) { emu_file file(m_options.ui_path(), OPEN_FLAG_READ); - if (file.open(favorite_filename) == osd_file::error::NONE) + if (file.open(FAVORITE_FILENAME) == osd_file::error::NONE) { char readbuf[1024]; file.gets(readbuf, 1024); @@ -332,7 +242,8 @@ void favorite_manager::parse_favorite() file.gets(readbuf, 1024); chartrimcarriage(readbuf); auto dx = driver_list::find(readbuf); - if (dx == -1) continue; + if (0 > dx) + continue; tmpmatches.driver = &driver_list::driver(dx); file.gets(readbuf, 1024); tmpmatches.listname = chartrimcarriage(readbuf); @@ -350,53 +261,319 @@ void favorite_manager::parse_favorite() tmpmatches.devicetype = chartrimcarriage(readbuf); file.gets(readbuf, 1024); tmpmatches.available = atoi(readbuf); - m_list.emplace(tmpmatches.longname, tmpmatches); + m_favorites.emplace(std::move(tmpmatches)); } file.close(); } } + +//------------------------------------------------- +// add +//------------------------------------------------- + +void favorite_manager::add_favorite_system(game_driver const &driver) +{ + add_impl(driver); +} + +void favorite_manager::add_favorite_software(ui_software_info const &swinfo) +{ + add_impl(swinfo); +} + +void favorite_manager::add_favorite(running_machine &machine) +{ + apply_running_machine( + machine, + [this, &machine] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done) + { + if (imagedev) + { + // creating this is fairly expensive, but we'll assume this usually succeeds + ui_software_info info; + software_part const *const part(imagedev->part_entry()); + assert(software); + assert(part); + + // start with simple stuff that can just be copied + info.shortname = software->shortname(); + info.longname = imagedev->longname(); + info.parentname = software->parentname(); + info.year = imagedev->year(); + info.publisher = imagedev->manufacturer(); + info.supported = imagedev->supported(); + info.part = part->name(); + info.driver = &driver; + info.listname = imagedev->software_list_name(); + info.interface = part->interface(); + info.instance = imagedev->instance_name(); + info.startempty = 0; + info.devicetype = strensure(imagedev->image_type_name()); + info.available = true; + + // look up the parent in the list if necessary (eugh, O(n) walk) + if (!info.parentname.empty()) + { + auto const listdev = software_list_device::find_by_name(machine.config(), info.listname); + assert(listdev); + for (software_info const &other : listdev->get_info()) + { + if (other.shortname() == info.parentname) + { + info.parentlongname = other.longname(); + break; + } + } + } + + // fill in with the first usage entry we find + for (feature_list_item const &feature : software->other_info()) + { + if (feature.name() == "usage") + { + info.usage = feature.value(); + break; + } + } + + // hooray for move semantics! + add_impl(std::move(info)); + } + else + { + add_impl(driver); + } + }); +} + +template void favorite_manager::add_impl(T &&key) +{ + auto const ins(m_favorites.emplace(std::forward(key))); + if (ins.second) + { + if (!m_sorted.empty()) + m_sorted.emplace_back(std::ref(*ins.first)); + m_need_sort = true; + save_favorites(); + } +} + + +//------------------------------------------------- +// check +//------------------------------------------------- + +bool favorite_manager::is_favorite_system(game_driver const &driver) const +{ + return check_impl(driver); +} + +bool favorite_manager::is_favorite_software(ui_software_info const &swinfo) const +{ + auto found(m_favorites.lower_bound(swinfo)); + if ((m_favorites.end() != found) && (found->listname == swinfo.listname) && (found->shortname == swinfo.shortname)) + return true; + else if (m_favorites.begin() == found) + return false; + + // need to back up and check for matching software with lexically earlier driver + --found; + return (found->listname == swinfo.listname) && (found->shortname == swinfo.shortname); +} + +bool favorite_manager::is_favorite(running_machine &machine) const +{ + bool result(false); + apply_running_machine( + machine, + [this, &result] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done) + { + assert(!result); + result = imagedev + ? check_impl(running_software_key(driver, imagedev->software_list_name(), software->shortname())) + : check_impl(driver); + done = done || result; + }); + return result; +} + +bool favorite_manager::is_favorite_system_software(ui_software_info const &swinfo) const +{ + return check_impl(swinfo); +} + +template bool favorite_manager::check_impl(T const &key) const +{ + return m_favorites.find(key) != m_favorites.end(); +} + + +//------------------------------------------------- +// remove +//------------------------------------------------- + +void favorite_manager::remove_favorite_system(game_driver const &driver) +{ + remove_impl(driver); +} + +void favorite_manager::remove_favorite_software(ui_software_info const &swinfo) +{ + remove_impl(swinfo); +} + +void favorite_manager::remove_favorite(running_machine &machine) +{ + apply_running_machine( + machine, + [this] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done) + { + done = imagedev + ? remove_impl(running_software_key(driver, imagedev->software_list_name(), software->shortname())) + : remove_impl(driver); + }); +} + +template bool favorite_manager::remove_impl(T const &key) +{ + auto const found(m_favorites.find(key)); + if (m_favorites.end() != found) + { + m_favorites.erase(found); + m_sorted.clear(); + m_need_sort = true; + save_favorites(); + return true; + } + else + { + return false; + } +} + + //------------------------------------------------- -// save favorite +// implementation //------------------------------------------------- -void favorite_manager::save_favorite_games() +template +void favorite_manager::apply_running_machine(running_machine &machine, T &&action) +{ + bool done(false); + + // TODO: this should be changed - it interacts poorly with cartslots on arcade systems + if ((machine.system().flags & machine_flags::MASK_TYPE) == machine_flags::TYPE_ARCADE) + { + action(machine.system(), nullptr, nullptr, done); + } + else + { + bool have_software(false); + for (device_image_interface &image_dev : image_interface_iterator(machine.root_device())) + { + software_info const *const sw(image_dev.software_entry()); + if (image_dev.exists() && image_dev.loaded_through_softlist() && sw) + { + assert(image_dev.software_list_name()); + + have_software = true; + action(machine.system(), &image_dev, sw, done); + if (done) + return; + } + } + + if (!have_software) + action(machine.system(), nullptr, nullptr, done); + } +} + +void favorite_manager::update_sorted() +{ + if (m_need_sort) + { + if (m_sorted.empty()) + std::copy(m_favorites.begin(), m_favorites.end(), std::back_inserter(m_sorted)); + + assert(m_favorites.size() == m_sorted.size()); + std::stable_sort( + m_sorted.begin(), + m_sorted.end(), + [] (ui_software_info const &lhs, ui_software_info const &rhs) -> bool + { + assert(lhs.driver); + assert(rhs.driver); + + int cmp; + + cmp = core_stricmp(lhs.longname.c_str(), rhs.longname.c_str()); + if (0 > cmp) + return true; + else if (0 < cmp) + return false; + + cmp = core_stricmp(lhs.driver->type.fullname(), rhs.driver->type.fullname()); + if (0 > cmp) + return true; + else if (0 < cmp) + return false; + + cmp = std::strcmp(lhs.listname.c_str(), rhs.listname.c_str()); + if (0 > cmp) + return true; + else if (0 < cmp) + return false; + + return false; + }); + + m_need_sort = false; + } +} + +void favorite_manager::save_favorites() { // attempt to open the output file emu_file file(m_options.ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open(favorite_filename) == osd_file::error::NONE) + if (file.open(FAVORITE_FILENAME) == osd_file::error::NONE) { - if (m_list.empty()) + if (m_favorites.empty()) { + // delete it if there are no favorites file.remove_on_close(); - file.close(); - return; } - - // generate the favorite INI - std::ostringstream text; - text << "[ROOT_FOLDER]\n[Favorite]\n\n"; - for (auto & e : m_list) + else { - auto elem = e.second; - text << elem.shortname << '\n'; - text << elem.longname << '\n'; - text << elem.parentname << '\n'; - text << elem.year << '\n'; - text << elem.publisher << '\n'; - util::stream_format(text, "%d\n", elem.supported); - text << elem.part << '\n'; - util::stream_format(text, "%s\n", elem.driver->name); - text << elem.listname << '\n'; - text << elem.interface << '\n'; - text << elem.instance << '\n'; - util::stream_format(text, "%d\n", elem.startempty); - text << elem.parentlongname << '\n'; - text << elem.usage << '\n'; - text << elem.devicetype << '\n'; - util::stream_format(text, "%d\n", elem.available); + // generate the favorite INI + file.puts("[ROOT_FOLDER]\n[Favorite]\n\n"); + util::ovectorstream buf; + for (ui_software_info const &info : m_favorites) + { + buf.clear(); + buf.rdbuf()->clear(); + + buf << info.shortname << '\n'; + buf << info.longname << '\n'; + buf << info.parentname << '\n'; + buf << info.year << '\n'; + buf << info.publisher << '\n'; + util::stream_format(buf, "%d\n", info.supported); + buf << info.part << '\n'; + util::stream_format(buf, "%s\n", info.driver->name); + buf << info.listname << '\n'; + buf << info.interface << '\n'; + buf << info.instance << '\n'; + util::stream_format(buf, "%d\n", info.startempty); + buf << info.parentlongname << '\n'; + buf << info.usage << '\n'; + buf << info.devicetype << '\n'; + util::stream_format(buf, "%d\n", info.available); + + buf.put('\0'); + file.puts(&buf.vec()[0]); + } } - file.puts(text.str().c_str()); file.close(); } } diff --git a/src/frontend/mame/ui/inifile.h b/src/frontend/mame/ui/inifile.h index 49344fd01eb..68adca3c917 100644 --- a/src/frontend/mame/ui/inifile.h +++ b/src/frontend/mame/ui/inifile.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /*************************************************************************** ui/inifile.h @@ -7,7 +7,6 @@ UI INIs file manager. ***************************************************************************/ - #ifndef MAME_FRONTEND_UI_INIFILE_H #define MAME_FRONTEND_UI_INIFILE_H @@ -15,6 +14,10 @@ #include "ui/utils.h" +#include +#include +#include +#include #include @@ -28,7 +31,7 @@ class inifile_manager { public: // construction/destruction - inifile_manager(running_machine &machine, ui_options &moptions); + inifile_manager(ui_options &options); // load systems from category void load_ini_category(size_t file, size_t category, std::unordered_set &result) const; @@ -59,52 +62,67 @@ class favorite_manager { public: // construction/destruction - favorite_manager(running_machine &machine, ui_options &moptions); - - // favorites comparator - struct ci_less - { - bool operator() (const std::string &s1, const std::string &s2) const - { - return (core_stricmp(s1.c_str(), s2.c_str()) < 0); - } - }; - - // favorite indices - std::multimap m_list; - - // getters - running_machine &machine() const { return m_machine; } + favorite_manager(ui_options &options); // add - void add_favorite_game(); - void add_favorite_game(const game_driver *driver); - void add_favorite_game(ui_software_info &swinfo); + void add_favorite_system(game_driver const &driver); + void add_favorite_software(ui_software_info const &swinfo); + void add_favorite(running_machine &machine); // check - bool isgame_favorite(); - bool isgame_favorite(const game_driver *driver); - bool isgame_favorite(ui_software_info const &swinfo); - - // save - void save_favorite_games(); + bool is_favorite_system(game_driver const &driver) const; + bool is_favorite_software(ui_software_info const &swinfo) const; + bool is_favorite_system_software(ui_software_info const &swinfo) const; + bool is_favorite(running_machine &machine) const; // remove - void remove_favorite_game(); - void remove_favorite_game(ui_software_info const &swinfo); + void remove_favorite_system(game_driver const &driver); + void remove_favorite_software(ui_software_info const &swinfo); + void remove_favorite(running_machine &machine); + + // walk + template void apply(T &&action) + { + for (auto const &item : m_favorites) + action(item); + } + template void apply_sorted(T &&action) + { + update_sorted(); + for (auto const &item : m_sorted) + action(item.get()); + } private: - const char *favorite_filename = "favorites.ini"; + using running_software_key = std::tuple; + + struct favorite_compare + { + using is_transparent = std::true_type; + + bool operator()(ui_software_info const &lhs, ui_software_info const &rhs) const; + bool operator()(ui_software_info const &lhs, game_driver const &rhs) const; + bool operator()(game_driver const &lhs, ui_software_info const &rhs) const; + bool operator()(ui_software_info const &lhs, running_software_key const &rhs) const; + bool operator()(running_software_key const &lhs, ui_software_info const &rhs) const; + }; - // current - std::multimap::iterator m_current; + using favorites_set = std::set; + using sorted_favorites = std::vector >; - // parse file ui_favorite - void parse_favorite(); + // implementation + template static void apply_running_machine(running_machine &machine, T &&action); + template void add_impl(T &&key); + template bool check_impl(T const &key) const; + template bool remove_impl(T const &key); + void update_sorted(); + void save_favorites(); // internal state - running_machine &m_machine; // reference to our machine ui_options &m_options; + favorites_set m_favorites; + sorted_favorites m_sorted; + bool m_need_sort; }; #endif // MAME_FRONTEND_UI_INIFILE_H diff --git a/src/frontend/mame/ui/mainmenu.cpp b/src/frontend/mame/ui/mainmenu.cpp index 30b2fbae1e0..b9befd90991 100644 --- a/src/frontend/mame/ui/mainmenu.cpp +++ b/src/frontend/mame/ui/mainmenu.cpp @@ -123,7 +123,7 @@ void menu_main::populate(float &customtop, float &custombottom) item_append(menu_item_type::SEPARATOR); - if (!mame_machine_manager::instance()->favorite().isgame_favorite()) + if (!mame_machine_manager::instance()->favorite().is_favorite(machine())) item_append(_("Add To Favorites"), "", 0, (void *)ADD_FAVORITE); else item_append(_("Remove From Favorites"), "", 0, (void *)REMOVE_FAVORITE); @@ -249,12 +249,12 @@ void menu_main::handle() break; case ADD_FAVORITE: - mame_machine_manager::instance()->favorite().add_favorite_game(); + mame_machine_manager::instance()->favorite().add_favorite(machine()); reset(reset_options::REMEMBER_POSITION); break; case REMOVE_FAVORITE: - mame_machine_manager::instance()->favorite().remove_favorite_game(); + mame_machine_manager::instance()->favorite().remove_favorite(machine()); reset(reset_options::REMEMBER_POSITION); break; diff --git a/src/frontend/mame/ui/menu.cpp b/src/frontend/mame/ui/menu.cpp index 3a0eff12c5e..eecaab68978 100644 --- a/src/frontend/mame/ui/menu.cpp +++ b/src/frontend/mame/ui/menu.cpp @@ -31,6 +31,7 @@ namespace ui { + /*************************************************************************** CONSTANTS ***************************************************************************/ @@ -85,14 +86,14 @@ menu::global_state::global_state(running_machine &machine, ui_options const &opt , m_machine(machine) , m_cleanup_callbacks() , m_bgrnd_bitmap() + , m_bgrnd_texture(nullptr, machine.render()) , m_stack() , m_free() { render_manager &render(machine.render()); - auto const texture_free([&render](render_texture *texture) { render.texture_free(texture); }); // create a texture for main menu background - m_bgrnd_texture = texture_ptr(render.texture_alloc(render_texture::hq_scale), texture_free); + m_bgrnd_texture.reset(render.texture_alloc(render_texture::hq_scale)); if (options.use_background_image() && (&machine.system() == &GAME_NAME(___empty))) { m_bgrnd_bitmap = std::make_unique(0, 0); @@ -161,8 +162,17 @@ void menu::global_state::stack_reset() void menu::global_state::clear_free_list() { + // free stack is in reverse order - unwind it properly + std::unique_ptr reversed; while (m_free) - m_free = std::move(m_free->m_parent); + { + std::unique_ptr menu(std::move(m_free)); + m_free = std::move(menu->m_parent); + menu->m_parent = std::move(reversed); + reversed = std::move(menu); + } + while (reversed) + reversed = std::move(reversed->m_parent); } diff --git a/src/frontend/mame/ui/menu.h b/src/frontend/mame/ui/menu.h index 1a6c2d977f8..ffcb0ed71e8 100644 --- a/src/frontend/mame/ui/menu.h +++ b/src/frontend/mame/ui/menu.h @@ -27,6 +27,7 @@ namespace ui { + /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ @@ -35,18 +36,17 @@ class menu { public: // flags for menu items - enum + enum : unsigned { - FLAG_LEFT_ARROW = (1 << 0), - FLAG_RIGHT_ARROW = (1 << 1), - FLAG_INVERT = (1 << 2), - FLAG_MULTILINE = (1 << 3), - FLAG_REDTEXT = (1 << 4), - FLAG_DISABLE = (1 << 5), - FLAG_UI_DATS = (1 << 6), - FLAG_UI_FAVORITE = (1 << 7), - FLAG_UI_HEADING = (1 << 8), - FLAG_COLOR_BOX = (1 << 9) + FLAG_LEFT_ARROW = 1U << 0, + FLAG_RIGHT_ARROW = 1U << 1, + FLAG_INVERT = 1U << 2, + FLAG_MULTILINE = 1U << 3, + FLAG_REDTEXT = 1U << 4, + FLAG_DISABLE = 1U << 5, + FLAG_UI_DATS = 1U << 6, + FLAG_UI_HEADING = 1U << 7, + FLAG_COLOR_BOX = 1U << 8 }; virtual ~menu(); @@ -151,7 +151,6 @@ protected: void reset(reset_options options); void reset_parent(reset_options options) { m_parent->reset(options); } - void reset_topmost(reset_options options) { m_global_state->reset_topmost(options); } template T *topmost_menu() const { return m_global_state->topmost_menu(); } template static T *topmost_menu(running_machine &machine) { return get_global_state(machine)->topmost_menu(); } @@ -310,8 +309,6 @@ private: bitmap_argb32 *bgrnd_bitmap() { return m_bgrnd_bitmap.get(); } render_texture *bgrnd_texture() { return m_bgrnd_texture.get(); } - void reset_topmost(reset_options options) { if (m_stack) m_stack->reset(options); } - template T *topmost_menu() const { return dynamic_cast(m_stack.get()); } diff --git a/src/frontend/mame/ui/miscmenu.cpp b/src/frontend/mame/ui/miscmenu.cpp index 57b1412dd5d..e2f1f0615fc 100644 --- a/src/frontend/mame/ui/miscmenu.cpp +++ b/src/frontend/mame/ui/miscmenu.cpp @@ -663,25 +663,40 @@ void menu_export::populate(float &customtop, float &custombottom) // ctor / dtor //------------------------------------------------- -menu_machine_configure::menu_machine_configure(mame_ui_manager &mui, render_container &container, const game_driver *prev, float _x0, float _y0) +menu_machine_configure::menu_machine_configure( + mame_ui_manager &mui, + render_container &container, + game_driver const &drv, + std::function &&handler, + float x0, float y0) : menu(mui, container) - , m_drv(prev) - , x0(_x0) - , y0(_y0) + , m_handler(std::move(handler)) + , m_drv(drv) + , m_x0(x0) + , m_y0(y0) , m_curbios(0) - , m_fav_reset(false) + , m_was_favorite(mame_machine_manager::instance()->favorite().is_favorite_system(drv)) + , m_want_favorite(m_was_favorite) { // parse the INI file std::ostringstream error; osd_setup_osd_specific_emu_options(m_opts); - mame_options::parse_standard_inis(m_opts, error, m_drv); + mame_options::parse_standard_inis(m_opts, error, &m_drv); setup_bios(); } menu_machine_configure::~menu_machine_configure() { - if (m_fav_reset) - reset_topmost(reset_options::SELECT_FIRST); + if (m_was_favorite != m_want_favorite) + { + if (m_want_favorite) + mame_machine_manager::instance()->favorite().add_favorite_system(m_drv); + else + mame_machine_manager::instance()->favorite().remove_favorite_system(m_drv); + } + + if (m_handler) + m_handler(m_want_favorite, m_was_favorite != m_want_favorite); } //------------------------------------------------- @@ -692,16 +707,16 @@ void menu_machine_configure::handle() { // process the menu process_parent(); - const event *menu_event = process(PROCESS_NOIMAGE, x0, y0); + const event *menu_event = process(PROCESS_NOIMAGE, m_x0, m_y0); if (menu_event != nullptr && menu_event->itemref != nullptr) { if (menu_event->iptkey == IPT_UI_SELECT) { switch ((uintptr_t)menu_event->itemref) { - case SAVE: + case SAVE: { - std::string filename(m_drv->name); + std::string filename(m_drv.name); emu_file file(machine().options().ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE); osd_file::error filerr = file.open(filename.c_str(), ".ini"); if (filerr == osd_file::error::NONE) @@ -710,36 +725,30 @@ void menu_machine_configure::handle() file.puts(inistring.c_str()); ui().popup_time(2, "%s", _("\n Configuration saved \n\n")); } - break; } - case ADDFAV: - mame_machine_manager::instance()->favorite().add_favorite_game(m_drv); - reset(reset_options::REMEMBER_POSITION); - break; - case DELFAV: - mame_machine_manager::instance()->favorite().remove_favorite_game(); - if (main_filters::actual == machine_filter::FAVORITE) - { - m_fav_reset = true; - menu::stack_pop(); - } - else - reset(reset_options::REMEMBER_POSITION); - break; - case CONTROLLER: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container(), submenu::control_options, m_drv, &m_opts); - break; - case VIDEO: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container(), submenu::video_options, m_drv, &m_opts); - break; - case ADVANCED: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container(), submenu::advanced_options, m_drv, &m_opts); - break; - default: - break; + break; + case ADDFAV: + m_want_favorite = true; + reset(reset_options::REMEMBER_POSITION); + break; + case DELFAV: + m_want_favorite = false; + reset(reset_options::REMEMBER_POSITION); + break; + case CONTROLLER: + if (menu_event->iptkey == IPT_UI_SELECT) + menu::stack_push(ui(), container(), submenu::control_options, &m_drv, &m_opts); + break; + case VIDEO: + if (menu_event->iptkey == IPT_UI_SELECT) + menu::stack_push(ui(), container(), submenu::video_options, &m_drv, &m_opts); + break; + case ADVANCED: + if (menu_event->iptkey == IPT_UI_SELECT) + menu::stack_push(ui(), container(), submenu::advanced_options, &m_drv, &m_opts); + break; + default: + break; } } else if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) @@ -773,7 +782,7 @@ void menu_machine_configure::populate(float &customtop, float &custombottom) item_append(_(submenu::control_options[0].description), "", 0, (void *)(uintptr_t)CONTROLLER); item_append(menu_item_type::SEPARATOR); - if (!mame_machine_manager::instance()->favorite().isgame_favorite(m_drv)) + if (!m_want_favorite) item_append(_("Add To Favorites"), "", 0, (void *)ADDFAV); else item_append(_("Remove From Favorites"), "", 0, (void *)DELFAV); @@ -790,7 +799,7 @@ void menu_machine_configure::populate(float &customtop, float &custombottom) void menu_machine_configure::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - char const *const text[] = { _("Configure machine:"), m_drv->type.fullname() }; + char const *const text[] = { _("Configure machine:"), m_drv.type.fullname() }; draw_text_box( std::begin(text), std::end(text), origx1, origx2, origy1 - top, origy1 - UI_BOX_TB_BORDER, @@ -800,19 +809,19 @@ void menu_machine_configure::custom_render(void *selectedref, float top, float b void menu_machine_configure::setup_bios() { - if (m_drv->rom == nullptr) + if (!m_drv.rom) return; std::string specbios(m_opts.bios()); char const *default_name(nullptr); - for (tiny_rom_entry const *rom = m_drv->rom; !ROMENTRY_ISEND(rom); ++rom) + for (tiny_rom_entry const *rom = m_drv.rom; !ROMENTRY_ISEND(rom); ++rom) { if (ROMENTRY_ISDEFAULT_BIOS(rom)) default_name = rom->name; } std::size_t bios_count = 0; - for (romload::system_bios const &bios : romload::entries(m_drv->rom).get_system_bioses()) + for (romload::system_bios const &bios : romload::entries(m_drv.rom).get_system_bioses()) { std::string name(bios.get_description()); u32 const bios_flags(bios.get_value()); diff --git a/src/frontend/mame/ui/miscmenu.h b/src/frontend/mame/ui/miscmenu.h index 65ef235de57..4df94c60a78 100644 --- a/src/frontend/mame/ui/miscmenu.h +++ b/src/frontend/mame/ui/miscmenu.h @@ -20,6 +20,7 @@ #include namespace ui { + class menu_keyboard_mode : public menu { public: @@ -134,7 +135,12 @@ private: class menu_machine_configure : public menu { public: - menu_machine_configure(mame_ui_manager &mui, render_container &container, const game_driver *prev, float x0 = 0.0f, float y0 = 0.0f); + menu_machine_configure( + mame_ui_manager &mui, + render_container &container, + game_driver const &drv, + std::function &&handler = nullptr, + float x0 = 0.0f, float y0 = 0.0f); virtual ~menu_machine_configure(); protected: @@ -158,13 +164,17 @@ private: virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; - const game_driver *m_drv; + void setup_bios(); + + std::function const m_handler; + game_driver const &m_drv; emu_options m_opts; - float x0, y0; + float const m_x0; + float const m_y0; s_bios m_bios; std::size_t m_curbios; - void setup_bios(); - bool m_fav_reset; + bool const m_was_favorite; + bool m_want_favorite; }; //------------------------------------------------- @@ -186,4 +196,4 @@ protected: } // namespace ui -#endif /* MAME_FRONTEND_UI_MISCMENU_H */ +#endif // MAME_FRONTEND_UI_MISCMENU_H diff --git a/src/frontend/mame/ui/optsmenu.cpp b/src/frontend/mame/ui/optsmenu.cpp index fd29c3329bc..4452fae9bce 100644 --- a/src/frontend/mame/ui/optsmenu.cpp +++ b/src/frontend/mame/ui/optsmenu.cpp @@ -30,182 +30,44 @@ namespace ui { // ctor //------------------------------------------------- -menu_game_options::menu_game_options(mame_ui_manager &mui, render_container &container) : menu(mui, container) +menu_simple_game_options::menu_simple_game_options( + mame_ui_manager &mui, + render_container &container, + std::function &&handler) + : menu(mui, container) + , m_handler(std::move(handler)) { - m_main = main_filters::actual; } //------------------------------------------------- // dtor //------------------------------------------------- -menu_game_options::~menu_game_options() +menu_simple_game_options::~menu_simple_game_options() { - main_filters::actual = m_main; - reset_topmost(reset_options::SELECT_FIRST); ui().save_ui_options(); - ui_globals::switch_image = true; + if (m_handler) + m_handler(); } //------------------------------------------------- // handle //------------------------------------------------- -void menu_game_options::handle() +void menu_simple_game_options::handle() { - bool changed = false; - // process the menu - const event *menu_event; - if (machine().options().ui() == emu_options::UI_SIMPLE) - { - menu_event = process(PROCESS_LR_REPEAT); - } - else - { - process_parent(); - menu_event = process(PROCESS_LR_REPEAT | PROCESS_NOIMAGE); - } - - if (menu_event != nullptr && menu_event->itemref != nullptr) - switch ((uintptr_t)menu_event->itemref) - { - case FILTER_MENU: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - (menu_event->iptkey == IPT_UI_RIGHT) ? ++m_main : --m_main; - changed = true; - } - else if (menu_event->iptkey == IPT_UI_SELECT) - { - std::vector s_sel(machine_filter::COUNT); - for (unsigned index = 0; index < s_sel.size(); ++index) - s_sel[index] = machine_filter::display_name(machine_filter::type(index)); - - menu::stack_push( - ui(), container(), std::move(s_sel), m_main, - [this] (int selection) - { - m_main = machine_filter::type(selection); - reset(reset_options::REMEMBER_REF); - }); - } - break; - case FILTER_ADJUST: - if (menu_event->iptkey == IPT_UI_LEFT) - { - changed = main_filters::filters.find(m_main)->second->adjust_left(); - } - else if (menu_event->iptkey == IPT_UI_RIGHT) - { - changed = main_filters::filters.find(m_main)->second->adjust_right(); - } - else if (menu_event->iptkey == IPT_UI_SELECT) - { - main_filters::filters.find(m_main)->second->show_ui( - ui(), - container(), - [this] (machine_filter &filter) - { - if (machine_filter::CUSTOM == filter.get_type()) - { - emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open("custom_", emulator_info::get_configname(), "_filter.ini") == osd_file::error::NONE) - { - filter.save_ini(file, 0); - file.close(); - } - } - reset(reset_options::REMEMBER_REF); - }); - } - break; - case CONF_DIR: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container()); - break; - case MISC_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push(ui(), container(), submenu::misc_options); - ui_globals::reset = true; - } - break; - case SOUND_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push(ui(), container()); - ui_globals::reset = true; - } - break; - case DISPLAY_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push(ui(), container(), submenu::video_options); - ui_globals::reset = true; - } - break; - case CUSTOM_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container()); - break; - case CONTROLLER_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container(), submenu::control_options); - break; - case CGI_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push(ui(), container()); - break; - case ADVANCED_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push(ui(), container(), submenu::advanced_options); - ui_globals::reset = true; - } - break; - case SAVE_CONFIG: - if (menu_event->iptkey == IPT_UI_SELECT) - ui().save_main_option(); - break; - } - - if (changed) - reset(reset_options::REMEMBER_REF); + event const *const menu_event(process(PROCESS_LR_REPEAT)); + if (menu_event && menu_event->itemref) + handle_item_event(*menu_event); } //------------------------------------------------- // populate //------------------------------------------------- -void menu_game_options::populate(float &customtop, float &custombottom) +void menu_simple_game_options::populate(float &customtop, float &custombottom) { - if (machine().options().ui() != emu_options::UI_SIMPLE) - { - // set filter arrow - std::string fbuff; - - // add filter item - uint32_t arrow_flags = get_arrow_flags(machine_filter::FIRST, machine_filter::LAST, m_main); - auto active_filter(main_filters::filters.find(m_main)); - if (main_filters::filters.end() == active_filter) - active_filter = main_filters::filters.emplace(m_main, machine_filter::create(m_main)).first; - item_append(_("Filter"), active_filter->second->display_name(), arrow_flags, (void *)(uintptr_t)FILTER_MENU); - - // add subitem if the filter wants it - if (active_filter->second->wants_adjuster()) - { - std::string name("^!"); - convert_command_glyph(name); - item_append(name, active_filter->second->adjust_text(), active_filter->second->arrow_flags(), (void *)(FILTER_ADJUST)); - } - - item_append(menu_item_type::SEPARATOR); - - // add options items - item_append(_("Customize UI"), "", 0, (void *)(uintptr_t)CUSTOM_MENU); - item_append(_("Configure Directories"), "", 0, (void *)(uintptr_t)CONF_DIR); - } item_append(_(submenu::video_options[0].description), "", 0, (void *)(uintptr_t)DISPLAY_MENU); item_append(_("Sound Options"), "", 0, (void *)(uintptr_t)SOUND_MENU); item_append(_(submenu::misc_options[0].description), "", 0, (void *)(uintptr_t)MISC_MENU); @@ -219,11 +81,62 @@ void menu_game_options::populate(float &customtop, float &custombottom) customtop = ui().get_line_height() + 3.0f * UI_BOX_TB_BORDER; } +//------------------------------------------------- +// handle item +//------------------------------------------------- + +void menu_simple_game_options::handle_item_event(event const &menu_event) +{ + switch ((uintptr_t)menu_event.itemref) + { + case MISC_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + { + menu::stack_push(ui(), container(), submenu::misc_options); + ui_globals::reset = true; + } + break; + case SOUND_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + { + menu::stack_push(ui(), container()); + ui_globals::reset = true; + } + break; + case DISPLAY_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + { + menu::stack_push(ui(), container(), submenu::video_options); + ui_globals::reset = true; + } + break; + case CONTROLLER_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + menu::stack_push(ui(), container(), submenu::control_options); + break; + case CGI_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + menu::stack_push(ui(), container()); + break; + case ADVANCED_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + { + menu::stack_push(ui(), container(), submenu::advanced_options); + ui_globals::reset = true; + } + break; + case SAVE_CONFIG: + if (menu_event.iptkey == IPT_UI_SELECT) + ui().save_main_option(); + break; + } +} + //------------------------------------------------- // perform our special rendering //------------------------------------------------- -void menu_game_options::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_simple_game_options::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { char const *const toptext[] = { _("Settings") }; draw_text_box( @@ -234,4 +147,150 @@ void menu_game_options::custom_render(void *selectedref, float top, float bottom } +//------------------------------------------------- +// ctor +//------------------------------------------------- + +menu_game_options::menu_game_options( + mame_ui_manager &mui, + render_container &container, + machine_filter_data &filter_data, + std::function &&handler) + : menu_simple_game_options(mui, container, std::move(handler)) + , m_filter_data(filter_data) + , m_main_filter(filter_data.get_current_filter_type()) +{ +} + +//------------------------------------------------- +// dtor +//------------------------------------------------- + +menu_game_options::~menu_game_options() +{ + m_filter_data.set_current_filter_type(m_main_filter); +} + +//------------------------------------------------- +// handle +//------------------------------------------------- + +void menu_game_options::handle() +{ + // process the menu + process_parent(); + event const *const menu_event(process(PROCESS_LR_REPEAT | PROCESS_NOIMAGE)); + if (menu_event && menu_event->itemref) + handle_item_event(*menu_event); +} + +//------------------------------------------------- +// populate +//------------------------------------------------- + +void menu_game_options::populate(float &customtop, float &custombottom) +{ + // set filter arrow + std::string fbuff; + + // add filter item + uint32_t arrow_flags = get_arrow_flags(machine_filter::FIRST, machine_filter::LAST, m_main_filter); + machine_filter &active_filter(m_filter_data.get_filter(m_main_filter)); + item_append(_("Filter"), active_filter.display_name(), arrow_flags, (void *)(uintptr_t)FILTER_MENU); + + // add subitem if the filter wants it + if (active_filter.wants_adjuster()) + { + std::string name("^!"); + convert_command_glyph(name); + item_append(name, active_filter.adjust_text(), active_filter.arrow_flags(), (void *)(FILTER_ADJUST)); + } + + item_append(menu_item_type::SEPARATOR); + + // add options items + item_append(_("Customize UI"), "", 0, (void *)(uintptr_t)CUSTOM_MENU); + item_append(_("Configure Directories"), "", 0, (void *)(uintptr_t)CONF_DIR); + + // add the options that don't relate to the UI + menu_simple_game_options::populate(customtop, custombottom); +} + +//------------------------------------------------- +// handle item +//------------------------------------------------- + +void menu_game_options::handle_item_event(event const &menu_event) +{ + bool changed = false; + + switch ((uintptr_t)menu_event.itemref) + { + case FILTER_MENU: + if (menu_event.iptkey == IPT_UI_LEFT || menu_event.iptkey == IPT_UI_RIGHT) + { + (menu_event.iptkey == IPT_UI_RIGHT) ? ++m_main_filter : --m_main_filter; + changed = true; + } + else if (menu_event.iptkey == IPT_UI_SELECT) + { + std::vector s_sel(machine_filter::COUNT); + for (unsigned index = 0; index < s_sel.size(); ++index) + s_sel[index] = machine_filter::display_name(machine_filter::type(index)); + + menu::stack_push( + ui(), container(), std::move(s_sel), m_main_filter, + [this] (int selection) + { + m_main_filter = machine_filter::type(selection); + reset(reset_options::REMEMBER_REF); + }); + } + break; + case FILTER_ADJUST: + if (menu_event.iptkey == IPT_UI_LEFT) + { + changed = m_filter_data.get_filter(m_main_filter).adjust_left(); + } + else if (menu_event.iptkey == IPT_UI_RIGHT) + { + changed = m_filter_data.get_filter(m_main_filter).adjust_right(); + } + else if (menu_event.iptkey == IPT_UI_SELECT) + { + m_filter_data.get_filter(m_main_filter).show_ui( + ui(), + container(), + [this] (machine_filter &filter) + { + if (machine_filter::CUSTOM == filter.get_type()) + { + emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (file.open("custom_", emulator_info::get_configname(), "_filter.ini") == osd_file::error::NONE) + { + filter.save_ini(file, 0); + file.close(); + } + } + reset(reset_options::REMEMBER_REF); + }); + } + break; + case CONF_DIR: + if (menu_event.iptkey == IPT_UI_SELECT) + menu::stack_push(ui(), container()); + break; + case CUSTOM_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + menu::stack_push(ui(), container()); + break; + default: + menu_simple_game_options::handle_item_event(menu_event); + return; + } + + if (changed) + reset(reset_options::REMEMBER_REF); +} + } // namespace ui diff --git a/src/frontend/mame/ui/optsmenu.h b/src/frontend/mame/ui/optsmenu.h index e9650e3a95b..2afe9b68a89 100644 --- a/src/frontend/mame/ui/optsmenu.h +++ b/src/frontend/mame/ui/optsmenu.h @@ -17,23 +17,27 @@ namespace ui { -class menu_game_options : public menu + +class menu_simple_game_options : public menu { public: - menu_game_options(mame_ui_manager &mui, render_container &container); - virtual ~menu_game_options() override; + menu_simple_game_options( + mame_ui_manager &mui, + render_container &container, + std::function &&handler); + virtual ~menu_simple_game_options() override; protected: virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void handle() override; + virtual void populate(float &customtop, float &custombottom) override; + + void handle_item_event(event const &menu_event); private: enum { - FILTER_MENU = 1, - FILTER_ADJUST, - CONF_DIR, - DISPLAY_MENU, - CUSTOM_MENU, + DISPLAY_MENU = 1001, SOUND_MENU, CONTROLLER_MENU, MISC_MENU, @@ -43,12 +47,39 @@ private: SAVE_CONFIG }; - virtual void populate(float &customtop, float &custombottom) override; + std::function const m_handler; +}; + + +class menu_game_options : public menu_simple_game_options +{ +public: + menu_game_options( + mame_ui_manager &mui, + render_container &container, + machine_filter_data &filter_data, + std::function &&handler); + virtual ~menu_game_options() override; + +protected: virtual void handle() override; + virtual void populate(float &customtop, float &custombottom) override; + + void handle_item_event(event const &menu_event); + +private: + enum + { + FILTER_MENU = 2001, + FILTER_ADJUST, + CONF_DIR, + CUSTOM_MENU + }; - machine_filter::type m_main; + machine_filter_data &m_filter_data; + machine_filter::type m_main_filter; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_OPTSMENU_H */ +#endif // MAME_FRONTEND_UI_OPTSMENU_H diff --git a/src/frontend/mame/ui/selector.cpp b/src/frontend/mame/ui/selector.cpp index 59aa51f9d13..83738641142 100644 --- a/src/frontend/mame/ui/selector.cpp +++ b/src/frontend/mame/ui/selector.cpp @@ -60,7 +60,6 @@ void menu_selector::handle() m_handler(selection); - ui_globals::switch_image = true; stack_pop(); } else if (menu_event->iptkey == IPT_SPECIAL) @@ -136,12 +135,17 @@ void menu_selector::custom_render(void *selectedref, float top, float bottom, fl void menu_selector::find_matches(const char *str) { // allocate memory to track the penalty value - std::vector penalty(VISIBLE_GAMES_IN_SEARCH, 9999); - int index = 0; + m_ucs_items.reserve(m_str_items.size()); + std::vector penalty(VISIBLE_GAMES_IN_SEARCH, 1.0); + std::u32string const search(ustr_from_utf8(normalize_unicode(str, unicode_normalization_form::D, true))); - for (; index < m_str_items.size(); ++index) + int index = 0; + for ( ; index < m_str_items.size(); ++index) { - int curpenalty = fuzzy_substring(str, m_str_items[index]); + assert(m_ucs_items.size() >= index); + if (m_ucs_items.size() == index) + m_ucs_items.emplace_back(ustr_from_utf8(normalize_unicode(m_str_items[index], unicode_normalization_form::D, true))); + double const curpenalty(util::edit_distance(search, m_ucs_items[index])); // insert into the sorted table of matches for (int matchnum = VISIBLE_GAMES_IN_SEARCH - 1; matchnum >= 0; --matchnum) diff --git a/src/frontend/mame/ui/selector.h b/src/frontend/mame/ui/selector.h index bd6a87c4ef9..f3921536267 100644 --- a/src/frontend/mame/ui/selector.h +++ b/src/frontend/mame/ui/selector.h @@ -7,14 +7,15 @@ Internal UI user interface. ***************************************************************************/ +#ifndef MAME_FRONTEND_UI_SELECTOR_H +#define MAME_FRONTEND_UI_SELECTOR_H #pragma once -#ifndef MAME_FRONTEND_UI_SELECTOR_H -#define MAME_FRONTEND_UI_SELECTOR_H #include "ui/menu.h" + namespace ui { //------------------------------------------------- @@ -44,11 +45,12 @@ private: void find_matches(const char *str); - std::string m_search; - std::vector m_str_items; - std::function m_handler; - int m_initial; - std::string *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; + std::string m_search; + std::vector m_str_items; + std::function m_handler; + std::vector m_ucs_items; + int m_initial; + std::string *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; }; } // namespace ui diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp index 0b47fa1d7d1..def4ceaa5f9 100644 --- a/src/frontend/mame/ui/selgame.cpp +++ b/src/frontend/mame/ui/selgame.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /********************************************************************* ui/selgame.cpp @@ -12,6 +12,7 @@ #include "ui/selgame.h" #include "ui/auditmenu.h" +#include "ui/icorender.h" #include "ui/inifile.h" #include "ui/miscmenu.h" #include "ui/optsmenu.h" @@ -31,17 +32,190 @@ #include "uiinput.h" #include "luaengine.h" +#include +#include #include #include +#include +#include +#include extern const char UI_VERSION_TAG[]; namespace ui { -bool menu_select_game::first_start = true; -std::vector menu_select_game::m_sortedlist; -int menu_select_game::m_isabios = 0; +namespace { + +constexpr uint32_t FLAGS_UI = ui::menu::FLAG_LEFT_ARROW | ui::menu::FLAG_RIGHT_ARROW; + +} // anonymous namespace + +class menu_select_game::persistent_data +{ +public: + enum available : unsigned + { + AVAIL_NONE = 0U, + AVAIL_SORTED_LIST = 1U << 0, + AVAIL_BIOS_COUNT = 1U << 1, + AVAIL_UCS_SHORTNAME = 1U << 2, + AVAIL_UCS_DESCRIPTION = 1U << 3, + AVAIL_UCS_MANUF_DESC = 1U << 4, + AVAIL_FILTER_DATA = 1U << 5 + }; + + ~persistent_data() + { + if (m_thread) + m_thread->join(); + } + + void cache_data() + { + std::unique_lock lock(m_mutex); + do_start_caching(); + } + + bool is_available(available desired) + { + return (m_available.load(std::memory_order_acquire) & desired) == desired; + } + + void wait_available(available desired) + { + if (!is_available(desired)) + { + std::unique_lock lock(m_mutex); + do_start_caching(); + m_condition.wait(lock, [this, desired] () { return is_available(desired); }); + } + } + + std::vector &sorted_list() + { + wait_available(AVAIL_SORTED_LIST); + return m_sorted_list; + } + + int bios_count() + { + wait_available(AVAIL_BIOS_COUNT); + return m_bios_count; + } + + bool unavailable_systems() + { + wait_available(AVAIL_SORTED_LIST); + return std::find_if(m_sorted_list.begin(), m_sorted_list.end(), [] (ui_system_info const &info) { return !info.available; }) != m_sorted_list.end(); + } + + machine_filter_data &filter_data() + { + wait_available(AVAIL_FILTER_DATA); + return m_filter_data; + } + + static persistent_data &instance() + { + static persistent_data data; + return data; + } + +private: + persistent_data() + : m_started(false) + , m_available(AVAIL_NONE) + , m_bios_count(0) + { + } + + void notify_available(available value) + { + std::unique_lock lock(m_mutex); + m_available.fetch_or(value, std::memory_order_release); + m_condition.notify_all(); + } + + void do_start_caching() + { + if (!m_started) + { + m_started = true; + m_thread = std::make_unique([this] { do_cache_data(); }); + } + } + + void do_cache_data() + { + // generate full list + m_sorted_list.reserve(driver_list::total()); + std::unordered_set manufacturers, years; + for (int x = 0; x < driver_list::total(); ++x) + { + game_driver const &driver(driver_list::driver(x)); + if (&driver != &GAME_NAME(___empty)) + { + if (driver.flags & machine_flags::IS_BIOS_ROOT) + ++m_bios_count; + + m_sorted_list.emplace_back(driver, x, false); + m_filter_data.add_manufacturer(driver.manufacturer); + m_filter_data.add_year(driver.year); + } + } + + // notify that BIOS count is valie + notify_available(AVAIL_BIOS_COUNT); + + // sort drivers and notify + std::stable_sort( + m_sorted_list.begin(), + m_sorted_list.end(), + [] (ui_system_info const &lhs, ui_system_info const &rhs) { return sorted_game_list(lhs.driver, rhs.driver); }); + notify_available(AVAIL_SORTED_LIST); + + // sort manufacturers and years + m_filter_data.finalise(); + notify_available(AVAIL_FILTER_DATA); + + // convert shortnames to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_shortname = ustr_from_utf8(normalize_unicode(info.driver->name, unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_SHORTNAME); + + // convert descriptions to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_description = ustr_from_utf8(normalize_unicode(info.driver->type.fullname(), unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_DESCRIPTION); + + // convert " " to UCS-4 + std::string buf; + for (ui_system_info &info : m_sorted_list) + { + buf.assign(info.driver->manufacturer); + buf.append(1, ' '); + buf.append(info.driver->type.fullname()); + info.ucs_manufacturer_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); + } + notify_available(AVAIL_UCS_MANUF_DESC); + } + + // synchronisation + std::mutex m_mutex; + std::condition_variable m_condition; + std::unique_ptr m_thread; + std::atomic m_started; + std::atomic m_available; + + // data + std::vector m_sorted_list; + machine_filter_data m_filter_data; + int m_bios_count; +}; + +bool menu_select_game::s_first_start = true; + //------------------------------------------------- // ctor @@ -49,33 +223,30 @@ int menu_select_game::m_isabios = 0; menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &container, const char *gamename) : menu_select_launch(mui, container, false) + , m_persistent_data(persistent_data::instance()) + , m_icons(MAX_ICONS_RENDER) + , m_icon_paths() + , m_displaylist() + , m_searchlist() + , m_searched_fields(persistent_data::AVAIL_NONE) + , m_populated_favorites(false) { std::string error_string, last_filter, sub_filter; ui_options &moptions = mui.options(); // load drivers cache - init_sorted_list(); + m_persistent_data.cache_data(); - // check if there are available icons - ui_globals::has_icons = false; - file_enumerator path(moptions.icons_directory()); - const osd::directory::entry *dir; - while ((dir = path.next()) != nullptr) - { - std::string src(dir->name); - if (src.find(".ico") != std::string::npos || src.find("icons") != std::string::npos) - { - ui_globals::has_icons = true; - break; - } - } + // check if there are available system icons + check_for_icons(nullptr); // build drivers list if (!load_available_machines()) build_available_list(); - if (first_start) + if (s_first_start) { + //s_first_start = false; TODO: why wansn't it ever clearing the first start flag? reselect_last::set_driver(moptions.last_used_machine()); ui_globals::rpanel = std::min(std::max(moptions.last_right_panel(), RP_FIRST), RP_LAST); @@ -96,32 +267,23 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta emu_file file(ui().options().ui_path(), OPEN_FLAG_READ); if (file.open_ram(fake_ini.c_str(), fake_ini.size()) == osd_file::error::NONE) { - machine_filter::ptr flt(machine_filter::create(file)); - if (flt) - { - main_filters::actual = flt->get_type(); - main_filters::filters.emplace(main_filters::actual, std::move(flt)); - } + m_persistent_data.filter_data().load_ini(file); file.close(); } } // do this after processing the last used filter setting so it overwrites the placeholder load_custom_filters(); - m_filter_highlight = main_filters::actual; + m_filter_highlight = m_persistent_data.filter_data().get_current_filter_type(); if (!moptions.remember_last()) reselect_last::reset(); mui.machine().options().set_value(OPTION_SNAPNAME, "%g/%i", OPTION_PRIORITY_CMDLINE); - ui_globals::curimage_view = FIRST_VIEW; ui_globals::curdats_view = 0; - ui_globals::switch_image = false; - ui_globals::default_image = true; ui_globals::panels_status = moptions.hide_panels(); ui_globals::curdats_total = 1; - m_searchlist[0] = nullptr; } //------------------------------------------------- @@ -140,17 +302,7 @@ menu_select_game::~menu_select_game() if (driver) last_driver = driver->name; - std::string filter; - auto const active_filter(main_filters::filters.find(main_filters::actual)); - if (main_filters::filters.end() != active_filter) - { - char const *val(active_filter->second->filter_text()); - filter = val ? util::string_format("%s,%s", active_filter->second->config_name(), val) : active_filter->second->config_name(); - } - else - { - filter = machine_filter::config_name(main_filters::actual); - } + std::string const filter(m_persistent_data.filter_data().get_config_string()); ui_options &mopt = ui().options(); mopt.set_value(OPTION_LAST_RIGHT_PANEL, ui_globals::rpanel, OPTION_PRIORITY_CMDLINE); @@ -178,13 +330,13 @@ void menu_select_game::handle() return; } - // if i have to reselect a software, force software list submenu + // if I have to select software, force software list submenu if (reselect_last::get()) { const game_driver *driver; const ui_software_info *software; get_selection(software, driver); - menu::stack_push(ui(), container(), driver); + menu::stack_push(ui(), container(), *driver); return; } @@ -241,7 +393,7 @@ void menu_select_game::handle() case IPT_UI_SELECT: if (get_focus() == focused_menu::MAIN) { - if (isfavorite()) + if (m_populated_favorites) inkey_select_favorite(menu_event); else inkey_select(menu_event); @@ -250,11 +402,12 @@ void menu_select_game::handle() case IPT_CUSTOM: // handle IPT_CUSTOM (mouse right click) - if (!isfavorite()) + if (!m_populated_favorites) { menu::stack_push( ui(), container(), - reinterpret_cast(m_prev_selected), + *reinterpret_cast(m_prev_selected), + nullptr, menu_event->mouse.x0, menu_event->mouse.y0); } else @@ -262,18 +415,21 @@ void menu_select_game::handle() ui_software_info *sw = reinterpret_cast(m_prev_selected); menu::stack_push( ui(), container(), - (const game_driver *)sw->driver, + *sw->driver, + [this, empty = sw->startempty] (bool fav, bool changed) + { + if (changed) + reset(empty ? reset_options::SELECT_FIRST : reset_options::REMEMBER_REF); + }, menu_event->mouse.x0, menu_event->mouse.y0); } break; case IPT_UI_LEFT: - if (ui_globals::rpanel == RP_IMAGES && ui_globals::curimage_view > FIRST_VIEW) + if (ui_globals::rpanel == RP_IMAGES) { // Images - ui_globals::curimage_view--; - ui_globals::switch_image = true; - ui_globals::default_image = false; + previous_image_view(); } else if (ui_globals::rpanel == RP_INFOS) { @@ -283,12 +439,10 @@ void menu_select_game::handle() break; case IPT_UI_RIGHT: - if (ui_globals::rpanel == RP_IMAGES && ui_globals::curimage_view < LAST_VIEW) + if (ui_globals::rpanel == RP_IMAGES) { // Images - ui_globals::curimage_view++; - ui_globals::switch_image = true; - ui_globals::default_image = false; + next_image_view(); } else if (ui_globals::rpanel == RP_INFOS) { @@ -301,17 +455,17 @@ void menu_select_game::handle() if (uintptr_t(menu_event->itemref) > skip_main_items) { favorite_manager &mfav(mame_machine_manager::instance()->favorite()); - if (!isfavorite()) + if (!m_populated_favorites) { game_driver const *const driver(reinterpret_cast(menu_event->itemref)); - if (!mfav.isgame_favorite(driver)) + if (!mfav.is_favorite_system(*driver)) { - mfav.add_favorite_game(driver); + mfav.add_favorite_system(*driver); machine().popmessage(_("%s\n added to favorites list."), driver->type.fullname()); } else { - mfav.remove_favorite_game(); + mfav.remove_favorite_system(*driver); machine().popmessage(_("%s\n removed from favorites list."), driver->type.fullname()); } } @@ -319,19 +473,19 @@ void menu_select_game::handle() { ui_software_info const *const swinfo(reinterpret_cast(menu_event->itemref)); machine().popmessage(_("%s\n removed from favorites list."), swinfo->longname); - mfav.remove_favorite_game(*swinfo); + mfav.remove_favorite_software(*swinfo); reset(reset_options::SELECT_FIRST); } } break; case IPT_UI_AUDIT_FAST: - if (std::find_if(m_availsortedlist.begin(), m_availsortedlist.end(), [] (ui_system_info const &info) { return !info.available; }) != m_availsortedlist.end()) - menu::stack_push(ui(), container(), m_availsortedlist, menu_audit::mode::FAST); + if (m_persistent_data.unavailable_systems()) + menu::stack_push(ui(), container(), m_persistent_data.sorted_list(), menu_audit::mode::FAST); break; case IPT_UI_AUDIT_ALL: - menu::stack_push(ui(), container(), m_availsortedlist, menu_audit::mode::ALL); + menu::stack_push(ui(), container(), m_persistent_data.sorted_list(), menu_audit::mode::ALL); break; } } @@ -348,98 +502,113 @@ void menu_select_game::handle() void menu_select_game::populate(float &customtop, float &custombottom) { - ui_globals::redraw_icon = true; - ui_globals::switch_image = true; + for (auto &icon : m_icons) // TODO: why is this here? maybe better on resize or setting change? + icon.second.texture.reset(); + + set_switch_image(); int old_item_selected = -1; - uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; if (!isfavorite()) { + m_populated_favorites = false; + m_displaylist.clear(); + machine_filter const *const flt(m_persistent_data.filter_data().get_current_filter()); + // if search is not empty, find approximate matches if (!m_search.empty()) { populate_search(); + if (flt) + { + for (auto it = m_searchlist.begin(); (m_searchlist.end() != it) && (MAX_VISIBLE_SEARCH > m_displaylist.size()); ++it) + { + if (flt->apply(it->second)) + m_displaylist.emplace_back(it->second); + } + } + else + { + std::transform( + m_searchlist.begin(), + std::next(m_searchlist.begin(), (std::min)(m_searchlist.size(), MAX_VISIBLE_SEARCH)), + std::back_inserter(m_displaylist), + [] (auto const &entry) { return entry.second; }); + } } else { - // reset search string - m_search.clear(); - m_displaylist.clear(); - // if filter is set on category, build category list - auto const it(main_filters::filters.find(main_filters::actual)); - if (main_filters::filters.end() == it) - m_displaylist = m_availsortedlist; + std::vector const &sorted(m_persistent_data.sorted_list()); + if (!flt) + std::copy(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist)); else - it->second->apply(m_availsortedlist.begin(), m_availsortedlist.end(), std::back_inserter(m_displaylist)); - - // iterate over entries - int curitem = 0; - for (ui_system_info const &elem : m_displaylist) - { - if (old_item_selected == -1 && elem.driver->name == reselect_last::driver()) - old_item_selected = curitem; + flt->apply(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist)); + } - bool cloneof = strcmp(elem.driver->parent, "0"); - if (cloneof) - { - int cx = driver_list::find(elem.driver->parent); - if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) - cloneof = false; - } + // iterate over entries + int curitem = 0; + for (ui_system_info const &elem : m_displaylist) + { + if (old_item_selected == -1 && elem.driver->name == reselect_last::driver()) + old_item_selected = curitem; - item_append(elem.driver->type.fullname(), "", (cloneof) ? (flags_ui | FLAG_INVERT) : flags_ui, (void *)elem.driver); - curitem++; + bool cloneof = strcmp(elem.driver->parent, "0"); + if (cloneof) + { + int cx = driver_list::find(elem.driver->parent); + if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) + cloneof = false; } + + item_append(elem.driver->type.fullname(), "", (cloneof) ? (FLAGS_UI | FLAG_INVERT) : FLAGS_UI, (void *)elem.driver); + curitem++; } } else { // populate favorites list + m_populated_favorites = true; m_search.clear(); - int curitem = 0; - - // iterate over entries - for (auto & favmap : mame_machine_manager::instance()->favorite().m_list) - { - auto flags = flags_ui | FLAG_UI_FAVORITE; - if (favmap.second.startempty == 1) - { - if (old_item_selected == -1 && favmap.second.shortname == reselect_last::driver()) - old_item_selected = curitem; - - bool cloneof = strcmp(favmap.second.driver->parent, "0"); - if (cloneof) + mame_machine_manager::instance()->favorite().apply_sorted( + [this, &old_item_selected, curitem = 0] (ui_software_info const &info) mutable { - int cx = driver_list::find(favmap.second.driver->parent); - if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) - cloneof = false; - } + if (info.startempty == 1) + { + if (old_item_selected == -1 && info.shortname == reselect_last::driver()) + old_item_selected = curitem; - item_append(favmap.second.longname, "", (cloneof) ? (flags | FLAG_INVERT) : flags, (void *)&favmap.second); - } - else - { - if (old_item_selected == -1 && favmap.second.shortname == reselect_last::driver()) - old_item_selected = curitem; - item_append(favmap.second.longname, favmap.second.devicetype, - favmap.second.parentname.empty() ? flags : (FLAG_INVERT | flags), (void *)&favmap.second); - } - curitem++; - } + bool cloneof = strcmp(info.driver->parent, "0"); + if (cloneof) + { + int cx = driver_list::find(info.driver->parent); + if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) + cloneof = false; + } + + item_append(info.longname, "", cloneof ? (FLAGS_UI | FLAG_INVERT) : FLAGS_UI, (void *)&info); + } + else + { + if (old_item_selected == -1 && info.shortname == reselect_last::driver()) + old_item_selected = curitem; + item_append(info.longname, info.devicetype, + info.parentname.empty() ? FLAGS_UI : (FLAG_INVERT | FLAGS_UI), (void *)&info); + } + curitem++; + }); } - item_append(menu_item_type::SEPARATOR, flags_ui); + item_append(menu_item_type::SEPARATOR, FLAGS_UI); // add special items if (stack_has_special_main_menu()) { - item_append(_("Configure Options"), "", flags_ui, (void *)(uintptr_t)CONF_OPTS); - item_append(_("Configure Machine"), "", flags_ui, (void *)(uintptr_t)CONF_MACHINE); + item_append(_("Configure Options"), "", FLAGS_UI, (void *)(uintptr_t)CONF_OPTS); + item_append(_("Configure Machine"), "", FLAGS_UI, (void *)(uintptr_t)CONF_MACHINE); skip_main_items = 2; if (machine().options().plugins()) { - item_append(_("Plugins"), "", flags_ui, (void *)(uintptr_t)CONF_PLUGINS); + item_append(_("Plugins"), "", FLAGS_UI, (void *)(uintptr_t)CONF_PLUGINS); skip_main_items++; } } @@ -487,11 +656,11 @@ void menu_select_game::build_available_list() // build a name for it for (src = dir->name; *src != 0 && *src != '.' && dst < &drivername[ARRAY_LENGTH(drivername) - 1]; ++src) - *dst++ = tolower((uint8_t) * src); + *dst++ = tolower(uint8_t(*src)); *dst = 0; - int drivnum = driver_list::find(drivername); - if (drivnum != -1 && !included[drivnum]) + int const drivnum = driver_list::find(drivername); + if (0 <= drivnum) included[drivnum] = true; } @@ -562,18 +731,9 @@ void menu_select_game::build_available_list() } } - // sort - m_availsortedlist.reserve(total); - for (std::size_t x = 0; total > x; ++x) - { - game_driver const &driver(driver_list::driver(x)); - if (&driver != &GAME_NAME(___empty)) - m_availsortedlist.emplace_back(driver, included[x]); - } - std::stable_sort( - m_availsortedlist.begin(), - m_availsortedlist.end(), - [] (ui_system_info const &a, ui_system_info const &b) { return sorted_game_list(a.driver, b.driver); }); + // copy into the persistent sorted list + for (ui_system_info &info : m_persistent_data.sorted_list()) + info.available = included[info.index]; } @@ -609,13 +769,17 @@ void menu_select_game::inkey_select(const event *menu_event) if ((uintptr_t)driver == CONF_OPTS) { // special case for configure options - menu::stack_push(ui(), container()); + menu::stack_push( + ui(), + container(), + m_persistent_data.filter_data(), + [this] () { reset(reset_options::SELECT_FIRST); }); } else if (uintptr_t(driver) == CONF_MACHINE) { // special case for configure machine if (m_prev_selected) - menu::stack_push(ui(), container(), reinterpret_cast(m_prev_selected)); + menu::stack_push(ui(), container(), *reinterpret_cast(m_prev_selected)); return; } else if ((uintptr_t)driver == CONF_PLUGINS) @@ -640,7 +804,7 @@ void menu_select_game::inkey_select(const event *menu_event) { if (!swlistdev.get_info().empty()) { - menu::stack_push(ui(), container(), driver); + menu::stack_push(ui(), container(), *driver); return; } } @@ -667,7 +831,11 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) if ((uintptr_t)ui_swinfo == CONF_OPTS) { // special case for configure options - menu::stack_push(ui(), container()); + menu::stack_push( + ui(), + container(), + m_persistent_data.filter_data(), + [this] () { reset(reset_options::SELECT_FIRST); }); } else if ((uintptr_t)ui_swinfo == CONF_MACHINE) { @@ -675,7 +843,15 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) if (m_prev_selected) { ui_software_info *swinfo = reinterpret_cast(m_prev_selected); - menu::stack_push(ui(), container(), (const game_driver *)swinfo->driver); + menu::stack_push( + ui(), + container(), + *swinfo->driver, + [this, empty = swinfo->startempty] (bool fav, bool changed) + { + if (changed) + reset(empty ? reset_options::SELECT_FIRST : reset_options::REMEMBER_REF); + }); } return; } @@ -698,7 +874,7 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) { if (!swlistdev.get_info().empty()) { - menu::stack_push(ui(), container(), ui_swinfo->driver); + menu::stack_push(ui(), container(), *ui_swinfo->driver); return; } } @@ -746,7 +922,7 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) bool menu_select_game::isfavorite() const { - return machine_filter::FAVORITE == main_filters::actual; + return machine_filter::FAVORITE == m_persistent_data.filter_data().get_current_filter_type(); } @@ -771,7 +947,7 @@ void menu_select_game::change_info_pane(int delta) game_driver const *drv; ui_software_info const *soft; get_selection(soft, drv); - if (!isfavorite()) + if (!m_populated_favorites) { if (uintptr_t(drv) > skip_main_items) cap_delta(ui_globals::curdats_view, ui_globals::curdats_total); @@ -791,49 +967,59 @@ void menu_select_game::change_info_pane(int delta) void menu_select_game::populate_search() { - // allocate memory to track the penalty value - std::vector penalty(VISIBLE_GAMES_IN_SEARCH, 9999); - int index = 0; - for (; index < m_displaylist.size(); ++index) + // ensure search list is populated + if (m_searchlist.empty()) { - // pick the best match between driver name and description - int curpenalty = fuzzy_substring(m_search, m_displaylist[index].driver->type.fullname()); - int tmp = fuzzy_substring(m_search, m_displaylist[index].driver->name); - curpenalty = std::min(curpenalty, tmp); + std::vector const &sorted(m_persistent_data.sorted_list()); + m_searchlist.reserve(sorted.size()); + for (ui_system_info const &info : sorted) + m_searchlist.emplace_back(1.0, std::ref(info)); + } - // insert into the sorted table of matches - for (int matchnum = VISIBLE_GAMES_IN_SEARCH - 1; matchnum >= 0; --matchnum) - { - // stop if we're worse than the current entry - if (curpenalty >= penalty[matchnum]) - break; + // keep track of what we matched against + const std::u32string ucs_search(ustr_from_utf8(normalize_unicode(m_search, unicode_normalization_form::D, true))); + + // match shortnames + if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_SHORTNAME)) + { + m_searched_fields |= persistent_data::AVAIL_UCS_SHORTNAME; + for (std::pair > &info : m_searchlist) + info.first = util::edit_distance(ucs_search, info.second.get().ucs_shortname); + } - // as long as this isn't the last entry, bump this one down - if (matchnum < VISIBLE_GAMES_IN_SEARCH - 1) + // match descriptions + if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_DESCRIPTION)) + { + m_searched_fields |= persistent_data::AVAIL_UCS_DESCRIPTION; + for (std::pair > &info : m_searchlist) + { + if (info.first) { - penalty[matchnum + 1] = penalty[matchnum]; - m_searchlist[matchnum + 1] = m_searchlist[matchnum]; + double const penalty(util::edit_distance(ucs_search, info.second.get().ucs_description)); + info.first = (std::min)(penalty, info.first); } - - m_searchlist[matchnum] = m_displaylist[index].driver; - penalty[matchnum] = curpenalty; } } - (index < VISIBLE_GAMES_IN_SEARCH) ? m_searchlist[index] = nullptr : m_searchlist[VISIBLE_GAMES_IN_SEARCH] = nullptr; - uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; - for (int curitem = 0; m_searchlist[curitem]; ++curitem) + // match " " + if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_MANUF_DESC)) { - bool cloneof = strcmp(m_searchlist[curitem]->parent, "0"); - if (cloneof) + m_searched_fields |= persistent_data::AVAIL_UCS_MANUF_DESC; + for (std::pair > &info : m_searchlist) { - int cx = driver_list::find(m_searchlist[curitem]->parent); - if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) - cloneof = false; + if (info.first) + { + double const penalty(util::edit_distance(ucs_search, info.second.get().ucs_manufacturer_description)); + info.first = (std::min)(penalty, info.first); + } } - item_append(m_searchlist[curitem]->type.fullname(), "", (!cloneof) ? flags_ui : (FLAG_INVERT | flags_ui), - (void *)m_searchlist[curitem]); } + + // sort according to edit distance + std::stable_sort( + m_searchlist.begin(), + m_searchlist.end(), + [] (auto const &lhs, auto const &rhs) { return lhs.first < rhs.first; }); } //------------------------------------------------- @@ -889,6 +1075,21 @@ void menu_select_game::general_info(const game_driver *driver, std::string &buff else str << _("Sound\tOK\n"); + if (flags.unemulated_features() & device_t::feature::CAPTURE) + str << _("Capture\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::CAPTURE) + str << _("Capture\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::CAMERA) + str << _("Camera\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::CAMERA) + str << _("Camera\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::MICROPHONE) + str << _("Microphone\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::MICROPHONE) + str << _("Microphone\tImperfect\n"); + if (flags.unemulated_features() & device_t::feature::CONTROLS) str << _("Controls\tUnimplemented\n"); else if (flags.imperfect_features() & device_t::feature::CONTROLS) @@ -904,15 +1105,10 @@ void menu_select_game::general_info(const game_driver *driver, std::string &buff else if (flags.imperfect_features() & device_t::feature::MOUSE) str << _("Mouse\tImperfect\n"); - if (flags.unemulated_features() & device_t::feature::MICROPHONE) - str << _("Microphone\tUnimplemented\n"); - else if (flags.imperfect_features() & device_t::feature::MICROPHONE) - str << _("Microphone\tImperfect\n"); - - if (flags.unemulated_features() & device_t::feature::CAMERA) - str << _("Camera\tUnimplemented\n"); - else if (flags.imperfect_features() & device_t::feature::CAMERA) - str << _("Camera\tImperfect\n"); + if (flags.unemulated_features() & device_t::feature::MEDIA) + str << _("Media\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::MEDIA) + str << _("Media\tImperfect\n"); if (flags.unemulated_features() & device_t::feature::DISK) str << _("Disk\tUnimplemented\n"); @@ -924,6 +1120,31 @@ void menu_select_game::general_info(const game_driver *driver, std::string &buff else if (flags.imperfect_features() & device_t::feature::PRINTER) str << _("Printer\tImperfect\n"); + if (flags.unemulated_features() & device_t::feature::TAPE) + str << _("Mag. Tape\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::TAPE) + str << _("Mag. Tape\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::PUNCH) + str << _("Punch Tape\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::PUNCH) + str << _("Punch Tape\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::DRUM) + str << _("Mag. Drum\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::DRUM) + str << _("Mag. Drum\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::ROM) + str << _("(EP)ROM\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::ROM) + str << _("(EP)ROM\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::COMMS) + str << _("Communications\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::COMMS) + str << _("Communications\tImperfect\n"); + if (flags.unemulated_features() & device_t::feature::LAN) str << _("LAN\tUnimplemented\n"); else if (flags.imperfect_features() & device_t::feature::LAN) @@ -987,71 +1208,86 @@ void menu_select_game::general_info(const game_driver *driver, std::string &buff buffer = str.str(); } -void menu_select_game::inkey_export() + +//------------------------------------------------- +// get (possibly cached) icon texture +//------------------------------------------------- + +render_texture *menu_select_game::get_icon_texture(int linenum, void *selectedref) { - std::vector list; - if (!m_search.empty()) - { - for (int curitem = 0; m_searchlist[curitem]; ++curitem) - list.push_back(m_searchlist[curitem]); - } - else + game_driver const *const driver(m_populated_favorites + ? reinterpret_cast(selectedref)->driver + : reinterpret_cast(selectedref)); + assert(driver); + + icon_cache::iterator icon(m_icons.find(driver)); + if ((m_icons.end() == icon) || !icon->second.texture) { - if (isfavorite()) + if (m_icon_paths.empty()) + m_icon_paths = make_icon_paths(nullptr); + + // allocate an entry or allocate a texture on forced redraw + if (m_icons.end() == icon) { - // iterate over favorites - for (auto & favmap : mame_machine_manager::instance()->favorite().m_list) - { - if (favmap.second.startempty == 1) - list.push_back(favmap.second.driver); - else - return; - } + icon = m_icons.emplace(driver, texture_ptr(machine().render().texture_alloc(), machine().render())).first; } else { - list.reserve(m_displaylist.size()); - for (ui_system_info const &info : m_displaylist) - list.emplace_back(info.driver); + assert(!icon->second.texture); + icon->second.texture.reset(machine().render().texture_alloc()); } + + // set clone status + bool cloneof = strcmp(driver->parent, "0"); + if (cloneof) + { + auto cx = driver_list::find(driver->parent); + if ((cx >= 0) && (driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT)) + cloneof = false; + } + + bitmap_argb32 tmp; + emu_file snapfile(std::string(m_icon_paths), OPEN_FLAG_READ); + if (snapfile.open(std::string(driver->name), ".ico") == osd_file::error::NONE) + { + render_load_ico_highest_detail(snapfile, tmp); + snapfile.close(); + } + if (!tmp.valid() && cloneof && (snapfile.open(std::string(driver->parent), ".ico") == osd_file::error::NONE)) + { + render_load_ico_highest_detail(snapfile, tmp); + snapfile.close(); + } + + scale_icon(std::move(tmp), icon->second); } - menu::stack_push(ui(), container(), std::move(list)); + return icon->second.bitmap.valid() ? icon->second.texture.get() : nullptr; } -//------------------------------------------------- -// save drivers infos to file -//------------------------------------------------- -void menu_select_game::init_sorted_list() +void menu_select_game::inkey_export() { - if (!m_sortedlist.empty()) - return; - - // generate full list - std::unordered_set manufacturers, years; - for (int x = 0; x < driver_list::total(); ++x) + std::vector list; + if (m_populated_favorites) { - game_driver const &driver(driver_list::driver(x)); - if (&driver != &GAME_NAME(___empty)) - { - if (driver.flags & machine_flags::IS_BIOS_ROOT) - m_isabios++; - - m_sortedlist.push_back(&driver); - manufacturers.emplace(c_mnfct::getname(driver.manufacturer)); - years.emplace(driver.year); - } + // iterate over favorites + mame_machine_manager::instance()->favorite().apply( + [&list] (ui_software_info const &info) + { + assert(info.driver); + if (info.startempty) + list.push_back(info.driver); + }); + } + else + { + list.reserve(m_displaylist.size()); + for (ui_system_info const &info : m_displaylist) + list.emplace_back(info.driver); } - // sort manufacturers - years and driver - for (auto it = manufacturers.begin(); manufacturers.end() != it; it = manufacturers.erase(it)) - c_mnfct::ui.emplace_back(*it); - std::sort(c_mnfct::ui.begin(), c_mnfct::ui.end(), [] (std::string const &x, std::string const &y) { return 0 > core_stricmp(x.c_str(), y.c_str()); }); - for (auto it = years.begin(); years.end() != it; it = years.erase(it)) - c_year::ui.emplace_back(*it); - std::stable_sort(c_year::ui.begin(), c_year::ui.end()); - std::stable_sort(m_sortedlist.begin(), m_sortedlist.end(), sorted_game_list); + menu::stack_push(ui(), container(), std::move(list)); } //------------------------------------------------- @@ -1093,27 +1329,18 @@ bool menu_select_game::load_available_machines() else available.emplace(std::move(readbuf)); } + file.close(); // turn it into the sorted system list we all love - m_availsortedlist.reserve(driver_list::total()); - for (std::size_t x = 0; driver_list::total() > x; ++x) + for (ui_system_info &info : m_persistent_data.sorted_list()) { - game_driver const &driver(driver_list::driver(x)); - if (&driver != &GAME_NAME(___empty)) - { - std::unordered_set::iterator const it(available.find(&driver.name[0])); - bool const found(available.end() != it); - m_availsortedlist.emplace_back(driver, found); - if (found) - available.erase(it); - } + std::unordered_set::iterator const it(available.find(&info.driver->name[0])); + bool const found(available.end() != it); + info.available = found; + if (found) + available.erase(it); } - std::stable_sort( - m_availsortedlist.begin(), - m_availsortedlist.end(), - [] (ui_system_info const &a, ui_system_info const &b) { return sorted_game_list(a.driver, b.driver); }); - file.close(); return true; } @@ -1126,9 +1353,9 @@ void menu_select_game::load_custom_filters() emu_file file(ui().options().ui_path(), OPEN_FLAG_READ); if (file.open("custom_", emulator_info::get_configname(), "_filter.ini") == osd_file::error::NONE) { - machine_filter::ptr flt(machine_filter::create(file)); + machine_filter::ptr flt(machine_filter::create(file, m_persistent_data.filter_data())); if (flt) - main_filters::filters[flt->get_type()] = std::move(flt); // not emplace/insert - could replace bogus filter from ui.ini line + m_persistent_data.filter_data().set_filter(std::move(flt)); // not emplace/insert - could replace bogus filter from ui.ini line file.close(); } @@ -1141,7 +1368,8 @@ void menu_select_game::load_custom_filters() float menu_select_game::draw_left_panel(float x1, float y1, float x2, float y2) { - return menu_select_launch::draw_left_panel(main_filters::actual, main_filters::filters, x1, y1, x2, y2); + machine_filter_data &filter_data(m_persistent_data.filter_data()); + return menu_select_launch::draw_left_panel(filter_data.get_current_filter_type(), filter_data.get_filters(), x1, y1, x2, y2); } @@ -1151,7 +1379,7 @@ float menu_select_game::draw_left_panel(float x1, float y1, float x2, float y2) void menu_select_game::get_selection(ui_software_info const *&software, game_driver const *&driver) const { - if (item[0].flags & FLAG_UI_FAVORITE) // TODO: work out why this doesn't use isfavorite() + if (m_populated_favorites) { software = reinterpret_cast(get_selection_ptr()); driver = software ? software->driver : nullptr; @@ -1170,18 +1398,18 @@ void menu_select_game::make_topbox_text(std::string &line0, std::string &line1, bare_build_version, visible_items, (driver_list::total() - 1), - m_isabios); + m_persistent_data.bios_count()); - if (isfavorite()) + if (m_populated_favorites) { line1.clear(); } else { - auto const it(main_filters::filters.find(main_filters::actual)); - char const *const filter((main_filters::filters.end() != it) ? it->second->filter_text() : nullptr); + machine_filter const *const it(m_persistent_data.filter_data().get_current_filter()); + char const *const filter(it ? it->filter_text() : nullptr); if (filter) - line1 = string_format(_("%1$s: %2$s - Search: %3$s_"), it->second->display_name(), filter, m_search); + line1 = string_format(_("%1$s: %2$s - Search: %3$s_"), it->display_name(), filter, m_search); else line1 = string_format(_("Search: %1$s_"), m_search); } @@ -1208,15 +1436,12 @@ void menu_select_game::filter_selected() { if ((machine_filter::FIRST <= m_filter_highlight) && (machine_filter::LAST >= m_filter_highlight)) { - m_search.clear(); - auto it(main_filters::filters.find(machine_filter::type(m_filter_highlight))); - if (main_filters::filters.end() == it) - it = main_filters::filters.emplace(machine_filter::type(m_filter_highlight), machine_filter::create(machine_filter::type(m_filter_highlight))).first; - it->second->show_ui( + m_persistent_data.filter_data().get_filter(machine_filter::type(m_filter_highlight)).show_ui( ui(), container(), [this] (machine_filter &filter) { + set_switch_image(); machine_filter::type const new_type(filter.get_type()); if (machine_filter::CUSTOM == new_type) { @@ -1227,7 +1452,7 @@ void menu_select_game::filter_selected() file.close(); } } - main_filters::actual = new_type; + m_persistent_data.filter_data().set_current_filter_type(new_type); reset(reset_options::SELECT_FIRST); }); } diff --git a/src/frontend/mame/ui/selgame.h b/src/frontend/mame/ui/selgame.h index 5778b2c544c..da65fee98ce 100644 --- a/src/frontend/mame/ui/selgame.h +++ b/src/frontend/mame/ui/selgame.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /*************************************************************************** ui/selgame.h @@ -15,6 +15,8 @@ #include "ui/selmenu.h" #include "ui/utils.h" +#include + class media_auditor; @@ -37,21 +39,27 @@ private: CONF_PLUGINS, }; - enum { VISIBLE_GAMES_IN_SEARCH = 200 }; - static bool first_start; - static int m_isabios; + using icon_cache = texture_lru; + + class persistent_data; + + persistent_data &m_persistent_data; + icon_cache m_icons; + std::string m_icon_paths; + std::vector > m_displaylist; - static std::vector m_sortedlist; - std::vector m_availsortedlist; - std::vector m_displaylist; + std::vector > > m_searchlist; + unsigned m_searched_fields; + bool m_populated_favorites; - const game_driver *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; + static bool s_first_start; virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; - // draw left panel + // drawing virtual float draw_left_panel(float x1, float y1, float x2, float y2) override; + virtual render_texture *get_icon_texture(int linenum, void *selectedref) override; // get selected software and/or driver virtual void get_selection(ui_software_info const *&software, game_driver const *&driver) const override; @@ -75,7 +83,6 @@ private: bool isfavorite() const; void populate_search(); - void init_sorted_list(); bool load_available_machines(); void load_custom_filters(); diff --git a/src/frontend/mame/ui/selmenu.cpp b/src/frontend/mame/ui/selmenu.cpp index 90dd7af789f..306c2cad1f0 100644 --- a/src/frontend/mame/ui/selmenu.cpp +++ b/src/frontend/mame/ui/selmenu.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods +// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb /*************************************************************************** ui/selmenu.cpp @@ -12,7 +12,6 @@ #include "ui/selmenu.h" #include "ui/datmenu.h" -#include "ui/icorender.h" #include "ui/info.h" #include "ui/inifile.h" @@ -45,6 +44,28 @@ namespace ui { namespace { +enum +{ + FIRST_VIEW = 0, + SNAPSHOT_VIEW = FIRST_VIEW, + CABINETS_VIEW, + CPANELS_VIEW, + PCBS_VIEW, + FLYERS_VIEW, + TITLES_VIEW, + ENDS_VIEW, + ARTPREV_VIEW, + BOSSES_VIEW, + LOGOS_VIEW, + VERSUS_VIEW, + GAMEOVER_VIEW, + HOWTO_VIEW, + SCORES_VIEW, + SELECT_VIEW, + MARQUEES_VIEW, + LAST_VIEW = MARQUEES_VIEW +}; + std::pair const arts_info[] = { { __("Snapshots"), OPTION_SNAPSHOT_DIRECTORY }, @@ -74,6 +95,8 @@ char const *const hover_msg[] = { } // anonymous namespace +constexpr std::size_t menu_select_launch::MAX_VISIBLE_SEARCH; // stupid non-inline semantics + class menu_select_launch::software_parts : public menu { @@ -159,7 +182,7 @@ void menu_select_launch::reselect_last::set_software(game_driver const &driver, s_driver = driver.name; if (swinfo.startempty) { - // magic strings are bad... + // FIXME: magic strings are bad... s_software = "[Start empty]"; s_swlist.clear(); } @@ -352,27 +375,26 @@ void menu_select_launch::bios_selection::custom_render(void *selectedref, float menu_select_launch::cache::cache(running_machine &machine) : m_snapx_bitmap(std::make_unique(0, 0)) - , m_snapx_texture() + , m_snapx_texture(nullptr, machine.render()) , m_snapx_driver(nullptr) , m_snapx_software(nullptr) , m_no_avail_bitmap(256, 256) , m_star_bitmap(32, 32) - , m_star_texture() + , m_star_texture(nullptr, machine.render()) , m_toolbar_bitmap() , m_sw_toolbar_bitmap() , m_toolbar_texture() , m_sw_toolbar_texture() { render_manager &render(machine.render()); - auto const texture_free([&render](render_texture *texture) { render.texture_free(texture); }); // create a texture for snapshot - m_snapx_texture = texture_ptr(render.texture_alloc(render_texture::hq_scale), texture_free); + m_snapx_texture.reset(render.texture_alloc(render_texture::hq_scale)); std::memcpy(&m_no_avail_bitmap.pix32(0), no_avail_bmp, 256 * 256 * sizeof(uint32_t)); std::memcpy(&m_star_bitmap.pix32(0), favorite_star_bmp, 32 * 32 * sizeof(uint32_t)); - m_star_texture = texture_ptr(render.texture_alloc(), texture_free); + m_star_texture.reset(render.texture_alloc()); m_star_texture->set_bitmap(m_star_bitmap, m_star_bitmap.cliprect(), TEXFORMAT_ARGB32); m_toolbar_bitmap.reserve(UI_TOOLBAR_BUTTONS); @@ -384,8 +406,8 @@ menu_select_launch::cache::cache(running_machine &machine) { m_toolbar_bitmap.emplace_back(32, 32); m_sw_toolbar_bitmap.emplace_back(32, 32); - m_toolbar_texture.emplace_back(texture_ptr(render.texture_alloc(), texture_free)); - m_sw_toolbar_texture.emplace_back(texture_ptr(render.texture_alloc(), texture_free)); + m_toolbar_texture.emplace_back(render.texture_alloc(), render); + m_sw_toolbar_texture.emplace_back(render.texture_alloc(), render); std::memcpy(&m_toolbar_bitmap.back().pix32(0), toolbar_bitmap_bmp[i], 32 * 32 * sizeof(uint32_t)); if (m_toolbar_bitmap.back().valid()) @@ -437,8 +459,11 @@ menu_select_launch::menu_select_launch(mame_ui_manager &mui, render_container &c , m_pressed(false) , m_repeat(0) , m_right_visible_lines(0) + , m_has_icons(false) + , m_switch_image(false) + , m_default_image(true) + , m_image_view(FIRST_VIEW) , m_flags(256) - , m_icons(MAX_ICONS_RENDER) { // set up persistent cache for machine run { @@ -459,6 +484,28 @@ menu_select_launch::menu_select_launch(mame_ui_manager &mui, render_container &c } +void menu_select_launch::next_image_view() +{ + if (LAST_VIEW > m_image_view) + { + ++m_image_view; + set_switch_image(); + m_default_image = false; + } +} + + +void menu_select_launch::previous_image_view() +{ + if (FIRST_VIEW < m_image_view) + { + --m_image_view; + set_switch_image(); + m_default_image = false; + } +} + + bool menu_select_launch::dismiss_error() { bool const result = m_ui_error; @@ -562,7 +609,7 @@ void menu_select_launch::custom_render(void *selectedref, float top, float botto rgb_t color = UI_BACKGROUND_COLOR; if (swinfo && ((swinfo->startempty != 1) || !driver)) { - isstar = mame_machine_manager::instance()->favorite().isgame_favorite(*swinfo); + isstar = mame_machine_manager::instance()->favorite().is_favorite_system_software(*swinfo); // first line is long name or system tempbuf[0] = make_software_description(*swinfo); @@ -598,7 +645,7 @@ void menu_select_launch::custom_render(void *selectedref, float top, float botto } else if (driver) { - isstar = mame_machine_manager::instance()->favorite().isgame_favorite(driver); + isstar = mame_machine_manager::instance()->favorite().is_favorite_system(*driver); // first line is game description/game name tempbuf[0] = make_driver_description(*driver); @@ -944,6 +991,150 @@ float menu_select_launch::draw_left_panel( } +//------------------------------------------------- +// icon helpers +//------------------------------------------------- + +void menu_select_launch::check_for_icons(char const *listname) +{ + // only ever set the flag, never clear it + if (m_has_icons) + return; + + // iterate over configured icon paths + path_iterator paths(ui().options().icons_directory()); + std::string current; + while (paths.next(current)) + { + // if we're doing a software list, append it to the configured path + if (listname) + { + if (!current.empty() && !util::is_directory_separator(current.back())) + current.append(PATH_SEPARATOR); + current.append(listname); + } + osd_printf_verbose("Checking for icons in directory %s\n", current.c_str()); + + // open and walk the directory + osd::directory::ptr const dir(osd::directory::open(current)); + if (dir) + { + // this could be improved in many ways - it's just a rough go/no-go + osd::directory::entry const *entry; + while ((entry = dir->read()) != nullptr) + { + current = entry->name; + std::string::size_type const found(current.rfind(".ico")); + if ((std::string::npos != found) && ((current.length() - 4) == found)) + { + osd_printf_verbose("Entry %s is a candidate icon file\n", entry->name); + m_has_icons = true; + return; + } + else if (("icons" == current) || (current.find("icons.") == 0U)) + { + osd_printf_verbose("Entry %s is a candidate icon collection\n", entry->name); + m_has_icons = true; + return; + } + } + } + } + + // nothing promising + osd_printf_verbose( + "No candidate icons found for %s%s\n", + listname ? "software list " : "", + listname ? listname : "machines"); +} + +std::string menu_select_launch::make_icon_paths(char const *listname) const +{ + // iterate over configured icon paths + path_iterator paths(ui().options().icons_directory()); + std::string current, result; + while (paths.next(current)) + { + // if we're doing a software list, append it to the configured path + if (listname) + { + if (!current.empty() && !util::is_directory_separator(current.back())) + current.append(PATH_SEPARATOR); + current.append(listname); + } + + // append the configured path + if (!result.empty()) + result.append(1, ';'); // FIXME: should be a macro + result.append(current); + + // append with "icons" appended so it'll search icons.zip or icons.7z in the directory + if (!current.empty()) + { + result.append(1, ';'); // FIXME: should be a macro + result.append(current); + if (!util::is_directory_separator(result.back())) + result.append(PATH_SEPARATOR); + } + result.append("icons"); + } + + // log the result for debugging + osd_printf_verbose( + "Icon path for %s%s set to %s\n", + listname ? "software list " : "", + listname ? listname : "machines", + result.c_str()); + return result; +} + +bool menu_select_launch::scale_icon(bitmap_argb32 &&src, texture_and_bitmap &dst) const +{ + assert(dst.texture); + if (src.valid()) + { + // calculate available space for the icon in pixels + float const height(ui().get_line_height()); + float const width(height * container().manager().ui_aspect()); + render_target const &target(machine().render().ui_target()); + uint32_t const dst_height(target.height()); + uint32_t const dst_width(target.width()); + bool const rotated((target.orientation() & ORIENTATION_SWAP_XY) != 0); + int const max_height(int((rotated ? dst_width : dst_height) * height)); + int const max_width(int((rotated ? dst_height : dst_width) * width)); + + // reduce the source bitmap if it's too big + bitmap_argb32 tmp; + float const ratio((std::min)({ float(max_height) / src.height(), float(max_width) / src.width(), 1.0F })); + if (1.0F > ratio) + { + float const pix_height(src.height() * ratio); + float const pix_width(src.width() * ratio); + tmp.allocate(int32_t(pix_width), int32_t(pix_height)); + render_resample_argb_bitmap_hq(tmp, src, render_color{ 1.0F, 1.0F, 1.0F, 1.0F }, true); + } + else + { + tmp = std::move(src); + } + + // copy into the destination + dst.bitmap.allocate(max_width, max_height); + for (int y = 0; tmp.height() > y; ++y) + for (int x = 0; tmp.width() > x; ++x) + dst.bitmap.pix32(y, x) = tmp.pix32(y, x); + dst.texture->set_bitmap(dst.bitmap, dst.bitmap.cliprect(), TEXFORMAT_ARGB32); + return true; + } + else + { + // couldn't load icon + dst.bitmap.reset(); + return false; + } +} + + template bool menu_select_launch::select_bios(T const &driver, bool inlist) { s_bios biosname; @@ -1049,124 +1240,16 @@ void menu_select_launch::set_pressed() // draw icons //------------------------------------------------- -float menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, float y0) +void menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, float y0) { - if (!ui_globals::has_icons || m_is_swlist) - return 0.0f; - - float ud_arrow_width = ui().get_line_height() * container().manager().ui_aspect(&container()); - const game_driver *driver = nullptr; - - if (item[0].flags & FLAG_UI_FAVORITE) - { - ui_software_info *soft = (ui_software_info *)selectedref; - if (soft->startempty == 1) - driver = soft->driver; - } - else - driver = (const game_driver *)selectedref; - - auto x1 = x0 + ud_arrow_width; - auto y1 = y0 + ui().get_line_height(); - - icon_cache::iterator icon(m_icons.find(driver)); - if ((m_icons.end() == icon) || ui_globals::redraw_icon) - { - if (m_icons.end() == icon) - { - texture_ptr texture(machine().render().texture_alloc(), [&render = machine().render()] (render_texture *texture) { render.texture_free(texture); }); - icon = m_icons.emplace( - std::piecewise_construct, - std::forward_as_tuple(driver), - std::forward_as_tuple(std::piecewise_construct, std::forward_as_tuple(std::move(texture)), std::tuple<>())).first; - } - - // set clone status - bool cloneof = strcmp(driver->parent, "0"); - if (cloneof) - { - auto cx = driver_list::find(driver->parent); - if ((cx >= 0) && (driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT)) - cloneof = false; - } - - // get search path - path_iterator path(ui().options().icons_directory()); - std::string curpath; - std::string searchstr(ui().options().icons_directory()); - - // iterate over path and add path for zipped formats - while (path.next(curpath)) - searchstr.append(";").append(curpath.c_str()).append(PATH_SEPARATOR).append("icons"); - - bitmap_argb32 tmp; - emu_file snapfile(searchstr.c_str(), OPEN_FLAG_READ); - std::string fullname = std::string(driver->name).append(".ico"); - render_load_ico(tmp, snapfile, nullptr, fullname.c_str()); - - if (!tmp.valid() && cloneof) - { - fullname.assign(driver->parent).append(".ico"); - render_load_ico(tmp, snapfile, nullptr, fullname.c_str()); - } - - bitmap_argb32 &bitmap(icon->second.second); - if (tmp.valid()) - { - float panel_width = x1 - x0; - float panel_height = y1 - y0; - auto screen_width = machine().render().ui_target().width(); - auto screen_height = machine().render().ui_target().height(); - - if (machine().render().ui_target().orientation() & ORIENTATION_SWAP_XY) - std::swap(screen_height, screen_width); - - int panel_width_pixel = panel_width * screen_width; - int panel_height_pixel = panel_height * screen_height; - - // Calculate resize ratios for resizing - auto ratioW = (float)panel_width_pixel / tmp.width(); - auto ratioH = (float)panel_height_pixel / tmp.height(); - auto dest_xPixel = tmp.width(); - auto dest_yPixel = tmp.height(); - - if (ratioW < 1 || ratioH < 1) - { - // smaller ratio will ensure that the image fits in the view - float ratio = std::min(ratioW, ratioH); - dest_xPixel = tmp.width() * ratio; - dest_yPixel = tmp.height() * ratio; - } - - bitmap_argb32 dest_bitmap; - - // resample if necessary - if (dest_xPixel != tmp.width() || dest_yPixel != tmp.height()) - { - dest_bitmap.allocate(dest_xPixel, dest_yPixel); - render_color color = { 1.0f, 1.0f, 1.0f, 1.0f }; - render_resample_argb_bitmap_hq(dest_bitmap, tmp, color, true); - } - else - dest_bitmap = std::move(tmp); - - bitmap.allocate(panel_width_pixel, panel_height_pixel); - for (int x = 0; x < dest_xPixel; x++) - for (int y = 0; y < dest_yPixel; y++) - bitmap.pix32(y, x) = dest_bitmap.pix32(y, x); - - icon->second.first->set_bitmap(bitmap, bitmap.cliprect(), TEXFORMAT_ARGB32); - } - else - { - bitmap.reset(); - } - } - - if (icon->second.second.valid()) - container().add_quad(x0, y0, x1, y1, rgb_t::white(), icon->second.first.get(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - - return ud_arrow_width * 1.5f; + render_texture *const icon(get_icon_texture(linenum, selectedref)); + if (icon) + { + float const ud_arrow_width = ui().get_line_height() * container().manager().ui_aspect(&container()); + float const x1 = x0 + ud_arrow_width; + float const y1 = y0 + ui().get_line_height(); + container().add_quad(x0, y0, x1, y1, rgb_t::white(), icon, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + }; } @@ -1177,21 +1260,21 @@ float menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, fl void menu_select_launch::get_title_search(std::string &snaptext, std::string &searchstr) { // get arts title text - snaptext.assign(_(arts_info[ui_globals::curimage_view].first)); + snaptext.assign(_(arts_info[m_image_view].first)); // get search path std::string addpath; - if (ui_globals::curimage_view == SNAPSHOT_VIEW) + if (m_image_view == SNAPSHOT_VIEW) { emu_options moptions; - searchstr = machine().options().value(arts_info[ui_globals::curimage_view].second); - addpath = moptions.value(arts_info[ui_globals::curimage_view].second); + searchstr = machine().options().value(arts_info[m_image_view].second); + addpath = moptions.value(arts_info[m_image_view].second); } else { ui_options moptions; - searchstr = ui().options().value(arts_info[ui_globals::curimage_view].second); - addpath = moptions.value(arts_info[ui_globals::curimage_view].second); + searchstr = ui().options().value(arts_info[m_image_view].second); + addpath = moptions.value(arts_info[m_image_view].second); } std::string tmp(searchstr); @@ -1680,8 +1763,9 @@ void menu_select_launch::draw(uint32_t flags) { bool noinput = (flags & PROCESS_NOINPUT); float line_height = ui().get_line_height(); - float ud_arrow_width = line_height * machine().render().ui_aspect(); - float gutter_width = 0.52f * ud_arrow_width; + float const ud_arrow_width = line_height * machine().render().ui_aspect(); + float const gutter_width = 0.52f * ud_arrow_width; + float const icon_offset = m_has_icons ? (1.5f * ud_arrow_width) : 0.0f; float right_panel_size = (ui_globals::panels_status == HIDE_BOTH || ui_globals::panels_status == HIDE_RIGHT_PANEL) ? 2.0f * UI_BOX_LR_BORDER : 0.3f; float visible_width = 1.0f - 4.0f * UI_BOX_LR_BORDER; float primary_left = (1.0f - visible_width) * 0.5f; @@ -1766,18 +1850,21 @@ void menu_select_launch::draw(uint32_t flags) if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) hover = itemnum; - // if we're selected, draw with a different background if (is_selected(itemnum) && m_focus == focused_menu::MAIN) { + // if we're selected, draw with a different background fgcolor = rgb_t(0xff, 0xff, 0x00); bgcolor = rgb_t(0xff, 0xff, 0xff); fgcolor3 = rgb_t(0xcc, 0xcc, 0x00); - ui().draw_textured_box(container(), line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, bgcolor, rgb_t(43, 43, 43), + ui().draw_textured_box( + container(), + line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, + bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - // else if the mouse is over this item, draw with a different background else if (itemnum == hover) { + // else if the mouse is over this item, draw with a different background fgcolor = fgcolor3 = UI_MOUSEOVER_COLOR; bgcolor = UI_MOUSEOVER_BG_COLOR; highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); @@ -1817,29 +1904,52 @@ void menu_select_launch::draw(uint32_t flags) else if (pitem.subtext.empty()) { // draw the item centered - int item_invert = pitem.flags & FLAG_INVERT; - auto icon = draw_icon(linenum, item[itemnum].ref, effective_left, line_y); - ui().draw_text_full(container(), itemtext, effective_left + icon, line_y, effective_width - icon, ui::text_layout::LEFT, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr); + int const item_invert = pitem.flags & FLAG_INVERT; + if (m_has_icons) + draw_icon(linenum, item[itemnum].ref, effective_left, line_y); + ui().draw_text_full( + container(), + itemtext, + effective_left + icon_offset, line_y, effective_width - icon_offset, + ui::text_layout::LEFT, ui::text_layout::TRUNCATE, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + nullptr, nullptr); } else { - auto item_invert = pitem.flags & FLAG_INVERT; + int const item_invert = pitem.flags & FLAG_INVERT; const char *subitem_text = pitem.subtext.c_str(); float item_width, subitem_width; // compute right space for subitem - ui().draw_text_full(container(), subitem_text, effective_left, line_y, ui().get_string_width(pitem.subtext.c_str()), - ui::text_layout::RIGHT, ui::text_layout::NEVER, mame_ui_manager::NONE, item_invert ? fgcolor3 : fgcolor, bgcolor, &subitem_width, nullptr); + ui().draw_text_full( + container(), + subitem_text, + effective_left + icon_offset, line_y, ui().get_string_width(pitem.subtext.c_str()), + ui::text_layout::RIGHT, ui::text_layout::NEVER, + mame_ui_manager::NONE, item_invert ? fgcolor3 : fgcolor, bgcolor, + &subitem_width, nullptr); subitem_width += gutter_width; // draw the item left-justified - ui().draw_text_full(container(), itemtext, effective_left, line_y, effective_width - subitem_width, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, &item_width, nullptr); + if (m_has_icons) + draw_icon(linenum, item[itemnum].ref, effective_left, line_y); + ui().draw_text_full( + container(), + itemtext, + effective_left + icon_offset, line_y, effective_width - icon_offset - subitem_width, + ui::text_layout::LEFT, ui::text_layout::TRUNCATE, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + &item_width, nullptr); // draw the subitem right-justified - ui().draw_text_full(container(), subitem_text, effective_left + item_width, line_y, effective_width - item_width, - ui::text_layout::RIGHT, ui::text_layout::NEVER, mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr); + ui().draw_text_full( + container(), + subitem_text, + effective_left + icon_offset + item_width, line_y, effective_width - icon_offset - item_width, + ui::text_layout::RIGHT, ui::text_layout::NEVER, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + nullptr, nullptr); } } @@ -1900,9 +2010,6 @@ void menu_select_launch::draw(uint32_t flags) // return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow m_visible_items = m_visible_lines - (top_line != 0) - (top_line + m_visible_lines != visible_items); - // reset redraw icon stage - if (!m_is_swlist) ui_globals::redraw_icon = false; - // noinput if (noinput) { @@ -2044,14 +2151,14 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f { m_cache->set_snapx_driver(nullptr); - if (ui_globals::default_image) - ui_globals::curimage_view = (software->startempty == 0) ? SNAPSHOT_VIEW : CABINETS_VIEW; + if (m_default_image) + m_image_view = (software->startempty == 0) ? SNAPSHOT_VIEW : CABINETS_VIEW; // arts title and searchpath std::string const searchstr = arts_render_common(origx1, origy1, origx2, origy2); // loads the image if necessary - if (!m_cache->snapx_software_is(software) || !snapx_valid() || ui_globals::switch_image) + if (!m_cache->snapx_software_is(software) || !snapx_valid() || m_switch_image) { emu_file snapfile(searchstr.c_str(), OPEN_FLAG_READ); bitmap_argb32 tmp_bitmap; @@ -2097,7 +2204,7 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f } m_cache->set_snapx_software(software); - ui_globals::switch_image = false; + m_switch_image = false; arts_render_images(std::move(tmp_bitmap), origx1, origy1, origx2, origy2); } @@ -2108,13 +2215,13 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f { m_cache->set_snapx_software(nullptr); - if (ui_globals::default_image) - ui_globals::curimage_view = ((driver->flags & machine_flags::MASK_TYPE) != machine_flags::TYPE_ARCADE) ? CABINETS_VIEW : SNAPSHOT_VIEW; + if (m_default_image) + m_image_view = ((driver->flags & machine_flags::MASK_TYPE) != machine_flags::TYPE_ARCADE) ? CABINETS_VIEW : SNAPSHOT_VIEW; std::string const searchstr = arts_render_common(origx1, origy1, origx2, origy2); // loads the image if necessary - if (!m_cache->snapx_driver_is(driver) || !snapx_valid() || ui_globals::switch_image) + if (!m_cache->snapx_driver_is(driver) || !snapx_valid() || m_switch_image) { emu_file snapfile(searchstr.c_str(), OPEN_FLAG_READ); snapfile.set_restrict_to_mediapath(true); @@ -2166,7 +2273,7 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f } m_cache->set_snapx_driver(driver); - ui_globals::switch_image = false; + m_switch_image = false; arts_render_images(std::move(tmp_bitmap), origx1, origy1, origx2, origy2); } @@ -2224,7 +2331,7 @@ std::string menu_select_launch::arts_render_common(float origx1, float origy1, f ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size); - draw_common_arrow(origx1, origy1 + UI_BOX_TB_BORDER, origx2, origy2, ui_globals::curimage_view, FIRST_VIEW, LAST_VIEW, title_size); + draw_common_arrow(origx1, origy1 + UI_BOX_TB_BORDER, origx2, origy2, m_image_view, FIRST_VIEW, LAST_VIEW, title_size); return searchstr; } @@ -2274,7 +2381,7 @@ void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap, float or auto dest_yPixel = tmp_bitmap.height(); // force 4:3 ratio min - if (ui().options().forced_4x3_snapshot() && ratioI < 0.75f && ui_globals::curimage_view == SNAPSHOT_VIEW) + if (ui().options().forced_4x3_snapshot() && ratioI < 0.75f && m_image_view == SNAPSHOT_VIEW) { // smaller ratio will ensure that the image fits in the view dest_yPixel = tmp_bitmap.width() * 0.75f; @@ -2367,7 +2474,7 @@ bool menu_select_launch::has_multiple_bios(game_driver const &driver, s_bios &bi for (romload::system_bios const &bios : romload::entries(driver.rom).get_system_bioses()) { std::string name(bios.get_description()); - u32 const bios_flags(bios.get_value()); + uint32_t const bios_flags(bios.get_value()); if (default_name && !std::strcmp(bios.get_name(), default_name)) { diff --git a/src/frontend/mame/ui/selmenu.h b/src/frontend/mame/ui/selmenu.h index 1c5b6bdadd9..5e85cb0ce96 100644 --- a/src/frontend/mame/ui/selmenu.h +++ b/src/frontend/mame/ui/selmenu.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods +// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb /*************************************************************************** ui/selmenu.h @@ -7,7 +7,6 @@ MAME system/software selection menu. ***************************************************************************/ - #ifndef MAME_FRONTEND_UI_SELMENU_H #define MAME_FRONTEND_UI_SELMENU_H @@ -34,6 +33,8 @@ public: virtual ~menu_select_launch() override; protected: + static constexpr std::size_t MAX_ICONS_RENDER = 128; + static constexpr std::size_t MAX_VISIBLE_SEARCH = 200; // tab navigation enum class focused_menu @@ -44,6 +45,20 @@ protected: RIGHTBOTTOM }; + struct texture_and_bitmap + { + template + texture_and_bitmap(T &&tex) : texture(std::forward(tex)) { } + texture_and_bitmap(texture_and_bitmap &&that) = default; + texture_and_bitmap &operator=(texture_and_bitmap &&that) = default; + + texture_ptr texture; + bitmap_argb32 bitmap; + }; + + template > + using texture_lru = util::lru_cache_map; + class system_flags { public: @@ -93,6 +108,8 @@ protected: focused_menu get_focus() const { return m_focus; } void set_focus(focused_menu focus) { m_focus = focus; } + void next_image_view(); + void previous_image_view(); bool dismiss_error(); void set_error(reset_options ropt, std::string &&message); @@ -122,6 +139,14 @@ protected: std::map const &filters, float x1, float y1, float x2, float y2); + // icon helpers + void check_for_icons(char const *listname); + std::string make_icon_paths(char const *listname) const; + bool scale_icon(bitmap_argb32 &&src, texture_and_bitmap &dst) const; + + // forcing refresh + void set_switch_image() { m_switch_image = true; } + template bool select_bios(T const &driver, bool inlist); bool select_part(software_info const &info, ui_software_info const &ui_info); @@ -188,9 +213,6 @@ private: using cache_ptr_map = std::map; using flags_cache = util::lru_cache_map; - using icon_cache = util::lru_cache_map >; - - static constexpr std::size_t MAX_ICONS_RENDER = 128; void reset_pressed() { m_pressed = false; m_repeat = 0; } bool mouse_pressed() const { return (osd_ticks() >= m_repeat); } @@ -230,7 +252,8 @@ private: void draw_toolbar(float x1, float y1, float x2, float y2); void draw_star(float x0, float y0); - float draw_icon(int linenum, void *selectedref, float x1, float y1); + void draw_icon(int linenum, void *selectedref, float x1, float y1); + virtual render_texture *get_icon_texture(int linenum, void *selectedref) = 0; void get_title_search(std::string &title, std::string &search); @@ -289,8 +312,11 @@ private: int m_right_visible_lines; // right box lines + bool m_has_icons; + bool m_switch_image; + bool m_default_image; + uint8_t m_image_view; flags_cache m_flags; - icon_cache m_icons; static std::mutex s_cache_guard; static cache_ptr_map s_caches; diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp index e7ebc3effa2..26635c6d31a 100644 --- a/src/frontend/mame/ui/selsoft.cpp +++ b/src/frontend/mame/ui/selsoft.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /*************************************************************************** ui/selsoft.cpp @@ -12,6 +12,7 @@ #include "ui/selsoft.h" #include "ui/ui.h" +#include "ui/icorender.h" #include "ui/inifile.h" #include "ui/selector.h" @@ -24,75 +25,97 @@ #include "uiinput.h" #include "luaengine.h" +#include +#include +#include + namespace ui { +namespace { + //------------------------------------------------- // compares two items in the software list and // sort them by parent-clone //------------------------------------------------- -bool compare_software(ui_software_info a, ui_software_info b) +bool compare_software(ui_software_info const &a, ui_software_info const &b) { - ui_software_info *x = &a; - ui_software_info *y = &b; - - bool clonex = !x->parentname.empty(); - bool cloney = !y->parentname.empty(); + bool const clonex = !a.parentname.empty() && !a.parentlongname.empty(); + bool const cloney = !b.parentname.empty() && !b.parentlongname.empty(); if (!clonex && !cloney) - return (strmakelower(x->longname) < strmakelower(y->longname)); - - std::string cx(x->parentlongname), cy(y->parentlongname); - - if (cx.empty()) - clonex = false; - - if (cy.empty()) - cloney = false; - - if (!clonex && !cloney) - return (strmakelower(x->longname) < strmakelower(y->longname)); - else if (clonex && cloney) { - if (!core_stricmp(x->parentname.c_str(), y->parentname.c_str()) && !core_stricmp(x->instance.c_str(), y->instance.c_str())) - return (strmakelower(x->longname) < strmakelower(y->longname)); - else - return (strmakelower(cx) < strmakelower(cy)); + return 0 > core_stricmp(a.longname.c_str(), b.longname.c_str()); } else if (!clonex && cloney) { - if (!core_stricmp(x->shortname.c_str(), y->parentname.c_str()) && !core_stricmp(x->instance.c_str(), y->instance.c_str())) + if ((a.shortname == b.parentname) && (a.instance == b.instance)) return true; else - return (strmakelower(x->longname) < strmakelower(cy)); + return 0 > core_stricmp(a.longname.c_str(), b.parentlongname.c_str()); } - else + else if (clonex && !cloney) { - if (!core_stricmp(x->parentname.c_str(), y->shortname.c_str()) && !core_stricmp(x->instance.c_str(), y->instance.c_str())) + if ((a.parentname == b.shortname) && (a.instance == b.instance)) return false; else - return (strmakelower(cx) < strmakelower(y->longname)); + return 0 > core_stricmp(a.parentlongname.c_str(), b.longname.c_str()); + } + else if ((a.parentname == b.parentname) && (a.instance == b.instance)) + { + return 0 > core_stricmp(a.longname.c_str(), b.longname.c_str()); + } + else + { + return 0 > core_stricmp(a.parentlongname.c_str(), b.parentlongname.c_str()); } } +} // anonymous namespace + + +menu_select_software::search_item::search_item(ui_software_info const &s) + : software(s) + , ucs_shortname(ustr_from_utf8(normalize_unicode(s.shortname, unicode_normalization_form::D, true))) + , ucs_longname(ustr_from_utf8(normalize_unicode(s.longname, unicode_normalization_form::D, true))) + , penalty(1.0) +{ +} + +void menu_select_software::search_item::set_penalty(std::u32string const &search) +{ + // TODO: search alternate title as well + penalty = util::edit_distance(search, ucs_shortname); + if (penalty) + penalty = (std::min)(penalty, util::edit_distance(search, ucs_longname)); +} + + //------------------------------------------------- // ctor //------------------------------------------------- -menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, const game_driver *driver) +menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver) : menu_select_launch(mui, container, true) + , m_icon_paths() + , m_icons(MAX_ICONS_RENDER) + , m_driver(driver) + , m_has_empty_start(false) + , m_filter_data() + , m_filters() , m_filter_type(software_filter::ALL) + , m_swinfo() + , m_searchlist() + , m_displaylist() { reselect_last::reselect(false); - m_driver = driver; build_software_list(); load_sw_custom_filters(); m_filter_highlight = m_filter_type; - ui_globals::curimage_view = SNAPSHOT_VIEW; - ui_globals::switch_image = true; + set_switch_image(); ui_globals::cur_sw_dats_view = 0; ui_globals::cur_sw_dats_total = 1; } @@ -103,8 +126,6 @@ menu_select_software::menu_select_software(mame_ui_manager &mui, render_containe menu_select_software::~menu_select_software() { - ui_globals::curimage_view = CABINETS_VIEW; - ui_globals::switch_image = true; } //------------------------------------------------- @@ -135,12 +156,10 @@ void menu_select_software::handle() break; case IPT_UI_LEFT: - if (ui_globals::rpanel == RP_IMAGES && ui_globals::curimage_view > FIRST_VIEW) + if (ui_globals::rpanel == RP_IMAGES) { // Images - ui_globals::curimage_view--; - ui_globals::switch_image = true; - ui_globals::default_image = false; + previous_image_view(); } else if (ui_globals::rpanel == RP_INFOS && ui_globals::cur_sw_dats_view > 0) { @@ -151,12 +170,10 @@ void menu_select_software::handle() break; case IPT_UI_RIGHT: - if (ui_globals::rpanel == RP_IMAGES && ui_globals::curimage_view < LAST_VIEW) + if (ui_globals::rpanel == RP_IMAGES) { // Images - ui_globals::curimage_view++; - ui_globals::switch_image = true; - ui_globals::default_image = false; + next_image_view(); } else if (ui_globals::rpanel == RP_INFOS && ui_globals::cur_sw_dats_view < (ui_globals::cur_sw_dats_total - 1)) { @@ -205,16 +222,16 @@ void menu_select_software::handle() if ((uintptr_t)swinfo > 2) { favorite_manager &mfav = mame_machine_manager::instance()->favorite(); - if (!mfav.isgame_favorite(*swinfo)) + if (!mfav.is_favorite_system_software(*swinfo)) { - mfav.add_favorite_game(*swinfo); + mfav.add_favorite_software(*swinfo); machine().popmessage(_("%s\n added to favorites list."), swinfo->longname.c_str()); } else { machine().popmessage(_("%s\n removed from favorites list."), swinfo->longname.c_str()); - mfav.remove_favorite_game(); + mfav.remove_favorite_software(*swinfo); } } } @@ -232,17 +249,27 @@ void menu_select_software::handle() void menu_select_software::populate(float &customtop, float &custombottom) { + for (auto &icon : m_icons) // TODO: why is this here? maybe better on resize or setting change? + icon.second.texture.reset(); + uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; m_has_empty_start = true; int old_software = -1; - machine_config config(*m_driver, machine().options()); + // FIXME: why does it do this relatively expensive operation every time? + machine_config config(m_driver, machine().options()); for (device_image_interface &image : image_interface_iterator(config.root_device())) - if (image.filename() == nullptr && image.must_be_loaded()) + { + if (!image.filename() && image.must_be_loaded()) { m_has_empty_start = false; break; } + } + + // start with an empty list + m_displaylist.clear(); + filter_map::const_iterator const flt(m_filters.find(m_filter_type)); // no active search if (m_search.empty()) @@ -251,37 +278,44 @@ void menu_select_software::populate(float &customtop, float &custombottom) if (m_has_empty_start) item_append("[Start empty]", "", flags_ui, (void *)&m_swinfo[0]); - m_displaylist.clear(); - m_tmp.clear(); - - filter_map::const_iterator const it(m_filters.find(m_filter_type)); - if (m_filters.end() == it) - m_displaylist = m_sortedlist; + if (m_filters.end() == flt) + std::copy(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist)); else - it->second->apply(std::begin(m_sortedlist), std::end(m_sortedlist), std::back_inserter(m_displaylist)); + flt->second->apply(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist)); + } + else + { + find_matches(); - // iterate over entries - for (size_t curitem = 0; curitem < m_displaylist.size(); ++curitem) + if (m_filters.end() == flt) { - if (reselect_last::software() == "[Start empty]" && !reselect_last::driver().empty()) - old_software = 0; - - else if (m_displaylist[curitem]->shortname == reselect_last::software() && m_displaylist[curitem]->listname == reselect_last::swlist()) - old_software = m_has_empty_start ? curitem + 1 : curitem; - - item_append(m_displaylist[curitem]->longname, m_displaylist[curitem]->devicetype, - m_displaylist[curitem]->parentname.empty() ? flags_ui : (FLAG_INVERT | flags_ui), (void *)m_displaylist[curitem]); + std::transform( + m_searchlist.begin(), + std::next(m_searchlist.begin(), (std::min)(m_searchlist.size(), MAX_VISIBLE_SEARCH)), + std::back_inserter(m_displaylist), + [] (search_item const &entry) { return entry.software; }); + } + else + { + for (auto it = m_searchlist.begin(); (m_searchlist.end() != it) && (MAX_VISIBLE_SEARCH > m_displaylist.size()); ++it) + { + if (flt->second->apply(it->software)) + m_displaylist.emplace_back(it->software); + } } } - else + // iterate over entries + for (size_t curitem = 0; curitem < m_displaylist.size(); ++curitem) { - find_matches(m_search.c_str(), VISIBLE_GAMES_IN_SEARCH); - - for (int curitem = 0; m_searchlist[curitem] != nullptr; ++curitem) - item_append(m_searchlist[curitem]->longname, m_searchlist[curitem]->devicetype, - m_searchlist[curitem]->parentname.empty() ? flags_ui : (FLAG_INVERT | flags_ui), - (void *)m_searchlist[curitem]); + if (reselect_last::software() == "[Start empty]" && !reselect_last::driver().empty()) + old_software = 0; + else if (m_displaylist[curitem].get().shortname == reselect_last::software() && m_displaylist[curitem].get().listname == reselect_last::swlist()) + old_software = m_has_empty_start ? curitem + 1 : curitem; + + item_append( + m_displaylist[curitem].get().longname, m_displaylist[curitem].get().devicetype, + m_displaylist[curitem].get().parentname.empty() ? flags_ui : (FLAG_INVERT | flags_ui), (void *)&m_displaylist[curitem].get()); } item_append(menu_item_type::SEPARATOR, flags_ui); @@ -306,21 +340,51 @@ void menu_select_software::populate(float &customtop, float &custombottom) void menu_select_software::build_software_list() { // add start empty item - m_swinfo.emplace_back(*m_driver); + m_swinfo.emplace_back(m_driver); - machine_config config(*m_driver, machine().options()); + machine_config config(m_driver, machine().options()); - // iterate thru all software lists + // iterate through all software lists + std::vector orphans; + struct orphan_less + { + std::vector &swinfo; + bool operator()(std::string const &a, std::string const &b) const { return a < b; }; + bool operator()(std::string const &a, std::size_t b) const { return a < swinfo[b].parentname; }; + bool operator()(std::size_t a, std::string const &b) const { return swinfo[a].parentname < b; }; + bool operator()(std::size_t a, std::size_t b) const { return swinfo[a].parentname < swinfo[b].parentname; }; + }; + orphan_less const orphan_cmp{ m_swinfo }; for (software_list_device &swlist : software_list_device_iterator(config.root_device())) { m_filter_data.add_list(swlist.list_name(), swlist.description()); + check_for_icons(swlist.list_name().c_str()); + orphans.clear(); + std::map parentnames; + std::map::const_iterator prevparent(parentnames.end()); for (const software_info &swinfo : swlist.get_info()) { + // check for previously-encountered clones + if (swinfo.parentname().empty()) + { + if (parentnames.emplace(swinfo.shortname(), swinfo.longname()).second) + { + auto const clones(std::equal_range(orphans.begin(), orphans.end(), swinfo.shortname(), orphan_cmp)); + for (auto it = clones.first; clones.second != it; ++it) + m_swinfo[*it].parentlongname = swinfo.longname(); + orphans.erase(clones.first, clones.second); + } + else + { + assert([] (auto const x) { return x.first == x.second; } (std::equal_range(orphans.begin(), orphans.end(), swinfo.shortname(), orphan_cmp))); + } + } + const software_part &part = swinfo.parts().front(); if (swlist.is_compatible(part) == SOFTWARE_IS_COMPATIBLE) { - const char *instance_name = nullptr; - const char *type_name = nullptr; + char const *instance_name(nullptr); + char const *type_name(nullptr); for (device_image_interface &image : image_interface_iterator(config.root_device())) { char const *const interface = image.image_interface(); @@ -331,45 +395,35 @@ void menu_select_software::build_software_list() break; } } - if (!instance_name || !type_name) - continue; - ui_software_info tmpmatches(swinfo, part, *m_driver, swlist.list_name(), instance_name, type_name); - - m_filter_data.add_region(tmpmatches.longname); - m_filter_data.add_publisher(tmpmatches.publisher); - m_filter_data.add_year(tmpmatches.year); - m_filter_data.add_device_type(tmpmatches.devicetype); - m_swinfo.emplace_back(std::move(tmpmatches)); - } - } - } - m_displaylist.resize(m_swinfo.size() + 1); - - // retrieve and set the long name of software for parents - for (size_t y = 1; y < m_swinfo.size(); ++y) - { - if (!m_swinfo[y].parentname.empty()) - { - std::string lparent(m_swinfo[y].parentname); - bool found = false; - - // first scan backward - for (int x = y; x > 0; --x) - if (lparent == m_swinfo[x].shortname && m_swinfo[y].listname == m_swinfo[x].listname) + if (instance_name && type_name) { - m_swinfo[y].parentlongname = m_swinfo[x].longname; - found = true; - break; - } + // add to collection and try to resolve parent if applicable + auto const ins(m_swinfo.emplace(m_swinfo.end(), swinfo, part, m_driver, swlist.list_name(), instance_name, type_name)); + if (!swinfo.parentname().empty()) + { + if ((parentnames.end() == prevparent) || (swinfo.parentname() != prevparent->first)) + prevparent = parentnames.find(swinfo.parentname()); - // not found? then scan forward - for (size_t x = y; !found && x < m_swinfo.size(); ++x) - if (lparent == m_swinfo[x].shortname && m_swinfo[y].listname == m_swinfo[x].listname) - { - m_swinfo[y].parentlongname = m_swinfo[x].longname; - break; + if (parentnames.end() != prevparent) + { + ins->parentlongname = prevparent->second; + } + else + { + orphans.emplace( + std::upper_bound(orphans.begin(), orphans.end(), swinfo.parentname(), orphan_cmp), + std::distance(m_swinfo.begin(), ins)); + } + } + + // populate filter choices + m_filter_data.add_region(ins->longname); + m_filter_data.add_publisher(ins->publisher); + m_filter_data.add_year(ins->year); + m_filter_data.add_device_type(ins->devicetype); } + } } } @@ -408,9 +462,6 @@ void menu_select_software::build_software_list() // sort array std::stable_sort(m_swinfo.begin() + 1, m_swinfo.end(), compare_software); m_filter_data.finalise(); - - for (size_t x = 1; x < m_swinfo.size(); ++x) - m_sortedlist.push_back(&m_swinfo[x]); } @@ -474,7 +525,7 @@ void menu_select_software::load_sw_custom_filters() { // attempt to open the output file emu_file file(ui().options().ui_path(), OPEN_FLAG_READ); - if (file.open("custom_", m_driver->name, "_filter.ini") == osd_file::error::NONE) + if (file.open("custom_", m_driver.name, "_filter.ini") == osd_file::error::NONE) { software_filter::ptr flt(software_filter::create(file, m_filter_data)); if (flt) @@ -487,38 +538,25 @@ void menu_select_software::load_sw_custom_filters() // find approximate matches //------------------------------------------------- -void menu_select_software::find_matches(const char *str, int count) +void menu_select_software::find_matches() { - // allocate memory to track the penalty value - std::vector penalty(count, 9999); - int index = 0; - - for (; index < m_displaylist.size(); ++index) + // ensure search list is populated + if (m_searchlist.empty()) { - // pick the best match between driver name and description - int curpenalty = fuzzy_substring(str, m_displaylist[index]->longname); - int tmp = fuzzy_substring(str, m_displaylist[index]->shortname); - curpenalty = std::min(curpenalty, tmp); - - // insert into the sorted table of matches - for (int matchnum = count - 1; matchnum >= 0; --matchnum) - { - // stop if we're worse than the current entry - if (curpenalty >= penalty[matchnum]) - break; + m_searchlist.reserve(m_swinfo.size()); + std::copy(m_swinfo.begin(), m_swinfo.end(), std::back_inserter(m_searchlist)); + } - // as long as this isn't the last entry, bump this one down - if (matchnum < count - 1) - { - penalty[matchnum + 1] = penalty[matchnum]; - m_searchlist[matchnum + 1] = m_searchlist[matchnum]; - } + // update search + const std::u32string ucs_search(ustr_from_utf8(normalize_unicode(m_search, unicode_normalization_form::D, true))); + for (search_item &entry : m_searchlist) + entry.set_penalty(ucs_search); - m_searchlist[matchnum] = m_displaylist[index]; - penalty[matchnum] = curpenalty; - } - } - (index < count) ? m_searchlist[index] = nullptr : m_searchlist[count] = nullptr; + // sort according to edit distance + std::stable_sort( + m_searchlist.begin(), + m_searchlist.end(), + [] (search_item const &lhs, search_item const &rhs) { return lhs.penalty < rhs.penalty; }); } //------------------------------------------------- @@ -531,6 +569,56 @@ float menu_select_software::draw_left_panel(float x1, float y1, float x2, float } +//------------------------------------------------- +// get (possibly cached) icon texture +//------------------------------------------------- + +render_texture *menu_select_software::get_icon_texture(int linenum, void *selectedref) +{ + ui_software_info const *const swinfo(reinterpret_cast(selectedref)); + assert(swinfo); + + if (swinfo->startempty) + return nullptr; + + icon_cache::iterator icon(m_icons.find(swinfo)); + if ((m_icons.end() == icon) || !icon->second.texture) + { + std::map::iterator paths(m_icon_paths.find(swinfo->listname)); + if (m_icon_paths.end() == paths) + paths = m_icon_paths.emplace(swinfo->listname, make_icon_paths(swinfo->listname.c_str())).first; + + // allocate an entry or allocate a texture on forced redraw + if (m_icons.end() == icon) + { + icon = m_icons.emplace(swinfo, texture_ptr(machine().render().texture_alloc(), machine().render())).first; + } + else + { + assert(!icon->second.texture); + icon->second.texture.reset(machine().render().texture_alloc()); + } + + bitmap_argb32 tmp; + emu_file snapfile(std::string(paths->second), OPEN_FLAG_READ); + if (snapfile.open(std::string(swinfo->shortname), ".ico") == osd_file::error::NONE) + { + render_load_ico_highest_detail(snapfile, tmp); + snapfile.close(); + } + if (!tmp.valid() && !swinfo->parentname.empty() && (snapfile.open(std::string(swinfo->parentname), ".ico") == osd_file::error::NONE)) + { + render_load_ico_highest_detail(snapfile, tmp); + snapfile.close(); + } + + scale_icon(std::move(tmp), icon->second); + } + + return icon->second.bitmap.valid() ? icon->second.texture.get() : nullptr; +} + + //------------------------------------------------- // get selected software and/or driver //------------------------------------------------- @@ -547,7 +635,7 @@ void menu_select_software::make_topbox_text(std::string &line0, std::string &lin // determine the text for the header int vis_item = !m_search.empty() ? visible_items : (m_has_empty_start ? visible_items - 1 : visible_items); line0 = string_format(_("%1$s %2$s ( %3$d / %4$d software packages )"), emulator_info::get_appname(), bare_build_version, vis_item, m_swinfo.size() - 1); - line1 = string_format(_("Driver: \"%1$s\" software list "), m_driver->type.fullname()); + line1 = string_format(_("Driver: \"%1$s\" software list "), m_driver.type.fullname()); filter_map::const_iterator const it(m_filters.find(m_filter_type)); char const *const filter((m_filters.end() != it) ? it->second->filter_text() : nullptr); @@ -576,20 +664,19 @@ void menu_select_software::filter_selected() { if ((software_filter::FIRST <= m_filter_highlight) && (software_filter::LAST >= m_filter_highlight)) { - m_search.clear(); filter_map::const_iterator it(m_filters.find(software_filter::type(m_filter_highlight))); if (m_filters.end() == it) it = m_filters.emplace(software_filter::type(m_filter_highlight), software_filter::create(software_filter::type(m_filter_highlight), m_filter_data)).first; it->second->show_ui( ui(), container(), - [this, driver = m_driver] (software_filter &filter) + [this] (software_filter &filter) { software_filter::type const new_type(filter.get_type()); if (software_filter::CUSTOM == new_type) { emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open("custom_", driver->name, "_filter.ini") == osd_file::error::NONE) + if (file.open("custom_", m_driver.name, "_filter.ini") == osd_file::error::NONE) { filter.save_ini(file, 0); file.close(); diff --git a/src/frontend/mame/ui/selsoft.h b/src/frontend/mame/ui/selsoft.h index e65a5ddc61f..1bfdd9c1e48 100644 --- a/src/frontend/mame/ui/selsoft.h +++ b/src/frontend/mame/ui/selsoft.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /*************************************************************************** ui/selsoft.h @@ -15,6 +15,10 @@ #include "ui/selmenu.h" #include "ui/utils.h" +#include +#include +#include + namespace ui { @@ -22,25 +26,34 @@ namespace ui { class menu_select_software : public menu_select_launch { public: - menu_select_software(mame_ui_manager &mui, render_container &container, const game_driver *driver); + menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver); virtual ~menu_select_software() override; private: - enum { VISIBLE_GAMES_IN_SEARCH = 200 }; - - typedef std::map filter_map; - - const game_driver *m_driver; - bool m_has_empty_start; - software_filter_data m_filter_data; - filter_map m_filters; - software_filter::type m_filter_type; + using filter_map = std::map; + using icon_cache = texture_lru; + + struct search_item + { + search_item(ui_software_info const &s); + search_item(search_item const &) = default; + search_item(search_item &&) = default; + search_item &operator=(search_item const &) = default; + search_item &operator=(search_item &&) = default; + void set_penalty(std::u32string const &search); + + std::reference_wrapper software; + std::u32string ucs_shortname; + std::u32string ucs_longname; + double penalty; + }; virtual void populate(float &customtop, float &custombottom) override; virtual void handle() override; - // draw left panel + // drawing virtual float draw_left_panel(float x1, float y1, float x2, float y2) override; + virtual render_texture *get_icon_texture(int linenum, void *selectedref) override; // get selected software and/or driver virtual void get_selection(ui_software_info const *&software, game_driver const *&driver) const override; @@ -56,20 +69,28 @@ private: // toolbar virtual void inkey_export() override { throw false; } - ui_software_info *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; - std::vector m_displaylist, m_tmp, m_sortedlist; - std::vector m_swinfo; - void build_software_list(); - void find_matches(const char *str, int count); + void find_matches(); void load_sw_custom_filters(); // handlers void inkey_select(const event *menu_event); - virtual void general_info(const game_driver *driver, std::string &buffer) override {} + virtual void general_info(const game_driver *driver, std::string &buffer) override { } + + std::map m_icon_paths; + icon_cache m_icons; + game_driver const &m_driver; + bool m_has_empty_start; + software_filter_data m_filter_data; + filter_map m_filters; + software_filter::type m_filter_type; + + std::vector m_swinfo; + std::vector m_searchlist; + std::vector > m_displaylist; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_SELSOFT_H */ +#endif // MAME_FRONTEND_UI_SELSOFT_H diff --git a/src/frontend/mame/ui/simpleselgame.cpp b/src/frontend/mame/ui/simpleselgame.cpp index f81b161dac1..e22e61b6a2b 100644 --- a/src/frontend/mame/ui/simpleselgame.cpp +++ b/src/frontend/mame/ui/simpleselgame.cpp @@ -163,7 +163,13 @@ void simple_menu_select_game::inkey_select(const event *menu_event) // special case for configure inputs if ((uintptr_t)driver == 1) - menu::stack_push(ui(), container()); + { + menu::stack_push( + ui(), + container(), + [this] () { reset(reset_options::SELECT_FIRST); }); + } + // anything else is a driver else { @@ -250,7 +256,7 @@ void simple_menu_select_game::populate(float &customtop, float &custombottom) // otherwise, rebuild the match list assert(m_drivlist != nullptr); if (!m_search.empty() || m_matchlist[0] == -1 || m_rerandomize) - m_drivlist->find_approximate_matches(m_search.c_str(), matchcount, m_matchlist); + m_drivlist->find_approximate_matches(m_search, matchcount, m_matchlist); m_rerandomize = false; // iterate over entries diff --git a/src/frontend/mame/ui/utils.cpp b/src/frontend/mame/ui/utils.cpp index 1a8ff0ba476..52857fc6dbf 100644 --- a/src/frontend/mame/ui/utils.cpp +++ b/src/frontend/mame/ui/utils.cpp @@ -22,7 +22,9 @@ #include "romload.h" #include "softlist.h" +#include #include +#include #include #include #include @@ -82,6 +84,7 @@ constexpr char const *SOFTWARE_FILTER_NAMES[software_filter::COUNT] = { __("Unfiltered"), __("Available"), __("Unavailable"), + __("Favorites"), __("Parents"), __("Clones"), __("Year"), @@ -642,7 +645,7 @@ template class available_machine_filter_impl : public simple_filter_impl_base { public: - available_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + available_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.available; } }; @@ -652,7 +655,7 @@ template class working_machine_filter_impl : public simple_filter_impl_base { public: - working_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + working_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return !(system.driver->flags & machine_flags::NOT_WORKING); } }; @@ -662,7 +665,7 @@ template class mechanical_machine_filter_impl : public simple_filter_impl_base { public: - mechanical_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + mechanical_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::MECHANICAL; } }; @@ -672,7 +675,7 @@ template class bios_machine_filter_impl : public simple_filter_impl_base { public: - bios_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + bios_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::IS_BIOS_ROOT; } }; @@ -682,7 +685,7 @@ template class parents_machine_filter_impl : public simple_filter_impl_base { public: - parents_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + parents_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { @@ -697,7 +700,7 @@ template class chd_machine_filter_impl : public simple_filter_impl_base { public: - chd_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + chd_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { @@ -715,7 +718,7 @@ template class save_machine_filter_impl : public simple_filter_impl_base { public: - save_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + save_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SUPPORTS_SAVE; } }; @@ -725,7 +728,7 @@ template class vertical_machine_filter_impl : public simple_filter_impl_base { public: - vertical_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + vertical_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SWAP_XY; } }; @@ -739,8 +742,8 @@ public: class manufacturer_machine_filter : public choice_filter_impl_base { public: - manufacturer_machine_filter(char const *value, emu_file *file, unsigned indent) - : choice_filter_impl_base(c_mnfct::ui, value) + manufacturer_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + : choice_filter_impl_base(data.manufacturers(), value) { } @@ -751,7 +754,7 @@ public: else if (!selection_valid()) return false; - std::string const name(c_mnfct::getname(system.driver->manufacturer)); + std::string const name(machine_filter_data::extract_manufacturer(system.driver->manufacturer)); return !name.empty() && (selection_text() == name); } }; @@ -760,8 +763,8 @@ public: class year_machine_filter : public choice_filter_impl_base { public: - year_machine_filter(char const *value, emu_file *file, unsigned indent) - : choice_filter_impl_base(c_year::ui, value) + year_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + : choice_filter_impl_base(data.years(), value) { } @@ -778,7 +781,10 @@ template