// license:BSD-3-Clause // copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Maurizio Petrarota /********************************************************************* ui/menu.cpp Internal MAME menus for the user interface. *********************************************************************/ #include "emu.h" #include "ui/menu.h" #include "ui/ui.h" #include "ui/mainmenu.h" #include "ui/utils.h" #include "ui/miscmenu.h" #include "cheat.h" #include "mame.h" #include "drivenum.h" #include "rendutil.h" #include "uiinput.h" #include #include #include #include namespace ui { /*************************************************************************** CONSTANTS ***************************************************************************/ #define UI_MENU_POOL_SIZE 65536 /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ std::mutex menu::s_global_state_guard; menu::global_state_map menu::s_global_states; /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ menu::global_state_ptr menu::get_global_state(running_machine &machine) { std::lock_guard guard(s_global_state_guard); auto const it(s_global_states.find(&machine)); return (it != s_global_states.end()) ? it->second : global_state_ptr(); } //------------------------------------------------- // exclusive_input_pressed - return true if the // given key is pressed and we haven't already // reported a key //------------------------------------------------- bool menu::exclusive_input_pressed(int &iptkey, int key, int repeat) { if ((iptkey == IPT_INVALID) && machine().ui_input().pressed_repeat(key, repeat)) { iptkey = key; return true; } else { return false; } } /*************************************************************************** CORE SYSTEM MANAGEMENT ***************************************************************************/ menu::global_state::global_state(running_machine &machine, ui_options const &options) : widgets_manager(machine) , m_machine(machine) , m_cleanup_callbacks() , m_bgrnd_bitmap() , m_bgrnd_texture(nullptr, machine.render()) , m_stack() , m_free() { render_manager &render(machine.render()); // create a texture for main menu background m_bgrnd_texture.reset(render.texture_alloc(render_texture::hq_scale)); if (options.use_background_image() && (&machine.system() == &GAME_NAME(___empty))) { m_bgrnd_bitmap = std::make_unique(0, 0); emu_file backgroundfile(".", OPEN_FLAG_READ); render_load_jpeg(*m_bgrnd_bitmap, backgroundfile, nullptr, "background.jpg"); if (!m_bgrnd_bitmap->valid()) render_load_png(*m_bgrnd_bitmap, backgroundfile, nullptr, "background.png"); if (m_bgrnd_bitmap->valid()) m_bgrnd_texture->set_bitmap(*m_bgrnd_bitmap, m_bgrnd_bitmap->cliprect(), TEXFORMAT_ARGB32); else m_bgrnd_bitmap->reset(); } } menu::global_state::~global_state() { // it shouldn't really be possible to get here with active menus because of reference loops assert(!m_stack); assert(!m_free); stack_reset(); clear_free_list(); for (auto const &callback : m_cleanup_callbacks) callback(m_machine); } void menu::global_state::add_cleanup_callback(cleanup_callback &&callback) { m_cleanup_callbacks.emplace_back(std::move(callback)); } void menu::global_state::stack_push(std::unique_ptr &&menu) { menu->m_parent = std::move(m_stack); m_stack = std::move(menu); m_stack->reset(reset_options::SELECT_FIRST); m_stack->machine().ui_input().reset(); } void menu::global_state::stack_pop() { if (m_stack) { std::unique_ptr menu(std::move(m_stack)); m_stack = std::move(menu->m_parent); menu->m_parent = std::move(m_free); m_free = std::move(menu); m_machine.ui_input().reset(); } } void menu::global_state::stack_reset() { while (m_stack) stack_pop(); } void menu::global_state::clear_free_list() { // free stack is in reverse order - unwind it properly std::unique_ptr reversed; while (m_free) { std::unique_ptr menu(std::move(m_free)); m_free = std::move(menu->m_parent); menu->m_parent = std::move(reversed); reversed = std::move(menu); } while (reversed) reversed = std::move(reversed->m_parent); } bool menu::global_state::stack_has_special_main_menu() const { for (auto menu = m_stack.get(); menu != nullptr; menu = menu->m_parent.get()) { if (menu->is_special_main_menu()) return true; } return false; } //------------------------------------------------- // init - initialize the menu system //------------------------------------------------- void menu::init(running_machine &machine, ui_options &mopt) { // initialize the menu stack { std::lock_guard guard(s_global_state_guard); auto const ins(s_global_states.emplace(&machine, std::make_shared(machine, mopt))); assert(ins.second); // calling init twice is bad if (ins.second) machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&menu::exit, &machine)); // add an exit callback to free memory else ins.first->second->stack_reset(); } } //------------------------------------------------- // exit - clean up after ourselves //------------------------------------------------- void menu::exit(running_machine &machine) { // free menus global_state_ptr const state(get_global_state(machine)); state->stack_reset(); state->clear_free_list(); std::lock_guard guard(s_global_state_guard); s_global_states.erase(&machine); } /*************************************************************************** CORE MENU MANAGEMENT ***************************************************************************/ //------------------------------------------------- // menu - menu constructor //------------------------------------------------- menu::menu(mame_ui_manager &mui, render_container &container) : m_visible_lines(0) , m_visible_items(0) , m_global_state(get_global_state(mui.machine())) , m_special_main_menu(false) , m_ui(mui) , m_container(container) , m_parent() , m_event() , m_pool(nullptr) , m_customtop(0.0f) , m_custombottom(0.0f) , m_resetpos(0) , m_resetref(nullptr) , m_mouse_hit(false) , m_mouse_button(false) , m_mouse_x(-1.0f) , m_mouse_y(-1.0f) { assert(m_global_state); // not calling init is bad reset(reset_options::SELECT_FIRST); top_line = 0; } //------------------------------------------------- // ~menu - menu destructor //------------------------------------------------- menu::~menu() { // free the pools while (m_pool) { pool *const ppool = m_pool; m_pool = m_pool->next; global_free_array(ppool); } } //------------------------------------------------- // reset - free all items in the menu, // and all memory allocated from the memory pool //------------------------------------------------- void menu::reset(reset_options options) { // based on the reset option, set the reset info m_resetpos = 0; m_resetref = nullptr; if (options == reset_options::REMEMBER_POSITION) m_resetpos = m_selected; else if (options == reset_options::REMEMBER_REF) m_resetref = get_selection_ref(); // reset all the pools and the item count back to 0 for (pool *ppool = m_pool; ppool != nullptr; ppool = ppool->next) ppool->top = (uint8_t *)(ppool + 1); m_items.clear(); m_visible_items = 0; m_selected = 0; // add an item to return if (!m_parent) { item_append(_("Return to Machine"), "", 0, nullptr); } else if (m_parent->is_special_main_menu()) { if (machine().options().ui() == emu_options::UI_SIMPLE) item_append(_("Exit"), "", 0, nullptr); else item_append(_("Exit"), "", FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, nullptr); } else { if (machine().options().ui() != emu_options::UI_SIMPLE && stack_has_special_main_menu()) item_append(_("Return to Previous Menu"), "", FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, nullptr); else item_append(_("Return to Previous Menu"), "", 0, nullptr); } } //------------------------------------------------- // is_special_main_menu - returns whether the // menu has special needs //------------------------------------------------- bool menu::is_special_main_menu() const { return m_special_main_menu; } //------------------------------------------------- // set_special_main_menu - set whether the // menu has special needs //------------------------------------------------- void menu::set_special_main_menu(bool special) { m_special_main_menu = special; } //------------------------------------------------- // item_append - append a new item to the // end of the menu //------------------------------------------------- void menu::item_append(menu_item item) { item_append(item.text, item.subtext, item.flags, item.ref, item.type); } //------------------------------------------------- // item_append - append a new item to the // end of the menu //------------------------------------------------- void menu::item_append(menu_item_type type, uint32_t flags) { if (type == menu_item_type::SEPARATOR) item_append(MENU_SEPARATOR_ITEM, "", flags, nullptr, menu_item_type::SEPARATOR); } //------------------------------------------------- // item_append - append a new item to the // end of the menu //------------------------------------------------- void menu::item_append(const std::string &text, const std::string &subtext, uint32_t flags, void *ref, menu_item_type type) { item_append(std::string(text), std::string(subtext), flags, ref, type); } //------------------------------------------------- // item_append - append a new item to the // end of the menu //------------------------------------------------- void menu::item_append(std::string &&text, std::string &&subtext, uint32_t flags, void *ref, menu_item_type type) { // only allow multiline as the first item if ((flags & FLAG_MULTILINE) != 0) assert(m_items.size() == 1); // only allow a single multi-line item else if (m_items.size() >= 2) assert((m_items[0].flags & FLAG_MULTILINE) == 0); // allocate a new item and populate it menu_item pitem; pitem.text = std::move(text); pitem.subtext = std::move(subtext); pitem.flags = flags; pitem.ref = ref; pitem.type = type; // append to array auto index = m_items.size(); if (!m_items.empty()) { m_items.emplace(m_items.end() - 1, std::move(pitem)); --index; } else m_items.emplace_back(std::move(pitem)); // update the selection if we need to if (m_resetpos == index || (m_resetref != nullptr && m_resetref == ref)) m_selected = index; if (m_resetpos == (m_items.size() - 1)) m_selected = m_items.size() - 1; } //------------------------------------------------- // item_append_on_off - append a new "On"/"Off" // item to the end of the menu //------------------------------------------------- void menu::item_append_on_off(const std::string &text, bool state, uint32_t flags, void *ref, menu_item_type type) { if (flags & FLAG_DISABLE) ref = nullptr; else flags |= state ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW; item_append(std::string(text), state ? _("On") : _("Off"), flags, ref, type); } //------------------------------------------------- // repopulate - repopulate menu items //------------------------------------------------- void menu::repopulate(reset_options options) { reset(options); populate(m_customtop, m_custombottom); } //------------------------------------------------- // process - process a menu, drawing it // and returning any interesting events //------------------------------------------------- const menu::event *menu::process(uint32_t flags, float x0, float y0) { // reset the event m_event.iptkey = IPT_INVALID; // first make sure our selection is valid validate_selection(1); // draw the menu if (m_items.size() > 1 && (m_items[0].flags & FLAG_MULTILINE) != 0) draw_text_box(); else draw(flags); // process input if (!(flags & PROCESS_NOKEYS) && !(flags & PROCESS_NOINPUT)) { // read events handle_events(flags, m_event); // handle the keys if we don't already have an event if (m_event.iptkey == IPT_INVALID) handle_keys(flags, m_event.iptkey); } // update the selected item in the event if ((m_event.iptkey != IPT_INVALID) && selection_valid()) { m_event.itemref = get_selection_ref(); m_event.type = m_items[m_selected].type; return &m_event; } else { return nullptr; } } //------------------------------------------------- // m_pool_alloc - allocate temporary memory // from the menu's memory pool //------------------------------------------------- void *menu::m_pool_alloc(size_t size) { assert(size < UI_MENU_POOL_SIZE); // find a pool with enough room for (pool *ppool = m_pool; ppool != nullptr; ppool = ppool->next) { if (ppool->end - ppool->top >= size) { void *result = ppool->top; ppool->top += size; return result; } } // allocate a new pool pool *ppool = (pool *)global_alloc_array_clear(sizeof(*ppool) + UI_MENU_POOL_SIZE); // wire it up ppool->next = m_pool; m_pool = ppool; ppool->top = (uint8_t *)(ppool + 1); ppool->end = ppool->top + UI_MENU_POOL_SIZE; return m_pool_alloc(size); } //------------------------------------------------- // set_selection - changes the index // of the currently selected menu item //------------------------------------------------- void menu::set_selection(void *selected_itemref) { m_selected = -1; for (int itemnum = 0; itemnum < m_items.size(); itemnum++) { if (m_items[itemnum].ref == selected_itemref) { m_selected = itemnum; break; } } } /*************************************************************************** INTERNAL MENU PROCESSING ***************************************************************************/ //------------------------------------------------- // draw - draw a menu //------------------------------------------------- void menu::draw(uint32_t flags) { // first draw the FPS counter if (ui().show_fps_counter()) { ui().draw_text_full(container(), machine().video().speed_text().c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::OPAQUE_, rgb_t::white(), rgb_t::black(), nullptr, nullptr); } bool const customonly = (flags & PROCESS_CUSTOM_ONLY); bool const noimage = (flags & PROCESS_NOIMAGE); bool const noinput = (flags & PROCESS_NOINPUT); float const line_height = ui().get_line_height(); float const lr_arrow_width = 0.4f * line_height * mach
// license:BSD-3-Clause
// copyright-holders:Andrew Gardner
#define NO_MEM_TRACKING

