File:  [Isaki's NoNo m68k/m88k emulator] / nono / wx / wxmainframe.cpp
Revision 1.1.1.25 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:58 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// メインウィンドウ
//

#include "wxmainframe.h"
#include "fontmanager.h"
#include "wxaccstatmonitor.h"
#include "wxdipswwindow.h"
#include "wxdumpmonitor.h"
#include "wxgvrammonitor.h"
#include "wxlcdwindow.h"
#include "wxlogmonitor.h"
#include "wxlogsetting.h"
#include "wxmainview.h"
#include "wxmonitor.h"
#include "wxmpumonitor.h"
#include "wxpagetablemonitor.h"
#include "wxpalettemonitor.h"
#include "wxromwindow.h"
#include "wxsoftkey.h"
#include "wxstatuspanel.h"
#include "wxthreadmonitor.h"
#include "wxtvrammonitor.h"
#include "wxuimessage.h"
#include "wxversion.h"
#include "config.h"
#include "fdc.h"
#include "fdd.h"
#include "hostcom.h"
#include "hostnet.h"
#include "hostsound.h"
#include "mainapp.h"
#include "mpu.h"
#include "netdriver_slirp.h"
#include "nmi.h"
#include "power.h"
#include "scsidev.h"
#include "scsidomain.h"
#include "syncer.h"
#include <algorithm>

// ID は 1 から (wxID_LOWEST = 5000 未満まで) 使えるっぽい
// (wxGTK は 0 から使用可能だが wxOSX は 0 が不可)。
// メニュー ID のうちウィンドウのオープン状態を維持する必要があるものは
// 0 (付近) から割り当てて 0 から始まる配列で管理してしまうのが手っ取り早い。
// ウィンドウに割り当てるウィンドウ ID とメニューイベントで飛んでくる
// メニュー ID は互いに干渉しないはずなので、同じ値を使うことでメニューと
// ウィンドウの紐付けをする。
//
// ウィンドウ(サブウィンドウ)は細かくは以下のように4つに分類できるが、
// 処理上区別が必要なのは A. (デバッガの show コマンドとも共用できる
// サブウィンドウ) とそれ以外のサブウィンドウだけ。
//
// -----------+------------+----
// モニター   | テキスト   | A. 単純なテキストモニタウィンドウ
// ウィンドウ | モニター   |    (SPC, MFP などのデバイスのモニター)
//            | ウィンドウ +----
//            |            | B. それ以外のテキストモニターウィンドウ
//            |            |    (ログ、メモリのように表示位置があるもの、
//            |            |     CMMU のように構造があるもの)
//            +------------+----
//            | それ以外の | C. ビデオモニターウィンドウなど
//            | モニター   |    (ビットマッププレーン、TVRAM など)
//            | ウィンドウ |
// -----------+------------+----
// それ以外のウィンドウ    | D. ソフトウェアキーボードウィンドウなど
// ------------------------+----
enum {
	ID_SUBWIN_END_DUMMY = ID_SUBWIN_END,

	// ここから (ウィンドウでない)メニュー ID
	ID_EXIT,
	ID_RESTART_VM,
	ID_POWER_BUTTON,
	ID_SOFTRESET,
	ID_RESET_BUTTON,
	ID_NMI_BUTTON,

	ID_FULL_SPEED,

	ID_INPUT_START,
	ID_INPUT_CHAR,
	ID_INPUT_JP,
	ID_INPUT_END,

	ID_HOSTNET0_START,
	ID_HOSTNET0_USERMODE,
	ID_HOSTNET0_TAP,
	ID_HOSTNET0_BPF,
	ID_HOSTNET0_AFPACKET,
	ID_HOSTNET0_NONE,	// 最後に置くこと

	ID_HOSTNET1_START,
	ID_HOSTNET1_USERMODE,
	ID_HOSTNET1_TAP,
	ID_HOSTNET1_BPF,
	ID_HOSTNET1_AFPACKET,
	ID_HOSTNET1_NONE,	// 最後に置くこと

	ID_HOSTCOM0_START,
	ID_HOSTCOM0_TCP,
	ID_HOSTCOM0_STDIO,
	ID_HOSTCOM0_CONSOLE,
	ID_HOSTCOM0_NONE,	// 最後に置くこと

	ID_HOSTCOM1_START,
	ID_HOSTCOM1_TCP,
	ID_HOSTCOM1_STDIO,
	ID_HOSTCOM1_CONSOLE,
	ID_HOSTCOM1_NONE,	// 最後に置くこと

	ID_HOSTCOM2_START,
	ID_HOSTCOM2_TCP,
	ID_HOSTCOM2_STDIO,
	ID_HOSTCOM2_CONSOLE,
	ID_HOSTCOM2_NONE,	// 最後に置くこと

	ID_HOSTCOM_DBG_START,
	ID_HOSTCOM_DBG_TCP,
	ID_HOSTCOM_DBG_STDIO,
	ID_HOSTCOM_DBG_CONSOLE,	// 存在しないけど HOSTCOMn と揃える必要がある。
	ID_HOSTCOM_DBG_NONE,	// 最後に置くこと。

	ID_HOSTSOUND_START,
	ID_HOSTSOUND_NETBSD,
	ID_HOSTSOUND_SNDIO,
	ID_HOSTSOUND_ALSA,
	ID_HOSTSOUND_WAV,
	ID_HOSTSOUND_NONE,	// 最後に置くこと

	ID_SCALE_START,
	ID_SCALE_END = ID_SCALE_START + 6,

	ID_FONTSIZE_START, // START 以降は fontsize_t と同じ順に並べること
	ID_FONTSIZE_12,
	ID_FONTSIZE_16,
	ID_FONTSIZE_24,
	ID_FONTSIZE_END,

	ID_DEVICE_EMPTY,

	ID_MONRATE_START,
	ID_MONRATE_END = ID_MONRATE_START + 4,

	ID_SHOW_STATPANEL,

	// FD
	ID_FD_INSERT_0,
	ID_FD_INSERT_1,
	ID_FD_INSERT_2,
	ID_FD_INSERT_3,
	ID_FD_MISINSERT_0,
	ID_FD_MISINSERT_1,
	ID_FD_MISINSERT_2,
	ID_FD_MISINSERT_3,
	ID_FD_EJECT_0,
	ID_FD_EJECT_1,
	ID_FD_EJECT_2,
	ID_FD_EJECT_3,
	ID_FD_FORCE_0,
	ID_FD_FORCE_1,
	ID_FD_FORCE_2,
	ID_FD_FORCE_3,

	// CD/MO (リムーバブルデバイス)
	ID_CD_INSERT_0,
	ID_CD_INSERT_1,
	ID_CD_INSERT_2,
	ID_CD_INSERT_3,
	ID_CD_INSERT_4,
	ID_CD_INSERT_5,
	ID_CD_INSERT_6,
	ID_CD_INSERT_7,
	ID_CD_EJECT_0,
	ID_CD_EJECT_1,
	ID_CD_EJECT_2,
	ID_CD_EJECT_3,
	ID_CD_EJECT_4,
	ID_CD_EJECT_5,
	ID_CD_EJECT_6,
	ID_CD_EJECT_7,
	ID_CD_FORCE_0,
	ID_CD_FORCE_1,
	ID_CD_FORCE_2,
	ID_CD_FORCE_3,
	ID_CD_FORCE_4,
	ID_CD_FORCE_5,
	ID_CD_FORCE_6,
	ID_CD_FORCE_7,

	ID_MOUSEMODE,
	ID_KEYBOARD_CONNECT,

	ID_local_end,	// 最後に置く (チェック用)
};
static_assert(ID_local_end - 1 <= (int)IDGROUP_MAINFRAME_END, "ID exceeded");

#define ID_FD_INSERT(id)	(ID_FD_INSERT_0 + (id))
#define ID_FD_MISINSERT(id)	(ID_FD_MISINSERT_0 + (id))
#define ID_FD_EJECT(id)		(ID_FD_EJECT_0 + (id))
#define ID_FD_FORCE(id)		(ID_FD_FORCE_0 + (id))
#define ID_CD_INSERT(id)	(ID_CD_INSERT_0 + (id))
#define ID_CD_EJECT(id) 	(ID_CD_EJECT_0 + (id))
#define ID_CD_FORCE(id) 	(ID_CD_FORCE_0 + (id))

wxBEGIN_EVENT_TABLE(WXMainFrame, inherited)
	EVT_CLOSE(WXMainFrame::OnClose)
	EVT_MENU(ID_EXIT, WXMainFrame::OnExit)

	EVT_MENU(ID_POWER_BUTTON, WXMainFrame::OnPowerButton)
	EVT_UPDATE_UI(ID_POWER_BUTTON, WXMainFrame::OnPowerButtonUI)
	EVT_MENU(ID_SOFTRESET, WXMainFrame::OnSoftReset)
	EVT_MENU(ID_RESET_BUTTON, WXMainFrame::OnResetButton)
	EVT_MENU(ID_NMI_BUTTON, WXMainFrame::OnNMIButton)
	EVT_MENU(ID_RESTART_VM, WXMainFrame::OnRestartVM)

	EVT_MENU(ID_FULL_SPEED, WXMainFrame::OnFullSpeed)
	EVT_UPDATE_UI(ID_FULL_SPEED, WXMainFrame::OnFullSpeedUI)
	EVT_MENU_RANGE(ID_INPUT_START + 1, ID_INPUT_END - 1,
		WXMainFrame::OnMenuInput)
	EVT_UPDATE_UI_RANGE(ID_INPUT_START + 1, ID_INPUT_END -1,
		WXMainFrame::OnMenuInputUI)

	EVT_MENU_RANGE(ID_HOSTNET0_START + 1, ID_HOSTNET1_NONE,
		WXMainFrame::OnMenuHostNet)
	EVT_UPDATE_UI_RANGE(ID_HOSTNET0_START + 1, ID_HOSTNET1_NONE,
		WXMainFrame::OnMenuHostNetUI)
	EVT_MENU_RANGE(ID_HOSTCOM0_START + 1, ID_HOSTCOM2_NONE,
		WXMainFrame::OnMenuHostCom)
	EVT_UPDATE_UI_RANGE(ID_HOSTCOM0_START + 1, ID_HOSTCOM2_NONE,
		WXMainFrame::OnMenuHostComUI)
	EVT_MENU_RANGE(ID_HOSTSOUND_START + 1, ID_HOSTSOUND_NONE,
		WXMainFrame::OnMenuHostSound)
	EVT_UPDATE_UI_RANGE(ID_HOSTSOUND_START + 1, ID_HOSTSOUND_NONE,
		WXMainFrame::OnMenuHostSoundUI)

	EVT_MENU_RANGE(ID_SCALE_START, ID_SCALE_END,
		WXMainFrame::OnMenuScale)
	EVT_UPDATE_UI_RANGE(ID_SCALE_START, ID_SCALE_END,
		WXMainFrame::OnMenuScaleUI)
	EVT_MENU_RANGE(ID_FONTSIZE_START + 1, ID_FONTSIZE_END - 1,
		WXMainFrame::OnMenuFontSize)
	EVT_UPDATE_UI_RANGE(ID_FONTSIZE_START + 1, ID_FONTSIZE_END - 1,
		WXMainFrame::OnMenuFontSizeUI)
	EVT_MENU_RANGE(ID_MONRATE_START, ID_MONRATE_END,
		WXMainFrame::OnMenuMonRate)
	EVT_UPDATE_UI_RANGE(ID_MONRATE_START, ID_MONRATE_END,
		WXMainFrame::OnMenuMonRateUI)
	EVT_MENU(ID_SHOW_STATPANEL, WXMainFrame::OnShowStatusPanel)
	EVT_UPDATE_UI(ID_SHOW_STATPANEL, WXMainFrame::OnShowStatusPanelUI)
	EVT_MENU_RANGE(ID_FD_INSERT_0, ID_FD_INSERT_3,
		WXMainFrame::OnFDInsert)
	EVT_UPDATE_UI_RANGE(ID_FD_INSERT_0, ID_FD_INSERT_3,
		WXMainFrame::OnFDInsertUI)
	EVT_MENU_RANGE(ID_FD_EJECT_0, ID_FD_EJECT_3,
		WXMainFrame::OnFDEject)
	EVT_UPDATE_UI_RANGE(ID_FD_EJECT_0, ID_FD_EJECT_3,
		WXMainFrame::OnFDEjectUI)
	EVT_MENU_RANGE(ID_FD_FORCE_0, ID_FD_FORCE_3,
		WXMainFrame::OnFDForce)
	EVT_UPDATE_UI_RANGE(ID_FD_FORCE_0, ID_FD_FORCE_3,
		WXMainFrame::OnFDForceUI)
	EVT_MENU_RANGE(ID_CD_INSERT_0, ID_CD_INSERT_7,
		WXMainFrame::OnCDInsert)
	EVT_UPDATE_UI_RANGE(ID_CD_INSERT_0, ID_CD_INSERT_7,
		WXMainFrame::OnCDInsertUI)
	EVT_MENU_RANGE(ID_CD_EJECT_0, ID_CD_EJECT_7,
		WXMainFrame::OnCDEject)
	EVT_UPDATE_UI_RANGE(ID_CD_EJECT_0, ID_CD_EJECT_7,
		WXMainFrame::OnCDEjectUI)
	EVT_MENU_RANGE(ID_CD_FORCE_0, ID_CD_FORCE_7,
		WXMainFrame::OnCDForce)
	EVT_UPDATE_UI_RANGE(ID_CD_FORCE_0, ID_CD_FORCE_7,
		WXMainFrame::OnCDForceUI)
	EVT_MENU(ID_MOUSEMODE, WXMainFrame::OnMouseMode)
	EVT_UPDATE_UI(ID_MOUSEMODE, WXMainFrame::OnMouseModeUI)
	EVT_MENU(ID_KEYBOARD_CONNECT, WXMainFrame::OnKeyboardConnect)
	EVT_UPDATE_UI(ID_KEYBOARD_CONNECT, WXMainFrame::OnKeyboardConnectUI)
	EVT_MENU_RANGE(0, ID_SUBWIN_END, WXMainFrame::OnWindow)
	EVT_UPDATE_UI_RANGE(0, ID_SUBWIN_END, WXMainFrame::OnWindowUI)

	EVT_MENU_RANGE(ID_HOSTCOM_DBG_START + 1, ID_HOSTCOM_DBG_NONE,
		WXMainFrame::OnMenuHostCom)
	EVT_UPDATE_UI_RANGE(ID_HOSTCOM_DBG_START + 1, ID_HOSTCOM_DBG_NONE,
		WXMainFrame::OnMenuHostComUI)

	EVT_MENU(wxID_ABOUT, WXMainFrame::OnAbout)
wxEND_EVENT_TABLE()

WXMainFrame::WXMainFrame(wxWindow *parent)
	: inherited(parent, wxID_ANY, "nono", wxDefaultPosition, wxDefaultSize,
		wxDEFAULT_FRAME_STYLE & ~wxMAXIMIZE_BOX & ~wxRESIZE_BORDER)
{
	// デバイスを取得
	power = GetPowerDevice();
	syncer = GetSyncer();
	// デバイスを取得。こっちはあるかどうかを調べる
	keyboard = gMainApp.FindObject<Keyboard>(OBJ_KEYBOARD);
	for (uint i = 0; i < hostnet.size(); i++) {
		hostnet[i] = gMainApp.FindObject<HostNetDevice>(OBJ_HOSTNET(i));
	}
	for (uint i = 0; i < hostcom.size(); i++) {
		hostcom[i] = gMainApp.FindObject<HostCOMDevice>(OBJ_HOSTCOM(i));
	}
	hostcom_dbg = gMainApp.FindObject<HostCOMDevice>(OBJ_HOSTCOM_DBG);
	hostsound = gMainApp.FindObject<HostSoundDevice>(OBJ_HOSTSOUND);

	// メイン MPU 名を取得。メニューとタイトルによく出てくるので。
	// "88xx0" とか "88200" は別処理。
	mpuname = GetMPUDevice()->GetMPUName();

	// メニュー
	wxMenuBar *menubar = new wxMenuBar();
	// メニュー > VM
	wxMenu *menuVM = new wxMenu();
	wxItemKind power_button_kind;
	if (gMainApp.Has(VMCap::LUNA)) {
		power_button_kind = wxITEM_NORMAL;
	} else {
		power_button_kind = wxITEM_CHECK;
	}
	menuVM->Append(ID_POWER_BUTTON, _("Push &Power button"),
		"", power_button_kind);
	if (gMainApp.IsX68030()) {
		menuVM->Append(ID_SOFTRESET, _("Send &CTRL+OPT.1+DEL"));
	}
	menuVM->Append(ID_RESET_BUTTON, _("Push &Reset button"));
	if (gMainApp.Has(VMCap::LUNA)) {
		menuVM->Append(ID_NMI_BUTTON, _("Push &ABORT button"));
	} else if (gMainApp.Has(VMCap::X68030)) {
		menuVM->Append(ID_NMI_BUTTON, _("Push &Interrupt button"));
	}
	menuVM->AppendSeparator();
	menuVM->Append(ID_RESTART_VM, _("Re&start VM (For developers)"), "");
	menuVM->AppendSeparator();
	menuVM->Append(ID_EXIT, _("E&xit"));
	menubar->Append(menuVM, _("&VM"));

	// メニュー > 設定
	wxMenu *menuSet = new wxMenu();
	menuSet->AppendCheckItem(ID_FULL_SPEED, _("Full &Speed Mode"));
	if (keyboard) {
		wxMenu *menuSetInput = new wxMenu();
		menuSetInput->AppendRadioItem(ID_INPUT_CHAR, _("By &Character"));
		menuSetInput->AppendRadioItem(ID_INPUT_JP, _("&JP Keyboard"));
		menuSet->AppendSubMenu(menuSetInput, _("&Key Input"));
	}
	for (uint i = 0; i < hostnet.size(); i++) {
		CreateHostNetMenu(menuSet, i);
	}
	for (uint i = 0; i < hostcom.size(); i++) {
		CreateHostComMenu(menuSet, i);
	}
	CreateHostSoundMenu(menuSet);
	menubar->Append(menuSet, _("&Setting"));

	// メニュー > 表示
	wxMenu *menuDisplay = new wxMenu();
	wxMenu *menuViewScale = new wxMenu();
	int user_idx = CreateMainviewScaleTable();
	for (int i = 0, end = view_scales.size(); i < end; i++) {
		std::string text;
		if (i == user_idx) {
			// ユーザ指定値は極力指定されたまま表示。
			text = string_format("x%g", view_scales[i]);
		} else {
			// プリセットの数値は "0.5", "0.75", "1.0" と都合よく整形したい。
			text = string_format("x%.2f", view_scales[i]);
			if (text.size() == 5 && text[4] == '0') {
				text.pop_back();
			}
		}
		menuViewScale->AppendRadioItem(ID_SCALE_START + i, text);
	}
	menuDisplay->AppendSubMenu(menuViewScale, _("&Scale"));
	wxMenu *menuFontSize = new wxMenu();
	menuFontSize->AppendRadioItem(ID_FONTSIZE_12, "1&2");
	menuFontSize->AppendRadioItem(ID_FONTSIZE_16, "1&6");
	menuFontSize->AppendRadioItem(ID_FONTSIZE_24, "2&4");
	menuDisplay->AppendSubMenu(menuFontSize, _("&Font Size"));
	wxMenu *menuMonRate = new wxMenu();
	CreateMonRateTable();
	for (int i = 0, end = monrates.size(); i < end; i++) {
		std::string text;
		switch (monrates[i]) {
		 case 20:	text = _("High (&20Hz)");	break;
		 case  5:	text = _("Middle (&5Hz)");	break;
		 case  1:	text = _("Low (&1Hz)");		break;
		 default:
			text = _("User specified") + string_format(" (%dHz)", monrates[i]);
			break;
		}
		menuMonRate->AppendRadioItem(ID_MONRATE_START + i, text);
	}
	menuDisplay->AppendSubMenu(menuMonRate, _("&Monitor Speed"));
	menuDisplay->AppendCheckItem(ID_SHOW_STATPANEL, _("Show Status &Panel"));
	menubar->Append(menuDisplay, _("V&iew"));

	// メニュー > デバイス
	wxMenu *menuDevice = new wxMenu();
	CreateRemovableMenu(menuDevice);
	if (keyboard) {
		menuDevice->AppendCheckItem(ID_SUBWIN_SOFTKEY, _("Software &Keyboard"));
		menuDevice->AppendCheckItem(ID_MOUSEMODE, _("&Mouse Mode\tCTRL+F12"));
	}
	if (gMainApp.FindObject(OBJ_LCD)) {
		menuDevice->AppendCheckItem(ID_SUBWIN_LCDPANEL, _("Front &LCD"));
	}
	if (gMainApp.FindObject(OBJ_DIPSW)) {
		menuDevice->AppendCheckItem(ID_SUBWIN_DIPSW, "&DIPSW");
	}
	// 今はこれしかないので
	if (keyboard) {
		wxMenu *menuOper = new wxMenu();
		menuOper->AppendCheckItem(ID_KEYBOARD_CONNECT, _("Connect &Keyboard"));
		menuDevice->AppendSubMenu(menuOper, _("&Operation"));
	}
	// アイテムが何もなければ本当はメニューバーのアイテムを無効にしたいが、
	// それは出来ないっぽい。何かアイテムを置かないと変なパネルが出たりする。
	if (menuDevice->GetMenuItems().IsEmpty()) {
		// TRANSLATORS: This is "Device > Nothing" in the menubar.
		menuDevice->Append(ID_DEVICE_EMPTY, _("Nothing"));
		menuDevice->Enable(ID_DEVICE_EMPTY, false);
	}
	menubar->Append(menuDevice, _("&Device"));

#define AppendIfSeparator(menu, vmcap_)	do { \
	if (gMainApp.Has(vmcap_)) { \
		(menu)->AppendSeparator(); \
	} \
} while (0)

	// メニュー > モニター
	wxMenu *menuMonitor = new wxMenu();
	AppendIf(menuMonitor, ID_MONITOR_SCHEDULER,	_("&Scheduler"));

	wxMenu *menuMPU = new wxMenu();
	AppendIf(menuMPU, ID_MONITOR_MPUREG,		_("&Registers"));
	AppendIf(menuMPU, ID_MONITOR_MPUATC,		  "&ATC");
	AppendIf(menuMPU, ID_SUBWIN_MPUPT,			_("&Page Table"));
	AppendIf(menuMPU, ID_MONITOR_MPUCACHE,		_("&Caches"));
	AppendIf(menuMPU, ID_MONITOR_INTERRUPT,		_("&Interrupts"));
	AppendIfSeparator(menuMPU, VMCap::M88K);
	AppendIf(menuMPU, ID_MONITOR_CMMU7,
								"CMMU#7" + _("(Inst)") + " " + _("Registers"));
	AppendIf(menuMPU, ID_MONITOR_ATC7,			  "ATC#7");
	AppendIf(menuMPU, ID_SUBWIN_CACHE7,			_("Cache") + "#7");
	AppendIfSeparator(menuMPU, VMCap::M88K);
	AppendIf(menuMPU, ID_MONITOR_CMMU6,
								"CMMU#6" + _("(Data)") + " " + _("Registers"));
	AppendIf(menuMPU, ID_MONITOR_ATC6,			  "ATC#6");
	AppendIf(menuMPU, ID_SUBWIN_CACHE6,			_("Cache") + "#6");
	menuMPU->AppendSeparator();
	// MEMDUMPn のラベルは UpdateUI で書き直すのでここでは適当に
	// 埋めておく。(empty だと怒られる)
	for (uint i = 0; i < MAX_MEMDUMP_MONITOR; i++) {
		AppendIf(menuMPU, ID_MONITOR_MEMDUMP(i), "memdump");
	}
	menuMPU->AppendSeparator();
	AppendIf(menuMPU, ID_SUBWIN_BRHIST,			_("Branch &History"));
	AppendIf(menuMPU, ID_SUBWIN_EXHIST,			_("&Exception History"));
	AppendIf(menuMPU, ID_SUBWIN_VECTOR,			_("&Vector Table"));
	wxString mpulabel;
	if (gMainApp.Has(VMCap::M68K)) {
		mpulabel = "&MPU(" + mpuname + ")";
	} else if (gMainApp.Has(VMCap::M88K)) {
		mpulabel = "&MPU(MC88xx0)";
	} else {
		mpulabel = "&MPU";
	}
	menuMonitor->AppendSubMenu(menuMPU, mpulabel);

	if (gMainApp.Has(VMCap::LUNA)) {
		wxMenu *menuXP = new wxMenu();
		AppendIf(menuXP, ID_MONITOR_XPREG,		_("&Registers"));
		AppendIf(menuXP, ID_MONITOR_XPINTR,		_("&Interrupts"));
		AppendIf(menuXP, ID_MONITOR_XPIO,		_("Internal &Devices"));
		AppendIf(menuXP, ID_MONITOR_XPASCI,		  "&ASCI");
		menuXP->AppendSeparator();
		for (uint i = 0; i < MAX_XPMEMDUMP_MONITOR; i++) {
			AppendIf(menuXP, ID_MONITOR_XPMEMDUMP(i), "memdump");
		}
		menuXP->AppendSeparator();
		AppendIf(menuXP, ID_SUBWIN_XPBRHIST,	_("Branch &History"));
		AppendIf(menuXP, ID_SUBWIN_XPEXHIST,	_("&Exception History"));
		menuMonitor->AppendSubMenu(menuXP, "XP(HD647180)");
	}

	// MONITOR_RTC は X68k の RTC、LUNA では MONITOR_MK48T02 だが今の所
	// NVRAM 側のことは表示してないのでラベルも RTC にしてある。
	wxMenu *menuDev = new wxMenu();
	// X68030 は基本が 24bit のほうなので区別しておく。
	if (gMainApp.IsX68030()) {
		AppendIf(menuDev, ID_MONITOR_MAINBUS,	_("Device Map (32bit space)"));
		AppendIf(menuDev, ID_SUBWIN_X68KIO,		_("Device Map (24bit space)"));
	} else {
		AppendIf(menuDev, ID_MONITOR_MAINBUS,	_("Device Map"));
	}
	AppendIf(menuDev, ID_SUBWIN_NEWSIO,			_("Device Map (NEWS)"));
	AppendIf(menuDev, ID_MONITOR_ACCSTAT,		_("Access Status"));
	menuDev->AppendSeparator();
	AppendIf(menuDev, ID_MONITOR_SPC,			  "SPC");
	AppendIf(menuDev, ID_MONITOR_SCSIDEVS,		_("SCSI Devices"));
	AppendIf(menuDev, ID_MONITOR_RTC,			  "RTC");
	AppendIf(menuDev, ID_MONITOR_MK48T02,		  "RTC");
	AppendIf(menuDev, ID_MONITOR_DMAC,			  "DMAC");
	AppendIf(menuDev, ID_MONITOR_MFP,			  "MFP");
	AppendIf(menuDev, ID_MONITOR_FDC,			  "FDC");
	AppendIf(menuDev, ID_MONITOR_SCC,			  "SCC");
	AppendIf(menuDev, ID_MONITOR_SIO,			  "SIO");
	AppendIf(menuDev, ID_MONITOR_PIO,			  "PIO0/PIO1");
	AppendIf(menuDev, ID_MONITOR_KEYBOARD,		_("Keyboard/Mouse"));
	AppendIf(menuDev, ID_MONITOR_LANCE,			  "Lance");
	AppendIf(menuDev, ID_MONITOR_NEREID,		  "Nereid");
	for (uint i = 0; i < hostnet.size(); i++) {
		uint id = ID_MONITOR_RTL8019AS(i);
		wxString name = string_format("Nereid#%u (RTL8019AS)", i);
		AppendIf(menuDev, id, name);
	}
	AppendIf(menuDev, ID_MONITOR_SYSPORT,		_("System Port"));
	AppendIf(menuDev, ID_MONITOR_AREASET,		_("Areaset"));
	AppendIf(menuDev, ID_MONITOR_LCD,			  "LCD");
	AppendIf(menuDev, ID_MONITOR_SYSCLK,		_("System Clock"));
	for (uint i = 0; i < 8; i++) {
		uint id = ID_MONITOR_VIRTIO_BLOCK(i);
		wxString name = _("VirtIO Block") + string_format(" #%u", i);
		AppendIf(menuDev, id, name);
	}
	AppendIf(menuDev, ID_MONITOR_VIRTIO_NET,	_("VirtIO Network"));
	AppendIf(menuDev, ID_MONITOR_VIRTIO_SCSI,	  "VirtIO SCSI");
	AppendIf(menuDev, ID_MONITOR_VIRTIO_ENTROPY,_("VirtIO Entropy"));
	AppendIf(menuDev, ID_MONITOR_CRTC,			_("CRTC and Video Controller"));
	AppendIf(menuDev, ID_MONITOR_LUNAVC,		_("Video Control"));
	AppendIf(menuDev, ID_MONITOR_BT45x,			  "Bt454/458");
	AppendIf(menuDev, ID_SUBWIN_PALETTE,		_("Palette Colormap"));
	AppendIf(menuDev, ID_MONITOR_RENDERER,		_("Renderer"));
	AppendIf(menuDev, ID_SUBWIN_LUNAFB,			_("Bitmap Plane"));
	AppendIf(menuDev, ID_SUBWIN_TVRAM,			_("Text Screen"));
	AppendIf(menuDev, ID_SUBWIN_GVRAM,			_("Graphic Screen"));
	AppendIf(menuDev, ID_MONITOR_SOUND,			_("Sound"));
	AppendIf(menuDev, ID_MONITOR_SSG,			  "SSG");
	AppendIf(menuDev, ID_MONITOR_ADPCM,			  "ADPCM");
	AppendIf(menuDev, ID_MONITOR_ROMCONS,		_("ROM Console (provisional)"));
	AppendIf(menuDev, ID_SUBWIN_ROM,			  "ROM");
	// LUNA はバスが複数あるので Mainbus Device とする。
	// 他の機種ではメインて言われても困るので Device とする。
	if (gMainApp.Has(VMCap::LUNA)) {
		menuMonitor->AppendSubMenu(menuDev, _("Mainbus De&vices"));
	} else {
		menuMonitor->AppendSubMenu(menuDev, _("De&vices"));
	}

	wxMenu *menuHost = new wxMenu();
	AppendIf(menuHost, ID_MONITOR_WXHOSTINFO,	_("Host &Information"));
	AppendIf(menuHost, ID_SUBWIN_THREADS,		_("Thread Status"));
	for (uint i = 0; i < hostnet.size(); i++) {
		uint id = ID_MONITOR_HOSTNET(i);
		if (gMonitorManager->Find(id)) {
			menuHost->AppendCheckItem(id, GetHostNetLabel(i));
		}
	}
	AppendIf(menuHost, ID_MONITOR_SLIRP,		_("SLIRP Internal States"));
	for (uint i = 0; i < hostcom.size(); i++) {
		uint id = ID_MONITOR_HOSTCOM(i);
		if (gMonitorManager->Find(id)) {
			menuHost->AppendCheckItem(id, GetHostComLabel(i));
		}
	}
	AppendIf(menuHost, ID_MONITOR_HOSTSOUND,	_("Host Sound"));
	menuMonitor->AppendSubMenu(menuHost, _("&Host"));

	wxMenu *menuDebug = new wxMenu();
	AppendIf(menuDebug, ID_MONITOR_LOG,			_("&Log"));
	AppendIf(menuDebug, ID_MONITOR_BREAKPOINT,	_("&Breakpoints"));
	menuDebug->AppendSeparator();
	AppendIf(menuDebug, ID_SUBWIN_LOGSETTING,	_("Log Level &Setting"));
	CreateHostComMenu(menuDebug, -1);
	menuMonitor->AppendSubMenu(menuDebug, _("&Debug"));

	menubar->Append(menuMonitor, _("&Monitor"));

	// メニュー > ヘルプ
#if defined(__WXOSX__)
	// OSX では wxID_ABOUT はリンゴメニューに入れるしきたりになっている。
	// Append() すると Quit より下に来てしまうので、Prepend() で上に追加。
	// 今のところヘルプメニューから About ダイアログを抜くとメニューが空に
	// なってしまうので、ヘルプメニューの追加ごと抑制する。
	menubar->OSXGetAppleMenu()->Prepend(wxID_ABOUT, _("About nono"));
#else
	wxMenu *menuHelp = new wxMenu();
	menuHelp->Append(wxID_ABOUT, _("About nono"));
	menubar->Append(menuHelp, _("&Help"));
#endif

	SetMenuBar(menubar);

	frame_title = wxString("nono");
	const char *vmname = gMainApp.GetVMName();
	if (vmname) {
		frame_title += '(';
		frame_title += wxString(vmname);
		frame_title += ')';
	}
	SetMyTitle(false);

	// UpdateUI イベントは Idle 時に時折チェックされるらしいが、
	// (メインビューとサブウィンドウを合わせた) タイマーイベントの粒度が
	// 小さいためか、秒間120回とかいうレベルで UpdateUIEvent が飛んでくる
	// っぽい (全タイマーをとめたら治る模様)。
	// さすがにそんなに来られても気持ち悪いのと、どうせメニュー開いた瞬間
	// 表示しようとする前には一度飛んでくるのだから、アイドル時のチェックは
	// いらない気がする。間隔広げておいても、ぱっと見副作用はなさそうだけど
	// 念のため事象が分かりやすいように 10秒くらいにしておく。
	wxUpdateUIEvent::SetUpdateInterval(10000);

	// WXStatusPanel コンストラクタが mainview を参照するので
	// mainview のほうを先に作成すること。
	mainview = new WXMainView(this);
	statuspanel = new WXStatusPanel(this);
	// 空き地用のパネル。
	spaceL = new WXBitmapPanel(this);
	spaceR = new WXBitmapPanel(this);
	spaceL->Hide();
	spaceR->Hide();

	// Sizer 使わず自前でレイアウトする。
	SetAutoLayout(true);
	// mainview のスケール、statuspanel のフォントサイズはいずれも
	// コンストラクタの時点で初期状態がセットされている。

	// SHOW イベントを最初にも一度起こしたいため
	// ステータスパネルを一旦非表示にする (これで SHOW(false) イベントが飛ぶ)
	statuspanel->Hide();
	if (gConfig->Find("show-statuspanel").AsBool()) {
		// 必要なら改めて表示 (これで SHOW(true) イベントが飛ぶ)
		statuspanel->Show();
	}

	Fit();

	// Activate イベントは、このトップレベルウィンドウにウィンドウフォーカスが
	// 来た or 外れたことを示すので、wxFrame (というかたぶん wxTopLevelWindow)
	// を継承している WXMainFrameに飛んでくるのだが、処理の都合上 WXMainView で
	// 扱いたいので、そちらに回す。
	Bind(wxEVT_ACTIVATE, &WXMainView::OnActivate, mainview);

	// ウィンドウ表示後に続きの処理をするための単発アイドルイベント。
	// こっちは Disconnect() するので Bind() は使えない。
	Connect(wxEVT_IDLE, wxIdleEventHandler(WXMainFrame::OnIdle));

	// VM からの通知を接続
	WXUIMessage::Connect(UIMessage::APPEXIT, this,
		wxCommandEventHandler(WXMainFrame::OnExit));
	WXUIMessage::Connect(UIMessage::SCSI_MEDIA_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnInsertFailed));
	WXUIMessage::Connect(UIMessage::FDD_MEDIA_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnInsertFailed));
	WXUIMessage::Connect(UIMessage::HOSTNET_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnHostNetFailed));
	WXUIMessage::Connect(UIMessage::HOSTCOM_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnHostComFailed));
	WXUIMessage::Connect(UIMessage::HOSTSOUND_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnHostSoundFailed));
	if (gMainApp.Has(VMCap::M68K)) {
		WXUIMessage::Connect(UIMessage::HALT, this,
			wxCommandEventHandler(WXMainFrame::OnHaltNotify));
	}
}

