mirror of
https://github.com/holub/mame
synced 2025-10-05 08:41:31 +03:00
Make search apply to the filtered list in system/software selection menus, and cache stuff used for searching in software selection menu
This commit is contained in:
parent
04aae3c533
commit
84e83a8bbc
@ -506,43 +506,57 @@ void menu_select_game::populate(float &customtop, float &custombottom)
|
||||
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())
|
||||
{
|
||||
// if search is not empty, find approximate matches
|
||||
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
|
||||
machine_filter const *const flt(m_persistent_data.filter_data().get_current_filter());
|
||||
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
|
||||
flt->apply(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist));
|
||||
}
|
||||
|
||||
// iterate over entries
|
||||
int curitem = 0;
|
||||
for (ui_system_info const &elem : 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;
|
||||
|
||||
bool cloneof = strcmp(elem.driver->parent, "0");
|
||||
if (cloneof)
|
||||
{
|
||||
if (old_item_selected == -1 && elem.driver->name == reselect_last::driver())
|
||||
old_item_selected = 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++;
|
||||
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
|
||||
@ -996,24 +1010,11 @@ void menu_select_game::populate_search()
|
||||
}
|
||||
}
|
||||
|
||||
// sort according to edit distance and put up to 200 in the menu
|
||||
// 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; });
|
||||
uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW;
|
||||
for (int curitem = 0; (std::min)(m_searchlist.size(), MAX_VISIBLE_SEARCH) > curitem; ++curitem)
|
||||
{
|
||||
game_driver const &drv(*m_searchlist[curitem].second.get().driver);
|
||||
bool cloneof = strcmp(drv.parent, "0") != 0;
|
||||
if (cloneof)
|
||||
{
|
||||
int const cx = driver_list::find(drv.parent);
|
||||
if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0))
|
||||
cloneof = false;
|
||||
}
|
||||
item_append(drv.type.fullname(), "", !cloneof ? flags_ui : (FLAG_INVERT | flags_ui), (void *)&drv);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
@ -1228,30 +1229,22 @@ render_texture *menu_select_game::get_icon_texture(int linenum, void *selectedre
|
||||
void menu_select_game::inkey_export()
|
||||
{
|
||||
std::vector<game_driver const *> list;
|
||||
if (!m_search.empty())
|
||||
if (m_populated_favorites)
|
||||
{
|
||||
for (int curitem = 0; (std::min)(m_searchlist.size(), MAX_VISIBLE_SEARCH); ++curitem)
|
||||
list.push_back(m_searchlist[curitem].second.get().driver);
|
||||
// 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
|
||||
{
|
||||
if (m_populated_favorites)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
list.reserve(m_displaylist.size());
|
||||
for (ui_system_info const &info : m_displaylist)
|
||||
list.emplace_back(info.driver);
|
||||
}
|
||||
|
||||
menu::stack_push<menu_export>(ui(), container(), std::move(list));
|
||||
|
@ -75,6 +75,23 @@ bool compare_software(ui_software_info const &a, ui_software_info const &b)
|
||||
} // 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
|
||||
//-------------------------------------------------
|
||||
@ -89,6 +106,8 @@ menu_select_software::menu_select_software(mame_ui_manager &mui, render_containe
|
||||
, m_filters()
|
||||
, m_filter_type(software_filter::ALL)
|
||||
, m_swinfo()
|
||||
, m_searchlist()
|
||||
, m_displaylist()
|
||||
{
|
||||
reselect_last::reselect(false);
|
||||
|
||||
@ -237,13 +256,20 @@ void menu_select_software::populate(float &customtop, float &custombottom)
|
||||
m_has_empty_start = true;
|
||||
int old_software = -1;
|
||||
|
||||
// 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())
|
||||
@ -252,34 +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();
|
||||
filter_map::const_iterator const it(m_filters.find(m_filter_type));
|
||||
if (m_filters.end() == it)
|
||||
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::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist));
|
||||
|
||||
// iterate over entries
|
||||
for (size_t curitem = 0; curitem < m_displaylist.size(); ++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());
|
||||
}
|
||||
flt->second->apply(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist));
|
||||
}
|
||||
else
|
||||
{
|
||||
find_matches(m_search.c_str(), MAX_VISIBLE_SEARCH);
|
||||
find_matches();
|
||||
|
||||
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 (m_filters.end() == flt)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iterate over entries
|
||||
for (size_t curitem = 0; curitem < m_displaylist.size(); ++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);
|
||||
@ -502,43 +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<double> penalty(count, 1.0);
|
||||
std::u32string const search(ustr_from_utf8(normalize_unicode(str, unicode_normalization_form::D, true)));
|
||||
|
||||
int index = 0;
|
||||
for ( ; index < m_displaylist.size(); ++index)
|
||||
// ensure search list is populated
|
||||
if (m_searchlist.empty())
|
||||
{
|
||||
// pick the best match between shortname and longname
|
||||
// TODO: search alternate title as well
|
||||
double curpenalty(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index].get().shortname, unicode_normalization_form::D, true))));
|
||||
if (curpenalty)
|
||||
{
|
||||
double const tmp(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index].get().longname, unicode_normalization_form::D, true))));
|
||||
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;
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
m_searchlist[matchnum] = &m_displaylist[index].get();
|
||||
penalty[matchnum] = curpenalty;
|
||||
}
|
||||
m_searchlist.reserve(m_swinfo.size());
|
||||
std::copy(m_swinfo.begin(), m_swinfo.end(), std::back_inserter(m_searchlist));
|
||||
}
|
||||
(index < count) ? m_searchlist[index] = nullptr : m_searchlist[count] = nullptr;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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; });
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
@ -646,7 +664,6 @@ 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;
|
||||
|
@ -15,6 +15,10 @@
|
||||
#include "ui/selmenu.h"
|
||||
#include "ui/utils.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace ui {
|
||||
|
||||
@ -29,6 +33,21 @@ private:
|
||||
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;
|
||||
|
||||
@ -51,7 +70,7 @@ private:
|
||||
virtual void inkey_export() override { throw false; }
|
||||
|
||||
void build_software_list();
|
||||
void find_matches(const char *str, int count);
|
||||
void find_matches();
|
||||
void load_sw_custom_filters();
|
||||
|
||||
// handlers
|
||||
@ -68,7 +87,7 @@ private:
|
||||
software_filter::type m_filter_type;
|
||||
|
||||
std::vector<ui_software_info> m_swinfo;
|
||||
ui_software_info const *m_searchlist[MAX_VISIBLE_SEARCH + 1];
|
||||
std::vector<search_item> m_searchlist;
|
||||
std::vector<std::reference_wrapper<ui_software_info const> > m_displaylist;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user