// license:BSD-3-Clause // copyright-holders:Maurizio Petrarota, Vas Crabb /*************************************************************************** ui/utils.cpp Internal UI user interface. ***************************************************************************/ #include "emu.h" #include "ui/utils.h" #include "ui/inifile.h" #include "ui/selector.h" #include "infoxml.h" #include "language.h" #include "mame.h" #include "drivenum.h" #include "rendfont.h" #include "romload.h" #include "corestr.h" #include #include #include #include #include #include #include #include namespace ui { namespace { constexpr char const *SOFTWARE_REGIONS[] = { "arab", "arg", "asia", "aus", "aut", "bel", "blr", "bra", "can", "chi", "chn", "cze", "den", "ecu", "esp", "euro", "fin", "fra", "gbr", "ger", "gre", "hkg", "hun", "irl", "isr", "isv", "ita", "jpn", "kaz", "kor", "lat", "lux", "mex", "ned", "nld", "nor", "nzl", "pol", "rus", "slo", "spa", "sui", "swe", "tha", "tpe", "tw", "uk", "ukr", "usa" }; // must be sorted in std::string comparison order constexpr std::pair SOFTWARE_INFO_NAMES[] = { { "alt_title", N_p("swlist-info", "Alternate Title") }, { "author", N_p("swlist-info", "Author") }, { "barcode", N_p("swlist-info", "Barcode Number") }, { "developer", N_p("swlist-info", "Developer") }, { "distributor", N_p("swlist-info", "Distributor") }, { "install", N_p("swlist-info", "Installation Instructions") }, { "isbn", N_p("swlist-info", "ISBN") }, { "oem", N_p("swlist-info", "OEM") }, { "original_publisher", N_p("swlist-info", "Original Publisher") }, { "partno", N_p("swlist-info", "Part Number") }, { "pcb", N_p("swlist-info", "PCB") }, { "programmer", N_p("swlist-info", "Programmer") }, { "release", N_p("swlist-info", "Release Date") }, { "serial", N_p("swlist-info", "Serial Number") }, { "usage", N_p("swlist-info", "Usage Instructions") }, { "version", N_p("swlist-info", "Version") } }; // must be in sync with the machine_filter::type enum constexpr char const *MACHINE_FILTER_NAMES[machine_filter::COUNT] = { N_p("machine-filter", "Unfiltered"), N_p("machine-filter", "Available"), N_p("machine-filter", "Unavailable"), N_p("machine-filter", "Working"), N_p("machine-filter", "Not Working"), N_p("machine-filter", "Mechanical"), N_p("machine-filter", "Not Mechanical"), N_p("machine-filter", "Category"), N_p("machine-filter", "Favorites"), N_p("machine-filter", "BIOS"), N_p("machine-filter", "Not BIOS"), N_p("machine-filter", "Parents"), N_p("machine-filter", "Clones"), N_p("machine-filter", "Manufacturer"), N_p("machine-filter", "Year"), N_p("machine-filter", "Source File"), N_p("machine-filter", "Save Supported"), N_p("machine-filter", "Save Unsupported"), N_p("machine-filter", "CHD Required"), N_p("machine-filter", "No CHD Required"), N_p("machine-filter", "Vertical Screen"), N_p("machine-filter", "Horizontal Screen"), N_p("machine-filter", "Custom Filter") }; // must be in sync with the software_filter::type enum constexpr char const *SOFTWARE_FILTER_NAMES[software_filter::COUNT] = { N_p("software-filter", "Unfiltered"), N_p("software-filter", "Available"), N_p("software-filter", "Unavailable"), N_p("software-filter", "Favorites"), N_p("software-filter", "Parents"), N_p("software-filter", "Clones"), N_p("software-filter", "Year"), N_p("software-filter", "Publisher"), N_p("software-filter", "Developer"), N_p("software-filter", "Distributor"), N_p("software-filter", "Author"), N_p("software-filter", "Programmer"), N_p("software-filter", "Supported"), N_p("software-filter", "Partially Supported"), N_p("software-filter", "Unsupported"), N_p("software-filter", "Release Region"), N_p("software-filter", "Device Type"), N_p("software-filter", "Software List"), N_p("software-filter", "Custom Filter") }; //------------------------------------------------- // helper for building a sorted vector //------------------------------------------------- template void add_info_value(std::vector &items, T &&value) { std::vector::iterator const pos(std::lower_bound(items.begin(), items.end(), value)); if ((items.end() == pos) || (*pos != value)) items.emplace(pos, std::forward(value)); } //------------------------------------------------- // base implementation for simple filters //------------------------------------------------- template class simple_filter_impl_base : public Base { public: virtual char const *config_name() const override { return Base::config_name(Type); } virtual char const *display_name() const override { return Base::display_name(Type); } virtual char const *filter_text() const override { return nullptr; } virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function &&handler) override { handler(*this); } virtual bool wants_adjuster() const override { return false; } virtual char const *adjust_text() const override { return filter_text(); } virtual uint32_t arrow_flags() const override { return 0; } virtual bool adjust_left() override { return false; } virtual bool adjust_right() override { return false; } virtual void save_ini(util::core_file &file, unsigned indent) const override { file.puts(util::string_format("%2$*1$s%3$s = 1\n", 2 * indent, "", config_name())); } virtual typename Base::type get_type() const override { return Type; } virtual std::string adorned_display_name(typename Base::type n) const override { std::string result; if (Type == n) result = convert_command_glyph("_> "); result.append(Base::display_name(n)); return result; } using Base::config_name; using Base::display_name; protected: simple_filter_impl_base() { } }; //------------------------------------------------- // base implementation for single-choice filters //------------------------------------------------- template class choice_filter_impl_base : public simple_filter_impl_base { public: virtual char const *filter_text() const override { return selection_valid() ? selection_text().c_str() : nullptr; } virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function &&handler) override { menu::stack_push( mui, container, _("Filter"), // TODO: get localised name of filter in here somehow std::vector(m_choices), // ouch, a vector copy! m_selection, [this, cb = std::move(handler)] (int selection) { m_selection = selection; cb(*this); }); } virtual bool wants_adjuster() const override { return have_choices(); } virtual uint32_t arrow_flags() const override { return ((have_choices() && m_selection) ? menu::FLAG_LEFT_ARROW : 0) | ((m_choices.size() > (m_selection + 1)) ? menu::FLAG_RIGHT_ARROW : 0); } virtual bool adjust_left() override { if (!have_choices() || !m_selection) return false; m_selection = (std::min)(m_selection - 1, unsigned(m_choices.size() - 1)); return true; } virtual bool adjust_right() override { if (m_choices.size() <= (m_selection + 1)) return false; ++m_selection; return true; } virtual void save_ini(util::core_file &file, unsigned indent) const override { char const *const text(filter_text()); file.puts(util::string_format("%2$*1$s%3$s = %4$s\n", 2 * indent, "", this->config_name(), text ? text : "")); } protected: choice_filter_impl_base(std::vector const &choices, char const *value) : m_choices(choices) , m_selection(0U) { if (value) set_value(value); } void set_value(char const *value) { auto const found(std::find(m_choices.begin(), m_choices.end(), value)); if (m_choices.end() != found) m_selection = std::distance(m_choices.begin(), found); } bool have_choices() const { return !m_choices.empty(); } bool selection_valid() const { return m_choices.size() > m_selection; } unsigned selection_index() const { return m_selection; } std::string const &selection_text() const { return m_choices[m_selection]; } private: std::vector const &m_choices; unsigned m_selection; }; //------------------------------------------------- // base implementation for composite filters //------------------------------------------------- template class composite_filter_impl_base : public simple_filter_impl_base { public: virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function &&handler) override; virtual bool wants_adjuster() const override { return true; } virtual char const *adjust_text() const override { return _(""); } virtual void save_ini(util::core_file &file, unsigned indent) const override { auto const tail(std::find_if(std::begin(m_filters), std::end(m_filters), [] (typename Base::ptr const &flt) { return !flt; })); file.puts(util::string_format("%2$*1$s%3$s = %4$d\n", 2 * indent, "", this->config_name(), std::distance(std::begin(m_filters), tail))); for (auto it = std::begin(m_filters); tail != it; ++it) (*it)->save_ini(file, indent + 1); } virtual std::string adorned_display_name(typename Base::type n) const override { std::string result; if (Type == n) result = convert_command_glyph("_> "); else { for (unsigned i = 0; (MAX > i) && m_filters[i]; ++i) { if (m_filters[i]->get_type() == n) { result = convert_command_glyph(util::string_format("@custom%u ", i + 1)); break; } } } result.append(Base::display_name(n)); return result; } virtual bool apply(typename Base::entry_type const &info) const override { std::bitset inclusions, included; for (typename Base::ptr const &flt : m_filters) { if (!flt) break; typename Base::type const t(flt->get_type()); if (Impl::is_inclusion(t)) { if (!included.test(t)) { inclusions.set(t); included.set(t, flt->apply(info)); } } else if (!flt->apply(info)) { return false; } } return inclusions == included; } protected: composite_filter_impl_base() { } void populate(char const *value, util::core_file *file, unsigned indent) { // try to load filters from a file if (value && file) { unsigned const cnt(std::clamp(std::atoi(value), 0, MAX)); for (unsigned i = 0; cnt > i; ++i) { typename Base::ptr flt(static_cast(*this).create(*file, indent + 1)); if (!flt || !check_type(i, flt->get_type())) break; m_filters[i] = std::move(flt); } } // instantiate first allowed filter type if we're still empty for (typename Base::type t = Base::FIRST; (Base::COUNT > t) && !m_filters[0]; ++t) { if (Impl::type_allowed(0, t)) m_filters[0] = static_cast(*this).create(t); } } private: static constexpr unsigned MAX = 8; class menu_configure : public menu { public: menu_configure( mame_ui_manager &mui, render_container &container, Impl &parent, std::function &&handler) : menu(mui, container) , m_parent(parent) , m_handler(std::move(handler)) , m_added(false) { set_process_flags(PROCESS_LR_REPEAT); set_heading(_("Select Filters")); } virtual ~menu_configure() override { m_handler(m_parent); } private: enum : uintptr_t { FILTER_FIRST = 1, FILTER_LAST = FILTER_FIRST + MAX - 1, ADJUST_FIRST, ADJUST_LAST = ADJUST_FIRST + MAX - 1, REMOVE_FILTER, ADD_FILTER }; virtual void populate() override; virtual bool handle(event const *ev) override; bool set_filter_type(unsigned pos, typename Base::type n) { if (!m_parent.m_filters[pos] || (m_parent.m_filters[pos]->get_type())) { save_filter(pos); retrieve_filter(pos, n); return true; } else { return false; } } bool append_filter() { unsigned pos = 0; while (m_parent.m_filters[pos]) { if (MAX <= ++pos) return false; } for (typename Base::type candidate = Base::FIRST; Base::COUNT > candidate; ++candidate) { if (m_parent.check_type(pos, candidate)) { set_filter_type(pos, candidate); return true; } } return false; } bool drop_last_filter() { for (unsigned i = 2; MAX >= i; ++i) { if ((MAX <= i) || !m_parent.m_filters[i]) { save_filter(i - 1); m_parent.m_filters[i - 1].reset(); return true; } } return false; } void save_filter(unsigned pos) { typename Base::ptr &flt(m_parent.m_filters[pos]); if (flt && flt->wants_adjuster()) m_saved_filters[pos][flt->get_type()] = std::move(flt); } void retrieve_filter(unsigned pos, typename Base::type n) { typename Base::ptr &flt(m_parent.m_filters[pos]); auto const found(m_saved_filters[pos].find(n)); if (m_saved_filters[pos].end() != found) { flt = std::move(found->second); m_saved_filters[pos].erase(found); } else { flt = m_parent.create(n); } } uint32_t get_arrow_flags(unsigned pos) { uint32_t result(0); typename Base::type const current(m_parent.m_filters[pos]->get_type()); // look for a lower type that's allowed and isn't contradictory typename Base::type prev(current); while ((Base::FIRST < prev) && !(FLAG_LEFT_ARROW & result)) { if (m_parent.check_type(pos, --prev)) result |= FLAG_LEFT_ARROW; } // look for a higher type that's allowed and isn't contradictory typename Base::type next(current); while ((Base::LAST > next) && !(FLAG_RIGHT_ARROW & result)) { if (m_parent.check_type(pos, ++next)) result |= FLAG_RIGHT_ARROW; } return result; } Impl &m_parent; std::map m_saved_filters[MAX]; std::function m_handler; bool m_added; }; bool check_type(unsigned pos, typename Base::type candidate) { if (!Impl::type_allowed(pos, candidate)) return false; unsigned j = 0; while ((MAX > j) && m_filters[j] && ((pos == j) || !Impl::types_contradictory(m_filters[j]->get_type(), candidate))) ++j; return (MAX <= j) || !m_filters[j]; }; typename Base::ptr m_filters[MAX]; }; template void composite_filter_impl_base::show_ui( mame_ui_manager &mui, render_container &container, std::function &&handler) { menu::stack_push(mui, container, static_cast(*this), std::move(handler)); } template void composite_filter_impl_base::menu_configure::populate() { // add items for each active filter unsigned i = 0; for (i = 0; (MAX > i) && m_parent.m_filters[i]; ++i) { item_append(util::string_format(_("Filter %1$u"), i + 1), m_parent.m_filters[i]->display_name(), get_arrow_flags(i), (void *)(FILTER_FIRST + i)); if (m_added) set_selected_index(item_count() - 2); if (m_parent.m_filters[i]->wants_adjuster()) { std::string name(convert_command_glyph("^!")); item_append(name, m_parent.m_filters[i]->adjust_text(), m_parent.m_filters[i]->arrow_flags(), (void *)(ADJUST_FIRST + i)); } item_append(menu_item_type::SEPARATOR); } m_added = false; // add remove/add handlers if (1 < i) item_append(_("Remove last filter"), 0, (void *)REMOVE_FILTER); if (MAX > i) item_append(_("Add filter"), 0, (void *)ADD_FILTER); item_append(menu_item_type::SEPARATOR); } template bool composite_filter_impl_base::menu_configure::handle(event const *ev) { if (!ev || !ev->itemref) return false; m_added = false; bool changed(false); uintptr_t const ref(reinterpret_cast(ev->itemref)); switch (ev->iptkey) { case IPT_UI_LEFT: case IPT_UI_RIGHT: if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref)) { // change filter type unsigned const pos(ref - FILTER_FIRST); typename Base::type const current(m_parent.m_filters[pos]->get_type()); if (IPT_UI_LEFT == ev->iptkey) { typename Base::type n(current); while ((Base::FIRST < n) && !changed) { if (m_parent.check_type(pos, --n)) changed = set_filter_type(pos, n); } } else { typename Base::type n(current); while ((Base::LAST > n) && !changed) { if (m_parent.check_type(pos, ++n)) changed = set_filter_type(pos, n); } } } else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref)) { // change filter value Base &pos(*m_parent.m_filters[ref - ADJUST_FIRST]); changed = (IPT_UI_LEFT == ev->iptkey) ? pos.adjust_left() : pos.adjust_right(); } break; case IPT_UI_SELECT: if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref)) { // show selector with non-contradictory types std::vector types; std::vector names; types.reserve(Base::COUNT); names.reserve(Base::COUNT); int sel(-1); unsigned const pos(ref - FILTER_FIRST); typename Base::type const current(m_parent.m_filters[pos]->get_type()); for (typename Base::type candidate = Base::FIRST; Base::COUNT > candidate; ++candidate) { if (Impl::type_allowed(pos, candidate)) { if (current == candidate) sel = types.size(); unsigned i = 0; while ((MAX > i) && m_parent.m_filters[i] && ((pos == i) || !Impl::types_contradictory(m_parent.m_filters[i]->get_type(), candidate))) ++i; if ((MAX <= i) || !m_parent.m_filters[i]) { types.emplace_back(candidate); names.emplace_back(Base::display_name(candidate)); } } } menu::stack_push( ui(), container(), std::string(ev->item->text()), std::move(names), sel, [this, pos, t = std::move(types)] (int selection) { if (set_filter_type(pos, t[selection])) reset(reset_options::REMEMBER_REF); }); } else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref)) { // show selected filter's UI m_parent.m_filters[ref - ADJUST_FIRST]->show_ui(ui(), container(), [this] (Base &filter) { reset(reset_options::REMEMBER_REF); }); } else if (REMOVE_FILTER == ref) { changed = drop_last_filter(); } else if (ADD_FILTER == ref) { m_added = append_filter(); } break; } // rebuild if anything changed if (changed) reset(reset_options::REMEMBER_REF); else if (m_added) reset(reset_options::SELECT_FIRST); return false; } //------------------------------------------------- // invertable machine filters //------------------------------------------------- template class available_machine_filter_impl : public simple_filter_impl_base { public: available_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.available; } }; template class working_machine_filter_impl : public simple_filter_impl_base { public: working_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return !(system.driver->flags & machine_flags::NOT_WORKING); } }; template class mechanical_machine_filter_impl : public simple_filter_impl_base { public: mechanical_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::MECHANICAL; } }; template class bios_machine_filter_impl : public simple_filter_impl_base { public: bios_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::IS_BIOS_ROOT; } }; template class parents_machine_filter_impl : public simple_filter_impl_base { public: parents_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { bool const have_parent(strcmp(system.driver->parent, "0")); auto const parent_idx(have_parent ? driver_list::find(system.driver->parent) : -1); return !have_parent || (0 > parent_idx) || (driver_list::driver(parent_idx).flags & machine_flags::IS_BIOS_ROOT); } }; template class chd_machine_filter_impl : public simple_filter_impl_base { public: chd_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { for (tiny_rom_entry const *rom = system.driver->rom; !ROMENTRY_ISEND(rom); ++rom) { if (ROMENTRY_ISREGION(rom) && ROMREGION_ISDISKDATA(rom)) return true; } return false; } }; template class save_machine_filter_impl : public simple_filter_impl_base { public: save_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SUPPORTS_SAVE; } }; template class vertical_machine_filter_impl : public simple_filter_impl_base { public: vertical_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SWAP_XY; } }; //------------------------------------------------- // concrete machine filters //------------------------------------------------- class manufacturer_machine_filter : public choice_filter_impl_base { public: manufacturer_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base(data.manufacturers(), value) { } virtual bool apply(ui_system_info const &system) const override { if (!have_choices()) return true; else if (!selection_valid()) return false; std::string const name(machine_filter_data::extract_manufacturer(system.driver->manufacturer)); return !name.empty() && (selection_text() == name); } }; class year_machine_filter : public choice_filter_impl_base { public: year_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base(data.years(), value) { } virtual bool apply(ui_system_info const &system) const override { return !have_choices() || (selection_valid() && (selection_text() == system.driver->year)); } }; class source_file_machine_filter : public choice_filter_impl_base { public: source_file_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base(data.source_files(), value) { } virtual bool apply(ui_system_info const &system) const override { return !have_choices() || (selection_valid() && (selection_text() == info_xml_creator::format_sourcefile(system.driver->type.source()))); } }; //------------------------------------------------- // complementary machine filters //------------------------------------------------- template