WXMainFrame::~WXMainFrame()
{
	WXUIMessage::Disconnect(UIMessage::APPEXIT, this,
		wxCommandEventHandler(WXMainFrame::OnExit));
	WXUIMessage::Disconnect(UIMessage::SCSI_MEDIA_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnInsertFailed));
	WXUIMessage::Disconnect(UIMessage::FDD_MEDIA_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnInsertFailed));
	WXUIMessage::Disconnect(UIMessage::HOSTNET_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnHostNetFailed));
	WXUIMessage::Disconnect(UIMessage::HOSTCOM_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnHostComFailed));
	WXUIMessage::Disconnect(UIMessage::HOSTSOUND_FAILED, this,
		wxCommandEventHandler(WXMainFrame::OnHostSoundFailed));
	if (gMainApp.Has(VMCap::M68K)) {
		WXUIMessage::Disconnect(UIMessage::HALT, this,
			wxCommandEventHandler(WXMainFrame::OnHaltNotify));
	}
}

// 指定のモニタ/サブウィンドウがあればメニューに追加する
void
WXMainFrame::AppendIf(wxMenu *menu, int id, const wxString& label)
{
	if (id >= ID_SUBWIN_START) {
		// サブウィンドウなら機種から判定する。機種情報は必ず持っているため。
		// 一方、サブウィンドウは必ずしもモニタと紐付いているわけではないので
		// モニタマネージャに問い合わせる方法は使えない。
		VMCap vmcap = gMonitorManager->GetVMCap(id);
		if (gMainApp.Has(vmcap)) {
			menu->AppendCheckItem(id, label);
		}
	} else {
		// モニタならインスタンスの有無で判定する。
		// モニタは機種情報は持ってないが、存在すべきインスタンスはこの時点で
		// すべて出揃っているため。
		if (gMonitorManager->Find(id)) {
			menu->AppendCheckItem(id, label);
		}
	}
}

