summaryrefslogtreecommitdiffstats
path: root/docs/release/src/frontend/mame/ui
diff options
context:
space:
mode:
Diffstat (limited to 'docs/release/src/frontend/mame/ui')
-rw-r--r--docs/release/src/frontend/mame/ui/about.cpp266
-rw-r--r--docs/release/src/frontend/mame/ui/inifile.cpp583
2 files changed, 849 insertions, 0 deletions
diff --git a/docs/release/src/frontend/mame/ui/about.cpp b/docs/release/src/frontend/mame/ui/about.cpp
new file mode 100644
index 00000000000..aa7de9671fc
--- /dev/null
+++ b/docs/release/src/frontend/mame/ui/about.cpp
@@ -0,0 +1,266 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/***************************************************************************
+
+ ui/about.cpp
+
+ About box
+
+***************************************************************************/
+
+#include "emu.h"
+#include "ui/about.h"
+
+#include "ui/ui.h"
+#include "ui/utils.h"
+
+#include "mame.h"
+
+#include <string_view>
+
+
+namespace ui {
+
+namespace {
+
+#include "copying.ipp"
+
+} // anonymous namespace
+
+
+/**************************************************
+ ABOUT BOX
+**************************************************/
+
+
+//-------------------------------------------------
+// ctor
+//-------------------------------------------------
+
+menu_about::menu_about(mame_ui_manager &mui, render_container &container)
+ : menu(mui, container)
+ , m_header{
+ util::string_format(
+#ifdef MAME_DEBUG
+ _("%1$s %2$s (%3$s%4$sP%5$s, debug)"),
+#else
+ _("%1$s %2$s (%3$s%4$sP%5$s)"),
+#endif
+ emulator_info::get_appname(),
+ bare_build_version,
+ (sizeof(int) == sizeof(void *)) ? "I" : "",
+ (sizeof(long) == sizeof(void *)) ? "L" : (sizeof(long long) == sizeof(void *)) ? "LL" : "",
+ sizeof(void *) * 8),
+ /*util::string_format(_("Revision: %1$s"), bare_vcs_revision)*/" " } // MESSUI
+{
+}
+
+
+//-------------------------------------------------
+// dtor
+//-------------------------------------------------
+
+menu_about::~menu_about()
+{
+}
+
+
+//-------------------------------------------------
+// perform our special rendering
+//-------------------------------------------------
+
+void menu_about::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
+{
+ // draw the title
+ draw_text_box(
+ std::begin(m_header), std::end(m_header),
+ origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(),
+ text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false,
+ ui().colors().text_color(), UI_GREEN_COLOR, 1.0f);
+}
+
+
+//-------------------------------------------------
+// draw - draw about
+//-------------------------------------------------
+
+void menu_about::draw(uint32_t flags)
+{
+ rgb_t const color = ui().colors().text_color();
+ float const aspect = machine().render().ui_aspect(&container());
+ float const line_height = ui().get_line_height();
+ float const ud_arrow_width = line_height * aspect;
+ float const gutter_width = 0.52f * line_height * aspect;
+ float const visible_width = 1.0f - (2.0f * ui().box_lr_border() * aspect);
+ float const visible_left = (1.0f - visible_width) * 0.5f;
+ float const extra_height = 2.0f * line_height;
+ float const visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height;
+
+ // determine effective positions taking into account the hilighting arrows
+ float const maximum_width = visible_width - 2.0f * gutter_width;
+
+ draw_background();
+ map_mouse();
+
+ // account for extra space at the top and bottom
+ float visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height;
+ m_visible_lines = int(std::trunc(visible_main_menu_height / line_height));
+ visible_main_menu_height = float(m_visible_lines) * line_height;
+
+ // compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust
+ float const visible_top = ((1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f) + get_customtop();
+
+ // lay out the text if necessary
+ if (!m_layout || (m_layout->width() != maximum_width))
+ {
+ m_layout.emplace(ui().create_layout(container(), maximum_width));
+ for (char const *const *line = copying_text; *line; ++line)
+ {
+ m_layout->add_text(*line, color);
+ m_layout->add_text("\n", color);
+ }
+ }
+ float const actual_width = m_layout->actual_width();
+
+ // compute text box size
+ float const x1 = visible_left + ((maximum_width - actual_width) * 0.5f);
+ float const y1 = visible_top - ui().box_tb_border();
+ float const x2 = visible_left + visible_width - ((maximum_width - actual_width) * 0.5f);
+ float const y2 = visible_top + visible_main_menu_height + ui().box_tb_border() + extra_height;
+ float const effective_left = x1 + gutter_width;
+ float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH;
+ float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH;
+ float const separator = visible_top + float(m_visible_lines) * line_height;
+
+ ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color());
+
+ int const visible_items = m_layout->lines();
+ m_visible_lines = (std::min)(visible_items, m_visible_lines);
+ top_line = (std::max)(0, top_line);
+ if (top_line + m_visible_lines >= visible_items)
+ top_line = visible_items - m_visible_lines;
+
+ clear_hover();
+ if (top_line)
+ {
+ // if we're on the top line, display the up arrow
+ rgb_t fgcolor = ui().colors().text_color();
+ if (mouse_in_rect(line_x0, visible_top, line_x1, visible_top + line_height))
+ {
+ fgcolor = ui().colors().mouseover_color();
+ highlight(
+ line_x0, visible_top,
+ line_x1, visible_top + line_height,
+ ui().colors().mouseover_bg_color());
+ set_hover(HOVER_ARROW_UP);
+ }
+ draw_arrow(
+ 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, visible_top + 0.25f * line_height,
+ 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, visible_top + 0.75f * line_height,
+ fgcolor, ROT0);
+ }
+ if ((top_line + m_visible_lines) < visible_items)
+ {
+ // if we're on the bottom line, display the down arrow
+ float const line_y = visible_top + float(m_visible_lines - 1) * line_height;
+ rgb_t fgcolor = ui().colors().text_color();
+ if (mouse_in_rect(line_x0, line_y, line_x1, line_y + line_height))
+ {
+ fgcolor = ui().colors().mouseover_color();
+ highlight(
+ line_x0, line_y,
+ line_x1, line_y + line_height,
+ ui().colors().mouseover_bg_color());
+ set_hover(HOVER_ARROW_DOWN);
+ }
+ draw_arrow(
+ 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, line_y + 0.25f * line_height,
+ 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, line_y + 0.75f * line_height,
+ fgcolor, ROT0 ^ ORIENTATION_FLIP_Y);
+ }
+
+ // return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow
+ m_visible_items = m_visible_lines - (top_line ? 1 : 0) - (top_line + m_visible_lines != visible_items);
+ m_layout->emit(
+ container(),
+ top_line ? (top_line + 1) : 0, m_visible_items,
+ effective_left, visible_top + (top_line ? line_height : 0.0f));
+
+ // add visual separator before the "return to prevous menu" item
+ container().add_line(
+ x1, separator + (0.5f * line_height),
+ x2, separator + (0.5f * line_height),
+ UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
+
+ menu_item const &pitem = item(0);
+ std::string_view const itemtext = pitem.text;
+ float const line_y0 = separator + line_height;
+ float const line_y1 = line_y0 + line_height;
+
+ if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem))
+ set_hover(0);
+
+ highlight(line_x0, line_y0, line_x1, line_y1, ui().colors().selected_bg_color());
+ ui().draw_text_full(
+ container(), itemtext,
+ effective_left, line_y0, actual_width,
+ text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE,
+ mame_ui_manager::NORMAL,
+ ui().colors().selected_color(), ui().colors().selected_bg_color(),
+ nullptr, nullptr);
+
+ // if there is something special to add, do it by calling the virtual method
+ custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2);
+}
+
+
+//-------------------------------------------------
+// populate - populates the about modal
+//-------------------------------------------------
+
+void menu_about::populate(float &customtop, float &custombottom)
+{
+ // make space for the title and revision
+ customtop = (ui().get_line_height() * m_header.size()) + (ui().box_tb_border() * 3.0f);
+}
+
+
+//-------------------------------------------------
+// handle - manages inputs in the about modal
+//-------------------------------------------------
+
+void menu_about::handle()
+{
+ const event *event = process(PROCESS_CUSTOM_NAV);
+ if (event)
+ {
+ switch (event->iptkey)
+ {
+ case IPT_UI_UP:
+ --top_line;
+ break;
+
+ case IPT_UI_DOWN:
+ ++top_line;
+ break;
+
+ case IPT_UI_PAGE_UP:
+ top_line -= m_visible_lines - 3;
+ break;
+
+ case IPT_UI_PAGE_DOWN:
+ top_line += m_visible_lines - 3;
+ break;
+
+ case IPT_UI_HOME:
+ top_line = 0;
+ break;
+
+ case IPT_UI_END:
+ top_line = m_layout->lines() - m_visible_lines;
+ break;
+ }
+ }
+}
+
+};
diff --git a/docs/release/src/frontend/mame/ui/inifile.cpp b/docs/release/src/frontend/mame/ui/inifile.cpp
new file mode 100644
index 00000000000..02713cd5f57
--- /dev/null
+++ b/docs/release/src/frontend/mame/ui/inifile.cpp
@@ -0,0 +1,583 @@
+// license:BSD-3-Clause
+// copyright-holders:Maurizio Petrarota, Vas Crabb
+/***************************************************************************
+
+ ui/inifile.cpp
+
+ UI INIs file manager.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "ui/inifile.h"
+
+#include "ui/moptions.h"
+
+#include "language.h"
+
+#include "drivenum.h"
+#include "softlist_dev.h"
+
+#include "corestr.h"
+
+#include <algorithm>
+#include <cstring>
+#include <iterator>
+
+
+namespace {
+
+char const FAVORITE_FILENAME[] = "favorites.ini";
+
+} // anonymous namespace
+
+
+//-------------------------------------------------
+// ctor
+//-------------------------------------------------
+
+inifile_manager::inifile_manager(ui_options &options)
+ : m_options(options)
+ , m_ini_index()
+{
+ // scan directories and create index
+ file_enumerator path(m_options.categoryini_path());
+ for (osd::directory::entry const *dir = path.next(); dir; dir = path.next())
+ {
+ std::string name(dir->name);
+ if (core_filename_ends_with(name, ".ini"))
+ {
+ emu_file file(m_options.categoryini_path(), OPEN_FLAG_READ);
+ if (!file.open(name))
+ {
+ init_category(std::move(name), file);
+ file.close();
+ }
+ }
+ }
+ std::stable_sort(m_ini_index.begin(), m_ini_index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); });
+}
+
+//-------------------------------------------------
+// load and indexing ini files
+//-------------------------------------------------
+
+// HBMAME - rewritten using native file usage because mame's file.gets doesn't always work
+void inifile_manager::load_ini_category(size_t file, size_t category, std::unordered_set<game_driver const *> &result) const
+{
+ std::string const &filename(m_ini_index[file].first);
+ FILE *fp = fopen(filename.c_str(), "r");
+ if (!fp)
+ {
+ osd_printf_error("Failed to open category file %s for reading\n", filename);
+ return;
+ }
+
+ int64_t const offset(m_ini_index[file].second[category].second);
+ if (fseek(fp, offset, SEEK_SET))
+ {
+ fclose(fp);
+ osd_printf_error("Failed to seek to category offset in file %s\n", filename);
+ return;
+ }
+
+ char rbuf[MAX_CHAR_INFO];
+ while (fgets(rbuf, MAX_CHAR_INFO, fp) && rbuf[0] && ('[' != rbuf[0]))
+ {
+ auto const tail(std::find_if(std::begin(rbuf), std::prev(std::end(rbuf)), [] (char ch) { return !ch || ('\r' == ch) || ('\n' == ch); }));
+ *tail = '\0';
+ int const dfind(driver_list::find(rbuf));
+ if (0 <= dfind)
+ result.emplace(&driver_list::driver(dfind));
+ }
+
+ fclose(fp);
+}
+
+//-------------------------------------------------
+// initialize category
+//-------------------------------------------------
+
+// HBMAME - rewritten using native file usage because mame's file.gets doesn't always work
+void inifile_manager::init_category(std::string &&filename, emu_file &file)
+{
+ FILE *fp;
+ fp = fopen(file.fullpath(), "r");
+ if (!fp)
+ return;
+ categoryindex index;
+ char rbuf[MAX_CHAR_INFO];
+ std::string name;
+ while (fgets(rbuf, std::size(rbuf), fp))
+ {
+ if ('[' == rbuf[0])
+ {
+ auto const head(std::next(std::begin(rbuf)));
+ auto const tail(std::find_if(head, std::end(rbuf), [] (char ch) { return !ch || (']' == ch); }));
+ name.assign(head, tail);
+ if ("FOLDER_SETTINGS" != name)
+ index.emplace_back(std::move(name), ftell(fp));
+ }
+ }
+ std::stable_sort(index.begin(), index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); });
+ if (!index.empty())
+ m_ini_index.emplace_back(file.fullpath(), std::move(index));
+
+ fclose(fp);
+}
+
+
+/**************************************************************************
+ FAVORITE MANAGER
+**************************************************************************/
+
+bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, ui_software_info const &rhs) const
+{
+ assert(lhs.driver);
+ assert(rhs.driver);
+
+ if (!lhs.startempty)
+ {
+ 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;
+ }
+ else if (!rhs.startempty)
+ {
+ return true;
+ }
+
+ return 0 > std::strncmp(lhs.driver->name, rhs.driver->name, std::size(lhs.driver->name));
+}
+
+bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, game_driver const &rhs) const
+{
+ assert(lhs.driver);
+
+ if (!lhs.startempty)
+ return false;
+ else
+ return 0 > std::strncmp(lhs.driver->name, rhs.name, std::size(rhs.name));
+}
+
+bool favorite_manager::favorite_compare::operator()(game_driver const &lhs, ui_software_info const &rhs) const
+{
+ assert(rhs.driver);
+
+ if (!rhs.startempty)
+ return true;
+ else
+ return 0 > std::strncmp(lhs.name, rhs.driver->name, std::size(lhs.name));
+}
+
+bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, running_software_key const &rhs) const
+{
+ 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, std::size(lhs.driver->name));
+}
+
+bool favorite_manager::favorite_compare::operator()(running_software_key const &lhs, ui_software_info const &rhs) const
+{
+ 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, std::size(rhs.driver->name));
+}
+
+
+//-------------------------------------------------
+// construction/destruction
+//-------------------------------------------------
+
+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))
+ {
+ char readbuf[1024];
+ file.gets(readbuf, std::size(readbuf));
+
+ while (readbuf[0] == '[')
+ file.gets(readbuf, std::size(readbuf));
+
+ while (file.gets(readbuf, std::size(readbuf)))
+ {
+ ui_software_info tmpmatches;
+ tmpmatches.shortname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.longname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.parentname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.year = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.publisher = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.supported = software_support(atoi(readbuf));
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.part = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ chartrimcarriage(readbuf);
+ auto dx = driver_list::find(readbuf);
+ if (0 > dx)
+ continue;
+ tmpmatches.driver = &driver_list::driver(dx);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.listname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.interface = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.instance = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.startempty = atoi(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.parentlongname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ //tmpmatches.usage = chartrimcarriage(readbuf); TODO: recover multi-line info
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.devicetype = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.available = atoi(readbuf);
+
+ // need to populate this, it isn't displayed anywhere else
+ tmpmatches.infotext.append(tmpmatches.longname);
+ tmpmatches.infotext.append(1, '\n');
+ tmpmatches.infotext.append(_("swlist-info", "Software list/item"));
+ tmpmatches.infotext.append(1, '\n');
+ tmpmatches.infotext.append(tmpmatches.listname);
+ tmpmatches.infotext.append(1, ':');
+ tmpmatches.infotext.append(tmpmatches.shortname);
+
+ 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
+ software_part const *const part(imagedev->part_entry());
+ assert(software);
+ assert(part);
+ ui_software_info info(
+ *software,
+ *part,
+ driver,
+ imagedev->software_list_name(),
+ imagedev->instance_name(),
+ strensure(imagedev->image_type_name()));
+
+ // assume it's available if it's mounted
+ 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;
+ }
+ }
+ }
+
+ // 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;
+ }
+}
+
+
+//-------------------------------------------------
+// implementation
+//-------------------------------------------------
+
+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_enumerator(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))
+ {
+ if (m_favorites.empty())
+ {
+ // delete it if there are no favorites
+ file.remove_on_close();
+ }
+ else
+ {
+ // 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", int(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 << '\n'; //buf << info.usage << '\n'; TODO: store multi-line info in a recoverable format
+ buf << info.devicetype << '\n';
+ util::stream_format(buf, "%d\n", info.available);
+
+ file.puts(util::buf_to_string_view(buf));
+ }
+ }
+ file.close();
+ }
+}