summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/modules/debugger/qt/mainwindow.cpp
blob: fffe3577e9e5f98fd7b877f846c2ca5a2e65b606 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
Using MAME
----------

If you want to dive right in and skip the command line, there's a nice graphical way to use MAME without the need to download and set up a front end. Simply start MAME with no parameters, by doubleclicking the **mame.exe** file or running it directly from the command line. If you're looking to harness the full power of MAME, keep reading further.

On Macintosh OS X and \*nix-based platforms, please be sure to set your font up to match your locale before starting, otherwise you may not be able to read the text due to missing glyphs.

If you are a new MAME user, you could find this emulator a bit complex at first. Let's take a moment to talk about softlists, as they can simplify matters quite a bit. If the content you are trying to play is a documented entry on one of the MAME softlists, starting the content is as easy as

    **mame.exe** *<system>* *<software>*

For instance:

    **mame.exe nes metroidu**

will load the USA version of Metroid for the Nintendo Entertainment System.


Alternatively, you could start MAME with

	**mame.exe nes**
	
and choose the *software list* from the cartridge slot. From there, you could pick any softlist-compatible software you have in your roms folders. Please note that many older dumps of cartridges and discs may either be bad or require renaming to match up to the softlist in order to work in this way.


If you are loading an arcade board or other non-softlist content, things are only a little more complicated:


The basic usage, from command line, is

	**mame.exe** *<system>* *<media>* *<software>* *<options>*

where