// HostNet 用のメニューを作成して menuSet に追加する。
void
WXMainFrame::CreateHostNetMenu(wxMenu *menuSet, uint unit)
{
	if (hostnet[unit] == NULL) {
		return;
	}

	int offset;
	if (unit == 0) {
		offset = ID_HOSTNET0_START - ID_HOSTNET0_START;
	} else {
		offset = ID_HOSTNET1_START - ID_HOSTNET0_START;
	}

	wxMenu *menu = new wxMenu();
#if defined(HAVE_HOSTNET_SLIRP)
	menu->AppendRadioItem(offset + ID_HOSTNET0_USERMODE, "&usermode");
#endif
#if defined(HAVE_HOSTNET_TAP)
	menu->AppendRadioItem(offset + ID_HOSTNET0_TAP, "&tap");
#endif
#if defined(HAVE_HOSTNET_BPF)
	menu->AppendRadioItem(offset + ID_HOSTNET0_BPF, "&bpf");
#endif
#if defined(HAVE_HOSTNET_AFPACKET)
	menu->AppendRadioItem(offset + ID_HOSTNET0_AFPACKET, "&afpacket");
#endif
	menu->AppendRadioItem(offset + ID_HOSTNET0_NONE, "&none");
	menuSet->AppendSubMenu(menu, GetHostNetLabel(unit));
}

