--- nono/wx/wxmainframe.cpp 2026/04/29 17:04:51 1.1.1.8 +++ nono/wx/wxmainframe.cpp 2026/04/29 17:05:58 1.1.1.25 @@ -4,173 +4,588 @@ // Licensed under nono-license.txt // +// +// メインウィンドウ +// + #include "wxmainframe.h" +#include "fontmanager.h" +#include "wxaccstatmonitor.h" +#include "wxdipswwindow.h" #include "wxdumpmonitor.h" -#include "wxhistmonitor.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 "wxtextpanel.h" +#include "wxstatuspanel.h" +#include "wxthreadmonitor.h" +#include "wxtvrammonitor.h" +#include "wxuimessage.h" #include "wxversion.h" -#include "wxvideomonitor.h" -#include "bitmap.h" -#include "bt454.h" -#include "debugger.h" -#include "dmac.h" -#include "interrupt.h" -#include "keyboard.h" -#include "lance.h" -#include "lcd.h" +#include "config.h" +#include "fdc.h" +#include "fdd.h" +#include "hostcom.h" +#include "hostnet.h" +#include "hostsound.h" #include "mainapp.h" -#include "mfp.h" -#include "mpu680x0.h" -#include "mpu88xx0.h" -#include "netdriver.h" -#include "pio.h" -#include "rtc.h" -#include "scc.h" -#include "scheduler.h" -#include "sio.h" -#include "spc.h" -#include "vm.h" +#include "mpu.h" +#include "netdriver_slirp.h" +#include "nmi.h" +#include "power.h" +#include "scsidev.h" +#include "scsidomain.h" +#include "syncer.h" +#include + +// 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, -static void wxmainframe_poweroff_callback(); -static void wxmainframe_halt_callback(void *); + ID_MOUSEMODE, + ID_KEYBOARD_CONNECT, -WXMainFrame *gMainFrame; + 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_CREATE(WXMainFrame::OnCreate) EVT_CLOSE(WXMainFrame::OnClose) - EVT_MENU(wxID_EXIT, WXMainFrame::OnExit) + 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(ID_RESET_VM, WXMainFrame::OnResetVM) - EVT_MENU_RANGE(ID_SCALE_START + 1, ID_SCALE_END - 1, + 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 + 1, ID_SCALE_END - 1, + 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 + 1, ID_MONRATE_END - 1, + EVT_MENU_RANGE(ID_MONRATE_START, ID_MONRATE_END, WXMainFrame::OnMenuMonRate) - EVT_UPDATE_UI_RANGE(ID_MONRATE_START + 1, ID_MONRATE_END - 1, + 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_RANGE(WinID_Start, WinID_MAX - 1, WXMainFrame::OnWindow) - EVT_UPDATE_UI_RANGE(WinID_Start, WinID_MAX - 1, WXMainFrame::OnWindowUI) - EVT_MENU(wxID_ABOUT, WXMainFrame::OnAbout) + 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_COMMAND(wxID_ANY, WXW_EVT_HALT_NOTIFY, WXMainFrame::OnHaltNotify) + EVT_MENU(wxID_ABOUT, WXMainFrame::OnAbout) wxEND_EVENT_TABLE() WXMainFrame::WXMainFrame(wxWindow *parent) - : wxFrame(parent, wxID_ANY, "nono", wxDefaultPosition, wxDefaultSize, + : inherited(parent, wxID_ANY, "nono", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE & ~wxMAXIMIZE_BOX & ~wxRESIZE_BORDER) { - vmtype_t vmtype; + // デバイスを取得 + power = GetPowerDevice(); + syncer = GetSyncer(); + // デバイスを取得。こっちはあるかどうかを調べる + keyboard = gMainApp.FindObject(OBJ_KEYBOARD); + for (uint i = 0; i < hostnet.size(); i++) { + hostnet[i] = gMainApp.FindObject(OBJ_HOSTNET(i)); + } + for (uint i = 0; i < hostcom.size(); i++) { + hostcom[i] = gMainApp.FindObject(OBJ_HOSTCOM(i)); + } + hostcom_dbg = gMainApp.FindObject(OBJ_HOSTCOM_DBG); + hostsound = gMainApp.FindObject(OBJ_HOSTSOUND); - vmtype = gMainApp.GetVMType(); + // メイン MPU 名を取得。メニューとタイトルによく出てくるので。 + // "88xx0" とか "88200" は別処理。 + mpuname = GetMPUDevice()->GetMPUName(); - // とりあえずメニュー - // XXX すぐにメニューファクトリーになる + // メニュー wxMenuBar *menubar = new wxMenuBar(); - wxMenu *menuFile = new wxMenu(); - // XXX とりあえず適当なところにおいとく - menuFile->Append(ID_FULL_SPEED, _("Full &Speed Mode"), "", wxITEM_CHECK); - menuFile->Append(ID_RESET_VM, _("&Reset VM"), ""); - menuFile->Append(wxID_EXIT, _("E&xit")); - menubar->Append(menuFile, _("&File")); + // メニュー > 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(); - menuViewScale->Append(ID_SCALE_1_00, _("x1.&0"), "", wxITEM_CHECK); - menuViewScale->Append(ID_SCALE_0_75, _("x0.&75"), "", wxITEM_CHECK); - menuViewScale->Append(ID_SCALE_0_50, _("x0.&5"), "", wxITEM_CHECK); + 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(); - menuMonRate->AppendRadioItem(ID_MONRATE_20, _("High (&20Hz)")); - menuMonRate->AppendRadioItem(ID_MONRATE_5, _("Middle (&5Hz)")); - menuMonRate->AppendRadioItem(ID_MONRATE_1, _("Low (&1Hz)")); + 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")); - menubar->Append(menuDisplay, _("&View")); + menuDisplay->AppendCheckItem(ID_SHOW_STATPANEL, _("Show Status &Panel")); + menubar->Append(menuDisplay, _("V&iew")); + // メニュー > デバイス wxMenu *menuDevice = new wxMenu(); - menuDevice->Append(WinID_SOFTKEY, _("Software &Keyboard"), - "", wxITEM_CHECK); - menuDevice->Append(ID_MOUSEMODE, _("&Mouse Mode\tCTRL+F12"), - "", wxITEM_CHECK); - if (gMainApp.HasVMFeature(VMF_LUNA)) { - menuDevice->Append(WinID_LCD, _("Front &LCD"), - "", wxITEM_CHECK); + 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 AppendIf(menu, id, label) do { \ - int id_ = __CONCAT(WinID_MONITOR_,id); \ - const auto& info_ = monitor_info[(id_)]; \ - if (gMainApp.HasVMFeature(info_.feature)) { \ - (menu)->Append((id_), (label), "", wxITEM_CHECK); \ +#define AppendIfSeparator(menu, vmcap_) do { \ + if (gMainApp.Has(vmcap_)) { \ + (menu)->AppendSeparator(); \ } \ } while (0) + // メニュー > モニター wxMenu *menuMonitor = new wxMenu(); - AppendIf(menuMonitor, SCHEDULER, _("&Scheduler")); - AppendIf(menuMonitor, LOG, _("&Log")); - - wxMenu *menuDebug = new wxMenu(); - AppendIf(menuDebug, BPOINT, _("&Breakpoint")); - AppendIf(menuDebug, BRHIST68K, _("Branch &History")); - AppendIf(menuDebug, BRHIST88K, _("Branch &History")); - AppendIf(menuDebug, EXHIST68K, _("&Exception History")); - AppendIf(menuDebug, EXHIST88K, _("&Exception History")); - menuMonitor->AppendSubMenu(menuDebug, _("&Debug")); + AppendIf(menuMonitor, ID_MONITOR_SCHEDULER, _("&Scheduler")); wxMenu *menuMPU = new wxMenu(); - AppendIf(menuMPU, MPUREG, _("&Register")); - AppendIf(menuMPU, MPUATC, "&ATC"); - AppendIf(menuMPU, CMMU7, "CMMU#7" + _("(Inst)")); - AppendIf(menuMPU, ATC7, "ATC#7"); - AppendIf(menuMPU, CACHE7, _("Cache#7")); - AppendIf(menuMPU, CMMU6, "CMMU#6" + _("(Data)")); - AppendIf(menuMPU, ATC6, "ATC#6"); - AppendIf(menuMPU, CACHE6, _("Cache#6")); - menuMonitor->AppendSubMenu(menuMPU, "MP&U"); - - AppendIf(menuMonitor, MEMDUMP, _("&Memory Dump")); - AppendIf(menuMonitor, INTERRUPT, _("&Interrupt")); - AppendIf(menuMonitor, SPC, "S&PC"); - AppendIf(menuMonitor, RTC, "&RTC"); - AppendIf(menuMonitor, DMAC, "DM&AC"); - AppendIf(menuMonitor, MFP, "M&FP"); - AppendIf(menuMonitor, SCC, "SCC(&Z)"); - AppendIf(menuMonitor, SIO, "SIO(&U)"); - AppendIf(menuMonitor, PIO0, "PIO0"); - AppendIf(menuMonitor, LUNAKEYBOARD, _("&Keyboard/Mouse")); - AppendIf(menuMonitor, LANCE, "Lance(&E)"); - AppendIf(menuMonitor, LCD, "LCD(&J)"); - AppendIf(menuMonitor, BITMAP, _("&Bitmap Control")); - AppendIf(menuMonitor, BITMAP0, _("Bitmap #&0")); + 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, HOSTINFO, _("Host &Information")); - AppendIf(menuHost, NETDRIVER, _("Host &Network")); + 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() で上に追加。 @@ -185,21 +600,14 @@ WXMainFrame::WXMainFrame(wxWindow *paren SetMenuBar(menubar); - wxString title("nono"); - switch (vmtype) { - case VMTYPE_X68030: - title += "(X68030)"; - break; - case VMTYPE_LUNA1: - title += "(LUNA-I)"; - break; - case VMTYPE_LUNA88K: - title += "(LUNA88K)"; - break; - default: - break; + frame_title = wxString("nono"); + const char *vmname = gMainApp.GetVMName(); + if (vmname) { + frame_title += '('; + frame_title += wxString(vmname); + frame_title += ')'; } - SetTitle(title); + SetMyTitle(false); // UpdateUI イベントは Idle 時に時折チェックされるらしいが、 // (メインビューとサブウィンドウを合わせた) タイマーイベントの粒度が @@ -211,88 +619,481 @@ WXMainFrame::WXMainFrame(wxWindow *paren // 念のため事象が分かりやすいように 10秒くらいにしておく。 wxUpdateUIEvent::SetUpdateInterval(10000); - wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); - - gMainView = new WXMainView(this); - vbox->Add(gMainView, 1, wxEXPAND | wxFIXED_MINSIZE); - - SetSizer(vbox); + // 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(); + } - Layout(); Fit(); - // モニタの更新頻度は初期値だけ設定から読んで、あとはここで管理する。 - // 不正値ならデフォルトの 15Hz にしておくか - const ConfigItem& item = gConfig->Find("monitor-rate"); - monitor_rate = item.AsInt(); - if (monitor_rate < 1 || monitor_rate > 60) { - monitor_rate = 15; - } - // Activate イベントは、このトップレベルウィンドウにウィンドウフォーカスが // 来た or 外れたことを示すので、wxFrame (というかたぶん wxTopLevelWindow) // を継承している WXMainFrameに飛んでくるのだが、処理の都合上 WXMainView で // 扱いたいので、そちらに回す。 - Connect(wxEVT_ACTIVATE, wxActivateEventHandler(WXMainView::OnActivate), - NULL, gMainView); + Bind(wxEVT_ACTIVATE, &WXMainView::OnActivate, mainview); - POST_CREATE(); + // ウィンドウ表示後に続きの処理をするための単発アイドルイベント。 + // こっちは 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::OnCreate(wxCommandEvent& event) +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。ただし負数ならデバッガ用。 +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(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(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 < monitor_start.size(); i++) { - if (monitor_start[i]) { + for (int i = 0; i < subwindow_start.size(); i++) { + if (subwindow_start[i]) { DoWindow(i, true); } } - // VM にコールバックを登録 - gVM->SetPowerOffCallback(wxmainframe_poweroff_callback); - if (gMPU680x0) { - gMPU680x0->SetHaltCallback(wxmainframe_halt_callback, this); - } + // 電源投入 + power->PushPowerButton(); - // VM 実行開始 - gVM->PowerButton(); + // 一度処理したら、このイベントを外す + Disconnect(wxEVT_IDLE, wxIdleEventHandler(WXMainFrame::OnIdle)); } // ウィンドウクローズイベント void WXMainFrame::OnClose(wxCloseEvent& event) { + // UIMessage を停止 + gMainApp.GetUIMessage()->StopUIMessage(); + // ステータスパネルの更新を停止 + statuspanel->Close(); + // メインビューの描画を停止 + mainview->Close(); + // サブウィンドウを全部閉じる - for (int i = 0; i < WinID_MAX; i++) { + for (int i = 0; i < ID_SUBWIN_MAX; i++) { if ((bool)windows[i]) { windows[i]->Close(); } } - // VM を解放 - gVM->Dispose(); - gVM.reset(); - // 後はデフォルト処理 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()) { // チェックが入ったので、高速モードにする - gScheduler->SetFullSpeed(true); + syncer->NotifyFullSpeed(true); } else { // チェックが消えたので、通常モードにする - gScheduler->SetFullSpeed(false); + syncer->NotifyFullSpeed(false); } } @@ -300,47 +1101,323 @@ WXMainFrame::OnFullSpeed(wxCommandEvent& void WXMainFrame::OnFullSpeedUI(wxUpdateUIEvent& event) { - bool is_fullspeed = gScheduler->GetFullSpeed(); + bool is_fullspeed = syncer->GetFullSpeed(); event.Check(is_fullspeed); } -// メニューの「Reset VM」イベント +// メニューの「設定 > 入力 > *」の共通イベント void -WXMainFrame::OnResetVM(wxCommandEvent& event) +WXMainFrame::OnMenuInput(wxCommandEvent& event) { - gScheduler->RequestResetHard(); + bool char_mode = (event.GetId() == ID_INPUT_CHAR); + + mainview->SetCharInputMode(char_mode); } -// メニューの「終了」イベント +// メニューの「設定 > 入力 > *」の共通 UI void -WXMainFrame::OnExit(wxCommandEvent& event) +WXMainFrame::OnMenuInputUI(wxUpdateUIEvent& event) { - // ウィンドウを閉じる - Close(); + 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::OnMenuScale(wxCommandEvent& event) +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) { - double scale; + 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: - case ID_SCALE_1_00: - scale = 1.0; - break; - case ID_SCALE_0_75: - scale = 0.75; - break; - case ID_SCALE_0_50: - scale = 0.5; - break; + assert(false); } - gMainView->DoResize(scale); + 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 +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]); // 再レイアウト - Layout(); Fit(); } @@ -348,15 +1425,10 @@ WXMainFrame::OnMenuScale(wxCommandEvent& void WXMainFrame::OnMenuScaleUI(wxUpdateUIEvent& event) { - double current_scale = gMainView->GetScale(); + int idx = event.GetId() - ID_SCALE_START; + double current_scale = mainview->GetScale(); - if (current_scale == 1.0) { - event.Check(event.GetId() == ID_SCALE_1_00); - } else if (current_scale == 0.75) { - event.Check(event.GetId() == ID_SCALE_0_75); - } else if (current_scale == 0.5) { - event.Check(event.GetId() == ID_SCALE_0_50); - } + event.Check(view_scales[idx] == current_scale); } // メニューの「表示 > フォントサイズ > *」の共通イベント @@ -366,7 +1438,7 @@ WXMainFrame::OnMenuFontSize(wxCommandEve FontId fontid; assertmsg( - ID_FONTSIZE_12 <= event.GetId() && event.GetId() <= ID_FONTSIZE_16, + ID_FONTSIZE_12 <= event.GetId() && event.GetId() <= ID_FONTSIZE_24, "event.GetId()=%d", event.GetId()); switch (event.GetId()) { case ID_FONTSIZE_12: @@ -375,22 +1447,30 @@ WXMainFrame::OnMenuFontSize(wxCommandEve case ID_FONTSIZE_16: fontid = FontId::_8x16; break; + case ID_FONTSIZE_24: + fontid = FontId::_12x24; + break; default: __unreachable(); break; } - if (fontid != WXTextPanel::global_fontid) { - WXTextPanel::global_fontid = fontid; + if (fontid != gFontManager->GetFontId()) { + gFontManager->SetFont(fontid); - // アクティブなサブウィンドウ全員に通知 - for (int i = 0; i < WinID_MAX; i++) { + // アクティブなサブウィンドウ全員にフォント変更通知 + for (int i = 0; i < ID_SUBWIN_MAX; i++) { if ((bool)windows[i]) { WXSubWindow *subwin = dynamic_cast(windows[i].get()); - subwin->SetFontSize(fontid); + subwin->FontChanged(); } } + // メインウィンドウはここで直接ステータスパネルに通知 + statuspanel->FontChanged(); + + // ステータスパネルの大きさが変わるので再レイアウト + Fit(); } } @@ -398,34 +1478,51 @@ WXMainFrame::OnMenuFontSize(wxCommandEve void WXMainFrame::OnMenuFontSizeUI(wxUpdateUIEvent& event) { - FontId current_size = WXTextPanel::global_fontid; + FontId current_size = gFontManager->GetFontId(); FontId event_size = (FontId)(event.GetId() - (ID_FONTSIZE_START+1)); event.Check(current_size == event_size); } -// 更新頻度 ID -> 数値 (メニューのテキスト、ID と一致させること) -/*static*/ const int -WXMainFrame::IDtoMonRate[] = { - 20, // ID_MONRATE_20 - 5, // ID_MONRATE_5 - 1, // ID_MONRATE_1 +// 更新頻度一覧 +/*static*/ std::vector +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 id = event.GetId(); - assert(ID_MONRATE_START < id && id < ID_MONRATE_END); - monitor_rate = IDtoMonRate[id - (ID_MONRATE_START + 1)]; + int idx = event.GetId() - ID_MONRATE_START; + monitor_rate = monrates[idx]; - // アクティブなサブウィンドウのテキストパネル全員に通知 - for (int i = 0; i < WinID_MAX; i++) { + // アクティブなサブウィンドウのモニタパネル全員に通知。 + for (int i = 0; i < ID_SUBWIN_MAX; i++) { if ((bool)windows[i]) { auto *subwin = dynamic_cast(windows[i].get()); for (wxWindow *child : subwin->GetChildren()) { - auto *panel = dynamic_cast(child); + auto *panel = dynamic_cast(child); if (panel) { panel->SetRate(monitor_rate); } @@ -438,35 +1535,250 @@ WXMainFrame::OnMenuMonRate(wxCommandEven void WXMainFrame::OnMenuMonRateUI(wxUpdateUIEvent& event) { - int id = event.GetId(); - assert(ID_MONRATE_START < id && id < ID_MONRATE_END); - int event_rate = IDtoMonRate[id - (ID_MONRATE_START + 1)]; + int idx = event.GetId() - ID_MONRATE_START; - event.Check(monitor_rate == event_rate); + event.Check(monrates[idx] == monitor_rate); } -// メニューの「デバイス > マウスモード」 +// メニューの「表示 > ステータスパネルを表示」 void -WXMainFrame::OnMouseMode(wxCommandEvent& event) +WXMainFrame::OnShowStatusPanel(wxCommandEvent& event) { // enable ならチェックを付けようとして呼ばれた bool enable = event.IsChecked(); - if (enable) { - // メッセージを表示したい + 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(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(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(scsidev[id]); + assert(disk); + + // 挿入されてて取り出し禁止の時のみ有効 + event.Enable(disk->IsMediumLoaded() && + disk->IsMediumRemovalPrevented()); +} + +// メニューの「デバイス > マウスモード」 +void +WXMainFrame::OnMouseMode(wxCommandEvent& event) +{ + // enable ならチェックを付けようとして呼ばれた + bool enable = event.IsChecked(); + // マウスモードを設定 - gMainView->SetMouseMode(enable); + wxCommandEvent newev(NONO_EVT_MOUSEMODE, GetId()); + newev.SetEventObject(this); + newev.SetInt(enable); + ::wxPostEvent(mainview, newev); } // メニューの「デバイス > マウスモード」UI void WXMainFrame::OnMouseModeUI(wxUpdateUIEvent& event) { - event.Check(gMainView->GetMouseMode()); + 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 共通イベント @@ -480,127 +1792,500 @@ WXMainFrame::OnWindow(wxCommandEvent& ev 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) { - // モニタウィンドウを作成する共通部分 -#define SetMonitor(name, obj) \ - windows[id].reset(new WXMonitorWindow(this, id, (name), *(obj))) + // モニタウィンドウを作成する共通部分。 + // id はモニター ID であると同時に、windows[] 配列の添字、さらにこの + // モニタウィンドウの wxWindowID としても使っている + // (WXSubWindow::OnClose() 参照)。 if (enable) { // チェックが入ったのでウィンドウを作成 switch (id) { - case WinID_MONITOR_LOG: - windows[id].reset(new WXLogMonitor(this, 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 WinID_MONITOR_SCHEDULER: - SetMonitor(_("Scheduler"), gScheduler.get()); + + case ID_MONITOR_FDC: + SetMonitor(id, "FDC (uPD72065B)"); break; - case WinID_MONITOR_MPUREG: - SetMonitor(_("MPU (Register)"), gMPU.get()); + + case ID_MONITOR_HOSTCOM0: + case ID_MONITOR_HOSTCOM1: + case ID_MONITOR_HOSTCOM2: + { + uint n = id - ID_MONITOR_HOSTCOM0; + SetMonitor(id, GetHostComLabel(n)); break; - case WinID_MONITOR_MPUATC: - SetMonitor(_("MPU (ATC)"), gMPUATC.get()); + } + + case ID_MONITOR_HOSTNET0: + case ID_MONITOR_HOSTNET1: + { + uint n = id - ID_MONITOR_HOSTNET0; + SetMonitor(id, GetHostNetLabel(n)); break; - case WinID_MONITOR_BPOINT: - SetMonitor(_("Breakpoint"), gBreakpointMonitor); + } + + case ID_MONITOR_HOSTSOUND: + SetMonitor(id, _("Host Sound")); break; - case WinID_MONITOR_BRHIST68K: - windows[id].reset(new WXBranchHistoryMonitor(this, id, - gMPU680x0->GetCPU()->brhist)); + + 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 WinID_MONITOR_BRHIST88K: - windows[id].reset(new WXBranchHistoryMonitor(this, id, - gMPU88xx0->GetCPU()->brhist)); + + 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 WinID_MONITOR_EXHIST68K: - windows[id].reset(new WXExceptionHistoryMonitor(this, id, - gMPU680x0->GetCPU()->exhist)); + + case ID_MONITOR_SCSIDEVS: + SetMonitor(id, _("SCSI Devices")); break; - case WinID_MONITOR_EXHIST88K: - windows[id].reset(new WXExceptionHistoryMonitor(this, id, - gMPU88xx0->GetCPU()->exhist)); + + case ID_MONITOR_SIO: + SetMonitor(id, "SIO (uPD7201)"); break; - case WinID_MONITOR_CMMU7: - SetMonitor("CMMU#7" + _("(Inst)"), - &gMPU88xx0->GetCPU()->cmmu[0]); + + case ID_MONITOR_SLIRP: + SetMonitor(id, _("SLIRP Internal States")); break; - case WinID_MONITOR_CMMU6: - SetMonitor("CMMU#6" + _("(Data)"), - &gMPU88xx0->GetCPU()->cmmu[1]); + + case ID_MONITOR_SOUND: + SetMonitor(id, _("Sound")); break; - case WinID_MONITOR_ATC7: - SetMonitor("ATC#7" + _("(Inst)"), gMPU88200ATC[0].get()); + + case ID_MONITOR_SPC: + SetMonitor(id, "SPC (MB89352)"); break; - case WinID_MONITOR_ATC6: - SetMonitor("ATC#6" + _("(Data)"), gMPU88200ATC[1].get()); + + case ID_MONITOR_SSG: + SetMonitor(id, "SSG (YM2149)"); break; - case WinID_MONITOR_CACHE7: - windows[id].reset(new WXCacheWindow(this, id, - _("Cache#7") + _("(Inst)"), &gMPU88xx0->GetCPU()->cmmu[0])); + + case ID_MONITOR_SYSCLK: + SetMonitor(id, _("System Clock")); break; - case WinID_MONITOR_CACHE6: - windows[id].reset(new WXCacheWindow(this, id, - _("Cache#6") + _("(Inst)"), &gMPU88xx0->GetCPU()->cmmu[1])); + + case ID_MONITOR_SYSPORT: + SetMonitor(id, _("System Port")); break; - case WinID_MONITOR_MEMDUMP: - windows[id].reset(new WXMemdumpWindow(this, id)); + + 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 WinID_MONITOR_INTERRUPT: - SetMonitor(_("Interrupt"), gInterrupt.get()); + } + + case ID_MONITOR_VIRTIO_NET: + SetMonitor(id, _("VirtIO Network")); break; - case WinID_MONITOR_SPC: - SetMonitor("SPC", gSPC.get()); + + case ID_MONITOR_VIRTIO_SCSI: + SetMonitor(id, "VirtIO SCSI"); break; - case WinID_MONITOR_RTC: - SetMonitor("RTC", gRTC.get()); + + case ID_MONITOR_VIRTIO_ENTROPY: + SetMonitor(id, _("VirtIO Entropy")); break; - case WinID_MONITOR_DMAC: - SetMonitor("DMAC", gDMAC.get()); + + case ID_MONITOR_WXHOSTINFO: + SetMonitor(id, _("Host Information")); break; - case WinID_MONITOR_MFP: - SetMonitor("MFP", gMFP.get()); + + case ID_MONITOR_XPASCI: + SetMonitor(id, "HD647180 ASCI"); break; - case WinID_MONITOR_SCC: - SetMonitor("SCC", gSCC.get()); + + case ID_MONITOR_XPREG: + SetMonitor(id, "HD647180 " + _("Registers")); break; - case WinID_MONITOR_SIO: - SetMonitor("SIO", gSIO.get()); + + case ID_MONITOR_XPINTR: + SetMonitor(id, "HD647180 " + _("Interrupts")); break; - case WinID_MONITOR_PIO0: - SetMonitor("PIO0", gPIO0.get()); + + case ID_MONITOR_XPIO: + SetMonitor(id, _("HD647180 Internal Devices")); break; - case WinID_MONITOR_LUNAKEYBOARD: - SetMonitor(_("Keyboard/Mouse"), gKeyboard.get()); + + case ID_SUBWIN_BRHIST: + { + wxString name = MPUName() + " " + _("Branch History"); + try { + windows[id].reset(new WXScrollMonitorWindow(this, name, + gMonitorManager->Get(id))); + } catch (...) { } break; - case WinID_MONITOR_LCD: - windows[id].reset(new WXLCDMonitor(this, id)); + } + + case ID_SUBWIN_EXHIST: + { + wxString name = MPUName() + " " + _("Exception History"); + try { + windows[id].reset(new WXScrollMonitorWindow(this, name, + gMonitorManager->Get(id))); + } catch (...) { } break; - case WinID_MONITOR_LANCE: - SetMonitor("Lance", dynamic_cast(gEthernet.get())); + } + + 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 WinID_MONITOR_HOSTINFO: - SetMonitor(_("Host Information"), gHostInfo.get()); + } + + case ID_SUBWIN_DIPSW: + try { + windows[id].reset(new WXDipswWindow(this)); + } catch (...) { } break; - case WinID_MONITOR_NETDRIVER: - SetMonitor(_("Host Network"), gNetDriver.get()); + + case ID_SUBWIN_GVRAM: + try { + windows[id].reset(new WXGVRAMWindow(this, _("Graphic Screen"))); + } catch (...) { } break; - case WinID_MONITOR_BITMAP: - SetMonitor(_("Bitmap Control"), gBitmap.get()); + + case ID_SUBWIN_LCDPANEL: + try { + windows[id].reset(new WXLCDWindow(this)); + } catch (...) { } break; - case WinID_MONITOR_BITMAP0: - windows[id].reset(new WXLunaBitmapWindow(this, id, _("Bitmap #0"))); + + case ID_SUBWIN_LOGSETTING: + try { + windows[id].reset(new WXLogSettingWindow(this)); + } catch (...) { } break; - case WinID_SOFTKEY: - if (gMainApp.GetVMType() == VMTYPE_X68030) { - windows[id].reset(new WXX68kSoftKeyWindow(this, id)); + + 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 { - windows[id].reset(new WXLunaSoftKeyWindow(this, id)); + // not supported } break; - case WinID_LCD: - windows[id].reset(new WXLCDWindow(this, id)); + } + + 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 { @@ -618,6 +2303,33 @@ WXMainFrame::OnWindowUI(wxUpdateUIEvent& // ウィンドウが存在していればチェックを表示 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(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); + } } // バージョンダイアログ @@ -628,49 +2340,80 @@ WXMainFrame::OnAbout(wxCommandEvent& eve dlg.ShowModal(); } -// ウィンドウをリストから削除する -// (ウィンドウの Close() から呼ばれる) +// FD を挿入する。 void -WXMainFrame::DeleteWindow(wxWindowID id) +WXMainFrame::InsertFD(int unit, const std::string& path) { - windows[id].reset(); + FDDDevice *fdd = fdd_array[unit]; + assert(fdd); + + // VM に依頼 + // (エラーが起きれば別途 UI メッセージが飛んでくる) + fdd->LoadDiskUI(path); + + // 次回以降のダイアログのカレントディレクトリをここに更新 + auto pos = path.rfind('/'); + current_dir = path.substr(0, pos); } -// VM 電源オフ時に呼ばれるコールバック。 -// (VM スレッドからアプリケーションを終了するため) +// FD をイジェクトする。 void -wxmainframe_poweroff_callback() +WXMainFrame::EjectFD(int unit, bool force) { - gMainFrame->PowerOffCallback(); + FDDDevice *fdd = fdd_array[unit]; + assert(fdd); + + fdd->UnloadDiskUI(force); } -// VM からの電源オフ処理 +// CD/MO を挿入する。 void -WXMainFrame::PowerOffCallback() +WXMainFrame::InsertCD(int id, const std::string& path) { - wxCommandEvent *ev = new wxCommandEvent(wxEVT_MENU, wxID_EXIT); - QueueEvent(ev); + SCSIDisk *scsidisk = dynamic_cast(scsidev[id]); + assert(scsidisk); + + // VM に依頼 + // (エラーが起きれば別途 UI メッセージが飛んでくる) + scsidisk->LoadDiskUI(path); + + // 次回以降のダイアログのカレントディレクトリをここに更新 + auto pos = path.rfind('/'); + current_dir = path.substr(0, pos); } -// m680x0 ダブルバスフォールト時に呼ばれるコールバック。 -// step1. グローバル関数で受けて、クラスメンバ関数を呼ぶだけ。 +// CD/MO をイジェクトする。 void -wxmainframe_halt_callback(void *arg) +WXMainFrame::EjectCD(int id, bool force) { - ((WXMainFrame *)arg)->HaltCallback(); + SCSIDisk *scsidisk = dynamic_cast(scsidev[id]); + assert(scsidisk); + + scsidisk->UnloadDiskUI(force); } -// m680x0 ダブルバスフォールト処理。 -// step2. クラス内だが VM スレッドなので、ここではイベントをポストするだけ。 +// CD/MO/FD の挿入失敗メッセージ。 +// CD/MO と FD でメッセージが同じなので共用する。 void -WXMainFrame::HaltCallback() +WXMainFrame::OnInsertFailed(wxCommandEvent& event) { - wxCommandEvent *ev = new wxCommandEvent(WXW_EVT_HALT_NOTIFY); - QueueEvent(ev); + ::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 ダブルバスフォールト処理 -// step3. ここが本題。でもダイアログを出すだけ。 void WXMainFrame::OnHaltNotify(wxCommandEvent& event) { @@ -679,45 +2422,38 @@ WXMainFrame::OnHaltNotify(wxCommandEvent wxICON_EXCLAMATION); } -// モニタウィンドウ識別名一覧 -// wxmainframe.h の識別子定義と順序を一致させること。 -// brhist は同じ名前だがフラグが異なるので共存できる。 -/*static*/ struct WXMainFrame::monitor_info -WXMainFrame::monitor_info[WinID_MAX] = { - { "log", VMF_ALL }, // LOG - { "scheduler", VMF_ALL }, // SCHEDULER - { "mpureg", VMF_ALL }, // MPUREG - { "mpuatc", VMF_M68K }, // MPUATC - { "breakpoint", VMF_ALL }, // BPOINT - { "brhist", VMF_M68K }, // BRHIST68K - { "brhist", VMF_M88K }, // BRHIST88K - { "exhist", VMF_M68K }, // EXHIST68K - { "exhist", VMF_M88K }, // EXHIST88K - { "cmmu7", VMF_M88K }, // CMMU#7 - { "cmmu6", VMF_M88K }, // CMMU#6 - { "atc7", VMF_M88K }, // ATC#7 - { "atc6", VMF_M88K }, // ATC#6 - { "cache7", VMF_M88K }, // Cache#7 - { "cache6", VMF_M88K }, // Cache#6 - { "memdump", VMF_ALL }, // MEMDUMP - { "interrupt", VMF_ALL }, // INTERRUPT - { "spc", VMF_ALL }, // SPC - { "rtc", VMF_ALL }, // RTC - { "dmac", VMF_X68K }, // DMAC - { "mfp", VMF_X68K }, // MFP - { "scc", VMF_X68K }, // SCC - { "sio", VMF_LUNA }, // SIO - { "pio0", VMF_LUNA }, // PIO0 - { "keyboard", VMF_LUNA }, // LunaKeyboard - { "lcd", VMF_LUNA }, // LCD (Monitor) - { "lance", VMF_LUNA }, // LANCE - { "bitmap", VMF_LUNA }, // BITMAP - { "bitmap0", VMF_LUNA }, // BITMAP0 - { "hostinfo", VMF_ALL }, // HOSTINFO - { "netdriver", VMF_LUNA }, // NETDRIVER - { "softkey", VMF_ALL }, // SOFTKEY - { "lcdpanel", VMF_LUNA }, // LCD (Panel) -}; +// HostNet に対する表示用テキストを返す。 +wxString +WXMainFrame::GetHostNetLabel(uint n) const +{ + return wxString(string_format("HostNet%u: ", n) + + hostnet[n]->GetPortName()); +} + +// HostCOM に対する表示用テキストを返す。 +// デバッガ用は呼び出し側で処理すること。 +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 WXMainFrame::subwindow_start; -// 起動時にオープンするモニタウィンドウ (-M オプション) -/*static*/ std::array WXMainFrame::monitor_start; +// モニタの更新頻度 +/*static*/ int WXMainFrame::monitor_rate;