* *<system>* is the shortname of the system you want to emulate (e.g. nes, c64, etc.)
* *<media>* is the switch for the media you want to load (if it's a cartridge, try **-cart** or **-cart1**; if it's a floppy disk, try **-flop** or **-flop1**; if it's a CD-ROM, try **-cdrom**)
* *<software>* is the program / game you want to load (and it can be given either as the fullpath to the file to load, or as the shortname of the file in our software lists)
* *<options>* is any additional command line option for controllers, video, sound, etc.

Remember that if you type a <system> name which does not correspond to any emulated system, MAME will suggest you some possible choices which are close to what you typed; and if you don't know which <media> switch are available, you can always launch

	**mame.exe** *<system>* **-listmedia**

If you don't know what *<options>* are available, there are a few things you can do. First of all, you can check the command line options section of this manual. You can also try one of the many :ref:`frontends` available for MAME.


Alternatively, you should keep in mind the following command line options, which might be very useful on occasion:


	**mame.exe -help**

tells what MAME is the basic structure of MAME launching options, i.e. as explained above.


	**mame.exe -showusage**

gives you the (quite long) list of available command line options for MAME. The main options are described, in the :ref:`universal-command-line` section of this manual.


	**mame.exe -showconfig**

gives you a (quite long) list of available configuration options for MAME. These configuration can always be modified at command line, or by editing them in mame.ini which is the main configuration file for MAME. You can find a description of some configuration options in the :ref:`universal-command-line` section of the manual (in most cases, each configuration option has a corresponding command line option to configure and modify it).


	**mame.exe -createconfig**

creates a brand new **mame.ini** file, with default configuration settings. Notice that mame.ini is basically a plain text file, hence you can open it with any text editor (e.g. Notepad, Emacs or TextEdit) and configure every option you need. However, no particular tweaks are needed to start, so you can basically leave most of the options unaltered.

If you execute **mame64 -createconfig** when you already have an existing mame.ini from a previous MAME version, MAME automatically updates the pre-existing mame.ini by copying changed options into it.


Once you are more confident with MAME options, you may want to configure a bit more your setup. In this case, keep in mind the order in which options are read; see :ref:`advanced-multi-CFG` for details.
5'>395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
// license:BSD-3-Clause
// copyright-holders:Andrew Gardner
#include "emu.h"
#include <QtWidgets/QAction>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QDockWidget>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QFileDialog>
#include <QtGui/QCloseEvent>

#include "mainwindow.h"

#include "debug/debugcon.h"
#include "debug/debugcpu.h"
#include "debug/dvdisasm.h"


MainWindow::MainWindow(running_machine* machine, QWidget* parent) :
	WindowQt(machine, nullptr),
	m_historyIndex(0),
	m_inputHistory()
{
	setGeometry(300, 300, 1000, 600);

	//
	// The main frame and its input and log widgets
	//
	QFrame* mainWindowFrame = new QFrame(this);

	// The input line
	m_inputEdit = new QLineEdit(mainWindowFrame);
	connect(m_inputEdit, &QLineEdit::returnPressed, this, &MainWindow::executeCommandSlot);
	m_inputEdit->installEventFilter(this);


	// The log view
	m_consoleView = new DebuggerView(DVT_CONSOLE,
										m_machine,
										mainWindowFrame);
	m_consoleView->setFocusPolicy(Qt::NoFocus);
	m_consoleView->setPreferBottom(true);

	QVBoxLayout* vLayout = new QVBoxLayout(mainWindowFrame);
	vLayout->addWidget(m_consoleView);
	vLayout->addWidget(m_inputEdit);
	vLayout->setSpacing(3);
	vLayout->setContentsMargins(4,0,4,2);

	setCentralWidget(mainWindowFrame);

	//
	// Options Menu
	//
	// Create three commands
	m_breakpointToggleAct = new QAction("Toggle Breakpoint at Cursor", this);
	m_breakpointEnableAct = new QAction("Disable Breakpoint at Cursor", this);
	m_runToCursorAct = new QAction("Run to Cursor", this);
	m_breakpointToggleAct->setShortcut(Qt::Key_F9);
	m_breakpointEnableAct->setShortcut(Qt::SHIFT + Qt::Key_F9);
	m_runToCursorAct->setShortcut(Qt::Key_F4);
	connect(m_breakpointToggleAct, &QAction::triggered, this, &MainWindow::toggleBreakpointAtCursor);
	connect(m_breakpointEnableAct, &QAction::triggered, this, &MainWindow::enableBreakpointAtCursor);
	connect(m_runToCursorAct, &QAction::triggered, this, &MainWindow::runToCursor);

	// Right bar options
	QActionGroup* rightBarGroup = new QActionGroup(this);
	rightBarGroup->setObjectName("rightbargroup");
	QAction* rightActRaw = new QAction("Raw Opcodes", this);
	QAction* rightActEncrypted = new QAction("Encrypted Opcodes", this);
	QAction* rightActComments = new QAction("Comments", this);
	rightActRaw->setCheckable(true);
	rightActEncrypted->setCheckable(true);
	rightActComments->setCheckable(true);
	rightActRaw->setActionGroup(rightBarGroup);
	rightActEncrypted->setActionGroup(rightBarGroup);
	rightActComments->setActionGroup(rightBarGroup);
	rightActRaw->setShortcut(QKeySequence("Ctrl+R"));
	rightActEncrypted->setShortcut(QKeySequence("Ctrl+E"));
	rightActComments->setShortcut(QKeySequence("Ctrl+N"));
	rightActRaw->setChecked(true);
	connect(rightBarGroup, &QActionGroup::triggered, this, &MainWindow::rightBarChanged);

	// Assemble the options menu
	QMenu* optionsMenu = menuBar()->addMenu("&Options");
	optionsMenu->addAction(m_breakpointToggleAct);
	optionsMenu->addAction(m_breakpointEnableAct);
	optionsMenu->addAction(m_runToCursorAct);
	optionsMenu->addSeparator();
	optionsMenu->addActions(rightBarGroup->actions());

	//
	// Images menu
	//
	image_interface_iterator imageIterTest(m_machine->root_device());
	if (imageIterTest.first() != nullptr)
	{
		createImagesMenu();
	}

	//
	// Dock window menu
	//
	QMenu* dockMenu = menuBar()->addMenu("Doc&ks");

	setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea);
	setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);

	// The processor dock
	QDockWidget* cpuDock = new QDockWidget("processor", this);
	cpuDock->setObjectName("cpudock");
	cpuDock->setAllowedAreas(Qt::LeftDockWidgetArea);
	m_procFrame = new ProcessorDockWidget(m_machine, cpuDock);
	cpuDock->setWidget(dynamic_cast<QWidget*>(m_procFrame));

	addDockWidget(Qt::LeftDockWidgetArea, cpuDock);
	dockMenu->addAction(cpuDock->toggleViewAction());

	// The disassembly dock
	QDockWidget* dasmDock = new QDockWidget("dasm", this);
	dasmDock->setObjectName("dasmdock");
	dasmDock->setAllowedAreas(Qt::TopDockWidgetArea);
	m_dasmFrame = new DasmDockWidget(m_machine, dasmDock);
	dasmDock->setWidget(m_dasmFrame);
	connect(m_dasmFrame->view(), &DebuggerView::updated, this, &MainWindow::dasmViewUpdated);

	addDockWidget(Qt::TopDockWidgetArea, dasmDock);
	dockMenu->addAction(dasmDock->toggleViewAction());
}


MainWindow::~MainWindow()
{
}