// HostCOM 用のメニューを作成して menuSet に追加する。
// 引数の n は COM<n>。ただし負数ならデバッガ用。
void
WXMainFrame::CreateHostComMenu(wxMenu *menuSet, int n)
{
	HostCOMDevice *host;

	// デバッガ用は配列では最後。
	if (n < 0) {
		host = hostcom_dbg;
	} else {
		host = hostcom[n];
	}

	if (host == NULL) {
		return;
	}

	int offset;
	if (n == 0) {
		offset = ID_HOSTCOM0_START - ID_HOSTCOM0_START;
	} else if (n == 1) {
		offset = ID_HOSTCOM1_START - ID_HOSTCOM0_START;
	} else if (n == 2) {
		offset = ID_HOSTCOM2_START - ID_HOSTCOM0_START;
	} else {
		offset = ID_HOSTCOM_DBG_START - ID_HOSTCOM0_START;
	}

	wxMenu *menu = new wxMenu();
	menu->AppendRadioItem(offset + ID_HOSTCOM0_TCP, "&tcp");
	menu->AppendRadioItem(offset + ID_HOSTCOM0_STDIO, "&stdio");
	if (n >= 0 && gMainApp.FindObject(OBJ_CONSOLE)) {
		menu->AppendRadioItem(offset + ID_HOSTCOM0_CONSOLE, "&console");
	}
	menu->AppendRadioItem(offset + ID_HOSTCOM0_NONE, "&none");
	wxString name;
	if (n >= 0) {
		name = GetHostComLabel(n);
	} else {
		name = _("&Debugger");
	}
	menuSet->AppendSubMenu(menu, name);
}

// HostSound 用のメニューを作成して menuSet に追加する。
void
WXMainFrame::CreateHostSoundMenu(wxMenu *menuSet)
{
	if (hostsound == NULL) {
		return;
	}

	wxMenu *menu = new wxMenu();
#if defined(HAVE_HOSTSOUND_NETBSD)
	menu->AppendRadioItem(ID_HOSTSOUND_NETBSD, "&netbsd");
#endif
#if defined(HAVE_HOSTSOUND_SNDIO)
	menu->AppendRadioItem(ID_HOSTSOUND_SNDIO, "&sndio");
#endif
#if defined(HAVE_HOSTSOUND_ALSA)
	menu->AppendRadioItem(ID_HOSTSOUND_ALSA, "&alsa");
#endif
	menu->AppendRadioItem(ID_HOSTSOUND_WAV, "&wav");
	menu->AppendRadioItem(ID_HOSTSOUND_NONE, "&none");
	menuSet->AppendSubMenu(menu, "HostSound");
}

// リムーバブルデバイス用のメニューを作成して menuDevice に追加する
void
WXMainFrame::CreateRemovableMenu(wxMenu *menuDevice)
{
	wxMenu *menu;
	bool added = false;

	// FD
	for (int unit = 0; unit < FDCDevice::MAX_DRIVE; unit++) {
		fdd_array[unit] = gMainApp.FindObject<FDDDevice>(OBJ_FDD(unit));
		if (fdd_array[unit]) {
			menu = CreateFDMenu(unit);
			std::string label = string_format("FD%d", unit);
			menuDevice->AppendSubMenu(menu, label);
			added = true;
		}
	}

	// CD, MO
	auto scsi = gMainApp.FindObject<SCSIDomain>(OBJ_SCSI);
	if (scsi) {
		for (int id = 0; id < 8; id++) {
			scsidev[id] = scsi->GetTarget(id);
			if (scsidev[id]) {
				menu = CreateSCSIRemovableMenu(id);
				if (menu) {
					std::string label = string_format("%s (ID%d)",
						SCSI::GetDevTypeName(scsidev[id]->GetDevType()), id);
					menuDevice->AppendSubMenu(menu, label);
					added = true;
				}
			}
		}
	}

	// 1つでも追加したらセパレータ
	if (added) {
		menuDevice->AppendSeparator();
	}
}

// FD 用のメニューを作成して返す。
wxMenu *
WXMainFrame::CreateFDMenu(int unit) const
{
	wxMenu *menu = new wxMenu();
	// Insert の実際のラベルは UpdateUI でセットする。
	// ただし何か入れておかないといけないので適当にセット。
	menu->Append(ID_FD_INSERT(unit), "Insert", "");
	// XXX 未実装
	//menu->Append(ID_FD_MISINSERT(unit), _("Insert incorrectly"), "");
	menu->Append(ID_FD_EJECT(unit), _("&Eject"), "");
	menu->Append(ID_FD_FORCE(unit), _("&Force Eject"), "");
	return menu;
}

// SCSI リムーバブルデバイス用のメニューを作成して返す。
// id はデバイスが接続されていること (リムーバブルデバイスであるかは問わない)。
// リムーバブルデバイスでなければ NULL を返す。
// メニューバーとポップアップメニュー共通。
wxMenu *
WXMainFrame::CreateSCSIRemovableMenu(uint id) const
{
	const auto *dev = scsidev[id];
	assert(dev);

	switch (dev->GetDevType()) {
	 case SCSI::DevType::CD:
	 case SCSI::DevType::MO:
	 {
		wxMenu *menu = new wxMenu();
		// Insert の実際のラベルは UpdateUI でセットする。
		// ただし何か入れておかないといけないので適当にセット。
		menu->Append(ID_CD_INSERT(id), "Insert", "");
		menu->Append(ID_CD_EJECT(id), _("&Eject"), "");
		menu->Append(ID_CD_FORCE(id), _("&Force Eject"), "");
		return menu;
	 }
	 default:
		return NULL;
	}
}

// (設定反映などの) 初期化。
// 成功すれば true、失敗すれば false を返す。
bool
WXMainFrame::Init()
{
	if (!mainview->Init()) {
		return false;
	}

	statuspanel->Init();

	return true;
}

void
WXMainFrame::Fit()
{
	wxSize statsize(0, 0);
	if (statuspanel->IsShown()) {
		statsize = statuspanel->GetMinSize();
	}
	const wxSize& viewsize = mainview->GetSize();

	wxSize size(std::max(statsize.x, viewsize.x), statsize.y + viewsize.y);
	SetClientSize(size);
}

bool
WXMainFrame::Layout()
{
	const wxSize& client = GetClientSize();

	wxSize statsize(0, 0);
	if (statuspanel->IsShown()) {
		statsize = statuspanel->GetSize();
		statuspanel->SetSize(0, 0, client.x, statsize.y);
	}

	// メインビューの方が小さければ両側に空き地を入れてセンタリングする。
	wxSize viewsize = mainview->GetSize();
	if (viewsize.x < client.x) {
		int view_x = (client.x - viewsize.x) / 2;
		spaceL->SetSize(0, statsize.y, view_x, viewsize.y);
		spaceL->Show();
		mainview->SetSize(view_x, statsize.y, viewsize.x, viewsize.y);
		int x = view_x + viewsize.x;
		spaceR->SetSize(x, statsize.y, client.x - x, viewsize.y);
		spaceR->Show();
	} else {
		mainview->SetPosition(wxPoint(0, statsize.y));
		spaceL->Hide();
		spaceR->Hide();
	}

	return true;
}

