diff options
Diffstat (limited to 'src/frontend')
34 files changed, 3250 insertions, 1820 deletions
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<std::string> &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<std::string> &args) void cli_frontend::listfull(const std::vector<std::string> &args) { - bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); - std::vector<bool> 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<std::string> &args) void cli_frontend::listsource(const std::vector<std::string> &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<std::string> &args) void cli_frontend::listcrc(const std::vector<std::string> &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<std::string> &args) { - bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); - std::vector<bool> 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<feature name=\"%s\" value=\"%s\" />\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<std::string> &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<std::string> &args) @@ -1427,6 +1313,7 @@ void cli_frontend::verifysoftware(const std::vector<std::string> &args) } + /*------------------------------------------------- getsoftlist - retrieve software list by name -------------------------------------------------*/ @@ -1561,6 +1448,105 @@ void cli_frontend::romident(const std::vector<std::string> &args) //------------------------------------------------- +// apply_action - apply action to matching +// systems/devices +//------------------------------------------------- + +template <typename T, typename U> void cli_frontend::apply_action(const std::vector<std::string> &args, T &&drvact, U &&devact) + +{ + bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); + std::vector<bool> 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 <typename T> void cli_frontend::apply_device_action(const std::vector<std::string> &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<std::string> &args); // internal helpers + template <typename T, typename U> void apply_action(const std::vector<std::string> &args, T &&drvact, U &&devact); + template <typename T> void apply_device_action(const std::vector<std::string> &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<!ATTLIST driver cocktail (good|imperfect|preliminary) #IMPLIED>\n" "\t\t\t<!ATTLIST driver savestate (supported|unsupported) #REQUIRED>\n" "\t\t<!ELEMENT feature EMPTY>\n" -"\t\t\t<!ATTLIST feature type (protection|palette|graphics|sound|controls|keyboard|mouse|microphone|camera|disk|printer|lan|wan|timing) #REQUIRED>\n" +"\t\t\t<!ATTLIST feature type (protection|timing|graphics|palette|sound|capture|camera|microphone|controls|keyboard|mouse|media|disk|printer|tape|punch|drum|rom|comms|lan|wan) #REQUIRED>\n" "\t\t\t<!ATTLIST feature status (unemulated|imperfect) #IMPLIED>\n" "\t\t\t<!ATTLIST feature overall (unemulated|imperfect) #IMPLIED>\n" "\t\t<!ELEMENT device (instance?, extension*)>\n" @@ -1639,19 +1639,26 @@ void info_xml_creator::output_features(device_type type, device_t::feature_type { static constexpr std::pair<device_t::feature_type, char const *> 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<std::string, std::string>(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<plugin_options>()), - 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<plugin_options>()), + m_lua(global_alloc(lua_engine)), + m_new_driver_pending(nullptr), + m_firstrun(true), + m_autoboot_timer(nullptr) { } @@ -100,7 +99,8 @@ std::vector<std::string> split(const std::string &text, char sep) { std::vector<std::string> 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<std::string> include = split(options().plugin(),','); - std::vector<std::string> exclude = split(options().no_plugin(),','); + std::vector<std::string> include = split(options().plugin(), ','); + std::vector<std::string> 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<inifile_manager>(machine, m_ui->options()); + m_inifile = std::make_unique<inifile_manager>(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<favorite_manager>(machine, m_ui->options()); + m_favorite = std::make_unique<favorite_manager>(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<plugin_options> 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<bool> 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 <algorithm> +#include <cassert> +#include <cstdint> +#include <cstring> + +// 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<uint32_t>(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<uint32_t>(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<rgb_t []> palette; + if (indexed) + { + // read palette and convert + std::unique_ptr<uint8_t []> 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<size_t>(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<uint8_t []> 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<icon_dir_entry_t []> dir(new icon_dir_entry_t [count]); + std::unique_ptr<unsigned []> 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<device_t::feature_type, char const *> 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 <algorithm> +#include <cstring> +#include <iterator> + + +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 <typename T> void favorite_manager::add_impl(T &&key) +{ + auto const ins(m_favorites.emplace(std::forward<T>(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 <typename T> 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 <typename T> 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 <typename T> +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 <functional> +#include <set> +#include <tuple> +#include <type_traits> #include <unordered_set> @@ -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<game_driver const *> &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<std::string, ui_software_info, ci_less> 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 <typename T> void apply(T &&action) + { + for (auto const &item : m_favorites) + action(item); + } + template <typename T> 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<game_driver const &, char const *, std::string const &>; + + 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<std::string, ui_software_info>::iterator m_current; + using favorites_set = std::set<ui_software_info, favorite_compare>; + using sorted_favorites = std::vector<std::reference_wrapper<ui_software_info const> >; - // parse file ui_favorite - void parse_favorite(); + // implementation + template <typename T> static void apply_running_machine(running_machine &machine, T &&action); + template <typename T> void add_impl(T &&key); + template <typename T> bool check_impl(T const &key) const; + template <typename T> 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<bitmap_argb32>(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<menu> reversed; while (m_free) - m_free = std::move(m_free->m_parent); + { + std::unique_ptr<menu> 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 <typename T> T *topmost_menu() const { return m_global_state->topmost_menu<T>(); } template <typename T> static T *topmost_menu(running_machine &machine) { return get_global_state(machine)->topmost_menu<T>(); } @@ -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 <typename T> T *topmost_menu() const { return dynamic_cast<T *>(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<void (bool, bool)> &&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<submenu>(ui(), container(), submenu::control_options, m_drv, &m_opts); - break; - case VIDEO: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<submenu>(ui(), container(), submenu::video_options, m_drv, &m_opts); - break; - case ADVANCED: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<submenu>(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<submenu>(ui(), container(), submenu::control_options, &m_drv, &m_opts); + break; + case VIDEO: + if (menu_event->iptkey == IPT_UI_SELECT) + menu::stack_push<submenu>(ui(), container(), submenu::video_options, &m_drv, &m_opts); + break; + case ADVANCED: + if (menu_event->iptkey == IPT_UI_SELECT) + menu::stack_push<submenu>(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 <vector> 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<void (bool, bool)> &&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<void (bool, bool)> 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<void ()> &&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<std::string> 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<menu_selector>( - 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<menu_directory>(ui(), container()); - break; - case MISC_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push<submenu>(ui(), container(), submenu::misc_options); - ui_globals::reset = true; - } - break; - case SOUND_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push<menu_sound_options>(ui(), container()); - ui_globals::reset = true; - } - break; - case DISPLAY_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push<submenu>(ui(), container(), submenu::video_options); - ui_globals::reset = true; - } - break; - case CUSTOM_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_custom_ui>(ui(), container()); - break; - case CONTROLLER_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<submenu>(ui(), container(), submenu::control_options); - break; - case CGI_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_input_groups>(ui(), container()); - break; - case ADVANCED_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push<submenu>(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<uint16_t>(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); @@ -220,10 +82,61 @@ void menu_game_options::populate(float &customtop, float &custombottom) } //------------------------------------------------- +// 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<submenu>(ui(), container(), submenu::misc_options); + ui_globals::reset = true; + } + break; + case SOUND_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + { + menu::stack_push<menu_sound_options>(ui(), container()); + ui_globals::reset = true; + } + break; + case DISPLAY_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + { + menu::stack_push<submenu>(ui(), container(), submenu::video_options); + ui_globals::reset = true; + } + break; + case CONTROLLER_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + menu::stack_push<submenu>(ui(), container(), submenu::control_options); + break; + case CGI_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + menu::stack_push<menu_input_groups>(ui(), container()); + break; + case ADVANCED_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + { + menu::stack_push<submenu>(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<void ()> &&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<uint16_t>(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<std::string> 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<menu_selector>( + 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<menu_directory>(ui(), container()); + break; + case CUSTOM_MENU: + if (menu_event.iptkey == IPT_UI_SELECT) + menu::stack_push<menu_custom_ui>(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<void ()> &&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<void ()> 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<void ()> &&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<int> penalty(VISIBLE_GAMES_IN_SEARCH, 9999); - int index = 0; + m_ucs_items.reserve(m_str_items.size()); + std::vector<double> 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<std::string> m_str_items; - std::function<void (int)> m_handler; - int m_initial; - std::string *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; + std::string m_search; + std::vector<std::string> m_str_items; + std::function<void (int)> m_handler; + std::vector<std::u32string> 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 <atomic> +#include <condition_variable> #include <cstring> #include <iterator> +#include <memory> +#include <mutex> +#include <thread> extern const char UI_VERSION_TAG[]; namespace ui { -bool menu_select_game::first_start = true; -std::vector<const game_driver *> 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<std::mutex> 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<std::mutex> lock(m_mutex); + do_start_caching(); + m_condition.wait(lock, [this, desired] () { return is_available(desired); }); + } + } + + std::vector<ui_system_info> &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<std::mutex> 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<std::thread>([this] { do_cache_data(); }); + } + } + + void do_cache_data() + { + // generate full list + m_sorted_list.reserve(driver_list::total()); + std::unordered_set<std::string> 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 "<manufacturer> <description>" 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<std::thread> m_thread; + std::atomic<bool> m_started; + std::atomic<unsigned> m_available; + + // data + std::vector<ui_system_info> 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<int>(std::max<int>(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<menu_select_software>(ui(), container(), driver); + menu::stack_push<menu_select_software>(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<menu_machine_configure>( ui(), container(), - reinterpret_cast<const game_driver *>(m_prev_selected), + *reinterpret_cast<const game_driver *>(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<ui_software_info *>(m_prev_selected); menu::stack_push<menu_machine_configure>( 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<game_driver const *>(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<ui_software_info const *>(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<menu_audit>(ui(), container(), m_availsortedlist, menu_audit::mode::FAST); + if (m_persistent_data.unavailable_systems()) + menu::stack_push<menu_audit>(ui(), container(), m_persistent_data.sorted_list(), menu_audit::mode::FAST); break; case IPT_UI_AUDIT_ALL: - menu::stack_push<menu_audit>(ui(), container(), m_availsortedlist, menu_audit::mode::ALL); + menu::stack_push<menu_audit>(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<ui_system_info> 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<menu_game_options>(ui(), container()); + menu::stack_push<menu_game_options>( + 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<menu_machine_configure>(ui(), container(), reinterpret_cast<const game_driver *>(m_prev_selected)); + menu::stack_push<menu_machine_configure>(ui(), container(), *reinterpret_cast<const game_driver *>(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<menu_select_software>(ui(), container(), driver); + menu::stack_push<menu_select_software>(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<menu_game_options>(ui(), container()); + menu::stack_push<menu_game_options>( + 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<ui_software_info *>(m_prev_selected); - menu::stack_push<menu_machine_configure>(ui(), container(), (const game_driver *)swinfo->driver); + menu::stack_push<menu_machine_configure>( + 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<menu_select_software>(ui(), container(), ui_swinfo->driver); + menu::stack_push<menu_select_software>(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<int> 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<ui_system_info> 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<double, std::reference_wrapper<ui_system_info const> > &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<double, std::reference_wrapper<ui_system_info const> > &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 "<manufacturer> <description>" + 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<double, std::reference_wrapper<ui_system_info const> > &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<game_driver const *> 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<ui_software_info const *>(selectedref)->driver + : reinterpret_cast<game_driver const *>(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<menu_export>(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<std::string> manufacturers, years; - for (int x = 0; x < driver_list::total(); ++x) + std::vector<game_driver const *> 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<menu_export>(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<std::string>::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<std::string>::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<machine_filter>(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<machine_filter>(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<ui_software_info const *>(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 <functional> + 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<game_driver const *>; + + class persistent_data; + + persistent_data &m_persistent_data; + icon_cache m_icons; + std::string m_icon_paths; + std::vector<std::reference_wrapper<ui_system_info const> > m_displaylist; - static std::vector<const game_driver *> m_sortedlist; - std::vector<ui_system_info> m_availsortedlist; - std::vector<ui_system_info> m_displaylist; + std::vector<std::pair<double, std::reference_wrapper<ui_system_info const> > > 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<char const *, char const *> 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<bitmap_argb32>(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 <typename T> 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 <typename T> + texture_and_bitmap(T &&tex) : texture(std::forward<T>(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 <typename Key, typename Compare = std::less<Key> > + using texture_lru = util::lru_cache_map<Key, texture_and_bitmap, Compare>; + 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<typename Filter::type, typename Filter::ptr> 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 <typename T> 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<running_machine *, cache_ptr>; using flags_cache = util::lru_cache_map<game_driver const *, system_flags>; - using icon_cache = util::lru_cache_map<game_driver const *, std::pair<texture_ptr, bitmap_argb32> >; - - 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 <algorithm> +#include <iterator> +#include <functional> + 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<std::size_t> orphans; + struct orphan_less + { + std::vector<ui_software_info> &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<std::string, std::string> parentnames; + std::map<std::string, std::string>::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<int> 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; }); } //------------------------------------------------- @@ -532,6 +570,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<ui_software_info const *>(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<std::string, std::string>::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 <map> +#include <string> +#include <vector> + 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<software_filter::type, software_filter::ptr> 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<software_filter::type, software_filter::ptr>; + using icon_cache = texture_lru<ui_software_info const *>; + + 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<ui_software_info const> 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<ui_software_info *> m_displaylist, m_tmp, m_sortedlist; - std::vector<ui_software_info> 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<std::string, std::string> 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<ui_software_info> m_swinfo; + std::vector<search_item> m_searchlist; + std::vector<std::reference_wrapper<ui_software_info const> > 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<menu_game_options>(ui(), container()); + { + menu::stack_push<menu_simple_game_options>( + 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 <atomic> #include <bitset> +#include <condition_variable> #include <cstdlib> #include <cstring> #include <iterator> @@ -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 <machine_filter::type Type = machine_filter::AVAILABLE> class available_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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 <machine_filter::type Type = machine_filter::WORKING> class working_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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 <machine_filter::type Type = machine_filter::MECHANICAL> class mechanical_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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 <machine_filter::type Type = machine_filter::BIOS> class bios_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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 <machine_filter::type Type = machine_filter::PARENTS> class parents_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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 <machine_filter::type Type = machine_filter::CHD> class chd_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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 <machine_filter::type Type = machine_filter::SAVE> class save_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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 <machine_filter::type Type = machine_filter::VERTICAL> class vertical_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { 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<machine_filter, machine_filter::MANUFACTURER> { public: - manufacturer_machine_filter(char const *value, emu_file *file, unsigned indent) - : choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER>(c_mnfct::ui, value) + manufacturer_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + : choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER>(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<machine_filter, machine_filter::YEAR> { public: - year_machine_filter(char const *value, emu_file *file, unsigned indent) - : choice_filter_impl_base<machine_filter, machine_filter::YEAR>(c_year::ui, value) + year_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + : choice_filter_impl_base<machine_filter, machine_filter::YEAR>(data.years(), value) { } @@ -778,7 +781,10 @@ template <template <machine_filter::type T> class Base, machine_filter::type Typ class inverted_machine_filter : public Base<Type> { public: - inverted_machine_filter(char const *value, emu_file *file, unsigned indent) : Base<Type>(value, file, indent) { } + inverted_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + : Base<Type>(data, value, file, indent) + { + } virtual bool apply(ui_system_info const &system) const override { return !Base<Type>::apply(system); } }; @@ -811,7 +817,7 @@ template <machine_filter::type Type> class inclusive_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - inclusive_machine_filter_impl(char const *value, emu_file *file, unsigned indent) { } + inclusive_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 true; } }; @@ -828,7 +834,7 @@ using favorite_machine_filter = inclusive_machine_filter_impl<machine_filt class category_machine_filter : public simple_filter_impl_base<machine_filter, machine_filter::CATEGORY> { public: - category_machine_filter(char const *value, emu_file *file, unsigned indent) + category_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) : m_ini(0) , m_group(0) , m_include_clones(false) @@ -1161,14 +1167,15 @@ void category_machine_filter::menu_configure::handle() class custom_machine_filter : public composite_filter_impl_base<custom_machine_filter, machine_filter, machine_filter::CUSTOM> { public: - custom_machine_filter(char const *value, emu_file *file, unsigned indent) + custom_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) : composite_filter_impl_base<custom_machine_filter, machine_filter, machine_filter::CUSTOM>() + , m_data(data) { populate(value, file, indent); } - ptr create(type n) const { return machine_filter::create(n); } - ptr create(emu_file &file, unsigned indent) const { return machine_filter::create(file, indent); } + ptr create(type n) const { return machine_filter::create(n, m_data); } + ptr create(emu_file &file, unsigned indent) const { return machine_filter::create(file, m_data, indent); } static bool type_allowed(unsigned pos, type n) { @@ -1212,6 +1219,9 @@ public: { return (CATEGORY == n) || (MANUFACTURER == n) || (YEAR == n); } + +private: + machine_filter_data const &m_data; }; @@ -1247,6 +1257,21 @@ public: }; +class favorite_software_filter : public simple_filter_impl_base<software_filter, software_filter::FAVORITE> +{ +public: + favorite_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + : m_manager(mame_machine_manager::instance()->favorite()) + { + } + + virtual bool apply(ui_software_info const &info) const override { return m_manager.is_favorite_software(info); } + +private: + favorite_manager const &m_manager; +}; + + class parents_software_filter : public simple_filter_impl_base<software_filter, software_filter::PARENTS> { public: @@ -1414,6 +1439,7 @@ public: case UNSUPPORTED: return (SUPPORTED == m) || (PARTIAL_SUPPORTED == m); case ALL: + case FAVORITE: case YEAR: case PUBLISHERS: case REGION: @@ -1440,20 +1466,100 @@ private: //------------------------------------------------- +// static data for machine filters +//------------------------------------------------- + +void machine_filter_data::add_manufacturer(std::string const &manufacturer) +{ + std::string name(extract_manufacturer(manufacturer)); + std::vector<std::string>::iterator const pos(std::lower_bound(m_manufacturers.begin(), m_manufacturers.end(), name)); + if ((m_manufacturers.end() == pos) || (*pos != name)) + m_manufacturers.emplace(pos, std::move(name)); +} + +void machine_filter_data::add_year(std::string const &year) +{ + std::vector<std::string>::iterator const pos(std::lower_bound(m_years.begin(), m_years.end(), year)); + if ((m_years.end() == pos) || (*pos != year)) + m_years.emplace(pos, year); +} + +void machine_filter_data::finalise() +{ + std::stable_sort(m_manufacturers.begin(), m_manufacturers.end()); + std::stable_sort(m_years.begin(), m_years.end()); +} + +std::string machine_filter_data::extract_manufacturer(std::string const &manufacturer) +{ + size_t const found(manufacturer.find('(')); + if ((found != std::string::npos) && (found > 0)) + return manufacturer.substr(0, found - 1); + else + return manufacturer; +} + +void machine_filter_data::set_filter(machine_filter::ptr &&filter) +{ + m_filters[filter->get_type()] = std::move(filter); +} + +machine_filter &machine_filter_data::get_filter(machine_filter::type type) +{ + auto it(m_filters.find(type)); + if (m_filters.end() == it) + it = m_filters.emplace(type, machine_filter::create(type, *this)).first; + + assert(it->second); + return *it->second; +} + +std::string machine_filter_data::get_config_string() const +{ + auto const active_filter(m_filters.find(m_current_filter)); + if (m_filters.end() != active_filter) + { + char const *const val(active_filter->second->filter_text()); + return val ? util::string_format("%s,%s", active_filter->second->config_name(), val) : active_filter->second->config_name(); + } + else + { + return machine_filter::config_name(m_current_filter); + } +} + +bool machine_filter_data::load_ini(emu_file &file) +{ + machine_filter::ptr flt(machine_filter::create(file, *this)); + if (flt) + { + // TODO: it should possibly replace an existing item here, but it may be relying on that not happening because it never clears the first start flag + m_current_filter = flt->get_type(); + m_filters.emplace(m_current_filter, std::move(flt)); + return true; + } + else + { + return false; + } +} + + +//------------------------------------------------- // static data for software filters //------------------------------------------------- -void software_filter_data::add_region(std::string const &str) +void software_filter_data::add_region(std::string const &longname) { - std::string name(extract_region(str)); + std::string name(extract_region(longname)); std::vector<std::string>::iterator const pos(std::lower_bound(m_regions.begin(), m_regions.end(), name)); - if ((m_regions.end() == pos) || (*pos != str)) + if ((m_regions.end() == pos) || (*pos != name)) m_regions.emplace(pos, std::move(name)); } -void software_filter_data::add_publisher(std::string const &str) +void software_filter_data::add_publisher(std::string const &publisher) { - std::string name(extract_publisher(str)); + std::string name(extract_publisher(publisher)); std::vector<std::string>::iterator const pos(std::lower_bound(m_publishers.begin(), m_publishers.end(), name)); if ((m_publishers.end() == pos) || (*pos != name)) m_publishers.emplace(pos, std::move(name)); @@ -1518,62 +1624,62 @@ std::string software_filter_data::extract_publisher(std::string const &publisher // public machine filter interface //------------------------------------------------- -machine_filter::ptr machine_filter::create(type n, char const *value, emu_file *file, unsigned indent) +machine_filter::ptr machine_filter::create(type n, machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { assert(COUNT > n); switch (n) { case ALL: - return std::make_unique<all_machine_filter>(value, file, indent); + return std::make_unique<all_machine_filter>(data, value, file, indent); case AVAILABLE: - return std::make_unique<available_machine_filter>(value, file, indent); + return std::make_unique<available_machine_filter>(data, value, file, indent); case UNAVAILABLE: - return std::make_unique<unavailable_machine_filter>(value, file, indent); + return std::make_unique<unavailable_machine_filter>(data, value, file, indent); case WORKING: - return std::make_unique<working_machine_filter>(value, file, indent); + return std::make_unique<working_machine_filter>(data, value, file, indent); case NOT_WORKING: - return std::make_unique<not_working_machine_filter>(value, file, indent); + return std::make_unique<not_working_machine_filter>(data, value, file, indent); case MECHANICAL: - return std::make_unique<mechanical_machine_filter>(value, file, indent); + return std::make_unique<mechanical_machine_filter>(data, value, file, indent); case NOT_MECHANICAL: - return std::make_unique<not_mechanical_machine_filter>(value, file, indent); + return std::make_unique<not_mechanical_machine_filter>(data, value, file, indent); case CATEGORY: - return std::make_unique<category_machine_filter>(value, file, indent); + return std::make_unique<category_machine_filter>(data, value, file, indent); case FAVORITE: - return std::make_unique<favorite_machine_filter>(value, file, indent); + return std::make_unique<favorite_machine_filter>(data, value, file, indent); case BIOS: - return std::make_unique<bios_machine_filter>(value, file, indent); + return std::make_unique<bios_machine_filter>(data, value, file, indent); case NOT_BIOS: - return std::make_unique<not_bios_machine_filter>(value, file, indent); + return std::make_unique<not_bios_machine_filter>(data, value, file, indent); case PARENTS: - return std::make_unique<parents_machine_filter>(value, file, indent); + return std::make_unique<parents_machine_filter>(data, value, file, indent); case CLONES: - return std::make_unique<clones_machine_filter>(value, file, indent); + return std::make_unique<clones_machine_filter>(data, value, file, indent); case MANUFACTURER: - return std::make_unique<manufacturer_machine_filter>(value, file, indent); + return std::make_unique<manufacturer_machine_filter>(data, value, file, indent); case YEAR: - return std::make_unique<year_machine_filter>(value, file, indent); + return std::make_unique<year_machine_filter>(data, value, file, indent); case SAVE: - return std::make_unique<save_machine_filter>(value, file, indent); + return std::make_unique<save_machine_filter>(data, value, file, indent); case NOSAVE: - return std::make_unique<nosave_machine_filter>(value, file, indent); + return std::make_unique<nosave_machine_filter>(data, value, file, indent); case CHD: - return std::make_unique<chd_machine_filter>(value, file, indent); + return std::make_unique<chd_machine_filter>(data, value, file, indent); case NOCHD: - return std::make_unique<nochd_machine_filter>(value, file, indent); + return std::make_unique<nochd_machine_filter>(data, value, file, indent); case VERTICAL: - return std::make_unique<vertical_machine_filter>(value, file, indent); + return std::make_unique<vertical_machine_filter>(data, value, file, indent); case HORIZONTAL: - return std::make_unique<horizontal_machine_filter>(value, file, indent); + return std::make_unique<horizontal_machine_filter>(data, value, file, indent); case CUSTOM: - return std::make_unique<custom_machine_filter>(value, file, indent); + return std::make_unique<custom_machine_filter>(data, value, file, indent); case COUNT: // not valid, but needed to suppress warnings break; } return nullptr; } -machine_filter::ptr machine_filter::create(emu_file &file, unsigned indent) +machine_filter::ptr machine_filter::create(emu_file &file, machine_filter_data const &data, unsigned indent) { char buffer[MAX_CHAR_INFO]; if (!file.gets(buffer, ARRAY_LENGTH(buffer))) @@ -1598,7 +1704,7 @@ machine_filter::ptr machine_filter::create(emu_file &file, unsigned indent) for (type n = FIRST; COUNT > n; ++n) { if (key == config_name(n)) - return create(n, value.c_str(), &file, indent); + return create(n, data, value.c_str(), &file, indent); } return nullptr; } @@ -1651,6 +1757,8 @@ software_filter::ptr software_filter::create(type n, software_filter_data const return std::make_unique<available_software_filter>(data, value, file, indent); case UNAVAILABLE: return std::make_unique<unavailable_software_filter>(data, value, file, indent); + case FAVORITE: + return std::make_unique<favorite_software_filter>(data, value, file, indent); case PARENTS: return std::make_unique<parents_software_filter>(data, value, file, indent); case CLONES: @@ -1715,31 +1823,16 @@ software_filter::ptr software_filter::create(emu_file &file, software_filter_dat extern const char UI_VERSION_TAG[]; const char UI_VERSION_TAG[] = "# UI INFO "; -// Years index -std::vector<std::string> c_year::ui; - -// Manufacturers index -std::vector<std::string> c_mnfct::ui; - -// Main filters -ui::machine_filter::type main_filters::actual = ui::machine_filter::ALL; -std::map<ui::machine_filter::type, ui::machine_filter::ptr> main_filters::filters; - // Globals uint8_t ui_globals::rpanel = 0; -uint8_t ui_globals::curimage_view = 0; uint8_t ui_globals::curdats_view = 0; uint8_t ui_globals::cur_sw_dats_total = 0; uint8_t ui_globals::curdats_total = 0; uint8_t ui_globals::cur_sw_dats_view = 0; -bool ui_globals::switch_image = false; -bool ui_globals::default_image = true; bool ui_globals::reset = false; -bool ui_globals::redraw_icon = false; int ui_globals::visible_main_lines = 0; int ui_globals::visible_sw_lines = 0; uint16_t ui_globals::panels_status = 0; -bool ui_globals::has_icons = false; char* chartrimcarriage(char str[]) { @@ -1782,72 +1875,6 @@ std::vector<std::string> tokenize(const std::string &text, char sep) return tokens; } -//------------------------------------------------- -// search a substring with even partial matching -//------------------------------------------------- - -int fuzzy_substring(std::string s_needle, std::string s_haystack) -{ - if (s_needle.empty()) - return s_haystack.size(); - if (s_haystack.empty()) - return s_needle.size(); - - strmakelower(s_needle); - strmakelower(s_haystack); - - if (s_needle == s_haystack) - return 0; - if (s_haystack.find(s_needle) != std::string::npos) - return 0; - - auto *row1 = global_alloc_array_clear<int>(s_haystack.size() + 2); - auto *row2 = global_alloc_array_clear<int>(s_haystack.size() + 2); - - for (int i = 0; i < s_needle.size(); ++i) - { - row2[0] = i + 1; - for (int j = 0; j < s_haystack.size(); ++j) - { - int cost = (s_needle[i] == s_haystack[j]) ? 0 : 1; - row2[j + 1] = std::min(row1[j + 1] + 1, std::min(row2[j] + 1, row1[j] + cost)); - } - - int *tmp = row1; - row1 = row2; - row2 = tmp; - } - - int *first, *smallest; - first = smallest = row1; - int *last = row1 + s_haystack.size(); - - while (++first != last) - if (*first < *smallest) - smallest = first; - - int rv = *smallest; - global_free_array(row1); - global_free_array(row2); - - return rv; -} - -//------------------------------------------------- -// set manufacturers -//------------------------------------------------- - -std::string c_mnfct::getname(const char *str) -{ - std::string name(str); - size_t found = name.find("("); - - if (found != std::string::npos) - return (name.substr(0, found - 1)); - else - return name; -} - ui_software_info::ui_software_info( software_info const &info, diff --git a/src/frontend/mame/ui/utils.h b/src/frontend/mame/ui/utils.h index aaedd75854e..e6f2acd6bc8 100644 --- a/src/frontend/mame/ui/utils.h +++ b/src/frontend/mame/ui/utils.h @@ -31,10 +31,15 @@ class render_container; struct ui_system_info { ui_system_info() { } - ui_system_info(game_driver const &d, bool a) : driver(&d), available(a) { } + ui_system_info(game_driver const &d, int i, bool a) : driver(&d), index(i), available(a) { } game_driver const *driver = nullptr; + int index; bool available = false; + + std::u32string ucs_shortname; + std::u32string ucs_description; + std::u32string ucs_manufacturer_description; }; struct ui_software_info @@ -90,35 +95,8 @@ struct ui_software_info namespace ui { -class software_filter_data -{ -public: - std::vector<std::string> const ®ions() const { return m_regions; } - std::vector<std::string> const &publishers() const { return m_publishers; } - std::vector<std::string> const &years() const { return m_years; } - std::vector<std::string> const &device_types() const { return m_device_types; } - std::vector<std::string> const &list_names() const { return m_list_names; } - std::vector<std::string> const &list_descriptions() const { return m_list_descriptions; } - - // adding entries - void add_region(std::string const &longname); - void add_publisher(std::string const &publisher); - void add_year(std::string const &year); - void add_device_type(std::string const &device_type); - void add_list(std::string const &name, std::string const &description); - void finalise(); - - // use heuristics to extract meaningful parts from software list fields - static std::string extract_region(std::string const &longname); - static std::string extract_publisher(std::string const &publisher); - -private: - std::vector<std::string> m_regions; - std::vector<std::string> m_publishers; - std::vector<std::string> m_years; - std::vector<std::string> m_device_types; - std::vector<std::string> m_list_names, m_list_descriptions; -}; +class machine_filter_data; +class software_filter_data; template <class Impl, typename Entry> @@ -196,8 +174,8 @@ public: virtual type get_type() const = 0; virtual std::string adorned_display_name(type n) const = 0; - static ptr create(type n) { return create(n, nullptr, nullptr, 0); } - static ptr create(emu_file &file) { return create(file, 0); } + static ptr create(type n, machine_filter_data const &data) { return create(n, data, nullptr, nullptr, 0); } + static ptr create(emu_file &file, machine_filter_data const &data) { return create(file, data, 0); } static char const *config_name(type n); static char const *display_name(type n); @@ -207,8 +185,8 @@ public: protected: machine_filter(); - static ptr create(type n, char const *value, emu_file *file, unsigned indent); - static ptr create(emu_file &file, unsigned indent); + static ptr create(type n, machine_filter_data const &data, char const *value, emu_file *file, unsigned indent); + static ptr create(emu_file &file, machine_filter_data const &data, unsigned indent); }; DECLARE_ENUM_INCDEC_OPERATORS(machine_filter::type) @@ -222,6 +200,7 @@ public: ALL = 0, AVAILABLE, UNAVAILABLE, + FAVORITE, PARENTS, CLONES, YEAR, @@ -259,32 +238,82 @@ protected: DECLARE_ENUM_INCDEC_OPERATORS(software_filter::type) -} // namespace ui -#define MAX_CHAR_INFO 256 +class machine_filter_data +{ +public: + std::vector<std::string> const &manufacturers() const { return m_manufacturers; } + std::vector<std::string> const &years() const { return m_years; } -enum + // adding entries + void add_manufacturer(std::string const &manufacturer); + void add_year(std::string const &year); + void finalise(); + + // use heuristics to extract meaningful parts from machine metadata + static std::string extract_manufacturer(std::string const &manufacturer); + + // the selected filter + machine_filter::type get_current_filter_type() const { return m_current_filter; } + void set_current_filter_type(machine_filter::type type) { m_current_filter = type; } + + // managing current filters + void set_filter(machine_filter::ptr &&filter); + auto const &get_filters() { return m_filters; } + machine_filter &get_filter(machine_filter::type type); + machine_filter *get_current_filter() + { + auto const it(m_filters.find(m_current_filter)); + return (m_filters.end() != it) ? it->second.get() : nullptr; + } + std::string get_config_string() const; + bool load_ini(emu_file &file); + +private: + using filter_map = std::map<machine_filter::type, machine_filter::ptr>; + + std::vector<std::string> m_manufacturers; + std::vector<std::string> m_years; + + machine_filter::type m_current_filter = machine_filter::ALL; + filter_map m_filters; +}; + + +class software_filter_data { - 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 +public: + std::vector<std::string> const ®ions() const { return m_regions; } + std::vector<std::string> const &publishers() const { return m_publishers; } + std::vector<std::string> const &years() const { return m_years; } + std::vector<std::string> const &device_types() const { return m_device_types; } + std::vector<std::string> const &list_names() const { return m_list_names; } + std::vector<std::string> const &list_descriptions() const { return m_list_descriptions; } + + // adding entries + void add_region(std::string const &longname); + void add_publisher(std::string const &publisher); + void add_year(std::string const &year); + void add_device_type(std::string const &device_type); + void add_list(std::string const &name, std::string const &description); + void finalise(); + + // use heuristics to extract meaningful parts from software list fields + static std::string extract_region(std::string const &longname); + static std::string extract_publisher(std::string const &publisher); + +private: + std::vector<std::string> m_regions; + std::vector<std::string> m_publishers; + std::vector<std::string> m_years; + std::vector<std::string> m_device_types; + std::vector<std::string> m_list_names, m_list_descriptions; }; +} // namespace ui + +#define MAX_CHAR_INFO 256 + enum { RP_FIRST = 0, @@ -326,34 +355,13 @@ enum // GLOBAL CLASS struct ui_globals { - static uint8_t curimage_view, curdats_view, curdats_total, cur_sw_dats_view, cur_sw_dats_total, rpanel; - static bool switch_image, redraw_icon, default_image, reset; + static uint8_t curdats_view, curdats_total, cur_sw_dats_view, cur_sw_dats_total, rpanel; + static bool default_image, reset; static int visible_main_lines, visible_sw_lines; - static uint16_t panels_status; - static bool has_icons; -}; - -// Manufacturers -struct c_mnfct -{ - static std::string getname(const char *str); - static std::vector<std::string> ui; -}; - -// Years -struct c_year -{ - static std::vector<std::string> ui; -}; - -struct main_filters -{ - static ui::machine_filter::type actual; - static std::map<ui::machine_filter::type, ui::machine_filter::ptr> filters; + static uint16_t panels_status; }; // GLOBAL FUNCTIONS -int fuzzy_substring(std::string needle, std::string haystack); char* chartrimcarriage(char str[]); const char* strensure(const char* s); int getprecisionchr(const char* s); diff --git a/src/frontend/mame/ui/videoopt.cpp b/src/frontend/mame/ui/videoopt.cpp index 7ee243ec4e2..6cf0076734b 100644 --- a/src/frontend/mame/ui/videoopt.cpp +++ b/src/frontend/mame/ui/videoopt.cpp @@ -75,7 +75,7 @@ void menu_video_options::handle() const event *menu_event = process(0); if (menu_event != nullptr && menu_event->itemref != nullptr) { - switch ((uintptr_t)menu_event->itemref) + switch ((intptr_t)menu_event->itemref) { /* rotate adds rotation depending on the direction */ case VIDEO_ITEM_ROTATE: diff --git a/src/frontend/mame/ui/widgets.cpp b/src/frontend/mame/ui/widgets.cpp index cc2c276abfc..d8907303b36 100644 --- a/src/frontend/mame/ui/widgets.cpp +++ b/src/frontend/mame/ui/widgets.cpp @@ -14,6 +14,7 @@ namespace ui { + /*************************************************************************** WIDGETS ***************************************************************************/ @@ -24,12 +25,12 @@ namespace ui { widgets_manager::widgets_manager(running_machine &machine) : m_hilight_bitmap(std::make_unique<bitmap_argb32>(256, 1)) - , m_hilight_texture() + , m_hilight_texture(nullptr, machine.render()) , m_hilight_main_bitmap(std::make_unique<bitmap_argb32>(1, 128)) - , m_hilight_main_texture() + , m_hilight_main_texture(nullptr, machine.render()) + , m_arrow_texture(nullptr, machine.render()) { render_manager &render(machine.render()); - auto const texture_free([&render](render_texture *texture) { render.texture_free(texture); }); // create a texture for hilighting items for (unsigned x = 0; x < 256; ++x) @@ -37,7 +38,7 @@ widgets_manager::widgets_manager(running_machine &machine) unsigned const alpha((x < 25) ? (0xff * x / 25) : (x >(256 - 25)) ? (0xff * (255 - x) / 25) : 0xff); m_hilight_bitmap->pix32(0, x) = rgb_t(alpha, 0xff, 0xff, 0xff); } - m_hilight_texture = texture_ptr(render.texture_alloc(), texture_free); + m_hilight_texture.reset(render.texture_alloc()); m_hilight_texture->set_bitmap(*m_hilight_bitmap, m_hilight_bitmap->cliprect(), TEXFORMAT_ARGB32); // create a texture for hilighting items in main menu @@ -50,11 +51,11 @@ widgets_manager::widgets_manager(running_machine &machine) unsigned const b = b1 + (y * (b2 - b1) / 128); m_hilight_main_bitmap->pix32(y, 0) = rgb_t(r, g, b); } - m_hilight_main_texture = texture_ptr(render.texture_alloc(), texture_free); + m_hilight_main_texture.reset(render.texture_alloc()); m_hilight_main_texture->set_bitmap(*m_hilight_main_bitmap, m_hilight_main_bitmap->cliprect(), TEXFORMAT_ARGB32); // create a texture for arrow icons - m_arrow_texture = texture_ptr(render.texture_alloc(render_triangle), texture_free); + m_arrow_texture.reset(render.texture_alloc(render_triangle)); } diff --git a/src/frontend/mame/ui/widgets.h b/src/frontend/mame/ui/widgets.h index 45f1725d7ec..567b0a4e499 100644 --- a/src/frontend/mame/ui/widgets.h +++ b/src/frontend/mame/ui/widgets.h @@ -21,6 +21,7 @@ namespace ui { + /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ @@ -34,8 +35,17 @@ public: render_texture *hilight_main_texture() { return m_hilight_main_texture.get(); } render_texture *arrow_texture() { return m_arrow_texture.get(); } + class texture_destroyer + { + public: + texture_destroyer(render_manager &manager) : m_manager(manager) { } + void operator()(render_texture *texture) const { m_manager.get().texture_free(texture); } + private: + std::reference_wrapper<render_manager> m_manager; + }; + using bitmap_ptr = std::unique_ptr<bitmap_argb32>; - using texture_ptr = std::unique_ptr<render_texture, std::function<void(render_texture *)> >; + using texture_ptr = std::unique_ptr<render_texture, texture_destroyer>; private: static void render_triangle(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param); |