// license:BSD-3-Clause // copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods /********************************************************************* ui/slotopt.cpp Internal menu for the slot options. *********************************************************************/ #include "emu.h" #include "ui/ui.h" #include "ui/slotopt.h" #include "ui/devopt.h" #include "emuopts.h" #include "mameopts.h" #include /*************************************************************************** UTILITY ***************************************************************************/ namespace { // constants void *const ITEMREF_RESET = ((void *)1); constexpr char const DIVIDER[] = "------"; } // anonymous namespace /*************************************************************************** SLOT MENU ***************************************************************************/ namespace ui { //------------------------------------------------- // menu_slot_devices constructor //------------------------------------------------- menu_slot_devices::menu_slot_devices(mame_ui_manager &mui, render_container &container) : menu(mui, container) { } //------------------------------------------------- // menu_slot_devices destructor //------------------------------------------------- menu_slot_devices::~menu_slot_devices() { } //------------------------------------------------- // get_current_option - returns the current // slot option //------------------------------------------------- device_slot_interface::slot_option const *menu_slot_devices::get_current_option(device_slot_interface &slot) const { std::string current; if (!slot.fixed()) { char const *const slot_option_name = slot.slot_name(); current = machine().options().slot_option(slot_option_name).value(); } else { if (!slot.default_option()) return nullptr; current.assign(slot.default_option()); } return slot.option(current.c_str()); } //------------------------------------------------- // set_slot_device //------------------------------------------------- void menu_slot_devices::set_slot_device(device_slot_interface &slot, const char *val) { // we might change slot options; in the spirit of user friendliness, we should record all current // options record_current_options(); // find the slot option slot_option &opt(machine().options().slot_option(slot.slot_name())); // specify it opt.specify(val); // erase this from our recorded options list - this is the slot we're trying to change! m_slot_options.erase(slot.slot_name()); // refresh any options that we might have annotated earlier while (try_refresh_current_options()) ; // changing the options may result in options changing; we need to reset reset(reset_options::REMEMBER_POSITION); } //------------------------------------------------- // record_current_options //------------------------------------------------- void menu_slot_devices::record_current_options() { for (device_slot_interface &slot : slot_interface_iterator(m_config->root_device())) { // we're doing this out of a desire to honor user-selectable options; therefore it only // makes sense to record values for selectable options if (slot.has_selectable_options()) { // get the slot option const slot_option &opt(machine().options().slot_option(slot.slot_name())); // and record the value in our local cache m_slot_options[slot.slot_name()] = opt.specified_value(); } } } //------------------------------------------------- // try_refresh_current_options //------------------------------------------------- bool menu_slot_devices::try_refresh_current_options() { // enumerate through all slot options that we've tracked for (const auto &opt : m_slot_options) { // do we have a value different than what we're tracking? slot_option *slotopt = machine().options().find_slot_option(opt.first); if (slotopt && slotopt->specified_value() != opt.second) { // specify this option (but catch errors) try { slotopt->specify(opt.second); // the option was successfully specified; it isn't safe to continue // checking slots as the act of specifying the slot may have drastically // changed the options list return true; } catch (options_exception &) { // this threw an exception - that is fine; we can just proceed } } } // we've went through all options without changing anything return false; } //------------------------------------------------- // populate //------------------------------------------------- void menu_slot_devices::populate(float &customtop, float &custombottom) { // we need to keep our own copy of the machine_config because we // can change this out from under the caller m_config = std::make_unique(machine().system(), machine().options()); // cycle through all devices for this system for (device_slot_interface &slot : slot_interface_iterator(m_config->root_device())) { // does this slot have any selectable options? bool has_selectable_options = slot.has_selectable_options(); // name this option std::string opt_name(DIVIDER); device_slot_interface::slot_option const *option = get_current_option(slot); if (option) { opt_name = has_selectable_options ? option->name() : string_format(_("%s [internal]"), option->name()); } // choose item flags uint32_t const item_flags = has_selectable_options ? FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW : FLAG_DISABLE; item_append(slot.slot_name(), opt_name, item_flags, (void *)&slot); } item_append(menu_item_type::SEPARATOR); item_append(_("Reset"), "", 0, ITEMREF_RESET); // leave space for the name of the current option at the bottom custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border(); } //------------------------------------------------- // custom_render - draw extra menu content //------------------------------------------------- void menu_slot_devices::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { if (selectedref && (ITEMREF_RESET != selectedref)) { device_slot_interface *const slot(reinterpret_cast(selectedref)); device_slot_interface::slot_option const *const option(get_current_option(*slot)); char const *const text[] = { option ? option->devtype().fullname() : _("[empty slot]") }; draw_text_box( std::begin(text), std::end(text), origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, ui().colors().text_color(), ui().colors().background_color(), 1.0f); } } //------------------------------------------------- // handle - process an input event //------------------------------------------------- void menu_slot_devices::handle() { // process the menu event const *const menu_event(process(0)); if (menu_event && menu_event->itemref != nullptr) { if (menu_event->itemref == ITEMREF_RESET) { if (menu_event->iptkey == IPT_UI_SELECT) machine().schedule_hard_reset(); } else if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) { device_slot_interface *slot = (device_slot_interface *)menu_event->itemref; rotate_slot_device(*slot, menu_event->iptkey == IPT_UI_LEFT ? step_t::PREVIOUS : step_t::NEXT); } else if (menu_event->iptkey == IPT_UI_SELECT) { device_slot_interface *slot = (device_slot_interface *)menu_event->itemref; device_slot_interface::slot_option const *const option = get_current_option(*slot); if (option) menu::stack_push(ui(), container(), slot, option); } } } //------------------------------------------------- // rotate_slot_device - rotates the specified slot //------------------------------------------------- void menu_slot_devices::rotate_slot_device(device_slot_interface &slot, menu_slot_devices::step_t step) { // first, we need to make sure our cache of options is up to date if (m_current_option_list_slot_tag != slot.device().tag()) { device_slot_interface::slot_option const *current = get_current_option(slot); // build the option list, including the blank option m_current_option_list.clear(); m_current_option_list.emplace_back(""); for (const auto &ent : slot.option_list()) { if (ent.second->selectable()) m_current_option_list.emplace_back(ent.second->name()); } // since the order is indeterminate, we need to sort the options std::sort(m_current_option_list.begin(), m_current_option_list.end()); // find the current position char const *const target = current ? current->name() : ""; m_current_option_list_iter = std::find_if( m_current_option_list.begin(), m_current_option_list.end(), [target] (const std::string &opt_value) { return opt_value == target; }); // we expect the above search to succeed, because if an internal // option was selected, the menu item should be disabled assert(m_current_option_list_iter != m_current_option_list.end()); // we've succeeded; don't do this again m_current_option_list_slot_tag.assign(slot.device().tag()); } // At this point, the current option list and accompanying iterator should // be good; perform the rotation switch (step) { case step_t::NEXT: m_current_option_list_iter++; if (m_current_option_list_iter == m_current_option_list.end()) m_current_option_list_iter = m_current_option_list.begin(); break; case step_t::PREVIOUS: if (m_current_option_list_iter == m_current_option_list.begin()) m_current_option_list_iter = m_current_option_list.end(); m_current_option_list_iter--; break; default: throw false; } set_slot_device(slot, m_current_option_list_iter->c_str()); } } // namespace ui