// ウィンドウ作成イベント (を実現するためここではアイドルイベントを使う)
//
// 初期化(っぽい)もののうちコンストラクタでは早すぎるものは、コンストラクタ
// 直後に起こすこのイベント内で処理する。使えそうなイベントはいくつかあり、
// 自前の Create (今はもうない) -> WindowCreate -> Idle -> Paint の順で
// 起きるようだ。
// 自前の Create 時点では早すぎて、ここでサブウィンドウを作成するとサイズが 0
// みたいなウィンドウが一瞬出来てしまい GTK のワーニングが出る (その後サイズを
// 矯正すれば表示自体は出来る)。
// WindowCreate 時点ではその問題は起きないが、ここはまだメインウィンドウ表示
// 前なので、ここでサブウィンドウを表示した後メインウィンドウを表示すると
// (ウィンドウマネージャにもよるかもだが)、メインウィンドウがサブウィンドウを
// 隠してしまう。
// Idle イベント時点まで来ると上の2つの問題は回避できるようだ。
// Paint イベントの時点でもタイミング的には問題ないが、環境によっては(?)
// このイベント自体が飛んで来ないことがあるようだ。WXMainFrame の再描画は
// 子コントロールが行っており、そのため WXMainFrame 自身が再描画しなければ
// ならない領域は存在しないからかも知れないが詳細未調査。
void
WXMainFrame::OnIdle(wxIdleEvent& ev)
{
	// UIMessage の処理を開始する。
	// 少なくとも WXMainFrame のコンストラクタが完了した後のほうがいいはず。
	gMainApp.GetUIMessage()->StartUIMessage(&WXUIMessage::Process);

	// ウィンドウを表示
	for (int i = 0; i < subwindow_start.size(); i++) {
		if (subwindow_start[i]) {
			DoWindow(i, true);
		}
	}

	// 電源投入
	power->PushPowerButton();

	// 一度処理したら、このイベントを外す
	Disconnect(wxEVT_IDLE, wxIdleEventHandler(WXMainFrame::OnIdle));
}

// ウィンドウクローズイベント
void
WXMainFrame::OnClose(wxCloseEvent& event)
{
	// UIMessage を停止
	gMainApp.GetUIMessage()->StopUIMessage();
	// ステータスパネルの更新を停止
	statuspanel->Close();
	// メインビューの描画を停止
	mainview->Close();

	// サブウィンドウを全部閉じる
	for (int i = 0; i < ID_SUBWIN_MAX; i++) {
		if ((bool)windows[i]) {
			windows[i]->Close();
		}
	}

	// 後はデフォルト処理
	event.Skip();
}

// メニューの「VM > Push Power Button」イベント
void
WXMainFrame::OnPowerButton(wxCommandEvent& event)
{
	// LUNA はモーメンタリ動作、X68030 はオルタネート動作のスイッチ。
	// この違いは VM 側で吸収するのでここはボタンを押したという動作だけ
	// 通知する。
	power->PushPowerButton();
}

// メニューの「VM > Push Power Button」UI
void
WXMainFrame::OnPowerButtonUI(wxUpdateUIEvent& event)
{
	PowerButtonState state = power->GetPowerButtonState();
	if (state == PowerButtonState::NoState) {
		// LUNA など電源ボタンに電源オフを指示する機構がないので、
		// メニューを無効にする。
		event.Enable(false);
	} else {
		// オルタネートスイッチならチェックで状態を表す。(X68030)
		event.Check((bool)state);
	}
}

// メニューの「VM > Send CTRL+OPT.1+DEL」イベント
void
WXMainFrame::OnSoftReset(wxCommandEvent& event)
{
	keyboard->MakeKey(KC_CTRL);
	keyboard->MakeKey(KC_OPT1);
	keyboard->MakeKey(KC_DEL);
	keyboard->BreakKey(KC_DEL);
	keyboard->BreakKey(KC_OPT1);
	keyboard->BreakKey(KC_CTRL);
}

// メニューの「VM > Push Reset Button」イベント
// こっちは本体リセットボタンによるリセット
void
WXMainFrame::OnResetButton(wxCommandEvent& event)
{
	power->MakeResetHard();
}

// メニューの「VM > Push ABORT(Interrupt) Button」イベント
void
WXMainFrame::OnNMIButton(wxCommandEvent& event)
{
	auto nmi = GetNMIDevice();
	nmi->PressNMI();
}

// メニューの「VM > Restart VM」イベント
void
WXMainFrame::OnRestartVM(wxCommandEvent& event)
{
	power->MakeRestart();
}

// メニューの「VM > 終了」イベント
void
WXMainFrame::OnExit(wxCommandEvent& event)
{
	// ウィンドウを閉じる
	Close();
}

// メニューの「高速モード」イベント
void
WXMainFrame::OnFullSpeed(wxCommandEvent& event)
{
	if (event.IsChecked()) {
		// チェックが入ったので、高速モードにする
		syncer->NotifyFullSpeed(true);
	} else {
		// チェックが消えたので、通常モードにする
		syncer->NotifyFullSpeed(false);
	}
}

// メニューの「高速モード」UI
void
WXMainFrame::OnFullSpeedUI(wxUpdateUIEvent& event)
{
	bool is_fullspeed = syncer->GetFullSpeed();
	event.Check(is_fullspeed);
}

// メニューの「設定 > 入力 > *」の共通イベント
void
WXMainFrame::OnMenuInput(wxCommandEvent& event)
{
	bool char_mode = (event.GetId() == ID_INPUT_CHAR);

	mainview->SetCharInputMode(char_mode);
}

// メニューの「設定 > 入力 > *」の共通 UI
void
WXMainFrame::OnMenuInputUI(wxUpdateUIEvent& event)
{
	bool char_mode = mainview->GetCharInputMode();

	if (char_mode) {
		event.Check(event.GetId() == ID_INPUT_CHAR);
	} else {
		event.Check(event.GetId() == ID_INPUT_JP);
	}
}

// メニューの「設定 > HostNet* > *」の選択肢イベント
void
WXMainFrame::OnMenuHostNet(wxCommandEvent& event)
{
	int id = event.GetId();
	uint unit;
	uint choice;

	if (id <= ID_HOSTNET0_NONE) {
		unit = 0;
		choice = id;
	} else {
		unit = 1;
		choice = id - ID_HOSTNET1_START + ID_HOSTNET0_START;
	}

	// SLIRP モニタウィンドウはあれば閉じておく必要がある。
	if ((bool)windows[ID_MONITOR_SLIRP]) {
		DoWindow(ID_MONITOR_SLIRP, false);
	}

	// 設定を更新。
	std::string line = hostnet[unit]->GetConfigKey() + "-driver=";
	switch (choice) {
	 case ID_HOSTNET0_USERMODE:	line += "usermode";	break;
	 case ID_HOSTNET0_TAP:		line += "tap";		break;
	 case ID_HOSTNET0_BPF:		line += "bpf";		break;
	 case ID_HOSTNET0_AFPACKET:	line += "afpacket";	break;
	 case ID_HOSTNET0_NONE:		line += "none";		break;
	 default:
		assert(false);
	}
	gConfig->UpdateRunning(line);

	// ホストスレッドに通知。
	hostnet[unit]->NotifyReselectDriver();
}

// メニューの「設定 > HostNet* > *」の UI
void
WXMainFrame::OnMenuHostNetUI(wxUpdateUIEvent& event)
{
	int id = event.GetId();
	uint unit;

	if (id <= ID_HOSTNET0_NONE) {
		unit = 0;
	} else {
		unit = 1;
		id = id - ID_HOSTNET1_START + ID_HOSTNET0_START;
	}

	const std::string driver = hostnet[unit]->GetDriverName();
	uint selected;
	if (driver == "usermode") {
		selected = ID_HOSTNET0_USERMODE;
	} else if (driver == "tap") {
		selected = ID_HOSTNET0_TAP;
	} else if (driver == "bpf") {
		selected = ID_HOSTNET0_BPF;
	} else if (driver == "afpacket") {
		selected = ID_HOSTNET0_AFPACKET;
	} else {
		selected = ID_HOSTNET0_NONE;
	}

	event.Check((id == selected));
}

// メニューの「設定 > HostCom* > *」の選択肢イベント
void
WXMainFrame::OnMenuHostCom(wxCommandEvent& event)
{
	int id = event.GetId();
	HostCOMDevice *host;
	uint choice;

	if (id <= ID_HOSTCOM0_NONE) {
		host = hostcom[0];
		choice = id;
	} else if (id <= ID_HOSTCOM1_NONE) {
		host = hostcom[1];
		choice = id - ID_HOSTCOM1_START + ID_HOSTCOM0_START;
	} else if (id <= ID_HOSTCOM2_NONE) {
		host = hostcom[2];
		choice = id - ID_HOSTCOM2_START + ID_HOSTCOM0_START;
	} else {
		host = hostcom_dbg;
		choice = id - ID_HOSTCOM_DBG_START + ID_HOSTCOM0_START;
	}

	// 設定を更新。
	std::string line = host->GetConfigKey() + "-driver=";
	switch (choice) {
	 case ID_HOSTCOM0_TCP:		line += "tcp";		break;
	 case ID_HOSTCOM0_STDIO:	line += "stdio";	break;
	 case ID_HOSTCOM0_CONSOLE:	line += "console";	break;
	 case ID_HOSTCOM0_NONE:		line += "none";		break;
	 default:
		assert(false);
	}
	gConfig->UpdateRunning(line);

	// ホストスレッドに通知。
	host->NotifyReselectDriver();
}

// メニューの「設定 > HostCom* > *」の UI
void
WXMainFrame::OnMenuHostComUI(wxUpdateUIEvent& event)
{
	int id = event.GetId();
	HostCOMDevice *host;

	if (id <= ID_HOSTCOM0_NONE) {
		host = hostcom[0];
	} else if (id <= ID_HOSTCOM1_NONE) {
		host = hostcom[1];
		id = id - ID_HOSTCOM1_START + ID_HOSTCOM0_START;
	} else if (id <= ID_HOSTCOM2_NONE) {
		host = hostcom[2];
		id = id - ID_HOSTCOM2_START + ID_HOSTCOM0_START;
	} else {
		host = hostcom_dbg;
		id = id - ID_HOSTCOM_DBG_START + ID_HOSTCOM0_START;
	}

	const std::string driver = host->GetDriverName();
	uint selected;
	if (driver == "tcp") {
		selected = ID_HOSTCOM0_TCP;
	} else if (driver == "stdio") {
		selected = ID_HOSTCOM0_STDIO;
	} else if (driver == "console") {
		selected = ID_HOSTCOM0_CONSOLE;
	} else {
		selected = ID_HOSTCOM0_NONE;
	}

	event.Check((id == selected));
}

// メニューの「設定 > HostSound > *」の選択肢イベント
void
WXMainFrame::OnMenuHostSound(wxCommandEvent& event)
{
	// 設定を更新。
	std::string line = "hostsound-driver=";
	switch (event.GetId()) {
	 case ID_HOSTSOUND_NETBSD:	line += "netbsd";	break;
	 case ID_HOSTSOUND_SNDIO:	line += "sndio";	break;
	 case ID_HOSTSOUND_ALSA:	line += "alsa";		break;
	 case ID_HOSTSOUND_WAV:		line += "wav";		break;
	 case ID_HOSTSOUND_NONE:	line += "none";		break;
	 default:
		assert(false);
	}
	gConfig->UpdateRunning(line);

	// ホストスレッドに通知。
	hostsound->NotifyReselectDriver();
}

