diff options
Diffstat (limited to 'src/frontend/mame/ui/filesel.cpp')
-rw-r--r-- | src/frontend/mame/ui/filesel.cpp | 446 |
1 files changed, 287 insertions, 159 deletions
diff --git a/src/frontend/mame/ui/filesel.cpp b/src/frontend/mame/ui/filesel.cpp index 19567b33659..5f4dddc65fc 100644 --- a/src/frontend/mame/ui/filesel.cpp +++ b/src/frontend/mame/ui/filesel.cpp @@ -12,19 +12,24 @@ ***************************************************************************/ #include "emu.h" - #include "ui/filesel.h" + #include "ui/ui.h" #include "ui/utils.h" #include "imagedev/floppy.h" -#include "zippath.h" +#include "uiinput.h" + +#include "util/corestr.h" +#include "util/zippath.h" #include <cstring> #include <locale> + namespace ui { + /*************************************************************************** CONSTANTS ***************************************************************************/ @@ -45,17 +50,29 @@ namespace ui { // ctor //------------------------------------------------- -menu_file_selector::menu_file_selector(mame_ui_manager &mui, render_container &container, device_image_interface *image, std::string ¤t_directory, std::string ¤t_file, bool has_empty, bool has_softlist, bool has_create, menu_file_selector::result &result) +menu_file_selector::menu_file_selector( + mame_ui_manager &mui, + render_container &container, + device_image_interface *image, + std::string_view directory, + std::string_view file, + bool has_empty, + bool has_softlist, + bool has_create, + handler_function &&handler) : menu(mui, container) + , m_handler(std::move(handler)) , m_image(image) - , m_current_directory(current_directory) - , m_current_file(current_file) + , m_current_directory(directory) + , m_current_file(file) + , m_result(result::INVALID) , m_has_empty(has_empty) , m_has_softlist(has_softlist) , m_has_create(has_create) - , m_result(result) + , m_clicked_directory(std::string::npos, std::string::npos) { (void)m_image; + set_process_flags(PROCESS_IGNOREPAUSE); } @@ -65,6 +82,23 @@ menu_file_selector::menu_file_selector(mame_ui_manager &mui, render_container &c menu_file_selector::~menu_file_selector() { + if (m_handler) + m_handler(m_result, std::move(m_current_directory), std::move(m_current_file)); +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_file_selector::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + + set_custom_space(line_height() + 3.0F * tb_border(), 0.0F); } @@ -72,102 +106,131 @@ menu_file_selector::~menu_file_selector() // custom_render - perform our special rendering //------------------------------------------------- -void menu_file_selector::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_file_selector::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { // lay out extra text - auto layout = ui().create_layout(container()); - layout.add_text(m_current_directory.c_str()); + if (!m_path_layout) + { + m_path_layout.emplace(create_layout()); + m_path_layout->add_text(m_current_directory); + } + else + { + rgb_t const fgcolor = ui().colors().text_color(); + rgb_t const bgcolor = rgb_t::transparent(); + m_path_layout->restyle(0, m_current_directory.length(), &fgcolor, &bgcolor); + } // position this extra text - float x1, y1, x2, y2; - extra_text_position(origx1, origx2, origy1, top, layout, -1, x1, y1, x2, y2); + float x2, y2; + extra_text_position(origx1, origx2, origy1, top, *m_path_layout, -1, m_path_position.first, m_path_position.second, x2, y2); // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + ui().draw_outlined_box(container(), m_path_position.first, m_path_position.second, x2, y2, ui().colors().background_color()); // take off the borders - x1 += ui().box_lr_border() * machine().render().ui_aspect(&container()); - y1 += ui().box_tb_border(); + m_path_position.first += lr_border(); + m_path_position.second += tb_border(); - size_t hit_start = 0, hit_span = 0; - if (is_mouse_hit() - && layout.hit_test(get_mouse_x() - x1, get_mouse_y() - y1, hit_start, hit_span) - && m_current_directory.substr(hit_start, hit_span) != PATH_SEPARATOR) + if (m_clicked_directory.second > m_clicked_directory.first) { - // we're hovering over a directory! highlight it - auto target_dir_start = m_current_directory.rfind(PATH_SEPARATOR, hit_start) + 1; - auto target_dir_end = m_current_directory.find(PATH_SEPARATOR, hit_start + hit_span); - m_hover_directory = m_current_directory.substr(0, target_dir_end + strlen(PATH_SEPARATOR)); - - // highlight the text in question - rgb_t fgcolor = ui().colors().mouseover_color(); - rgb_t bgcolor = ui().colors().mouseover_bg_color(); - layout.restyle(target_dir_start, target_dir_end - target_dir_start, &fgcolor, &bgcolor); + // see if it's still over the clicked path component + auto const [x, y] = pointer_location(); + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + if ((start >= m_clicked_directory.first) && ((start + span) <= m_clicked_directory.second)) + { + rgb_t const fgcolor = ui().colors().selected_color(); + rgb_t const bgcolor = ui().colors().selected_bg_color(); + m_path_layout->restyle(m_clicked_directory.first, m_clicked_directory.second - m_clicked_directory.first, &fgcolor, &bgcolor); + } + } } - else + else if (pointer_idle()) { - // we are not hovering over anything - m_hover_directory.clear(); + // see if it's hovering over a path component + auto const [x, y] = pointer_location(); + auto const [target_dir_start, target_dir_end] = get_directory_range(x, y); + if (target_dir_end > target_dir_start) + { + rgb_t const fgcolor = ui().colors().mouseover_color(); + rgb_t const bgcolor = ui().colors().mouseover_bg_color(); + m_path_layout->restyle(target_dir_start, target_dir_end - target_dir_start, &fgcolor, &bgcolor); + } } // draw the text within it - layout.emit(container(), x1, y1); + m_path_layout->emit(container(), m_path_position.first, m_path_position.second); } //------------------------------------------------- -// custom_mouse_down - perform our special mouse down +// custom_pointer_updated - perform our special +// pointer handling //------------------------------------------------- -bool menu_file_selector::custom_mouse_down() +std::tuple<int, bool, bool> menu_file_selector::custom_pointer_updated(bool changed, ui_event const &uievt) { - if (m_hover_directory.length() > 0) + // track pointer after clicking a path component + if (m_clicked_directory.second > m_clicked_directory.first) { - m_current_directory = m_hover_directory; - reset(reset_options::SELECT_FIRST); - return true; + if (ui_event::type::POINTER_ABORT == uievt.event_type) + { + // abort always cancels + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else if (uievt.pointer_released & 0x01) + { + // releasing the primary button - check for dragging out + auto const [x, y] = pointer_location(); + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + // abuse IPT_CUSTOM to change to the clicked directory + if ((start >= m_clicked_directory.first) && ((start + span) <= m_clicked_directory.second)) + return std::make_tuple(IPT_CUSTOM, false, true); + } + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else if (uievt.pointer_buttons & ~u32(1)) + { + // pressing more buttons cancels + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else + { + // keep tracking the pointer + return std::make_tuple(IPT_INVALID, true, false); + } } - return false; + // check for clicks if we have up-to-date content on-screen + if (m_path_layout && pointer_idle() && (uievt.pointer_buttons & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + { + auto const [x, y] = pointer_location(); + auto const [target_dir_start, target_dir_end] = get_directory_range(x, y); + if (target_dir_end > target_dir_start) + { + m_clicked_directory = std::make_pair(target_dir_start, target_dir_end); + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); } //------------------------------------------------- -// compare_file_selector_entries - sorting proc -// for file selector entries +// menu_activated - menu has gained focus //------------------------------------------------- -int menu_file_selector::compare_entries(const file_selector_entry *e1, const file_selector_entry *e2) +void menu_file_selector::menu_activated() { - int result; - const char *e1_basename = e1->basename.c_str(); - const char *e2_basename = e2->basename.c_str(); - - if (e1->type < e2->type) - { - result = -1; - } - else if (e1->type > e2->type) - { - result = 1; - } - else - { - result = core_stricmp(e1_basename, e2_basename); - if (result == 0) - { - result = strcmp(e1_basename, e2_basename); - if (result == 0) - { - if (e1 < e2) - result = -1; - else if (e1 > e2) - result = 1; - } - } - } - - return result; + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); } @@ -202,7 +265,7 @@ menu_file_selector::file_selector_entry &menu_file_selector::append_entry( entry.fullpath = std::move(entry_fullpath); // find the end of the list - return *m_entrylist.emplace(m_entrylist.end(), std::move(entry)); + return m_entrylist.emplace_back(std::move(entry)); } @@ -310,18 +373,20 @@ void menu_file_selector::select_item(const file_selector_entry &entry) case SELECTOR_ENTRY_TYPE_DRIVE: case SELECTOR_ENTRY_TYPE_DIRECTORY: - // drive/directory - first check the path { + // drive/directory - first check the path util::zippath_directory::ptr dir; - osd_file::error const err = util::zippath_directory::open(entry.fullpath, dir); - if (err != osd_file::error::NONE) + std::error_condition const err = util::zippath_directory::open(entry.fullpath, dir); + if (err) { // this path is problematic; present the user with an error and bail ui().popup_time(1, _("Error accessing %s"), entry.fullpath); break; } } - m_current_directory.assign(entry.fullpath); + m_current_directory = entry.fullpath; + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); reset(reset_options::SELECT_FIRST); break; @@ -336,74 +401,98 @@ void menu_file_selector::select_item(const file_selector_entry &entry) //------------------------------------------------- -// type_search_char +// update_search //------------------------------------------------- -void menu_file_selector::type_search_char(char32_t ch) +void menu_file_selector::update_search() { - std::string const current(m_filename); - if (input_character(m_filename, ch, uchar_is_printable)) - { - ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename.c_str()); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename); - file_selector_entry const *const cur_selected(reinterpret_cast<file_selector_entry const *>(get_selection_ref())); + file_selector_entry const *const cur_selected(reinterpret_cast<file_selector_entry const *>(get_selection_ref())); - // if it's a perfect match for the current selection, don't move it - if (!cur_selected || core_strnicmp(cur_selected->basename.c_str(), m_filename.c_str(), m_filename.size())) + // if it's a perfect match for the current selection, don't move it + if (!cur_selected || core_strnicmp(cur_selected->basename.c_str(), m_filename.c_str(), m_filename.size())) + { + std::string::size_type bestmatch(0); + file_selector_entry const *selected_entry(cur_selected); + for (auto &entry : m_entrylist) { - std::string::size_type bestmatch(0); - file_selector_entry const *selected_entry(cur_selected); - for (auto &entry : m_entrylist) + // TODO: more efficient "common prefix" code + std::string::size_type match(0); + for (std::string::size_type i = 1; m_filename.size() >= i; ++i) { - // TODO: more efficient "common prefix" code - std::string::size_type match(0); - for (std::string::size_type i = 1; m_filename.size() >= i; ++i) - { - if (!core_strnicmp(entry.basename.c_str(), m_filename.c_str(), i)) - match = i; - else - break; - } - - if (match > bestmatch) - { - bestmatch = match; - selected_entry = &entry; - } + if (!core_strnicmp(entry.basename.c_str(), m_filename.c_str(), i)) + match = i; + else + break; } - if (selected_entry && (selected_entry != cur_selected)) + if (match > bestmatch) { - set_selection((void *)selected_entry); - centre_selection(); + bestmatch = match; + selected_entry = &entry; } } + + if (selected_entry && (selected_entry != cur_selected)) + { + set_selection((void *)selected_entry); + centre_selection(); + } } } //------------------------------------------------- +// get_directory_range +//------------------------------------------------- + +std::pair<size_t, size_t> menu_file_selector::get_directory_range(float x, float y) +{ + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + if (std::string_view(m_current_directory).substr(start, span) != PATH_SEPARATOR) + { + auto target_start = m_current_directory.rfind(PATH_SEPARATOR, start); + if (std::string::npos == target_start) + target_start = 0; + else + target_start += 1; + + auto target_end = m_current_directory.find(PATH_SEPARATOR, start + span); + if (std::string::npos == target_end) + target_end = m_current_directory.length(); + + return std::make_pair(target_start, target_end); + } + } + + return std::make_pair(std::string::npos, std::string::npos); +} + + +//------------------------------------------------- // populate //------------------------------------------------- -void menu_file_selector::populate(float &customtop, float &custombottom) +void menu_file_selector::populate() { const file_selector_entry *selected_entry = nullptr; - // clear out the menu entries m_entrylist.clear(); // open the directory util::zippath_directory::ptr directory; - osd_file::error const err = util::zippath_directory::open(m_current_directory, directory); + std::error_condition const err = util::zippath_directory::open(m_current_directory, directory); // add the "[empty slot]" entry if available if (m_has_empty) append_entry(SELECTOR_ENTRY_TYPE_EMPTY, "", ""); // add the "[create]" entry - if (m_has_create && !directory->is_archive()) + if (m_has_create && directory && !directory->is_archive()) append_entry(SELECTOR_ENTRY_TYPE_CREATE, "", ""); // add and select the "[software list]" entry if available @@ -411,17 +500,18 @@ void menu_file_selector::populate(float &customtop, float &custombottom) selected_entry = &append_entry(SELECTOR_ENTRY_TYPE_SOFTWARE_LIST, "", ""); // add the drives - int i = 0; - for (char const *volume_name = osd_get_volume_name(i); volume_name; volume_name = osd_get_volume_name(++i)) + for (std::string const &volume_name : osd_get_volume_names()) append_entry(SELECTOR_ENTRY_TYPE_DRIVE, volume_name, volume_name); // mark first filename entry std::size_t const first = m_entrylist.size() + 1; // build the menu for each item - if (osd_file::error::NONE != err) + if (err) { - osd_printf_verbose("menu_file_selector::populate: error opening directory '%s' (%d)\n", m_current_directory, int(err)); + osd_printf_verbose( + "menu_file_selector::populate: error opening directory '%s' (%s:%d %s)\n", + m_current_directory, err.category().name(), err.value(), err.message()); } else { @@ -436,24 +526,28 @@ void menu_file_selector::populate(float &customtop, float &custombottom) selected_entry = entry; // do we have to select this file? - if (!core_stricmp(m_current_file.c_str(), dirent->name)) + if (!core_stricmp(m_current_file, dirent->name)) selected_entry = entry; } } } directory.reset(); - // sort the menu entries - const std::collate<wchar_t> &coll = std::use_facet<std::collate<wchar_t>>(std::locale()); - std::sort( - m_entrylist.begin() + first, - m_entrylist.end(), - [&coll] (file_selector_entry const &x, file_selector_entry const &y) - { - std::wstring const xstr = wstring_from_utf8(x.basename); - std::wstring const ystr = wstring_from_utf8(y.basename); - return coll.compare(xstr.data(), xstr.data()+xstr.size(), ystr.data(), ystr.data()+ystr.size()) < 0; - }); + if (m_entrylist.size() > first) + { + // sort the menu entries + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + std::sort( + m_entrylist.begin() + first, + m_entrylist.end(), + [&coll] (file_selector_entry const &x, file_selector_entry const &y) + { + std::wstring const xstr = wstring_from_utf8(x.basename); + std::wstring const ystr = wstring_from_utf8(y.basename); + return coll.compare(xstr.data(), xstr.data() + xstr.size(), ystr.data(), ystr.data() + ystr.size()) < 0; + }); + } // append all of the menu entries for (file_selector_entry const &entry : m_entrylist) @@ -462,9 +556,6 @@ void menu_file_selector::populate(float &customtop, float &custombottom) // set the selection (if we have one) if (selected_entry) set_selection((void *)selected_entry); - - // set up custom render proc - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); } @@ -472,31 +563,61 @@ void menu_file_selector::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_file_selector::handle() +bool menu_file_selector::handle(event const *ev) { - // process the menu - event const *const event = process(0); - if (event && event->itemref) + if (!ev) + return false; + + if (ev->iptkey == IPT_SPECIAL) { - // handle selections - if (event->iptkey == IPT_UI_SELECT) + // if it's any other key and we're not maxed out, update + if (input_character(m_filename, ev->unichar, uchar_is_printable)) { - select_item(*reinterpret_cast<file_selector_entry const *>(event->itemref)); - - // reset the char buffer when pressing IPT_UI_SELECT - m_filename.clear(); + update_search(); + return true; } - else if (event->iptkey == IPT_SPECIAL) + } + else if (ev->iptkey == IPT_UI_PASTE) + { + if (paste_text(m_filename, uchar_is_printable)) { - // if it's any other key and we're not maxed out, update - type_search_char(event->unichar); + update_search(); + return true; } - else if (event->iptkey == IPT_UI_CANCEL) + } + else if (ev->iptkey == IPT_UI_CANCEL) + { + // reset the char buffer also in this case + if (!m_filename.empty()) { - // reset the char buffer also in this case m_filename.clear(); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename); + return true; } } + else if (ev->iptkey == IPT_CUSTOM) + { + // clicked a path component + if (m_clicked_directory.second > m_clicked_directory.first) + { + m_current_directory.resize(m_clicked_directory.second + strlen(PATH_SEPARATOR)); + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + reset(reset_options::SELECT_FIRST); + return true; + } + } + else if (ev->itemref && (ev->iptkey == IPT_UI_SELECT)) + { + // handle selections + select_item(*reinterpret_cast<file_selector_entry const *>(ev->itemref)); + + // reset the char buffer when pressing IPT_UI_SELECT + m_filename.clear(); + return true; + } + + return false; } @@ -509,11 +630,15 @@ void menu_file_selector::handle() // ctor //------------------------------------------------- -menu_select_rw::menu_select_rw(mame_ui_manager &mui, render_container &container, - bool can_in_place, result &result) - : menu(mui, container), - m_can_in_place(can_in_place), - m_result(result) +menu_select_rw::menu_select_rw( + mame_ui_manager &mui, + render_container &container, + bool can_in_place, + handler_function &&handler) + : menu(mui, container) + , m_handler(std::move(handler)) + , m_can_in_place(can_in_place) + , m_result(result::INVALID) { } @@ -524,6 +649,8 @@ menu_select_rw::menu_select_rw(mame_ui_manager &mui, render_container &container menu_select_rw::~menu_select_rw() { + if (m_handler) + m_handler(m_result); } @@ -531,14 +658,15 @@ menu_select_rw::~menu_select_rw() // populate //------------------------------------------------- -void menu_select_rw::populate(float &customtop, float &custombottom) +void menu_select_rw::populate() { - item_append(_("Select access mode"), "", FLAG_DISABLE, nullptr); - item_append(_("Read-only"), "", 0, itemref_from_result(result::READONLY)); + set_heading(_("Select access mode")); + + item_append(_("Read-only"), 0, itemref_from_result(result::READONLY)); if (m_can_in_place) - item_append(_("Read-write"), "", 0, itemref_from_result(result::READWRITE)); - item_append(_("Read this image, write to another image"), "", 0, itemref_from_result(result::WRITE_OTHER)); - item_append(_("Read this image, write to diff"), "", 0, itemref_from_result(result::WRITE_DIFF)); + item_append(_("Read-write"), 0, itemref_from_result(result::READWRITE)); + item_append(_("Read this image, write to another image"), 0, itemref_from_result(result::WRITE_OTHER)); + item_append(_("Read this image, write to diff"), 0, itemref_from_result(result::WRITE_DIFF)); } @@ -546,15 +674,15 @@ void menu_select_rw::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_select_rw::handle() +bool menu_select_rw::handle(event const *ev) { - // process the menu - const event *event = process(0); - if (event != nullptr && event->iptkey == IPT_UI_SELECT) + if (ev && ev->iptkey == IPT_UI_SELECT) { - m_result = result_from_itemref(event->itemref); + m_result = result_from_itemref(ev->itemref); stack_pop(); } + + return false; } |