--- nono/wx/wxstatuspanel.cpp 2026/04/29 17:04:56 1.1.1.2 +++ nono/wx/wxstatuspanel.cpp 2026/04/29 17:05:00 1.1.1.3 @@ -17,9 +17,10 @@ #include "wxstatuspanel.h" #include "wxcolor.h" +#include "wxmainframe.h" #include "wxmainview.h" +#include "wxuimessage.h" #include "wxversion.h" -#include "bitrev.h" #include "netdriver.h" #include "scheduler.h" #include "spc.h" @@ -33,6 +34,7 @@ wxBEGIN_EVENT_TABLE(WXStatusPanel, inher EVT_TIMER(wxID_ANY, WXStatusPanel::OnTimer) EVT_PAINT(WXStatusPanel::OnPaint) EVT_LEFT_DCLICK(WXStatusPanel::OnLeftDClick) + EVT_CONTEXT_MENU(WXStatusPanel::OnContextMenu) wxEND_EVENT_TABLE() // ステータスパネルの更新頻度 [Hz] @@ -63,6 +65,10 @@ WXStatusPanel::WXStatusPanel(wxWindow *p gMainView); Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL, gMainView); + + // VM からのメディア変更通知を受け取る + WXUIMessage::Connect(UIMessage::SCSI_MEDIA_CHANGE, this, + wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged)); } // デストラクタ @@ -161,91 +167,6 @@ WXStatusPanel::OnTimer(wxTimerEvent& eve } } -// 状態をチェック。 -// 前回値からどれかでも変更があれば true を返す。 -bool -WXStatusPanel::UpdateStat() -{ - bool updated = false; - - // store に value を代入。更新されていれば updated を立てる。 - // value は一度だけ評価される。 - auto MODIFY = [&](auto& store, auto value) { - bool rv = store != value; - updated |= rv; - store = value; - return rv; - }; - - // 電源オンか - MODIFY(ispower, gScheduler->IsPower()); - - // パフォーマンスカウンタ - MODIFY(perf_mode, gScheduler->GetRunMode()); - MODIFY(perf_rate, gScheduler->GetPerfCounter()); - - // FD - - // SCSI アクセス - MODIFY(scsi_bsy, scsibus->GetBSY()); - // 転送フェーズで DataOut の時だけ書き込みとみなす - bool out = (scsibus->GetPhase() == SCSI::Phase::Transfer) - && (scsibus->GetXfer() == SCSI::XferPhase::DataOut); - MODIFY(scsi_out, out); - - // ネットワーク - // パケット数が変化したとき、 - // およびその変化を監視しているアクティブ状態が変化したとき - // に変更があったとしたいので二重になっている - if (net_ok != NOT_AVAILABLE) { - MODIFY(net_tx_active, MODIFY(net_tx_pkts, gEthernet->GetTXPkts())); - MODIFY(net_rx_active, MODIFY(net_rx_pkts, gEthernet->GetRXPkts())); - } - - // 電源 - - return updated; -} - -// 描画イベント -void -WXStatusPanel::OnPaint(wxPaintEvent& event) -{ - // メモリ DC に描画 - wxMemoryDC memDC; - memDC.SelectObject(bitmap); - Draw(memDC); - - // 実画面 DC にコピー - wxPaintDC dstDC(this); - wxSize size = bitmap.GetSize(); - dstDC.Blit(0, 0, size.x, size.y, &memDC, 0, 0); -} - -// 描画本体 -void -WXStatusPanel::Draw(wxDC& dc) -{ - std::string text; - - // 背景はいつものグレー。 - wxSize size = GetSize(); - dc.SetPen(wxPen(BGPANEL)); - dc.SetBrush(wxBrush(BGPANEL)); - dc.DrawRectangle(wxPoint(0, 0), size); - - // なんとなく見栄えのため上下に薄い線を引く。 - // 四方を線で囲むとオーバーレイウィンドウのように見えるので上下だけ。 - dc.SetPen(wxPen(UD_GREY)); - dc.DrawLine(0, 0, size.x - 1, 0); - dc.DrawLine(0, size.y - 1, size.x - 1, size.y - 1); - - // 各インジケータを描画 - for (auto& ind : indicators) { - (this->*(ind.draw))(dc, ind); - } -} - static const std::vector data = { 0x0b, 0x06, 0x0b, 0x06, 0x00, 0x4c, 0x55, 0x4e, 0x41, 0x74, 0x69, 0x63, 0x20, 0x54, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x72, @@ -256,8 +177,8 @@ void WXStatusPanel::InitIndicators() { // パフォーマンスカウンタ - Indicator perf(">>>9999%", &WXStatusPanel::DrawPerf); - perf.dclick = &WXStatusPanel::DClickPerf; + Indicator *perf = new Indicator(">>>9999%", &WXStatusPanel::DrawPerf); + perf->dclick = &WXStatusPanel::DClickPerf; indicators.emplace_back(perf); // FD @@ -267,30 +188,33 @@ WXStatusPanel::InitIndicators() // (ホストアダプタ2つ目のことは考えてない) const std::vector& conn = gSPC->GetConnectedDevices(); for (const auto& dev : conn) { - int id = dev->GetMyID(); SCSI::DevType devtype = dev->GetDevType(); - - std::string disp; - switch (devtype) { - case SCSI::DevType::Initiator: + if (devtype == SCSI::DevType::Initiator) { continue; - case SCSI::DevType::HD: - { - disp = string_format("HD%d", id); - const auto devhd = dynamic_cast(dev); - assert(devhd); - // ライトプロテクト - if (devhd->GetWriteProtect()) { - disp += "\x02\x03"; - } - break; - } - default: - disp = string_format("??%d", id); - break; } + int id = dev->GetMyID(); - indicators.emplace_back(disp, &WXStatusPanel::DrawSCSI, id); + 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; + si->contextmenu = &WXStatusPanel::ContextMenuCD; + si->panel = new wxPanel(this); + // このパネルがフォーカスを受け取らないようにする。 + // StatusPanel がフォーカスを持っていてもキー入力を MainFrame に渡す + // ようにしているため、StatusPanel のフォーカスを奪ってはいけない。 + si->panel->SetCanFocus(false); + + indicators.emplace_back(si); } // BSY 取得用 scsibus = gSPC->GetSCSIBus(); @@ -303,14 +227,15 @@ WXStatusPanel::InitIndicators() net_ok = DISABLE; } // \x1e は上向き矢印、\x1f は下向き矢印 - indicators.emplace_back("LAN\x1e\x1f", &WXStatusPanel::DrawNet); + Indicator *net = new Indicator("LAN\x1e\x1f", &WXStatusPanel::DrawNet); + indicators.emplace_back(net); } else { net_ok = NOT_AVAILABLE; } // 電源 LED。点灯ロジックが分からないのでハリボテ。 - // ロジックが分かった時のために一応 3 文字にしておく。 - indicators.emplace_back("POWER", &WXStatusPanel::DrawPower); + Indicator *power = new Indicator("POWER", &WXStatusPanel::DrawPower); + indicators.emplace_back(power); // テスト用 if (__predict_false(0||(bool)*gHostInfo)) { @@ -341,7 +266,7 @@ WXStatusPanel::LayoutIndicators() // まずは前から順に列挙して必要な幅を積算していく x = 0; - for (auto& ind : indicators) { + for (auto ind : indicators) { // 前の枠とは1つ開ける if (x != 0) { x += 1; @@ -350,36 +275,131 @@ WXStatusPanel::LayoutIndicators() // 上の枠はキャンバス上端から2px y = 2; // 4 は、左右の外枠(1*2) と 枠と文字の間(1*2) - w = ind.text.length() * font_width + 2 + 2; + w = ind->text.length() * font_width + 2 + 2; // そこから上下の外枠と枠と文字の間 h = font_height + 2 + 2; - ind.rect = wxRect(x, y, w, h); + ind->rect = wxRect(x, y, w, h); x += w; } // 全員を一旦右寄せ wxSize sz = GetClientSize(); int offsetx = sz.x - x; - for (auto& ind : indicators) { - ind.rect.Offset(offsetx, 0); + for (auto ind : indicators) { + ind->rect.Offset(offsetx, 0); } // そのうちパフォーマンスカウンタだけ中央寄せまで戻す if (indicators.size() > 0) { - auto& p = indicators.front(); - x = (sz.x / 2) - (p.rect.GetWidth() / 2); - if (p.rect.GetX() > x) { - p.rect.SetX(x); + auto p = indicators.front(); + x = (sz.x / 2) - (p->rect.GetWidth() / 2); + if (p->rect.GetX() > x) { + p->rect.SetX(x); + } + } + + // パネルをもっていれば、パネルをその位置に(再)設定。 + for (auto ind : indicators) { + auto si = dynamic_cast(ind); + if (si && si->panel) { + const auto& rect = ind->rect; + si->panel->SetPosition(wxPoint(rect.GetX(), rect.GetY())); + si->panel->SetClientSize(wxSize(rect.GetWidth(), rect.GetWidth())); } } } +// 状態をチェック。 +// 前回値からどれかでも変更があれば true を返す。 +bool +WXStatusPanel::UpdateStat() +{ + bool updated = false; + + // store に value を代入。更新されていれば updated を立てる。 + // value は一度だけ評価される。 + auto MODIFY = [&](auto& store, auto value) { + bool rv = store != value; + updated |= rv; + store = value; + return rv; + }; + + // 電源オンか + MODIFY(ispower, gScheduler->IsPower()); + + // パフォーマンスカウンタ + MODIFY(perf_mode, gScheduler->GetRunMode()); + MODIFY(perf_rate, gScheduler->GetPerfCounter()); + + // FD + + // SCSI アクセス + MODIFY(scsi_bsy, scsibus->GetBSY()); + // 転送フェーズで DataOut の時だけ書き込みとみなす + bool out = (scsibus->GetPhase() == SCSI::Phase::Transfer) + && (scsibus->GetXfer() == SCSI::XferPhase::DataOut); + MODIFY(scsi_out, out); + + // ネットワーク + // パケット数が変化したとき、 + // およびその変化を監視しているアクティブ状態が変化したとき + // に変更があったとしたいので二重になっている + if (net_ok != NOT_AVAILABLE) { + MODIFY(net_tx_active, MODIFY(net_tx_pkts, gEthernet->GetTXPkts())); + MODIFY(net_rx_active, MODIFY(net_rx_pkts, gEthernet->GetRXPkts())); + } + + // 電源 + + return updated; +} + +// 描画イベント +void +WXStatusPanel::OnPaint(wxPaintEvent& event) +{ + // メモリ DC に描画 + wxMemoryDC memDC; + memDC.SelectObject(bitmap); + Draw(memDC); + + // 実画面 DC にコピー + wxPaintDC dstDC(this); + wxSize size = bitmap.GetSize(); + dstDC.Blit(0, 0, size.x, size.y, &memDC, 0, 0); +} + +// 描画本体 +void +WXStatusPanel::Draw(wxDC& dc) +{ + std::string text; + + // 背景はいつものグレー。 + wxSize size = GetSize(); + dc.SetPen(wxPen(BGPANEL)); + dc.SetBrush(wxBrush(BGPANEL)); + dc.DrawRectangle(wxPoint(0, 0), size); + + // なんとなく見栄えのため上下に薄い線を引く。 + // 四方を線で囲むとオーバーレイウィンドウのように見えるので上下だけ。 + dc.SetPen(wxPen(UD_GREY)); + dc.DrawLine(0, 0, size.x - 1, 0); + dc.DrawLine(0, size.y - 1, size.x - 1, size.y - 1); + + // 各インジケータを描画 + for (auto ind : indicators) { + (this->*(ind->draw))(dc, ind); + } +} + // パフォーマンスカウンタ void -WXStatusPanel::DrawPerf(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawPerf(wxDC& dc, const Indicator *ind) { - const wxRect& rect = ind.rect; + const wxRect& rect = ind->rect; char text[16]; // 枠 @@ -426,11 +446,23 @@ WXStatusPanel::DrawPerf(wxDC& dc, const // SCSI デバイス側のアクセスランプ // 本体ではなく各 SCSI デバイス側 (外付け HDD の前面とか) にあるやつ。 void -WXStatusPanel::DrawSCSI(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawSCSI(wxDC& dc, const Indicator *ind) { + wxColour fg; wxColour bg; + uint idmask; + + const auto si = dynamic_cast(ind); + assert(si); + idmask = 1U << si->GetId(); - if ((scsi_bsy & (1U << ind.userdata)) != 0) { + if ((scsi_loaded & idmask) != 0) { + fg = *wxBLACK; + } else { + fg = UD_GREY; + } + + if ((scsi_bsy & idmask) != 0) { if (scsi_out) { bg = UD_RED; } else { @@ -440,16 +472,16 @@ WXStatusPanel::DrawSCSI(wxDC& dc, const bg = BGPANEL; } - DrawTextLED(dc, ind, *wxBLACK, bg); + DrawTextLED(dc, ind, fg, bg); } // ネットワーク(LAN) void -WXStatusPanel::DrawNet(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawNet(wxDC& dc, const Indicator *ind) { wxColour fg; wxColour bg = BGPANEL; - const wxRect& rect = ind.rect; + const wxRect& rect = ind->rect; if (net_ok) { fg = *wxBLACK; @@ -468,8 +500,8 @@ WXStatusPanel::DrawNet(wxDC& dc, const I int x = rect.x + 2; int y = rect.y + 2; - for (int i = 0; i < ind.text.size(); i++) { - auto c = ind.text[i]; + for (int i = 0; i < ind->text.size(); i++) { + auto c = ind->text[i]; switch (c) { case '\x1e': // 上向き矢印 if (net_tx_active) { @@ -495,18 +527,18 @@ WXStatusPanel::DrawNet(wxDC& dc, const I // 電源LED? void -WXStatusPanel::DrawPower(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawPower(wxDC& dc, const Indicator *ind) { DrawTextLED(dc, ind, *wxBLACK, UD_YELLOW_GREEN); } // 共通の描画処理。 -// ind.text を前景色 fg、背景色 bg で描画するだけの場合。 +// ind->text を前景色 fg、背景色 bg で描画するだけの場合。 void -WXStatusPanel::DrawTextLED(wxDC& dc, const Indicator& ind, +WXStatusPanel::DrawTextLED(wxDC& dc, const Indicator *ind, const wxColour& fg, const wxColour& bg) { - const wxRect& rect = ind.rect; + const wxRect& rect = ind->rect; // 枠 dc.SetPen(wxPen(UD_GREY)); @@ -514,7 +546,7 @@ WXStatusPanel::DrawTextLED(wxDC& dc, con dc.DrawRectangle(rect); SetTextColor(fg, bg); - DrawStringSJIS(dc, rect.x + 2, rect.y + 2, ind.text.c_str()); + DrawStringSJIS(dc, rect.x + 2, rect.y + 2, ind->text.c_str()); ResetTextColor(); } @@ -524,10 +556,10 @@ WXStatusPanel::OnLeftDClick(wxMouseEvent { const wxPoint& pt = event.GetPosition(); - for (auto& ind : indicators) { + for (auto ind : indicators) { // dclick を処理できる枠内なら - if (ind.rect.Contains(pt) && ind.dclick != NULL) { - (this->*(ind.dclick))(ind); + if (ind->rect.Contains(pt) && ind->dclick != NULL) { + (this->*(ind->dclick))(ind); break; } } @@ -535,8 +567,97 @@ WXStatusPanel::OnLeftDClick(wxMouseEvent // パフォーマンスカウンタ枠へのダブルクリック void -WXStatusPanel::DClickPerf(const Indicator& ind) +WXStatusPanel::DClickPerf(const Indicator *ind) { // モードを切り替える gScheduler->SetFullSpeed(!gScheduler->GetFullSpeed()); } + +// コンテキストメニューイベント +void +WXStatusPanel::OnContextMenu(wxContextMenuEvent& event) +{ + wxPoint pos = ScreenToClient(event.GetPosition()); + + for (auto ind : indicators) { + // コンテキストメニューを処理できる枠内なら + const auto si = dynamic_cast(ind); + if (si && si->contextmenu && ind->rect.Contains(pos)) { + (this->*(si->contextmenu))(ind); + break; + } + } +} + +// CD 枠でのコンテキストメニュー +void +WXStatusPanel::ContextMenuCD(const Indicator *ind) +{ + const auto si = dynamic_cast(ind); + assert(si); + + auto menu = gMainFrame->CreateSCSIRemovableMenu(si->GetId()); + if (menu) { + PopupMenu(menu); + } +} + +// SCSI メディア変更イベント (UIMessage イベント) +void +WXStatusPanel::OnSCSIMediaChanged(wxCommandEvent& event) +{ + SCSIIndicator *si = 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) { + break; + } + } + assert(si); + const auto disk = dynamic_cast(si->dev); + + // メディアの状態を一旦クリア + new_loaded = scsi_loaded; + new_loaded &= ~(1U << id); + + // ツールチップを設定 + SCSI::DevType devtype = disk->GetDevType(); + std::string tip = string_format("%s%d: ", + SCSI::GetDevTypeName(devtype), id); + const std::string& path = disk->GetPathName(); + // 同時にメディア状態もセット + if (path.empty()) { + tip += "-"; + } else { + tip += path; + new_loaded |= (1U << id); + } + switch (disk->GetWriteMode()) { + case SCSIDisk::RW::ReadOnly: + tip += "\n(Read Only)"; + break; + case SCSIDisk::RW::WriteProtect: + tip += "\n(Write Protected)"; + break; + case SCSIDisk::RW::WriteIgnore: + tip += "\n(Write Ignored)"; + break; + default: + break; + } + si->panel->SetToolTip(tip); + + // メディアのロード状態が変われば (再配置して) 再描画。 + // 通常周期のタイマーによる更新チェックではメディア状態はチェックして + // いないので、ここで自発的に行う。 + if (scsi_loaded != new_loaded) { + scsi_loaded = new_loaded; + + LayoutIndicators(); + Refresh(); + } +}