// メニューの「設定 > HostSound > *」の UI
void
WXMainFrame::OnMenuHostSoundUI(wxUpdateUIEvent& event)
{
	int id = event.GetId();
	const std::string driver = hostsound->GetDriverName();
	int selected;
	if (driver == "netbsd") {
		selected = ID_HOSTSOUND_NETBSD;
	} else if (driver == "sndio") {
		selected = ID_HOSTSOUND_SNDIO;
	} else if (driver == "alsa") {
		selected = ID_HOSTSOUND_ALSA;
	} else if (driver == "wav") {
		selected = ID_HOSTSOUND_WAV;
	} else {
		selected = ID_HOSTSOUND_NONE;
	}

	event.Check((id == selected));
}

// hostnet ドライバ起動失敗のメッセージ。
void
WXMainFrame::OnHostNetFailed(wxCommandEvent& event)
{
	wxString hostdev;
	wxString errmsg;
	uint n = event.GetInt();

	hostdev.Printf("hostnet%u", n);
	errmsg = hostnet[n]->GetErrmsg();
	HostDriverFailed(hostdev, errmsg);
}

// hostcom ドライバ起動失敗のメッセージ。
void
WXMainFrame::OnHostComFailed(wxCommandEvent& event)
{
	wxString hostdev;
	wxString errmsg;
	int n = event.GetInt();

	if (n >= 0) {
		hostdev.Printf("hostcom%u", n);
		errmsg = hostcom[n]->GetErrmsg();
	} else {
		hostdev = "debugger";
		errmsg = hostcom_dbg->GetErrmsg();
	}
	HostDriverFailed(hostdev, errmsg);
}

// hostsound ドライバ起動失敗のメッセージ
void
WXMainFrame::OnHostSoundFailed(wxCommandEvent& event)
{
	wxString errmsg;

	errmsg = hostsound->GetErrmsg();
	HostDriverFailed("hostsound", errmsg);
}

// ホストドライバ起動失敗のメッセージ。
void
WXMainFrame::HostDriverFailed(const wxString& hostdev, const wxString& errmsg)
{
	wxString msg;

	msg = hostdev;
	msg += ": ";
	msg += _("Failed to switch the driver.\n"
		"(Using 'none' instead)");
	if (!errmsg.empty()) {
		msg += "\n\n";
		msg += _("Error message:");
		msg += ' ';
		msg += errmsg;
	}
	::wxMessageBox(msg);
}

// スケール一覧
/*static*/ std::vector<double>
WXMainFrame::view_scales {
	0.50,
	0.75,
	1.00,
	1.50,
	2.00,
};

#define contains(v, key)	(std::find(v.begin(), v.end(), key) != v.end())

// 「表示 > スケール」のメニュー項目作成に必要な view_scale を作成し、
// ユーザ指定値があれば view_scales[] 内でのインデックスを返す。
// ユーザ指定値がなければ -1 を返す。
int
WXMainFrame::CreateMainviewScaleTable()
{
	// 設定ファイル mainview-scale で指定されたサイズを追加。
	// 値はこの時点で WXMainView::screen_scale にセットされている。
	double user_scale = WXMainView::screen_scale;
	if (contains(view_scales, user_scale)) {
		// プリセットと同じなら、このままでよい。
	} else {
		// 追加したらソート。
		view_scales.push_back(user_scale);
		std::sort(view_scales.begin(), view_scales.end());

		// ユーザ指定の値が何番目かを返す。指定されてなければ -1。
		for (int i = 0, end = view_scales.size(); i < end; i++) {
			if (view_scales[i] == user_scale) {
				return i;
			}
		}
		// 見付からないことはないはずだが
	}
	return -1;
}

// メニューの「表示 > スケール > *」の共通イベント
void
WXMainFrame::OnMenuScale(wxCommandEvent& event)
{
	int idx = event.GetId() - ID_SCALE_START;
	mainview->DoResize(view_scales[idx]);
	// 再レイアウト
	Fit();
}

// メニューの「表示 > スケール > *」の共通 UI
void
WXMainFrame::OnMenuScaleUI(wxUpdateUIEvent& event)
{
	int idx = event.GetId() - ID_SCALE_START;
	double current_scale = mainview->GetScale();

	event.Check(view_scales[idx] == current_scale);
}

// メニューの「表示 > フォントサイズ > *」の共通イベント
void
WXMainFrame::OnMenuFontSize(wxCommandEvent& event)
{
	FontId fontid;

	assertmsg(
		ID_FONTSIZE_12 <= event.GetId() && event.GetId() <= ID_FONTSIZE_24,
		"event.GetId()=%d", event.GetId());
	switch (event.GetId()) {
	 case ID_FONTSIZE_12:
		fontid = FontId::_6x12;
		break;
	 case ID_FONTSIZE_16:
		fontid = FontId::_8x16;
		break;
	 case ID_FONTSIZE_24:
		fontid = FontId::_12x24;
		break;
	 default:
		__unreachable();
		break;
	}

	if (fontid != gFontManager->GetFontId()) {
		gFontManager->SetFont(fontid);

		// アクティブなサブウィンドウ全員にフォント変更通知
		for (int i = 0; i < ID_SUBWIN_MAX; i++) {
			if ((bool)windows[i]) {
				WXSubWindow *subwin =
					dynamic_cast<WXSubWindow *>(windows[i].get());
				subwin->FontChanged();
			}
		}
		// メインウィンドウはここで直接ステータスパネルに通知
		statuspanel->FontChanged();

		// ステータスパネルの大きさが変わるので再レイアウト
		Fit();
	}
}

// メニューの「表示 > フォントサイズ > *」共通 UI
void
WXMainFrame::OnMenuFontSizeUI(wxUpdateUIEvent& event)
{
	FontId current_size = gFontManager->GetFontId();
	FontId event_size = (FontId)(event.GetId() - (ID_FONTSIZE_START+1));

	event.Check(current_size == event_size);
}

// 更新頻度一覧
/*static*/ std::vector<int>
WXMainFrame::monrates {
	20,
	5,
	1,
};

// 「表示 > モニタ更新頻度」のメニュー項目作成に必要な monrates を作成する。
void
WXMainFrame::CreateMonRateTable()
{
	// 設定ファイル monitor-rate で指定された値を追加。
	// 値はこの時点で monitor_rate にセットされている。
	int user_rate = monitor_rate;
	if (contains(monrates, user_rate)) {
		// プリセットと同じなら、このままでよい。
	} else {
		// 追加したら降順にソート。
		monrates.push_back(user_rate);
		std::sort(monrates.begin(), monrates.end(),
			[](int a, int b) { return a > b; }
		);
	}
}

// メニューの「表示 > モニタ更新頻度 > *」の共通イベント
void
WXMainFrame::OnMenuMonRate(wxCommandEvent& event)
{
	int idx = event.GetId() - ID_MONRATE_START;
	monitor_rate = monrates[idx];

	// アクティブなサブウィンドウのモニタパネル全員に通知。
	for (int i = 0; i < ID_SUBWIN_MAX; i++) {
		if ((bool)windows[i]) {
			auto *subwin = dynamic_cast<WXSubWindow *>(windows[i].get());
			for (wxWindow *child : subwin->GetChildren()) {
				auto *panel = dynamic_cast<WXMonitorPanel*>(child);
				if (panel) {
					panel->SetRate(monitor_rate);
				}
			}
		}
	}
}

// メニューの「表示 > モニタ更新頻度 > *」共通 UI
void
WXMainFrame::OnMenuMonRateUI(wxUpdateUIEvent& event)
{
	int idx = event.GetId() - ID_MONRATE_START;

	event.Check(monrates[idx] == monitor_rate);
}

// メニューの「表示 > ステータスパネルを表示」
void
WXMainFrame::OnShowStatusPanel(wxCommandEvent& event)
{
	// enable ならチェックを付けようとして呼ばれた
	bool enable = event.IsChecked();

	statuspanel->Show(enable);

	// 再レイアウト
	Fit();
}

// メニューの「表示 > ステータスパネルを表示」UI
void
WXMainFrame::OnShowStatusPanelUI(wxUpdateUIEvent& event)
{
	event.Check(statuspanel->IsShown());
}

// メニューの「デバイス > FD > 挿入」
void
WXMainFrame::OnFDInsert(wxCommandEvent& event)
{
	int unit = event.GetId() - ID_FD_INSERT_0;
	const auto *fdd = fdd_array[unit];
	assert(fdd);

	std::string title(_("Open a FD image"));
	std::string wildcard("FD images (*.xdf;*.img;*.fs)"
		"|*.xdf;*.XDF;*.img;*.IMG;*.fs;*.FS"
		"|All files (*.*)|*.*");

	// ディレクトリは、まだ一度も使ってなければ VM ディレクトリにする
	if (current_dir.empty()) {
		current_dir = gMainApp.GetVMDir();
	}

	wxFileDialog dialog(this, title, current_dir, "", wildcard,
		wxFD_OPEN | wxFD_FILE_MUST_EXIST);
	if (dialog.ShowModal() == wxID_CANCEL) {
		return;
	}
	auto path = std::string(dialog.GetPath());
	InsertFD(unit, path);
}

// メニューの「デバイス > FD > 挿入」UI
void
WXMainFrame::OnFDInsertUI(wxUpdateUIEvent& event)
{
	int unit = event.GetId() - ID_FD_INSERT_0;
	const auto *fdd = fdd_array[unit];
	assert(fdd);

	if (fdd->IsMediumLoaded()) {
		event.SetText(_("Ex&change"));
	} else {
		event.SetText(_("&Insert"));
	}
}

// メニューの「デバイス > FD > 取り出し」
void
WXMainFrame::OnFDEject(wxCommandEvent& event)
{
	int unit = event.GetId() - ID_FD_EJECT_0;

	EjectFD(unit, false);
}

// メニューの「デバイス > FD > 取り出し」UI
void
WXMainFrame::OnFDEjectUI(wxUpdateUIEvent& event)
{
	int unit = event.GetId() - ID_FD_EJECT_0;
	FDDDevice *fdd = fdd_array[unit];
	assert(fdd);

	// 挿入されてて取り出し可能な時のみ有効
	event.Enable(fdd->IsMediumLoaded() && fdd->IsEjectEnable());
}

// メニューの「デバイス > FD > 強制取り出し」
void
WXMainFrame::OnFDForce(wxCommandEvent& event)
{
	int unit = event.GetId() - ID_FD_FORCE_0;

	EjectFD(unit, true);
}

// メニューの「デバイス > FD > 強制取り出し」UI
void
WXMainFrame::OnFDForceUI(wxUpdateUIEvent& event)
{
	int unit = event.GetId() - ID_FD_FORCE_0;
	FDDDevice *fdd = fdd_array[unit];
	assert(fdd);

	// 挿入されてて取り出し禁止の時のみ有効
	event.Enable(fdd->IsMediumLoaded() && fdd->IsEjectEnable() == false);
}

// メニューの「デバイス > CD/MO > 挿入」
void
WXMainFrame::OnCDInsert(wxCommandEvent& event)
{
	std::string title;
	std::string wildcard;

	int id = event.GetId() - ID_CD_INSERT_0;
	const auto *dev = scsidev[id];
	assert(dev);

	switch (dev->GetDevType()) {
	 case SCSI::DevType::CD:
		title = _("Open a CD image");
		wildcard = "ISO images (*.iso;*.img)|*.iso;*.img;*.ISO;*.IMG";
		break;
	 case SCSI::DevType::MO:
		title = _("Open an MO image");
		wildcard = "MO images (*.img;*.mos)|*.img;*.mos;*.IMG;*.MOS";
		break;
	 default:
		break;
	}
	wildcard += "|All files (*.*)|*.*";

	// ディレクトリは、まだ一度も使ってなければ VM ディレクトリにする
	if (current_dir.empty()) {
		current_dir = gMainApp.GetVMDir();
	}

	wxFileDialog dialog(this, title, current_dir, "", wildcard,
		wxFD_OPEN | wxFD_FILE_MUST_EXIST);
	if (dialog.ShowModal() == wxID_CANCEL) {
		return;
	}
	auto path = std::string(dialog.GetPath());
	InsertCD(id, path);
}