#include <QtWidgets/QScrollBar>
#include <QtWidgets/QApplication>
#include <QtGui/QPainter>
#include <QtGui/QKeyEvent>

#include "debuggerview.h"

#include "modules/lib/osdobj_common.h"


DebuggerView::DebuggerView(const debug_view_type& type,
							running_machine* machine,
							QWidget* parent) :
	QAbstractScrollArea(parent),
	m_preferBottom(false),
	m_view(NULL),
	m_machine(machine)
{
	// I like setting the font per-view since it doesn't override the menuing fonts.
	const char *const selectedFont(downcast<osd_options &>(m_machine->options()).debugger_font());
	const float selectedFontSize(downcast<osd_options &>(m_machine->options()).debugger_font_size());
	QFont viewFontRequest((!*selectedFont || !strcmp(selectedFont, OSDOPTVAL_AUTO)) ? "Courier New" : selectedFont);
	viewFontRequest.setFixedPitch(true);
	viewFontRequest.setStyleHint(QFont::TypeWriter);
	viewFontRequest.setPointSize((selectedFontSize <= 0) ? 11 : selectedFontSize);
	setFont(viewFontRequest);

	m_view = m_machine->debug_view().alloc_view(type,
												DebuggerView::debuggerViewUpdate,
												this);

	connect(verticalScrollBar(), &QScrollBar::valueChanged,
			this, &DebuggerView::verticalScrollSlot);
	connect(horizontalScrollBar(), &QScrollBar::valueChanged,
			this, &DebuggerView::horizontalScrollSlot);
}


