mirror of
https://github.com/holub/mame
synced 2025-10-06 09:00:04 +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())
|
if (!isfavorite())
|
||||||
{
|
{
|
||||||
m_populated_favorites = false;
|
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 (!m_search.empty())
|
||||||
{
|
{
|
||||||
// if search is not empty, find approximate matches
|
|
||||||
populate_search();
|
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
|
else
|
||||||
{
|
{
|
||||||
// reset search string
|
|
||||||
m_search.clear();
|
|
||||||
m_displaylist.clear();
|
|
||||||
|
|
||||||
// if filter is set on category, build category list
|
// 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());
|
std::vector<ui_system_info> const &sorted(m_persistent_data.sorted_list());
|
||||||
if (!flt)
|
if (!flt)
|
||||||
std::copy(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist));
|
std::copy(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist));
|
||||||
else
|
else
|
||||||
flt->apply(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist));
|
flt->apply(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist));
|
||||||
|
}
|
||||||
|
|
||||||
// iterate over entries
|
// iterate over entries
|
||||||
int curitem = 0;
|
int curitem = 0;
|
||||||
for (ui_system_info const &elem : m_displaylist)
|
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())
|
int cx = driver_list::find(elem.driver->parent);
|
||||||
old_item_selected = curitem;
|
if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0))
|
||||||
|
cloneof = false;
|
||||||
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++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item_append(elem.driver->type.fullname(), "", (cloneof) ? (flags_ui | FLAG_INVERT) : flags_ui, (void *)elem.driver);
|
||||||
|
curitem++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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(
|
std::stable_sort(
|
||||||
m_searchlist.begin(),
|
m_searchlist.begin(),
|
||||||
m_searchlist.end(),
|
m_searchlist.end(),
|
||||||
[] (auto const &lhs, auto const &rhs) { return lhs.first < rhs.first; });
|
[] (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()
|
void menu_select_game::inkey_export()
|
||||||
{
|
{
|
||||||
std::vector<game_driver const *> list;
|
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)
|
// iterate over favorites
|
||||||
list.push_back(m_searchlist[curitem].second.get().driver);
|
mame_machine_manager::instance()->favorite().apply(
|
||||||
|
[&list] (ui_software_info const &info)
|
||||||
|
{
|
||||||
|
assert(info.driver);
|
||||||
|
if (info.startempty)
|
||||||
|
list.push_back(info.driver);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (m_populated_favorites)
|
list.reserve(m_displaylist.size());
|
||||||
{
|
for (ui_system_info const &info : m_displaylist)
|
||||||
// iterate over favorites
|
list.emplace_back(info.driver);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
menu::stack_push<menu_export>(ui(), container(), std::move(list));
|
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
|
} // 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
|
// ctor
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
@ -89,6 +106,8 @@ menu_select_software::menu_select_software(mame_ui_manager &mui, render_containe
|
|||||||
, m_filters()
|
, m_filters()
|
||||||
, m_filter_type(software_filter::ALL)
|
, m_filter_type(software_filter::ALL)
|
||||||
, m_swinfo()
|
, m_swinfo()
|
||||||
|
, m_searchlist()
|
||||||
|
, m_displaylist()
|
||||||
{
|
{
|
||||||
reselect_last::reselect(false);
|
reselect_last::reselect(false);
|
||||||
|
|
||||||
@ -237,13 +256,20 @@ void menu_select_software::populate(float &customtop, float &custombottom)
|
|||||||
m_has_empty_start = true;
|
m_has_empty_start = true;
|
||||||
int old_software = -1;
|
int old_software = -1;
|
||||||
|
|
||||||
|
// FIXME: why does it do this relatively expensive operation every time?
|
||||||
machine_config config(m_driver, machine().options());
|
machine_config config(m_driver, machine().options());
|
||||||
for (device_image_interface &image : image_interface_iterator(config.root_device()))
|
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;
|
m_has_empty_start = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start with an empty list
|
||||||
|
m_displaylist.clear();
|
||||||
|
filter_map::const_iterator const flt(m_filters.find(m_filter_type));
|
||||||
|
|
||||||
// no active search
|
// no active search
|
||||||
if (m_search.empty())
|
if (m_search.empty())
|
||||||
@ -252,34 +278,44 @@ void menu_select_software::populate(float &customtop, float &custombottom)
|
|||||||
if (m_has_empty_start)
|
if (m_has_empty_start)
|
||||||
item_append("[Start empty]", "", flags_ui, (void *)&m_swinfo[0]);
|
item_append("[Start empty]", "", flags_ui, (void *)&m_swinfo[0]);
|
||||||
|
|
||||||
m_displaylist.clear();
|
if (m_filters.end() == flt)
|
||||||
filter_map::const_iterator const it(m_filters.find(m_filter_type));
|
|
||||||
if (m_filters.end() == it)
|
|
||||||
std::copy(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist));
|
std::copy(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist));
|
||||||
else
|
else
|
||||||
it->second->apply(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist));
|
flt->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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
find_matches(m_search.c_str(), MAX_VISIBLE_SEARCH);
|
find_matches();
|
||||||
|
|
||||||
for (int curitem = 0; m_searchlist[curitem] != nullptr; ++curitem)
|
if (m_filters.end() == flt)
|
||||||
item_append(m_searchlist[curitem]->longname, m_searchlist[curitem]->devicetype,
|
{
|
||||||
m_searchlist[curitem]->parentname.empty() ? flags_ui : (FLAG_INVERT | flags_ui),
|
std::transform(
|
||||||
(void *)m_searchlist[curitem]);
|
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);
|
item_append(menu_item_type::SEPARATOR, flags_ui);
|
||||||
@ -502,43 +538,25 @@ void menu_select_software::load_sw_custom_filters()
|
|||||||
// find approximate matches
|
// 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
|
// ensure search list is populated
|
||||||
std::vector<double> penalty(count, 1.0);
|
if (m_searchlist.empty())
|
||||||
std::u32string const search(ustr_from_utf8(normalize_unicode(str, unicode_normalization_form::D, true)));
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
for ( ; index < m_displaylist.size(); ++index)
|
|
||||||
{
|
{
|
||||||
// pick the best match between shortname and longname
|
m_searchlist.reserve(m_swinfo.size());
|
||||||
// TODO: search alternate title as well
|
std::copy(m_swinfo.begin(), m_swinfo.end(), std::back_inserter(m_searchlist));
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(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))
|
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)));
|
filter_map::const_iterator it(m_filters.find(software_filter::type(m_filter_highlight)));
|
||||||
if (m_filters.end() == it)
|
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 = 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/selmenu.h"
|
||||||
#include "ui/utils.h"
|
#include "ui/utils.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@ -29,6 +33,21 @@ private:
|
|||||||
using filter_map = std::map<software_filter::type, software_filter::ptr>;
|
using filter_map = std::map<software_filter::type, software_filter::ptr>;
|
||||||
using icon_cache = texture_lru<ui_software_info const *>;
|
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 populate(float &customtop, float &custombottom) override;
|
||||||
virtual void handle() override;
|
virtual void handle() override;
|
||||||
|
|
||||||
@ -51,7 +70,7 @@ private:
|
|||||||
virtual void inkey_export() override { throw false; }
|
virtual void inkey_export() override { throw false; }
|
||||||
|
|
||||||
void build_software_list();
|
void build_software_list();
|
||||||
void find_matches(const char *str, int count);
|
void find_matches();
|
||||||
void load_sw_custom_filters();
|
void load_sw_custom_filters();
|
||||||
|
|
||||||
// handlers
|
// handlers
|
||||||
@ -68,7 +87,7 @@ private:
|
|||||||
software_filter::type m_filter_type;
|
software_filter::type m_filter_type;
|
||||||
|
|
||||||
std::vector<ui_software_info> m_swinfo;
|
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;
|
std::vector<std::reference_wrapper<ui_software_info const> > m_displaylist;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user