// メニューの「デバイス > CD/MO > 挿入」UI
void
WXMainFrame::OnCDInsertUI(wxUpdateUIEvent& event)
{
	int id = event.GetId() - ID_CD_INSERT_0;
	SCSIDisk *disk = dynamic_cast<SCSIDisk *>(scsidev[id]);
	assert(disk);

	if (disk->IsMediumLoaded()) {
		event.SetText(_("Ex&change"));
	} else {
		event.SetText(_("&Insert"));
	}
}

// メニューの「デバイス > CD/MO > 取り出し」
void
WXMainFrame::OnCDEject(wxCommandEvent& event)
{
	int id = event.GetId() - ID_CD_EJECT_0;

	EjectCD(id, false);
}

// メニューの「デバイス > CD/MO > 取り出し」UI
void
WXMainFrame::OnCDEjectUI(wxUpdateUIEvent& event)
{
	int id = event.GetId() - ID_CD_EJECT_0;
	SCSIDisk *disk = dynamic_cast<SCSIDisk *>(scsidev[id]);
	assert(disk);

	// 挿入されてて取り出し可能な時のみ有効
	event.Enable(disk->IsMediumLoaded() &&
	             disk->IsMediumRemovalAllowed());
}

// メニューの「デバイス > CD/MO > 強制取り出し」
void
WXMainFrame::OnCDForce(wxCommandEvent& event)
{
	int id = event.GetId() - ID_CD_FORCE_0;

	EjectCD(id, true);
}

// メニューの「デバイス > CD/MO > 強制取り出し」UI
void
WXMainFrame::OnCDForceUI(wxUpdateUIEvent& event)
{
	int id = event.GetId() - ID_CD_FORCE_0;
	SCSIDisk *disk = dynamic_cast<SCSIDisk *>(scsidev[id]);
	assert(disk);

	// 挿入されてて取り出し禁止の時のみ有効
	event.Enable(disk->IsMediumLoaded() &&
	             disk->IsMediumRemovalPrevented());
}

// メニューの「デバイス > マウスモード」
void
WXMainFrame::OnMouseMode(wxCommandEvent& event)
{
	// enable ならチェックを付けようとして呼ばれた
	bool enable = event.IsChecked();

	// マウスモードを設定
	wxCommandEvent newev(NONO_EVT_MOUSEMODE, GetId());
	newev.SetEventObject(this);
	newev.SetInt(enable);
	::wxPostEvent(mainview, newev);
}

// メニューの「デバイス > マウスモード」UI
void
WXMainFrame::OnMouseModeUI(wxUpdateUIEvent& event)
{
	event.Check(mainview->GetMouseMode());
}

// メニューの「デバイス > Operation > Keyboard Connect」イベント
void
WXMainFrame::OnKeyboardConnect(wxCommandEvent& event)
{
	// enable ならチェックを付けようとして呼ばれた
	bool enable = event.IsChecked();

	// キーボード接続を設定
	keyboard->Connect(enable);
}

// メニューの「デバイス > Operation > Keyboard Connect」UI
void
WXMainFrame::OnKeyboardConnectUI(wxUpdateUIEvent& event)
{
	event.Check(keyboard->IsConnected());
}

// メニューのウィンドウ ID 共通イベント
// チェックが入る方なら、ウィンドウを作成して表示。
// チェックが消える方なら、ウィンドウを削除。
void
WXMainFrame::OnWindow(wxCommandEvent& event)
{
	int id = event.GetId();

	DoWindow(id, event.IsChecked());
}

#define SetMonitor(id, name)	do {	\
	try {	\
		windows[id].reset(new WXMonitorWindow(this, (name), \
			gMonitorManager->Get(id)));	\
	} catch (...) {	}	\
} while (0)

