--- nono/wx/wxmainframe.cpp 2026/04/29 17:04:56 1.1.1.10 +++ nono/wx/wxmainframe.cpp 2026/04/29 17:04:59 1.1.1.11 @@ -15,6 +15,7 @@ #include "wxsoftkey.h" #include "wxstatuspanel.h" #include "wxtextpanel.h" +#include "wxuimessage.h" #include "wxversion.h" #include "wxvideomonitor.h" #include "debugger.h" @@ -23,6 +24,7 @@ #include "mpu88xx0.h" #include "netdriver.h" #include "scheduler.h" +#include "spc.h" #include "vm.h" WXMainFrame *gMainFrame; @@ -48,14 +50,24 @@ wxBEGIN_EVENT_TABLE(WXMainFrame, inherit WXMainFrame::OnMenuMonRateUI) EVT_MENU(ID_SHOW_STATPANEL, WXMainFrame::OnShowStatusPanel) EVT_UPDATE_UI(ID_SHOW_STATPANEL, WXMainFrame::OnShowStatusPanelUI) + 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_RESET_BUTTON, WXMainFrame::OnResetButton) EVT_MENU_RANGE(0, ID_SUBWIN_END, WXMainFrame::OnWindow) EVT_UPDATE_UI_RANGE(0, ID_SUBWIN_END, WXMainFrame::OnWindowUI) EVT_MENU(wxID_ABOUT, WXMainFrame::OnAbout) - - EVT_COMMAND(wxID_ANY, WXW_EVT_HALT_NOTIFY, WXMainFrame::OnHaltNotify) wxEND_EVENT_TABLE() WXMainFrame::WXMainFrame(wxWindow *parent) @@ -96,6 +108,7 @@ WXMainFrame::WXMainFrame(wxWindow *paren menubar->Append(menuDisplay, _("&View")); wxMenu *menuDevice = new wxMenu(); + CreateRemovableMenu(menuDevice); menuDevice->Append(ID_SUBWIN_SOFTKEY, _("Software &Keyboard"), "", wxITEM_CHECK); menuDevice->Append(ID_MOUSEMODE, _("&Mouse Mode\tCTRL+F12"), @@ -157,6 +170,7 @@ WXMainFrame::WXMainFrame(wxWindow *paren menuMonitor->AppendSubMenu(menuMemdump, _("&Memory Dump")); AppendIf(menuMonitor, ID_MONITOR_INTERRUPT, _("&Interrupt")); AppendIf(menuMonitor, ID_MONITOR_SPC, "S&PC"); + AppendIf(menuMonitor, ID_MONITOR_SCSIDEVS, _("SCSI Devices")); AppendIf(menuMonitor, ID_MONITOR_RTC, "&RTC"); AppendIf(menuMonitor, ID_MONITOR_MK48T02, "&RTC"); AppendIf(menuMonitor, ID_MONITOR_DMAC, "DM&AC"); @@ -255,32 +269,107 @@ WXMainFrame::WXMainFrame(wxWindow *paren Connect(wxEVT_ACTIVATE, wxActivateEventHandler(WXMainView::OnActivate), NULL, gMainView); - // ウィンドウ表示後にサブウィンドウを表示するための Paint イベント。 - Connect(wxEVT_PAINT, wxPaintEventHandler(WXMainFrame::OnPaint)); + // ウィンドウ表示後に続きの処理をするための単発アイドルイベント。 + Connect(wxEVT_IDLE, wxIdleEventHandler(WXMainFrame::OnIdle)); + + // VM からの通知を接続 + WXUIMessage::Connect(UIMessage::POWEROFF, this, + wxCommandEventHandler(WXMainFrame::OnExit)); + WXUIMessage::Connect(UIMessage::SCSI_MEDIA_FAILED, this, + wxCommandEventHandler(WXMainFrame::OnInsertCDFailed)); + if (gMPU680x0) { + WXUIMessage::Connect(UIMessage::HALT, this, + wxCommandEventHandler(WXMainFrame::OnHaltNotify)); + } } WXMainFrame::~WXMainFrame() { } -// ウィンドウ作成イベント (を実現するためここでは再描画イベントを使う) +// リムーバブルデバイス用のメニューを作成して menuDevice に追加する +void +WXMainFrame::CreateRemovableMenu(wxMenu *menuDevice) +{ + wxMenu *menu; + bool added = false; + + // FD + + // CD, MO + for (int id = 0; id < 8; id++) { + scsidev[id] = gSPC->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(); + } +} + +// 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; + } +} + +// ウィンドウ作成イベント (を実現するためここではアイドルイベントを使う) // -// 初期化(っぽい)もののうちコンストラクタでは早すぎる処理のため。 -// 似たようなイベントは WindowCreate イベントと自前の Create イベント -// (今はもうない)があり、Create -> WindowCreate -> Paint の順で発生する -// ようだ (Ubuntu 20.04 で確認、他でも同様だと思いたい)。 -// Create 時点では早すぎて、ここでサブウィンドウを作成するとサイズが 0 みたい -// なウィンドウが一瞬出来てしまい GTK のワーニングが出る (その後サイズを矯正 -// すれば表示自体は出来る)。 +// 初期化(っぽい)もののうちコンストラクタでは早すぎるものは、コンストラクタ +// 直後に起こすこのイベント内で処理する。使えそうなイベントはいくつかあり、 +// 自前の Create (今はもうない) -> WindowCreate -> Idle -> Paint の順で +// 起きるようだ。 +// 自前の Create 時点では早すぎて、ここでサブウィンドウを作成するとサイズが 0 +// みたいなウィンドウが一瞬出来てしまい GTK のワーニングが出る (その後サイズを +// 矯正すれば表示自体は出来る)。 // WindowCreate 時点ではその問題は起きないが、ここはまだメインウィンドウ表示 // 前なので、ここでサブウィンドウを表示した後メインウィンドウを表示すると // (ウィンドウマネージャにもよるかもだが)、メインウィンドウがサブウィンドウを // 隠してしまう。 -// Paint イベント時点まで来るとこの2つの問題が回避できるようだ。 -// なおこの Paint イベントは通常の再描画の用途では使わないため、一度処理した -// 後はこのイベントを外す。 -void WXMainFrame::OnPaint(wxPaintEvent& ev) +// Idle イベント時点まで来ると上の2つの問題は回避できるようだ。 +// Paint イベントの時点でもタイミング的には問題ないが、環境によっては(?) +// このイベント自体が飛んで来ないことがあるようだ。WXMainFrame の再描画は +// Sizer の子コントロールが行っており、そのため WXMainFrame 自身が再描画 +// しなければならない領域は存在しないからかも知れないが詳細未調査。 +void +WXMainFrame::OnIdle(wxIdleEvent& ev) { + Fit(); + + // UIMessage の処理を開始する。 + // 少なくとも WXMainFrame のコンストラクタが完了した後のほうがいいはず。 + UIMessage::Attach(&WXUIMessage::Process); + // ウィンドウを表示 for (int i = 0; i < subwindow_start.size(); i++) { if (subwindow_start[i]) { @@ -292,7 +381,7 @@ void WXMainFrame::OnPaint(wxPaintEvent& gVM->PowerButton(); // 一度処理したら、このイベントを外す - Disconnect(wxEVT_PAINT, wxPaintEventHandler(WXMainFrame::OnPaint)); + Disconnect(wxEVT_IDLE, wxIdleEventHandler(WXMainFrame::OnIdle)); } // ウィンドウクローズイベント @@ -504,6 +593,104 @@ WXMainFrame::OnShowStatusPanelUI(wxUpdat event.Check(gStatusPanel->IsShown()); } +// メニューの「デバイス > 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: + __unreachable(); + } + 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) @@ -673,6 +860,10 @@ WXMainFrame::DoWindow(int id, bool enabl SetMonitor(id, _("Scheduler")); break; + case ID_MONITOR_SCSIDEVS: + SetMonitor(id, _("SCSI Devices")); + break; + case ID_MONITOR_SIO: SetMonitor(id, "SIO"); break; @@ -771,54 +962,53 @@ WXMainFrame::OnAbout(wxCommandEvent& eve dlg.ShowModal(); } -// ウィンドウをリストから削除する -// (ウィンドウの Close() から呼ばれる) +// CD/MO を挿入する。 void -WXMainFrame::DeleteWindow(wxWindow *win) +WXMainFrame::InsertCD(int id, const std::string& path) { - for (int id = 0; id < ID_SUBWIN_MAX; id++) { - if (windows[id].get() == win) { - windows[id].reset(); - break; - } - } -} + SCSIDisk *scsidisk = dynamic_cast(scsidev[id]); + assert(scsidisk); -// VM 電源オフ時に呼ばれるコールバック。 -// (VM スレッドからアプリケーションを終了するため) -void -wxmainframe_poweroff_callback() -{ - gMainFrame->PowerOffCallback(); + // VM に依頼 + // (エラーが起きれば別途 UI メッセージが飛んでくる) + scsidisk->LoadDiskUI(path); + + // 次回以降のダイアログのカレントディレクトリをここに更新 + auto pos = path.rfind('/'); + current_dir = path.substr(0, pos); } -// VM からの電源オフ処理 +// CD/MO をイジェクトする。 void -WXMainFrame::PowerOffCallback() +WXMainFrame::EjectCD(int id, bool force) { - wxCommandEvent *ev = new wxCommandEvent(wxEVT_MENU, wxID_EXIT); - QueueEvent(ev); + SCSIDisk *scsidisk = dynamic_cast(scsidev[id]); + assert(scsidisk); + + scsidisk->UnloadDisk(force); } -// m680x0 ダブルバスフォールト時に呼ばれるコールバック。 -// step1. グローバル関数で受けて、クラスメンバ関数を呼ぶだけ。 +// CD/MO の挿入失敗メッセージ void -wxmainframe_halt_callback(void *arg) +WXMainFrame::OnInsertCDFailed(wxCommandEvent& event) { - ((WXMainFrame *)arg)->HaltCallback(); + ::wxMessageBox(_("Cannot load the image file")); } -// m680x0 ダブルバスフォールト処理。 -// step2. クラス内だが VM スレッドなので、ここではイベントをポストするだけ。 +// ウィンドウをリストから削除する +// (ウィンドウの Close() から呼ばれる) void -WXMainFrame::HaltCallback() +WXMainFrame::DeleteWindow(wxWindow *win) { - wxCommandEvent *ev = new wxCommandEvent(WXW_EVT_HALT_NOTIFY); - QueueEvent(ev); + 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) {