--- nono/wx/wxstatuspanel.cpp 2026/04/29 17:05:08 1.1.1.4 +++ nono/wx/wxstatuspanel.cpp 2026/04/29 17:05:12 1.1.1.5 @@ -25,6 +25,7 @@ #include "wxuimessage.h" #include "wxversion.h" #include "ethernet.h" +#include "fdc.h" #include "hostnet.h" #include "power.h" #include "spc.h" @@ -70,9 +71,13 @@ WXStatusPanel::WXStatusPanel(wxWindow *p Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL, gMainView); - // VM からのメディア変更通知を受け取る + // VM からの通知を受け取る WXUIMessage::Connect(UIMessage::SCSI_MEDIA_CHANGE, this, wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged)); + WXUIMessage::Connect(UIMessage::FDD_MEDIA_CHANGE, this, + wxCommandEventHandler(WXStatusPanel::OnFDDMediaChanged)); + WXUIMessage::Connect(UIMessage::LED, this, + wxCommandEventHandler(WXStatusPanel::OnLEDChanged)); } // デストラクタ @@ -83,8 +88,13 @@ WXStatusPanel::~WXStatusPanel() indicators.pop_back(); delete ind; } + WXUIMessage::Disconnect(UIMessage::SCSI_MEDIA_CHANGE, wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged)); + WXUIMessage::Disconnect(UIMessage::FDD_MEDIA_CHANGE, + wxCommandEventHandler(WXStatusPanel::OnFDDMediaChanged)); + WXUIMessage::Disconnect(UIMessage::LED, + wxCommandEventHandler(WXStatusPanel::OnLEDChanged)); } // クローズイベント @@ -137,8 +147,8 @@ WXStatusPanel::FontChanged() void WXStatusPanel::OnSize(wxSizeEvent& event) { - // Mac ではウィンドウ作成時に 0 以下の値が来ることがある - // GTK では 1 で来ることがある + // Mac ではウィンドウ作成時に 0 以下の値が来ることがある。 + // GTK では 1 で来ることがある。 const wxSize& size = event.GetSize(); if (size.x <= 1 || size.y <= 1) { return; @@ -172,12 +182,11 @@ void WXStatusPanel::InitIndicators() { // パフォーマンスカウンタ - Indicator *perf = new Indicator(">>>9999%", &WXStatusPanel::DrawPerf); + Indicator *perf = new Indicator(Indicator::PERF, 8/* >>>9999% */, + &WXStatusPanel::DrawPerf); perf->dclick = &WXStatusPanel::DClickPerf; indicators.emplace_back(perf); - // FD - // SCSI // SPC の接続済みデバイスリストから取得。 // (ホストアダプタ2つ目のことは考えてない) @@ -187,22 +196,13 @@ WXStatusPanel::InitIndicators() if (devtype == SCSI::DevType::Initiator) { continue; } - int id = dev->GetMyID(); + int scsiid = dev->GetMyID(); - SCSIIndicator *si = new SCSIIndicator(); - si->text = string_format("%s%d", SCSI::GetDevTypeName(devtype), id); - // 書き込み無視だけ、これによって shutdown が必要かどうかという - // オペレーションに違いが出るため表示する。 - // MO (と FD の) ライトプロテクト表示は必要になったら考える…。 - const SCSIDisk *disk = dynamic_cast(dev); - assert(disk); - if (disk->GetWriteMode() == SCSIDisk::RW::WriteIgnore) { - si->text += "\x02\x03"; - } - si->draw = &WXStatusPanel::DrawSCSI; - si->id = id; - si->dev = dev; + // 文字列は OnSCSIMediaChanged() でセットする。 + Indicator *si = new Indicator(Indicator::SCSI0 + scsiid, 3, + &WXStatusPanel::DrawSCSI); si->contextmenu = &WXStatusPanel::ContextMenuCD; + si->dev = dev; si->panel = new wxPanel(this); // このパネルがフォーカスを受け取らないようにする。 // StatusPanel がフォーカスを持っていてもキー入力を MainFrame に渡す @@ -224,14 +224,39 @@ WXStatusPanel::InitIndicators() net_ok = ENABLE; } // \x1e は上向き矢印、\x1f は下向き矢印 - Indicator *net = new Indicator("LAN\x1e\x1f", &WXStatusPanel::DrawNet); + Indicator *net = new Indicator(Indicator::LAN, 5, + &WXStatusPanel::DrawNet); + net->text = "LAN\x1e\x1f"; indicators.emplace_back(net); } else { net_ok = NOT_AVAILABLE; } - // 電源 LED。点灯ロジックが分からないのでハリボテ。 - Indicator *power = new Indicator("POWER", &WXStatusPanel::DrawPower); + // FD + if (gFDC) { + const std::vector& fdd_vector = gFDC->GetFDD(); + for (const auto fdd : fdd_vector) { + if (fdd) { + int unit = fdd->GetUnitNo(); + // 文字列は OnFDDMediaChanged() でセットする。 + Indicator *ind = new Indicator(Indicator::FDD0 + unit, 7, + &WXStatusPanel::DrawFD); + + ind->contextmenu = &WXStatusPanel::ContextMenuFD; + ind->dev = fdd; + ind->panel = new wxPanel(this); + // フォーカスを受け取らない。少し上の CD のところ参照。 + ind->panel->SetCanFocus(false); + + indicators.emplace_back(ind); + } + } + } + + // 電源 LED + Indicator *power = new Indicator(Indicator::POWER, 5, + &WXStatusPanel::DrawPower); + power->text = "POWER"; indicators.emplace_back(power); // テスト用 @@ -273,7 +298,8 @@ WXStatusPanel::LayoutIndicators() x += 1; // 4 は、左右の外枠(1*2) と 枠と文字の間(1*2) - w = ind->text.length() * font_width + 2 + 2; + int len = std::max(ind->minlen, (int)ind->text.length()); + w = len * font_width + 2 + 2; ind->rect = Rect(x, y, w, h); x += w; @@ -304,12 +330,13 @@ WXStatusPanel::LayoutIndicators() // パネルをもっていれば、パネルをその位置に(再)設定。 for (auto ind : indicators) { - auto si = dynamic_cast(ind); - if (si && si->panel) { + if (ind->panel) { const auto& rect = ind->rect; - si->panel->SetSize(rect.x, rect.y, rect.w, rect.h); + ind->panel->SetSize(rect.x, rect.y, rect.w, rect.h); } } + + refresh_background = true; } // 状態をチェック。 @@ -330,13 +357,12 @@ WXStatusPanel::UpdateStat() // 電源オンか MODIFY(ispower, gPower->IsPower()); + MODIFY(powerled, gPower->GetPowerLED()); // パフォーマンスカウンタ MODIFY(perf_mode, gSync->GetRunMode()); MODIFY(perf_rate, gSync->GetPerfCounter()); - // FD - // SCSI アクセス MODIFY(scsi_bsy, scsibus->GetBSY()); // 転送フェーズで DataOut の時だけ書き込みとみなす @@ -353,6 +379,18 @@ WXStatusPanel::UpdateStat() MODIFY(net_rx_active, MODIFY(net_rx_pkts, hostnet->GetRXPkts())); } + // FD + if (gFDC) { + const std::vector& fdd_vector = gFDC->GetFDD(); + for (const auto fdd : fdd_vector) { + if (fdd) { + int unit = fdd->GetUnitNo(); + MODIFY(fd_access_led[unit], fdd->GetAccessLED()); + MODIFY(fd_eject_led[unit], fdd->GetEjectLED()); + } + } + } + // 電源 return updated; @@ -364,6 +402,12 @@ WXStatusPanel::Draw() { std::string text; + // 指定された時だけ背景を再描画 + if (refresh_background) { + refresh_background = false; + bitmap.Fill(BGPANEL); + } + // なんとなく見栄えのため上下に薄い線を引く。 // 四方を線で囲むとオーバーレイウィンドウのように見えるので上下だけ。 wxSize size = GetSize(); @@ -432,11 +476,12 @@ WXStatusPanel::DrawSCSI(const Indicator { Color fg; Color bg; + int scsiid; uint idmask; - const auto si = dynamic_cast(ind); - assert(si); - idmask = 1U << si->GetId(); + scsiid = ind->GetSCSIID(); + assertmsg(scsiid >= 0, "id=%d", ind->id); + idmask = 1U << scsiid; if ((scsi_loaded & idmask) != 0) { fg = UD_BLACK; @@ -506,11 +551,137 @@ WXStatusPanel::DrawNet(const Indicator * ResetTextColor(); } -// 電源LED? +// FD +void +WXStatusPanel::DrawFD(const Indicator *ind) +{ + BitmapI8 accmark; + Rect iconrect; + const uint8 *src; + Color pal[3]; + enum { + BG = 0, + FG, + IN, + }; + int unit; + + unit = ind->GetFDUnit(); + const Rect& rect = ind->rect; + + pal[BG] = BGPANEL; + if (fd_loaded[unit]) { + pal[FG] = UD_BLACK; + } else { + pal[FG] = UD_GREY; + } + + // まず枠とテキストを通常描画 + DrawTextLED(ind, pal[FG], pal[BG]); + + // 続いて 5,6文字目の空き地に四角いインジケータを描画。 + // 同じ色で済むので先に描画する。 + iconrect.x = rect.x + 2 + font_width * 5 + 1; + iconrect.y = rect.y + 2 + 1; + iconrect.w = font_width * 2 - 2; + iconrect.h = font_height - 2; + bitmap.DrawRect(pal[FG], iconrect); + // ディスク挿入(取り出し可)なら黄緑点灯 + if (fd_eject_led[unit]) { + iconrect.x += 1; + iconrect.y += 1; + iconrect.w -= 2; + iconrect.h -= 2; + bitmap.FillRect(UD_YELLOW_GREEN, iconrect); + } + + // その後で 3,4文字目の空き地に丸いインジケータを描画。 + switch (font_height) { + case 12: + src = AccessMark12; + break; + case 16: + src = AccessMark16; + break; + default: + PANIC("Unexpected font_height=%d", font_height); + } + accmark.Create(src, font_height, font_height); + int dx = rect.x + 2 + font_width * 3; + int dy = rect.y + 2; + + // アクセス LED はディスク非挿入時に点滅するケースがあって、 + // その場合、フチがグレーのまま中を黄緑で塗りつぶすのは見えづらいので + // 中を塗りつぶす時はフチを濃くしたほうがいい。 + switch (fd_access_led[unit]) { + case FDDDevice::LED::GREEN: + pal[FG] = UD_BLACK; + pal[IN] = UD_YELLOW_GREEN; + break; + case FDDDevice::LED::RED: + pal[FG] = UD_BLACK; + pal[IN] = UD_RED; + break; + default: + pal[IN] = pal[BG]; + break; + } + bitmap.DrawBitmap(dx, dy, accmark, pal); +} + +/*static*/ const uint8 +WXStatusPanel::AccessMark12[] = { + 0,0,0,0,1,1,1,0,0,0,0,0, + 0,0,1,1,2,2,2,1,1,0,0,0, + 0,1,2,2,2,2,2,2,2,1,0,0, + 0,1,2,2,2,2,2,2,2,1,0,0, + 1,2,2,2,2,2,2,2,2,2,1,0, + 1,2,2,2,2,2,2,2,2,2,1,0, + 1,2,2,2,2,2,2,2,2,2,1,0, + 0,1,2,2,2,2,2,2,2,1,0,0, + 0,1,2,2,2,2,2,2,2,1,0,0, + 0,0,1,1,2,2,2,1,1,0,0,0, + 0,0,0,0,1,1,1,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0, +}; +/*static*/ const uint8 +WXStatusPanel::AccessMark16[] = { + 0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0, + 0,0,0,1,1,2,2,2,2,2,1,1,0,0,0,0, + 0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0, + 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0, + 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0, + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0, + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0, + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0, + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0, + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0, + 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0, + 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0, + 0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0, + 0,0,0,1,1,2,2,2,2,2,1,1,0,0,0,0, + 0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + +// 電源 LED void WXStatusPanel::DrawPower(const Indicator *ind) { - DrawTextLED(ind, UD_BLACK, UD_YELLOW_GREEN); + Color bg; + + if (ispower) { + if (powerled) { + bg = UD_YELLOW_GREEN; + } else { + bg = UD_GREY; + } + } else { + // XXX 電源オフが出来たら X68030 なら赤 + bg = BGPANEL; + } + + DrawTextLED(ind, UD_BLACK, bg); } // 共通の描画処理。 @@ -560,9 +731,8 @@ WXStatusPanel::OnContextMenu(wxContextMe for (auto ind : indicators) { // コンテキストメニューを処理できる枠内なら - const auto si = dynamic_cast(ind); - if (si && si->contextmenu && ind->rect.Contains(pos.x, pos.y)) { - (this->*(si->contextmenu))(ind); + if (ind->contextmenu && ind->rect.Contains(pos.x, pos.y)) { + (this->*(ind->contextmenu))(ind); break; } } @@ -572,10 +742,23 @@ WXStatusPanel::OnContextMenu(wxContextMe void WXStatusPanel::ContextMenuCD(const Indicator *ind) { - const auto si = dynamic_cast(ind); - assert(si); + int scsiid = ind->GetSCSIID(); + assertmsg(scsiid >= 0, "id=%d", ind->id); + + auto menu = gMainFrame->CreateSCSIRemovableMenu(scsiid); + if (menu) { + PopupMenu(menu); + } +} + +// FD 枠でのコンテキストメニュー +void +WXStatusPanel::ContextMenuFD(const Indicator *ind) +{ + int unit = ind->GetFDUnit(); + assertmsg(unit >= 0, "id=%d", ind->id); - auto menu = gMainFrame->CreateSCSIRemovableMenu(si->GetId()); + auto menu = gMainFrame->CreateFDMenu(unit); if (menu) { PopupMenu(menu); } @@ -585,35 +768,36 @@ WXStatusPanel::ContextMenuCD(const Indic void WXStatusPanel::OnSCSIMediaChanged(wxCommandEvent& event) { - SCSIIndicator *si = NULL; + Indicator *ind = NULL; uint8 new_loaded; // 該当の Indicator を探す (見付かるはず) - int id = event.GetInt(); - for (int i = 0; i < indicators.size(); i++) { - si = dynamic_cast(indicators[i]); - if (si && si->GetId() == id) { + int scsiid = event.GetInt(); + int id = Indicator::SCSI0 + scsiid; + for (auto i : indicators) { + if (i->id == id) { + ind = i; break; } } - assert(si); - const auto disk = dynamic_cast(si->dev); + assert(ind); + const auto disk = dynamic_cast(ind->dev); // メディアの状態を一旦クリア new_loaded = scsi_loaded; - new_loaded &= ~(1U << id); + new_loaded &= ~(1U << scsiid); // ツールチップを設定 SCSI::DevType devtype = disk->GetDevType(); std::string tip = string_format("%s%d: ", - SCSI::GetDevTypeName(devtype), id); + SCSI::GetDevTypeName(devtype), scsiid); const std::string& path = disk->GetPathName(); // 同時にメディア状態もセット if (path.empty()) { tip += "-"; } else { tip += path; - new_loaded |= (1U << id); + new_loaded |= (1U << scsiid); } switch (disk->GetWriteMode()) { case SCSIDisk::RW::ReadOnly: @@ -628,7 +812,18 @@ WXStatusPanel::OnSCSIMediaChanged(wxComm default: break; } - si->panel->SetToolTip(tip); + ind->panel->SetToolTip(tip); + + // 書き込み無視だけ、これによって shutdown が必要かどうかという + // オペレーションに違いが出るため表示する。 + // 無駄に幅を取らないためメディア挿入時のみ表示。 + // MO のライトプロテクト表示は必要になったら考える…。 + ind->text = string_format("%s%d", SCSI::GetDevTypeName(devtype), scsiid); + if ((new_loaded & (1U << scsiid))) { + if (disk->GetWriteMode() == SCSIDisk::RW::WriteIgnore) { + ind->text += "\x02\x03"; + } + } // メディアのロード状態が変われば (再配置して) 再描画。 // 通常周期のタイマーによる更新チェックではメディア状態はチェックして @@ -640,3 +835,108 @@ WXStatusPanel::OnSCSIMediaChanged(wxComm Refresh(); } } + +// FDD メディア変更イベント (UIMessage イベント) +void +WXStatusPanel::OnFDDMediaChanged(wxCommandEvent& event) +{ + Indicator *ind = NULL; + bool new_loaded; + + // 該当の Indicator を探す (見付かるはず) + int unit = event.GetInt(); + int id = Indicator::FDD0 + unit; + for (auto i : indicators) { + if (i->id == id) { + ind = i; + break; + } + } + assert(ind); + const auto fdd = dynamic_cast(ind->dev); + + // メディアの状態を一旦クリア + new_loaded = false; + + // ツールチップを設定 + std::string tip = string_format("FD%d: ", unit); + const std::string& path = fdd->GetPathName(); + // 同時にメディア状態もセット + if (path.empty()) { + tip += "-"; + } else { + tip += path; + new_loaded = true; + } + switch (fdd->GetWriteMode()) { + case FDDDevice::RW::WriteProtect: + tip += "\n(Write Protected)"; + break; + case FDDDevice::RW::WriteIgnore: + tip += "\n(Write Ignored)"; + break; + default: + break; + } + ind->panel->SetToolTip(tip); + + ind->text = string_format("FD%d", fdd->GetUnitNo()); + // 書き込み無視だけ表示する。無駄に幅を取らないためメディア挿入時のみ表示。 + // 4文字分のマークの後ろに "WI" を追加。 + if (new_loaded) { + if (fdd->GetWriteMode() == FDDDevice::RW::WriteIgnore) { + ind->text += " \x02\x03"; + } + } + + // メディアのロード状態が変われば (再配置して) 再描画。 + // 通常周期のタイマーによる更新チェックではメディア状態はチェックして + // いないので、ここで自発的に行う。 + if (fd_loaded[unit] != new_loaded) { + fd_loaded[unit] = new_loaded; + + LayoutIndicators(); + Refresh(); + } +} + +// LED 状態変更イベント (UIMessage イベント)。 +// +// 状態変更があったことをプッシュ通知する。主に、通常の更新頻度では +// 間に合わない X68030 の電源 LED の 16Hz 点滅用。 +void +WXStatusPanel::OnLEDChanged(wxCommandEvent& event) +{ + if (UpdateStat()) { + Refresh(); + } +} + + +// +// インジケータ情報 +// + +// SCSI なら SCSI ID を返す。そうでなければ -1 を返す。 +int +Indicator::GetSCSIID() const +{ + int scsiid = id - SCSI0; + if (0 <= scsiid && scsiid <= 7) { + return scsiid; + } else { + return -1; + } +} + +// FDD ならユニット番号を返す。そうでなければ -1 を返す。 +int +Indicator::GetFDUnit() const +{ + int unit = id - FDD0; + if (0 <= unit && unit <= 3) { + return unit; + } else { + return -1; + } +}