void
WXMainFrame::DoWindow(int id, bool enable)
{
	// モニタウィンドウを作成する共通部分。
	// id はモニター ID であると同時に、windows[] 配列の添字、さらにこの
	// モニタウィンドウの wxWindowID としても使っている
	// (WXSubWindow::OnClose() 参照)。

	if (enable) {
		// チェックが入ったのでウィンドウを作成
		switch (id) {
		 case ID_MONITOR_ACCSTAT:
			// こいつだけモニタ ID を持ちつつ上位互換のサブウィンドウがある。
			try {
				windows[id].reset(new
					WXAccStatWindow(this, gMonitorManager->Get(id)));
			} catch (...) { }
			break;

		 case ID_MONITOR_ADPCM:
			SetMonitor(id, "ADPCM");
			break;

		 case ID_MONITOR_AREASET:
			SetMonitor(id, _("Areaset"));
			break;

		 case ID_MONITOR_ATC0:
		 case ID_MONITOR_ATC1:
		 case ID_MONITOR_ATC2:
		 case ID_MONITOR_ATC3:
		 case ID_MONITOR_ATC4:
		 case ID_MONITOR_ATC5:
		 case ID_MONITOR_ATC6:
		 case ID_MONITOR_ATC7:
		 {
			uint n = id - ID_MONITOR_ATC0;
			wxString name(string_format("ATC#%u", n));
			if ((n & 1) == 0) {
				name += _("(Data)");
			} else {
				name += _("(Inst)");
			}
			SetMonitor(id, name);
			break;
		 }

		 case ID_MONITOR_BREAKPOINT:
			SetMonitor(id, _("Breakpoints"));
			break;

		 case ID_MONITOR_BT45x:
			SetMonitor(id, "Bt454/458");
			break;

		 case ID_MONITOR_CMMU0:
		 case ID_MONITOR_CMMU1:
		 case ID_MONITOR_CMMU2:
		 case ID_MONITOR_CMMU3:
		 case ID_MONITOR_CMMU4:
		 case ID_MONITOR_CMMU5:
		 case ID_MONITOR_CMMU6:
		 case ID_MONITOR_CMMU7:
		 {
			uint n = id - ID_MONITOR_CMMU0;
			wxString name(string_format("CMMU#%u", n));
			if ((n & 1) == 0) {
				name += _("(Data)");
			} else {
				name += _("(Inst)");
			}
			name += ' ';
			name += _("Registers");
			SetMonitor(id, name);
			break;
		 }

		 case ID_MONITOR_CRTC:
			SetMonitor(id, _("CRTC and Video Controller"));
			break;

		 case ID_MONITOR_DMAC:
			SetMonitor(id, "DMAC (HD63450)");
			break;

		 case ID_MONITOR_FDC:
			SetMonitor(id, "FDC (uPD72065B)");
			break;

		 case ID_MONITOR_HOSTCOM0:
		 case ID_MONITOR_HOSTCOM1:
		 case ID_MONITOR_HOSTCOM2:
		 {
			uint n = id - ID_MONITOR_HOSTCOM0;
			SetMonitor(id, GetHostComLabel(n));
			break;
		 }

		 case ID_MONITOR_HOSTNET0:
		 case ID_MONITOR_HOSTNET1:
		 {
			uint n = id - ID_MONITOR_HOSTNET0;
			SetMonitor(id, GetHostNetLabel(n));
			break;
		 }

		 case ID_MONITOR_HOSTSOUND:
			SetMonitor(id, _("Host Sound"));
			break;

		 case ID_MONITOR_INTERRUPT:
		 {
			wxString name;
			if (gMainApp.Has(VMCap::M68K)) {
				name = MPUName() + " " + _("Interrupts");
			} else {
				// 実際はほぼシステムコントローラなのでややこしい。
				name = "MPU " + _("Interrupts");
			}
			SetMonitor(id, name);
			break;
		 }

		 case ID_MONITOR_KEYBOARD:
			SetMonitor(id, _("Keyboard/Mouse"));
			break;

		 case ID_MONITOR_LANCE:
			SetMonitor(id, "LANCE (AM7990)");
			break;

		 case ID_MONITOR_LCD:
			try {
				windows[id].reset(new WXLCDMonitor(this));
			} catch (...) { }
			break;

		 case ID_MONITOR_LOG:
			try {
				windows[id].reset(new WXLogMonitor(this));
			} catch (...) { }
			break;

		 case ID_MONITOR_LUNAVC:
			SetMonitor(id, _("Video Control"));
			break;

		 case ID_MONITOR_MAINBUS:
		 {
			wxString name;
			if (gMainApp.IsX68030()) {
				name = _("Device Map (32bit space)");
			} else {
				name = _("Device Map");
			}
			SetMonitor(id, name);
			break;
		 }

		 case ID_MONITOR_MEMDUMP0 ...
		      ID_MONITOR_MEMDUMP(MAX_MEMDUMP_MONITOR - 1):
		 case ID_MONITOR_XPMEMDUMP0 ...
		      ID_MONITOR_XPMEMDUMP(MAX_XPMEMDUMP_MONITOR - 1):
		 {
			try {
				windows[id].reset(new WXMemdumpWindow(this, id));
			} catch (...) { }
			break;
		 }

		 case ID_MONITOR_MFP:
			SetMonitor(id, "MFP (MC68901)");
			break;

		 case ID_MONITOR_MK48T02:
			SetMonitor(id, "RTC (MK48T02)");
			break;

		 case ID_MONITOR_MPUATC:
			SetMonitor(id, MPUName() + " ATC");
			break;

		 case ID_MONITOR_MPUCACHE:
			SetMonitor(id, MPUName() + " " + _("Caches"));
			break;

		 case ID_MONITOR_MPUREG:
			SetMonitor(id, MPUName() + " " + _("Registers"));
			break;

		 case ID_MONITOR_NEREID:
			SetMonitor(id, "Nereid");
			break;

		 case ID_MONITOR_ROMCONS:
			SetMonitor(id, _("ROM Console (provisional)"));
			break;

		 case ID_MONITOR_PIO:
			SetMonitor(id, "PIO0/PIO1");
			break;

		 case ID_MONITOR_RENDERER:
			SetMonitor(id, _("Renderer"));
			break;

		 case ID_MONITOR_RTC:
		 {
			// X68k と virt68k が RTC のモニタ ID を共有している。
			// MK48T02 は NVRAM のモニタ ID を使っているのでここではない。
			wxString name;
			if (gMainApp.IsX68030()) {
				name = "RTC (RP5C15)";
			} else if (gMainApp.IsVIRT68K()) {
				name = "RTC (Goldfish RTC/Timer)";
			} else {
				name = "RTC";
			}
			SetMonitor(id, name);
			break;
		 }

		 case ID_MONITOR_RTL8019AS0:
		 case ID_MONITOR_RTL8019AS1:
		 {
			uint n = id - ID_MONITOR_RTL8019AS0;
			wxString name = string_format("Nereid#%u (RTL8019AS)", n);
			SetMonitor(id, name);
			break;
		 }

		 case ID_MONITOR_SCC:
			SetMonitor(id, "SCC (Z8530)");
			break;

		 case ID_MONITOR_SCHEDULER:
			SetMonitor(id, _("Scheduler"));
			break;

		 case ID_MONITOR_SCSIDEVS:
			SetMonitor(id, _("SCSI Devices"));
			break;

		 case ID_MONITOR_SIO:
			SetMonitor(id, "SIO (uPD7201)");
			break;

		 case ID_MONITOR_SLIRP:
			SetMonitor(id, _("SLIRP Internal States"));
			break;

		 case ID_MONITOR_SOUND:
			SetMonitor(id, _("Sound"));
			break;

		 case ID_MONITOR_SPC:
			SetMonitor(id, "SPC (MB89352)");
			break;

		 case ID_MONITOR_SSG:
			SetMonitor(id, "SSG (YM2149)");
			break;

		 case ID_MONITOR_SYSCLK:
			SetMonitor(id, _("System Clock"));
			break;

		 case ID_MONITOR_SYSPORT:
			SetMonitor(id, _("System Port"));
			break;

		 case ID_MONITOR_VIRTIO_BLOCK0 ...
		      ID_MONITOR_VIRTIO_BLOCK7:
		 {
			uint n = id - ID_MONITOR_VIRTIO_BLOCK0;
			wxString name = _("VirtIO Block") + string_format(" #%u", n);
			SetMonitor(id, name);
			break;
		 }

		 case ID_MONITOR_VIRTIO_NET:
			SetMonitor(id, _("VirtIO Network"));
			break;

		 case ID_MONITOR_VIRTIO_SCSI:
			SetMonitor(id, "VirtIO SCSI");
			break;

		 case ID_MONITOR_VIRTIO_ENTROPY:
			SetMonitor(id, _("VirtIO Entropy"));
			break;

		 case ID_MONITOR_WXHOSTINFO:
			SetMonitor(id, _("Host Information"));
			break;

		 case ID_MONITOR_XPASCI:
			SetMonitor(id, "HD647180 ASCI");
			break;

		 case ID_MONITOR_XPREG:
			SetMonitor(id, "HD647180 " + _("Registers"));
			break;

		 case ID_MONITOR_XPINTR:
			SetMonitor(id, "HD647180 " + _("Interrupts"));
			break;

		 case ID_MONITOR_XPIO:
			SetMonitor(id, _("HD647180 Internal Devices"));
			break;

		 case ID_SUBWIN_BRHIST:
		 {
			wxString name = MPUName() + " " + _("Branch History");
			try {
				windows[id].reset(new WXScrollMonitorWindow(this, name,
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;
		 }

		 case ID_SUBWIN_EXHIST:
		 {
			wxString name = MPUName() + " " + _("Exception History");
			try {
				windows[id].reset(new WXScrollMonitorWindow(this, name,
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;
		 }

		 case ID_SUBWIN_CACHE0:
		 case ID_SUBWIN_CACHE1:
		 case ID_SUBWIN_CACHE2:
		 case ID_SUBWIN_CACHE3:
		 case ID_SUBWIN_CACHE4:
		 case ID_SUBWIN_CACHE5:
		 case ID_SUBWIN_CACHE6:
		 case ID_SUBWIN_CACHE7:
		 {
			uint n = id - ID_SUBWIN_CACHE0;
			wxString name = _("Cache");
			name += string_format("#%u", n);
			if ((n & 1) == 0) {
				name += _("(Data)");
			} else {
				name += _("(Inst)");
			}
			try {
				windows[id].reset(new WXCacheWindow(this, name,
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;
		 }

		 case ID_SUBWIN_DIPSW:
			try {
				windows[id].reset(new WXDipswWindow(this));
			} catch (...) { }
			break;

		 case ID_SUBWIN_GVRAM:
			try {
				windows[id].reset(new WXGVRAMWindow(this, _("Graphic Screen")));
			} catch (...) { }
			break;

		 case ID_SUBWIN_LCDPANEL:
			try {
				windows[id].reset(new WXLCDWindow(this));
			} catch (...) { }
			break;

		 case ID_SUBWIN_LOGSETTING:
			try {
				windows[id].reset(new WXLogSettingWindow(this));
			} catch (...) { }
			break;

		 case ID_SUBWIN_LUNAFB:
		 case ID_SUBWIN_TVRAM:
		 {
			// 呼び方が違うので名前と ID が違う
			wxString name;
			if (id == ID_SUBWIN_LUNAFB) {
				name = _("Bitmap Plane");
			} else {
				name = _("Text Screen");
			}
			try {
				windows[id].reset(new WXTVRAMWindow(this, name));
			} catch (...) { }
			break;
		 }

		 case ID_SUBWIN_MPUPT:
		 {
			wxString name = MPUName() + " " + _("Page Table");
			if (MPUName() == "MC68030") {
				try {
					windows[id].reset(new WXPageTable68030Window(this, name));
				} catch (...) { }
			} else if (MPUName() == "MC68040") {
				try {
					windows[id].reset(new WXPageTable68040Window(this, name));
				} catch (...) { }
			} else {
				// not supported
			}
			break;
		 }

		 case ID_SUBWIN_NEWSIO:
			try {
				windows[id].reset(new WXScrollMonitorWindow(this,
					_("Device Map (NEWS)"),
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;

		 case ID_SUBWIN_PALETTE:
			try {
				windows[id].reset(new WXPaletteWindow(this,
					_("Palette Colormap")));
			} catch (...) { }
			break;

		 case ID_SUBWIN_ROM:
			try {
				windows[id].reset(new WXROMWindow(this));
			} catch (...) { }
			break;

		 case ID_SUBWIN_SOFTKEY:
			try {
				windows[id].reset(new WXSoftKeyWindow(this,
					gMainApp.GetVMType()));
			} catch (...) { }
			break;

		 case ID_SUBWIN_THREADS:
			try {
				windows[id].reset(new WXThreadMonitorWindow(this));
			} catch (...) { }
			break;

		 case ID_SUBWIN_VECTOR:
		 {
			wxString name = MPUName() + " " + _("Vector Table");
			try {
				windows[id].reset(new WXScrollMonitorWindow(this, name,
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;
		 }

		 case ID_SUBWIN_X68KIO:
			try {
				windows[id].reset(new WXScrollMonitorWindow(this,
					_("Device Map (24bit space)"),
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;

		 case ID_SUBWIN_XPBRHIST:
		 {
			wxString name = "HD647180 " + _("Branch History");
			try {
				windows[id].reset(new WXScrollMonitorWindow(this, name,
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;
		 }

		 case ID_SUBWIN_XPEXHIST:
		 {
			wxString name = "HD647180 " + _("Exception History");
			try {
				windows[id].reset(new WXScrollMonitorWindow(this, name,
					gMonitorManager->Get(id)));
			} catch (...) { }
			break;
		 }

		 default:
			PANIC("'%s' not listed", Monitor::GetIdText(id));
		}
		windows[id]->Show();
	} else {
		// チェックが消えたのでウィンドウを終了
		windows[id]->Close();
		// (削除自体はこの少し下の DeleteWindow() で行なっている)
	}
}

// Update UI イベント
void
WXMainFrame::OnWindowUI(wxUpdateUIEvent& event)
{
	int id = event.GetId();

	// ウィンドウが存在していればチェックを表示
	event.Check((bool)windows[id]);

	// SLIRP モニタだけ無効状態がある。
#if defined(HAVE_HOSTNET_SLIRP)
	if (__predict_false(id == ID_MONITOR_SLIRP)) {
		event.Enable(NetDriverSlirp::IsOccupied());
	}
#endif

	if (IS_MONITOR_MEMDUMP(id) || IS_MONITOR_XPMEMDUMP(id)) {
		// メニューに現在のアドレスを載せる
		// (ウィンドウの生存期間外にも呼ばれることに注意)
		auto mon = gMonitorManager->Get(id);
		auto *memdump = dynamic_cast<Memdump *>(mon->obj);
		assert(memdump);
		uint32 addr = memdump->GetAddr().Addr();

		uint n;
		wxString text = _("Memory Dump");
		if (IS_MONITOR_XPMEMDUMP(id)) {
			n = id - ID_MONITOR_XPMEMDUMP0;
			text += string_format(" &%u: $%05x", n, addr);
		} else {
			n = id - ID_MONITOR_MEMDUMP0;
			text += string_format(" &%u: $%08x", n, addr);
		}
		event.SetText(text);
	}
}

// バージョンダイアログ
void
WXMainFrame::OnAbout(wxCommandEvent& event)
{
	WXVersionDlg dlg(this);
	dlg.ShowModal();
}

// FD を挿入する。
void
WXMainFrame::InsertFD(int unit, const std::string& path)
{
	FDDDevice *fdd = fdd_array[unit];
	assert(fdd);

	// VM に依頼
	// (エラーが起きれば別途 UI メッセージが飛んでくる)
	fdd->LoadDiskUI(path);

	// 次回以降のダイアログのカレントディレクトリをここに更新
	auto pos = path.rfind('/');
	current_dir = path.substr(0, pos);
}

// FD をイジェクトする。
void
WXMainFrame::EjectFD(int unit, bool force)
{
	FDDDevice *fdd = fdd_array[unit];
	assert(fdd);

	fdd->UnloadDiskUI(force);
}

// CD/MO を挿入する。
void
WXMainFrame::InsertCD(int id, const std::string& path)
{
	SCSIDisk *scsidisk = dynamic_cast<SCSIDisk*>(scsidev[id]);
	assert(scsidisk);

	// VM に依頼
	// (エラーが起きれば別途 UI メッセージが飛んでくる)
	scsidisk->LoadDiskUI(path);

	// 次回以降のダイアログのカレントディレクトリをここに更新
	auto pos = path.rfind('/');
	current_dir = path.substr(0, pos);
}

// CD/MO をイジェクトする。
void
WXMainFrame::EjectCD(int id, bool force)
{
	SCSIDisk *scsidisk = dynamic_cast<SCSIDisk*>(scsidev[id]);
	assert(scsidisk);

	scsidisk->UnloadDiskUI(force);
}

// CD/MO/FD の挿入失敗メッセージ。
// CD/MO と FD でメッセージが同じなので共用する。
void
WXMainFrame::OnInsertFailed(wxCommandEvent& event)
{
	::wxMessageBox(_("Cannot load the image file"));
}

// ウィンドウをリストから削除する
// (ウィンドウの Close() から呼ばれる)
void
WXMainFrame::DeleteWindow(wxWindow *win)
{
	for (int id = 0; id < ID_SUBWIN_MAX; id++) {
		if (windows[id].get() == win) {
			windows[id].reset();
			break;
		}
	}
}

// m680x0 ダブルバスフォールト処理
void
WXMainFrame::OnHaltNotify(wxCommandEvent& event)
{
	::wxMessageBox(_("Double bus fault has occurred in VM"),
		wxEmptyString,
		wxICON_EXCLAMATION);
}

// HostNet<n> に対する表示用テキストを返す。
wxString
WXMainFrame::GetHostNetLabel(uint n) const
{
	return wxString(string_format("HostNet%u: ", n) +
		hostnet[n]->GetPortName());
}

// HostCOM<n> に対する表示用テキストを返す。
// デバッガ用は呼び出し側で処理すること。
wxString
WXMainFrame::GetHostComLabel(uint n) const
{
	assert((int)n >= 0);
	return wxString(string_format("HostCom%u: ", n) +
		hostcom[n]->GetPortName());
}

// メインウィンドウのタイトルを (mousemode によって) 変更する。
void
WXMainFrame::SetMyTitle(bool mousemode)
{
	wxString new_title = frame_title;
	if (mousemode) {
		// メッセージを表示したい
		new_title += " (CTRL + F12 to quit mouse mode)";
	}
	SetTitle(new_title);
}

// 起動時にオープンするサブウィンドウ (-M オプション)
/*static*/ std::array<bool, ID_SUBWIN_MAX> WXMainFrame::subwindow_start;

// モニタの更新頻度
/*static*/ int WXMainFrame::monitor_rate;

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.