// license:BSD-3-Clause
// copyright-holders:Maurizio Petrarota
/*********************************************************************
ui/selector.cpp
Internal UI user interface.
*********************************************************************/
#include "emu.h"
#include "ui/selector.h"
#include "ui/ui.h"
#include "ui/utils.h"
#include "corestr.h"
#include "unicode.h"
namespace ui {
//-------------------------------------------------
// ctor / dtor
//-------------------------------------------------
menu_selector::menu_selector(
mame_ui_manager &mui,
render_container &container,
std::string &&title,
std::vector<std::string> &&sel,
int initial,
std::function<void (int)> &&handler)
: menu(mui, container)
, m_title(std::move(title))
, m_search()
, m_str_items(std::move(sel))
, m_handler(std::move(handler))
, m_initial(initial)
{
m_searchlist[0] = nullptr;
}
menu_selector::~menu_selector()
{
}
//-------------------------------------------------
// handle
//-------------------------------------------------
bool menu_selector::handle(event const *ev)
{
if (!ev)
return false;
switch (ev->iptkey)
{
case IPT_UI_SELECT:
if (ev->itemref)
{
int selection(-1);
for (size_t idx = 0; (m_str_items.size() > idx) && (0 > selection); ++idx)
if ((void*)&m_str_items[idx] == ev->itemref)
selection = int(unsigned(idx));
m_handler(selection);
stack_pop();
}
break;
case IPT_UI_PASTE:
if (paste_text(m_search, uchar_is_printable))
reset(reset_options::SELECT_FIRST);
break;
case IPT_SPECIAL:
if (input_character(m_search, ev->unichar, uchar_is_printable))
reset(reset_options::SELECT_FIRST);
break;
case IPT_UI_CANCEL:
if (!m_search.empty())
{
// escape pressed with non-empty search text clears the search text
m_search.clear();
reset(reset_options::SELECT_FIRST);
}
break;
}
return false; // any changes will trigger an item reset
}
//-------------------------------------------------
// populate
//-------------------------------------------------
void menu_selector::populate()
{
set_heading(util::string_format(_("menu-selector", "%1$s - Search: %2$s_"), m_title, m_search));
if (!m_search.empty())
{
find_matches(m_search.c_str());
int curitem;
for (curitem = 0; m_searchlist[curitem]; ++curitem)
item_append(*m_searchlist[curitem], 0, (void *)m_searchlist[curitem]);
if (!curitem)
item_append(_("menu-selector", "[no matches]"), FLAG_DISABLE, nullptr);
}
else
{
for (size_t index = 0; index < m_str_items.size(); ++index)
{
if ((0 <= m_initial) && (unsigned(m_initial) == index))
set_selected_index(index);
item_append(m_str_items[index], 0, (void *)&m_str_items[index]);
}
if (m_str_items.empty())
item_append(_("menu-selector", "[no choices]"), FLAG_DISABLE, nullptr); // the caller was probably being dumb
}
item_append(menu_item_type::SEPARATOR);
m_initial = -1;
}
//-------------------------------------------------
// recompute metrics
//-------------------------------------------------
void menu_selector::recompute_metrics(uint32_t width, uint32_t height, float aspect)
{
menu::recompute_metrics(width, height, aspect);
set_custom_space(0.0F, line_height() + 3.0F * tb_border());
}
//-------------------------------------------------
// perform our special rendering
//-------------------------------------------------
void menu_selector::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
{
// get the text for 'UI Select'
std::string const tempbuf[] = { util::string_format(_("menu-selector", "Double-click or press %1$s to select"), ui().get_general_input_setting(IPT_UI_SELECT)) };
draw_text_box(
std::begin(tempbuf), std::end(tempbuf),
origx1, origx2, origy2 + tb_border(), origy2 + bottom,
text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false,
ui().colors().text_color(), ui().colors().background_color());
}
//-------------------------------------------------
// find approximate matches
//-------------------------------------------------
void menu_selector::find_matches(const char *str)
{
// allocate memory to track the penalty value
m_ucs_items.reserve(m_str_items.size());
std::vector<double> penalty(VISIBLE_SEARCH_ITEMS, 2.0); // impossibly high penalty for unpopulated slots
std::u32string const search(ustr_from_utf8(normalize_unicode(str, unicode_normalization_form::D, true)));
int index = 0;
for ( ; index < m_str_items.size(); ++index)
{
assert(m_ucs_items.size() >= index);
if (m_ucs_items.size() == index)
m_ucs_items.emplace_back(ustr_from_utf8(normalize_unicode(m_str_items[index], unicode_normalization_form::D, true)));
double const curpenalty(util::edit_distance(search, m_ucs_items[index]));
// insert into the sorted table of matches
for (int matchnum = VISIBLE_SEARCH_ITEMS - 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 < VISIBLE_SEARCH_ITEMS - 1)
{
penalty[matchnum + 1] = penalty[matchnum];
m_searchlist[matchnum + 1] = m_searchlist[matchnum];
}
m_searchlist[matchnum] = &m_str_items[index];
penalty[matchnum] = curpenalty;
}
}
(index < VISIBLE_SEARCH_ITEMS) ? m_searchlist[index] = nullptr : m_searchlist[VISIBLE_SEARCH_ITEMS] = nullptr;
}
} // namespace ui