DebuggerView::~DebuggerView()
{
	if (m_machine && m_view)
		m_machine->debug_view().free_view(*m_view);
}

void DebuggerView::paintEvent(QPaintEvent* event)
{
	// Tell the MAME debug view how much real estate is available
	QFontMetrics actualFont = fontMetrics();
	const double fontWidth = actualFont.width(QString(100, '_')) / 100.;
	const int fontHeight = MAX(1, actualFont.lineSpacing());
	m_view->set_visible_size(debug_view_xy(width()/fontWidth, height()/fontHeight));


	// Handle the scroll bars
	const int horizontalScrollCharDiff = m_view->total_size().x - m_view->visible_size().x;
	const int horizontalScrollSize = horizontalScrollCharDiff < 0 ? 0 : horizontalScrollCharDiff;
	horizontalScrollBar()->setRange(0, horizontalScrollSize);

	// If the horizontal scroll bar appears, make sure to adjust the vertical scrollbar accordingly
	const int verticalScrollAdjust = horizontalScrollSize > 0 ? 1 : 0;

	const int verticalScrollCharDiff = m_view->total_size().y - m_view->visible_size().y;
	const int verticalScrollSize = verticalScrollCharDiff < 0 ? 0 : verticalScrollCharDiff+verticalScrollAdjust;
	bool atEnd = false;
	if (verticalScrollBar()->value() == verticalScrollBar()->maximum())
	{
		atEnd = true;
	}
	verticalScrollBar()->setRange(0, verticalScrollSize);
	if (m_preferBottom && atEnd)
	{
		verticalScrollBar()->setValue(verticalScrollSize);
	}


	// Draw the viewport widget
	QPainter painter(viewport());
	painter.fillRect(0, 0, width(), height(), QBrush(Qt::white));
	painter.setBackgroundMode(Qt::OpaqueMode);
	painter.setBackground(QColor(255,255,255));

	// Background control
	QBrush bgBrush;
	bgBrush.setStyle(Qt::SolidPattern);
	painter.setPen(QPen(QColor(0,0,0)));

	size_t viewDataOffset = 0;
	const debug_view_xy& visibleCharDims = m_view->visible_size();
	const debug_view_char* viewdata = m_view->viewdata();
	for (int y = 0; y < visibleCharDims.y; y++)
	{
		int width = 1;
		for (int x = 0; x < visibleCharDims.x; viewDataOffset += width, x += width)
		{
			const unsigned char textAttr = viewdata[viewDataOffset].attrib;

			// Text color handling
			QColor fgColor(0,0,0);
			QColor bgColor(255,255,255);

			if(textAttr & DCA_VISITED)
			{
				bgColor.setRgb(0xc6, 0xe2, 0xff);
			}
			if(textAttr & DCA_ANCILLARY)