void MainWindow::setProcessor(device_t* processor)
{
	// Cpu swap
	m_procFrame->view()->view()->set_source(*m_procFrame->view()->view()->source_for_device(processor));
	m_dasmFrame->view()->view()->set_source(*m_dasmFrame->view()->view()->source_for_device(processor));

	// Scrollbar refresh - seems I should be able to do in the DebuggerView
	m_dasmFrame->view()->verticalScrollBar()->setValue(m_dasmFrame->view()->view()->visible_position().y);
	m_dasmFrame->view()->verticalScrollBar()->setValue(m_dasmFrame->view()->view()->visible_position().y);

	// Window title
	string_format("Debug: %s - %s '%s'", m_machine->system().name, processor->name(), processor->tag());
	setWindowTitle(string_format("Debug: %s - %s '%s'", m_machine->system().name, processor->name(), processor->tag()).c_str());
}


// Used to intercept the user clicking 'X' in the upper corner
void MainWindow::closeEvent(QCloseEvent* event)
{
	debugActQuit();

	// Insure the window doesn't disappear before we get a chance to save its parameters
	event->ignore();
}


// Used to intercept the user hitting the up arrow in the input widget
bool MainWindow::eventFilter(QObject* obj, QEvent* event)
{
	// Only filter keypresses
	QKeyEvent* keyEvent = nullptr;
	if (event->type() == QEvent::KeyPress)
	{
		keyEvent = static_cast<QKeyEvent*>(event);
	}
	else
	{
		return QObject::eventFilter(obj, event);
	}

	// Catch up & down keys
	if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)
	{
		if (keyEvent->key() == Qt::Key_Up)
		{
			if (m_historyIndex > 0)
				m_historyIndex--;
		}
		else if (keyEvent->key() == Qt::Key_Down)
		{
			if (m_historyIndex < m_inputHistory.size())
				m_historyIndex++;
		}

		// Populate the input edit or clear it if you're at the end
		if (m_historyIndex == m_inputHistory.size())
		{
			m_inputEdit->setText("");
		}
		else
		{
			m_inputEdit->setText(m_inputHistory[m_historyIndex]);
		}
	}
	else if (keyEvent->key() == Qt::Key_Enter)
	{
		executeCommand(false);
	}
	else
	{
		return QObject::eventFilter(obj, event);
	}

	return true;
}


void MainWindow::toggleBreakpointAtCursor(bool changedTo)
{
	debug_view_disasm *const dasmView = downcast<debug_view_disasm*>(m_dasmFrame->view()->view());
	if (dasmView->cursor_visible() && (m_machine->debugger().cpu().get_visible_cpu() == dasmView->source()->device()))
	{
		offs_t const address = downcast<debug_view_disasm *>(dasmView)->selected_address();
		device_debug *const cpuinfo = dasmView->source()->device()->debug();

		// Find an existing breakpoint at this address
		int32_t bpindex = -1;
		for (device_debug::breakpoint* bp = cpuinfo->breakpoint_first();
				bp != nullptr;
				bp = bp->next())
		{
			if (address == bp->address())
			{
				bpindex = bp->index();
				break;
			}
		}

		// If none exists, add a new one
		std::string command;
		if (bpindex == -1)
		{
			command = string_format("bpset 0x%X", address);
		}
		else
		{
			command = string_format("bpclear 0x%X", bpindex);
		}
		m_machine->debugger().console().execute_command(command.c_str(), true);
	}

	refreshAll();
}


void MainWindow::enableBreakpointAtCursor(bool changedTo)
{
	debug_view_disasm *const dasmView = downcast<debug_view_disasm*>(m_dasmFrame->view()->view());
	if (dasmView->cursor_visible() && (m_machine->debugger().cpu().get_visible_cpu() == dasmView->source()->device()))
	{
		offs_t const address = dasmView->selected_address();
		device_debug *const cpuinfo = dasmView->source()->device()->debug();

		// Find an existing breakpoint at this address
		device_debug::breakpoint* bp = cpuinfo->breakpoint_first();
		while ((bp != nullptr) && (bp->address() != address))
			bp = bp->next();

		if (bp != nullptr)
		{
			int32_t const bpindex = bp->index();
			std::string command = string_format(bp->enabled() ? "bpdisable 0x%X" : "bpenable 0x%X", bpindex);
			m_machine->debugger().console().execute_command(command.c_str(), true);
		}
	}

	refreshAll();
}


void MainWindow::runToCursor(bool changedTo)
{
	debug_view_disasm* dasmView = downcast<debug_view_disasm*>(m_dasmFrame->view()->view());
	if (dasmView->cursor_visible() && (m_machine->debugger().cpu().get_visible_cpu() == dasmView->source()->device()))
	{
		offs_t address = downcast<debug_view_disasm*>(dasmView)->selected_address();
		std::string command = string_format("go 0x%X", address);
		m_machine->debugger().console().execute_command(command.c_str(), true);
	}
}


