// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods
/*********************************************************************
ui/swlist.cpp
Internal MAME user interface for software list.
*********************************************************************/
#include "emu.h"
#include "ui/ui.h"
#include "ui/swlist.h"
#include "ui/utils.h"
#include "corestr.h"
#include "softlist_dev.h"
#include <locale>
namespace ui {
/***************************************************************************
CONSTANTS
***************************************************************************/
// time (in seconds) to display errors
#define ERROR_MESSAGE_TIME 5
// item reference for "Switch Item Ordering"
#define ITEMREF_SWITCH_ITEM_ORDERING ((void *)1)
/***************************************************************************
SOFTWARE PARTS
***************************************************************************/
//-------------------------------------------------
// is_valid_softlist_part_char - returns whether
// this character is a valid char for a softlist
// part
//-------------------------------------------------
static bool is_valid_softlist_part_char(char32_t ch)
{
return (ch == (char)ch) && isalnum(ch);
}
//-------------------------------------------------
// ctor
//-------------------------------------------------
menu_software_parts::menu_software_parts(mame_ui_manager &mui, render_container &container, const software_info *info, const char *interface, const software_part **part, bool other_opt, result &result)
: menu(mui, container)
, m_result(result)
{
m_info = info;
m_interface = interface;
m_selected_part = part;
m_other_opt = other_opt;
}
//-------------------------------------------------
// dtor
//-------------------------------------------------
menu_software_parts::~menu_software_parts()
{
}
//-------------------------------------------------
// populate
//-------------------------------------------------
void menu_software_parts::populate()
{
m_entries.clear();
if (m_other_opt)
{
software_part_menu_entry &entry1(*m_entries.emplace(m_entries.end()));
entry1.type = result::EMPTY;
entry1.part = nullptr;
item_append(_("[empty slot]"), 0, &entry1);
software_part_menu_entry &entry2(*m_entries.emplace(m_entries.end()));
entry2.type = result::FMGR;
entry2.part = nullptr;
item_append(_("[file manager]"), 0, &entry2);
software_part_menu_entry &entry3(*m_entries.emplace(m_entries.end()));
entry3.type = result::SWLIST;
entry3.part = nullptr;
item_append(_("[software list]"), 0, &entry3);
}
for (const software_part &swpart : m_info->parts())
{
if (swpart.matches_interface(m_interface))
{
software_part_menu_entry &entry(*m_entries.emplace(m_entries.end()));
// check if the available parts have specific part_id to be displayed (e.g. "Map Disc", "Bonus Disc", etc.)
// if not, we simply display "part_name"; if yes we display "part_name (part_id)"
std::string menu_part_name(swpart.name());
if (swpart.feature("part_id") != nullptr)
menu_part_name.append(" (").append(swpart.feature("part_id")).append(")");
entry.type = result::ENTRY;
entry.part = &swpart;
item_append(m_info->shortname(), menu_part_name, 0, &entry);
}
}
item_append(menu_item_type::SEPARATOR);
}
//-------------------------------------------------
// handle
//-------------------------------------------------
bool menu_software_parts::handle(event const *ev)
{
if (ev && (ev->iptkey == IPT_UI_SELECT) && ev->itemref)
{
software_part_menu_entry *entry = (software_part_menu_entry *)ev->itemref;
m_result = entry->type;
*m_selected_part = entry->part;
stack_pop();
}
return false;
}
/***************************************************************************
SOFTWARE LIST
***************************************************************************/
//-------------------------------------------------
// ctor
//-------------------------------------------------
menu_software_list::menu_software_list(mame_ui_manager &mui, render_container &container, software_list_device *swlist, const char *interface, std::string &result)
: menu(mui, container), m_result(result)
{
set_heading(swlist->description());
set_process_flags(PROCESS_IGNOREPAUSE);
m_swlist = swlist;
m_interface = interface;
m_ordered_by_shortname = false;
}
//-------------------------------------------------
// dtor
//-------------------------------------------------
menu_software_list::~menu_software_list()
{
}
//-------------------------------------------------
// append_software_entry - populate a specific list
//-------------------------------------------------
void menu_software_list::append_software_entry(const software_info &swinfo)
{
entry_info entry;
bool entry_updated = false;
// check if at least one of the parts has the correct interface and add a menu entry only in this case
for (const software_part &swpart : swinfo.parts())
{
if (swpart.matches_interface(m_interface) && m_swlist->is_compatible(swpart) == SOFTWARE_IS_COMPATIBLE)
{
entry_updated = true;
entry.short_name.assign(swinfo.shortname());
entry.long_name.assign(swinfo.longname());
break;
}
}
// skip this if no new entry has been allocated (e.g. if the software has no matching interface for this image device)
if (entry_updated)
m_entrylist.emplace_back(std::move(entry));
}
//-------------------------------------------------
// update_search - update meunu for new search text
//-------------------------------------------------
void menu_software_list::update_search(void *selectedref)
{
// display the popup
ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_search);
// identify the selected entry
entry_info const *const cur_selected = (uintptr_t(selectedref) != 1)
? reinterpret_cast<entry_info const *>(get_selection_ref())
: nullptr;
// if it's a perfect match for the current selection, don't move it
if (!cur_selected || core_strnicmp((m_ordered_by_shortname ? cur_selected->short_name : cur_selected->long_name).c_str(), m_search.c_str(), m_search.size()))
{
std::string::size_type bestmatch(0);
entry_info const *selected_entry(cur_selected);
for (auto &entry : m_entrylist)
{
// TODO: more efficient "common prefix" code
auto const &compare_name = m_ordered_by_shortname ? entry.short_name : entry.long_name;
std::string::size_type match(0);
for (std::string::size_type i = 1; m_search.size() >= i; ++i)
{
if (!core_strnicmp(compare_name.c_str(), m_search.c_str(), i))
match = i;
else
break;
}
if (match > bestmatch)
{
bestmatch = match;
selected_entry = &entry;
}
}
if (selected_entry && (selected_entry != cur_selected))
{
set_selection((void *)selected_entry);
centre_selection();
}
}
}
//-------------------------------------------------
// populate
//-------------------------------------------------
void menu_software_list::populate()
{
// build up the list of entries for the menu
if (m_entrylist.empty())
for (const software_info &swinfo : m_swlist->get_info())
append_software_entry(swinfo);
if (m_entrylist.size() > 1)
{
// add an entry to change ordering
item_append(_("Switch Item Ordering"), 0, ITEMREF_SWITCH_ITEM_ORDERING);
// initial cursor to first entry in the list
set_selected_index(1);
}
if (m_ordered_by_shortname)
{
// short names are restricted to lowercase ASCII anyway, a dumb compare works
m_entrylist.sort([] (entry_info const &e1, entry_info const &e2) { return e1.short_name < e2.short_name; });
// append all of the menu entries
for (auto &entry : m_entrylist)
item_append(entry.short_name, entry.long_name, 0, &entry);
}
else
{
std::locale const lcl;
std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl);
m_entrylist.sort(
[&coll] (entry_info const &e1, entry_info const &e2) -> bool
{
std::wstring const xstr = wstring_from_utf8(e1.long_name);
std::wstring const ystr = wstring_from_utf8(e2.long_name);
auto const cmp = coll.compare(xstr.data(), xstr.data() + xstr.size(), ystr.data(), ystr.data() + ystr.size());
if (cmp)
return cmp < 0;
else
return e1.short_name < e2.short_name;
});
// append all of the menu entries
for (auto &entry : m_entrylist)
item_append(entry.long_name, entry.short_name, 0, &entry);
}
item_append(menu_item_type::SEPARATOR);
}
//-------------------------------------------------
// handle
//-------------------------------------------------
bool menu_software_list::handle(event const *ev)
{
if (!ev)
{
return false;
}
else if (ev->iptkey == IPT_UI_SELECT)
{
if (ev->itemref == ITEMREF_SWITCH_ITEM_ORDERING)
{
m_ordered_by_shortname = !m_ordered_by_shortname;
// reset the char buffer if we change ordering criterion
m_search.clear();
// reload the menu with the new order
reset(reset_options::REMEMBER_REF);
machine().popmessage(
m_ordered_by_shortname
? _("Switched Order: entries now ordered by shortname")
: _("Switched Order: entries now ordered by description"));
}
else if (ev->itemref)
{
// handle selections
entry_info *info = (entry_info *)ev->itemref;
m_result = info->short_name;
stack_pop();
}
return false;
}
else if (ev->iptkey == IPT_UI_PASTE)
{
if (paste_text(m_search, m_ordered_by_shortname ? is_valid_softlist_part_char : uchar_is_printable))
{
update_search(ev->itemref);
return true;
}
else
{
return false;
}
}
else if (ev->iptkey == IPT_SPECIAL)
{
if (input_character(m_search, ev->unichar, m_ordered_by_shortname ? is_valid_softlist_part_char : uchar_is_printable))
{
update_search(ev->itemref);
return true;
}
else
{
return false;
}
}
else if (ev->iptkey == IPT_UI_CANCEL)
{
// reset the char buffer also in this case
if (!m_search.empty())
{
m_search.clear();
ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_search);
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
/***************************************************************************
SOFTWARE MENU - list of available software lists - i.e. cartridges,
floppies
***************************************************************************/
//-------------------------------------------------
// ctor
//-------------------------------------------------
menu_software::menu_software(mame_ui_manager &mui, render_container &container, const char *interface, software_list_device **result)
: menu(mui, container)
{
set_heading(_("Software List"));
m_interface = interface;
m_result = result;
}
//-------------------------------------------------
// dtor
//-------------------------------------------------
menu_software::~menu_software()
{
}
//-------------------------------------------------
// populate
//-------------------------------------------------
void menu_software::populate()
{
bool have_compatible = false;
// Add original software lists for this system
software_list_device_enumerator iter(machine().config().root_device());
for (software_list_device &swlistdev : iter)
if (swlistdev.is_original())
if (!swlistdev.get_info().empty() && m_interface != nullptr)
{
bool found = false;
for (const software_info &swinfo : swlistdev.get_info())
for (const software_part &swpart : swinfo.parts())
if (swpart.matches_interface(m_interface))
{
found = true;
break;
}
if (found)
item_append(swlistdev.description(), 0, (void *)&swlistdev);
}
// add compatible software lists for this system
for (software_list_device &swlistdev : iter)
if (swlistdev.is_compatible())
if (!swlistdev.get_info().empty() && m_interface != nullptr)
{
bool found = false;
for (const software_info &swinfo : swlistdev.get_info())
for (const software_part &swpart : swinfo.parts())
if (swpart.matches_interface(m_interface))
{
found = true;
break;
}
if (found)
{
if (!have_compatible)
item_append(_("[compatible lists]"), FLAG_UI_HEADING | FLAG_DISABLE, nullptr);
item_append(swlistdev.description(), 0, (void *)&swlistdev);
}
have_compatible = true;
}
item_append(menu_item_type::SEPARATOR);
}
//-------------------------------------------------
// handle
//-------------------------------------------------
bool menu_software::handle(event const *ev)
{
if (ev && (ev->iptkey == IPT_UI_SELECT))
{
//menu::stack_push<menu_software_list>(ui(), container(), (software_list_config *)ev->itemref, image);
*m_result = reinterpret_cast<software_list_device *>(ev->itemref);
stack_pop();
}
return false;
}
} // namespace ui