void MainWindow::rightBarChanged(QAction* changedTo)
{
	debug_view_disasm* dasmView = downcast<debug_view_disasm*>(m_dasmFrame->view()->view());
	if (changedTo->text() == "Raw Opcodes")
	{
		dasmView->set_right_column(DASM_RIGHTCOL_RAW);
	}
	else if (changedTo->text() == "Encrypted Opcodes")
	{
		dasmView->set_right_column(DASM_RIGHTCOL_ENCRYPTED);
	}
	else if (changedTo->text() == "Comments")
	{
		dasmView->set_right_column(DASM_RIGHTCOL_COMMENTS);
	}
	m_dasmFrame->view()->viewport()->update();
}

void MainWindow::executeCommandSlot()
{
	executeCommand(true);
}

void MainWindow::executeCommand(bool withClear)
{
	QString command = m_inputEdit->text();

	// A blank command is a "silent step"
	if (command == "")
	{
		m_machine->debugger().cpu().get_visible_cpu()->debug()->single_step();
		return;
	}

	// Send along the command
	m_machine->debugger().console().execute_command(command.toLocal8Bit().data(), true);

	// Add history & set the index to be the top of the stack
	addToHistory(command);

	// Clear out the text and reset the history pointer only if asked
	if (withClear)
	{
		m_inputEdit->clear();
		m_historyIndex = m_inputHistory.size();
	}

	// Refresh
	m_consoleView->viewport()->update();
	refreshAll();
}


void MainWindow::mountImage(bool changedTo)
{
	// The image interface index was assigned to the QAction's data memeber
	const int imageIndex = dynamic_cast<QAction*>(sender())->data().toInt();
	image_interface_iterator iter(m_machine->root_device());
	device_image_interface *img = iter.byindex(imageIndex);
	if (img == nullptr)
	{
		m_machine->debugger().console().printf("Something is wrong with the mount menu.\n");
		refreshAll();
		return;
	}

	// File dialog
	QString filename = QFileDialog::getOpenFileName(this,
													"Select an image file",
													QDir::currentPath(),
													tr("All files (*.*)"));

	if (img->load(filename.toUtf8().data()) != image_init_result::PASS)
	{
		m_machine->debugger().console().printf("Image could not be mounted.\n");
		refreshAll();
		return;
	}

	// Activate the unmount menu option
	QAction* unmountAct = sender()->parent()->findChild<QAction*>("unmount");
	unmountAct->setEnabled(true);

	// Set the mount name
	QMenu* parentMenuItem = dynamic_cast<QMenu*>(sender()->parent());
	QString baseString = parentMenuItem->title();
	baseString.truncate(baseString.lastIndexOf(QString(" : ")));
	const QString newTitle = baseString + QString(" : ") + QString(img->filename());
	parentMenuItem->setTitle(newTitle);

	m_machine->debugger().console().printf("Image %s mounted successfully.\n", filename.toUtf8().data());
	refreshAll();
}


void MainWindow::unmountImage(bool changedTo)
{
	// The image interface index was assigned to the QAction's data memeber
	const int imageIndex = dynamic_cast<QAction*>(sender())->data().toInt();
	image_interface_iterator iter(m_machine->root_device());
	device_image_interface *img = iter.byindex(imageIndex);

	img->unload();

	// Deactivate the unmount menu option
	dynamic_cast<QAction*>(sender())->setEnabled(false);

	// Set the mount name
	QMenu* parentMenuItem = dynamic_cast<QMenu*>(sender()->parent());
	QString baseString = parentMenuItem->title();
	baseString.truncate(baseString.lastIndexOf(QString(" : ")));
	const QString newTitle = baseString + QString(" : ") + QString("[empty slot]");
	parentMenuItem->setTitle(newTitle);

	m_machine->debugger().console().printf("Image successfully unmounted.\n");
	refreshAll();
}


void MainWindow::dasmViewUpdated()
{
	debug_view_disasm *const dasmView = downcast<debug_view_disasm*>(m_dasmFrame->view()->view());
	bool const haveCursor = dasmView->cursor_visible() && (m_machine->debugger().cpu().get_visible_cpu() == dasmView->source()->device());
	bool haveBreakpoint = false;
	bool breakpointEnabled = false;
	if (haveCursor)
	{
		offs_t const address = dasmView->selected_address();
		device_t *const device = dasmView->source()->device();
		device_debug *const cpuinfo = device->debug();

		// Find an existing breakpoint at this address
		device_debug::breakpoint* bp = cpuinfo->breakpoint_first();
		while ((bp != nullptr) && (bp->address() != address))
			bp = bp->next();

		if (bp != nullptr)
		{
			haveBreakpoint = true;
			breakpointEnabled = bp->enabled();
		}
	}

	m_breakpointToggleAct->setText(haveBreakpoint ? "Clear Breakpoint at Cursor" : haveCursor ? "Set Breakpoint at Cursor" : "Toggle Breakpoint at Cursor");
	m_breakpointEnableAct->setText((!haveBreakpoint || breakpointEnabled) ? "Disable Breakpoint at Cursor" : "Enable Breakpoint at Cursor");
	m_breakpointToggleAct->setEnabled(haveCursor);
	m_breakpointEnableAct->setEnabled(haveBreakpoint);
	m_runToCursorAct->setEnabled(haveCursor);
}


void MainWindow::debugActClose()
{
	m_machine->schedule_exit();
}


void MainWindow::addToHistory(const QString& command)
{
	if (command == "")
		return;

	// Always push back when there is no previous history
	if (m_inputHistory.size() == 0)
	{
		m_inputHistory.push_back(m_inputEdit->text());
		return;
	}

	// If there is previous history, make sure it's not what you just executed
	if (m_inputHistory.back() != m_inputEdit->text())
	{
		m_inputHistory.push_back(m_inputEdit->text());
	}
}


void MainWindow::createImagesMenu()
{
	QMenu* imagesMenu = menuBar()->addMenu("&Images");

	int interfaceIndex = 0;
	for (device_image_interface &img : image_interface_iterator(m_machine->root_device()))
	{
		std::string menuName = string_format("%s : %s", img.device().name(), img.exists() ? img.filename() : "[empty slot]");

		QMenu* interfaceMenu = imagesMenu->addMenu(menuName.c_str());
		interfaceMenu->setObjectName(img.device().name());

		QAction* mountAct = new QAction("Mount...", interfaceMenu);
		QAction* unmountAct = new QAction("Unmount", interfaceMenu);
		mountAct->setObjectName("mount");
		mountAct->setData(QVariant(interfaceIndex));
		unmountAct->setObjectName("unmount");
		unmountAct->setData(QVariant(interfaceIndex));
		connect(mountAct, &QAction::triggered, this, &MainWindow::mountImage);
		connect(unmountAct, &QAction::triggered, this, &MainWindow::unmountImage);

		if (!img.exists())
			unmountAct->setEnabled(false);

		interfaceMenu->addAction(mountAct);
		interfaceMenu->addAction(unmountAct);

		// TODO: Cassette operations

		interfaceIndex++;
	}
}


//=========================================================================
//  MainWindowQtConfig
//=========================================================================
void MainWindowQtConfig::buildFromQWidget(QWidget* widget)
{
	WindowQtConfig::buildFromQWidget(widget);
	MainWindow* window = dynamic_cast<MainWindow*>(widget);
	m_windowState = window->saveState();

	QActionGroup* rightBarGroup = window->findChild<QActionGroup*>("rightbargroup");
	if (rightBarGroup->checkedAction()->text() == "Raw Opcodes")
		m_rightBar = 0;
	else if (rightBarGroup->checkedAction()->text() == "Encrypted Opcodes")
		m_rightBar = 1;
	else if (rightBarGroup->checkedAction()->text() == "Comments")
		m_rightBar = 2;
}


void MainWindowQtConfig::applyToQWidget(QWidget* widget)
{
	WindowQtConfig::applyToQWidget(widget);
	MainWindow* window = dynamic_cast<MainWindow*>(widget);
	window->restoreState(m_windowState);

	QActionGroup* rightBarGroup = window->findChild<QActionGroup*>("rightbargroup");
	rightBarGroup->actions()[m_rightBar]->trigger();
}


void MainWindowQtConfig::addToXmlDataNode(util::xml::data_node &node) const
{
	WindowQtConfig::addToXmlDataNode(node);
	node.set_attribute_int("rightbar", m_rightBar);
	node.set_attribute("qtwindowstate", m_windowState.toPercentEncoding().data());
}


void MainWindowQtConfig::recoverFromXmlNode(util::xml::data_node const &node)
{
	WindowQtConfig::recoverFromXmlNode(node);
	const char* state = node.get_attribute_string("qtwindowstate", "");
	m_windowState = QByteArray::fromPercentEncoding(state);
	m_rightBar = node.get_attribute_int("rightbar", m_rightBar);
}

DasmDockWidget::~DasmDockWidget()
{
}

ProcessorDockWidget::~ProcessorDockWidget()
{
}