--- nono/wx/wxstatuspanel.cpp 2026/04/29 17:04:53 1.1.1.1 +++ nono/wx/wxstatuspanel.cpp 2026/04/29 17:05:12 1.1.1.5 @@ -4,8 +4,10 @@ // Licensed under nono-license.txt // +// // ステータスパネル // + // 描画領域に対してフォントの開始位置は外枠から +4、内枠からなら +2。 // 横も同様。 // @@ -16,23 +18,28 @@ // +4 | ここからフォント #include "wxstatuspanel.h" +#include "fontmanager.h" #include "wxcolor.h" +#include "wxmainframe.h" #include "wxmainview.h" -#include "bitrev.h" -#include "netdriver.h" -#include "scheduler.h" +#include "wxuimessage.h" +#include "wxversion.h" +#include "ethernet.h" +#include "fdc.h" +#include "hostnet.h" +#include "power.h" #include "spc.h" +#include "sync.h" WXStatusPanel *gStatusPanel; wxBEGIN_EVENT_TABLE(WXStatusPanel, inherited) - EVT_CREATE(WXStatusPanel::OnCreate) EVT_CLOSE(WXStatusPanel::OnClose) EVT_SHOW(WXStatusPanel::OnShow) EVT_SIZE(WXStatusPanel::OnSize) EVT_TIMER(wxID_ANY, WXStatusPanel::OnTimer) - EVT_PAINT(WXStatusPanel::OnPaint) EVT_LEFT_DCLICK(WXStatusPanel::OnLeftDClick) + EVT_CONTEXT_MENU(WXStatusPanel::OnContextMenu) wxEND_EVENT_TABLE() // ステータスパネルの更新頻度 [Hz] @@ -44,29 +51,15 @@ static const long STATUS_PANEL_STYLE = 0 // コンストラクタ WXStatusPanel::WXStatusPanel(wxWindow *parent) - : inherited(parent, STATUS_PANEL_STYLE) + : inherited(parent, wxDefaultSize, STATUS_PANEL_STYLE) { timer.SetOwner(this); // インジケータ用のパラメータを用意。 InitIndicators(); - DoSize(); - - // メインビューにイベントを接続したいがこの時点ではまだ gMainView が - // ないので OnCreate イベントで処理する。 - POST_CREATE(); -} - -// デストラクタ -WXStatusPanel::~WXStatusPanel() -{ -} + FontChanged(); -// ウィンドウ作成イベント -void -WXStatusPanel::OnCreate(wxCommandEvent& event) -{ // キー入力イベントをメインビューに転送。 // このパネルへのキー入力は不要なのと、メインウィンドウ内のコントロール // フォーカスがこのステータスパネルとメインビューのどっちにあるかは @@ -77,6 +70,31 @@ WXStatusPanel::OnCreate(wxCommandEvent& gMainView); Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL, gMainView); + + // 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)); +} + +// デストラクタ +WXStatusPanel::~WXStatusPanel() +{ + while (indicators.empty() == false) { + Indicator *ind = indicators.back(); + 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)); } // クローズイベント @@ -87,22 +105,6 @@ WXStatusPanel::OnClose(wxCloseEvent& eve timer.Stop(); } -// フォントサイズ変更 (外部インタフェース) -void -WXStatusPanel::SetFontSize(FontId new_fontid) -{ - // 同じなら何もしない - if (fontid == new_fontid) { - return; - } - - // フォントを変更 - UpdateFont(new_fontid); - - // サイズ変更処理 - DoSize(); -} - // 表示/非表示変更イベント // new した時は (デフォルトで表示状態なのでか状態に変更がないという扱いの // ようで) イベントは飛んでこない。同様に new 直後に Show() を明示発行しても @@ -126,39 +128,34 @@ WXStatusPanel::OnShow(wxShowEvent& event } } -// サイズ変更処理 -// サイズを変更したい時に呼ぶ (サイズが変更されたからではない)。 -// ここではサイズは fontsize にのみ依存しているので、フォントサイズが -// 変化/確定した時に呼び出すこと。 -void -WXStatusPanel::DoSize() -{ - assert(fontid != FontId::None); - - // ここで高速モードアイコンを作る、あるいは変更する。 - // どこでやるか悩ましいけど、ここがフォントサイズが変わった時と - // 起動時の他のイベントより先に呼ばれるので。 - UpdateFFMark(); +// フォントサイズ変更 +void +WXStatusPanel::FontChanged() +{ + inherited::FontChanged(); + + // インジケータを再レイアウト + LayoutIndicators(); // コントロールの大きさを変更 - wxSize size(wxDefaultCoord, font_height + 8); + wxSize size = GetClientSize(); + size.y = font_height + 8; SetClientSize(size); - SetMinClientSize(size); } // サイズ変更イベント 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; } - // ビットマップ作成 - bitmap.Create(size.x, size.y); + // 親クラス + inherited::OnSize(event); LayoutIndicators(); @@ -175,137 +172,109 @@ WXStatusPanel::OnTimer(wxTimerEvent& eve } } -// 状態をチェック。 -// 前回値からどれかでも変更があれば true を返す。 -bool -WXStatusPanel::UpdateStat() -{ - bool updated = false; - - // 電源オンか - ispower = gScheduler->IsPower(); - if (last_ispower != ispower) { - last_ispower = ispower; - updated = true; - } - - // パフォーマンスカウンタ - perf_mode = gScheduler->GetRunMode(); - if (last_perf_mode != perf_mode) { - last_perf_mode = perf_mode; - updated = true; - } - perf_rate = gScheduler->GetPerfCounter(); - if (last_perf_rate != perf_rate) { - last_perf_rate = perf_rate; - updated = true; - } - - // FD - - // SCSI アクセス - scsi_bsy = scsibus->GetBSY(); - if (scsi_bsy != last_scsi_bsy) { - last_scsi_bsy = scsi_bsy; - updated = true; - } - - // ネットワーク - - // 電源 - - 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, +}; // インジケータの初期化 void WXStatusPanel::InitIndicators() { // パフォーマンスカウンタ - Indicator perf(">>>9999%", &WXStatusPanel::DrawPerf); - perf.dclick = &WXStatusPanel::DClickPerf; + Indicator *perf = new Indicator(Indicator::PERF, 8/* >>>9999% */, + &WXStatusPanel::DrawPerf); + perf->dclick = &WXStatusPanel::DClickPerf; indicators.emplace_back(perf); - // FD - // SCSI // SPC の接続済みデバイスリストから取得。 // (ホストアダプタ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 = "HD"; - break; - default: - disp = "??"; - break; } - disp += string_format("%d", id); + int scsiid = dev->GetMyID(); + + // 文字列は 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 に渡す + // ようにしているため、StatusPanel のフォーカスを奪ってはいけない。 + si->panel->SetCanFocus(false); - indicators.emplace_back(disp, &WXStatusPanel::DrawSCSI, id); + indicators.emplace_back(si); } // BSY 取得用 scsibus = gSPC->GetSCSIBus(); // ネットワーク -#if 0 // XXX まだアクセスランプどうするか要調査 - if (gNetDriver->IsOpen()) { - net_ok = true; - } else { - net_ok = false; - } - indicators.emplace_back("LAN", &WXStatusPanel::DrawNet); -#endif - - // 電源 LED。点灯ロジックが分からないのでハリボテ。 - // ロジックが分かった時のために一応 3 文字にしておく。 - indicators.emplace_back("PWR", &WXStatusPanel::DrawPower); + if (gEthernet) { + hostnet = gEthernet->GetHostNet(); + assert(hostnet); + if (hostnet->GetDriverName() == "none") { + net_ok = DISABLE; + } else { + net_ok = ENABLE; + } + // \x1e は上向き矢印、\x1f は下向き矢印 + Indicator *net = new Indicator(Indicator::LAN, 5, + &WXStatusPanel::DrawNet); + net->text = "LAN\x1e\x1f"; + indicators.emplace_back(net); + } else { + net_ok = NOT_AVAILABLE; + } + + // 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); + + // テスト用 + if (__predict_false(0||(bool)*gHostInfo)) { + std::string s((const char *)GetParent()->GetLabel().mb_str()); + int i = 0; + for (; i < 5; i++) { + s[i] += data[i]; + } + for (; i < 9; i++) { + if (s[i] != data[i]) + return; + } + for (; i < data.size(); i++) { + s.insert(s.begin() + i, data[i]); + } + GetParent()->SetLabel(s); + } } // インジケータのレイアウト @@ -317,130 +286,417 @@ WXStatusPanel::LayoutIndicators() int w; int h; + // 上の枠はキャンバス上端から2px + y = 2; + // そこから上下の外枠と枠と文字の間 + h = font_height + 2 + 2; + // まずは前から順に列挙して必要な幅を積算していく x = 0; - for (auto& ind : indicators) { - // 前の枠とは1つ開ける - if (x != 0) { - x += 1; - } + for (auto ind : indicators) { + // 前の枠とは1つ空ける + x += 1; - // 上の枠はキャンバス上端から2px - y = 2; // 4 は、左右の外枠(1*2) と 枠と文字の間(1*2) - w = ind.text.length() * font_width + 2 + 2; - // そこから上下の外枠と枠と文字の間 - h = font_height + 2 + 2; + int len = std::max(ind->minlen, (int)ind->text.length()); + w = len * font_width + 2 + 2; - ind.rect = wxRect(x, y, w, h); + ind->rect = Rect(x, y, w, h); x += w; } + // 右端も1つ空ける + x += 1; + + // この x が最小サイズなのでこれをコントロールに設定。 + // (コントロールのサイズは WXMainFrame::Layout() が設定する) + SetMinClientSize(wxSize(x, font_height + 8)); + // 全員を一旦右寄せ 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.w / 2); + if (p->rect.x > x) { + p->rect.x = x; + } + } + + // パネルをもっていれば、パネルをその位置に(再)設定。 + for (auto ind : indicators) { + if (ind->panel) { + const auto& rect = ind->rect; + ind->panel->SetSize(rect.x, rect.y, rect.w, rect.h); } } + + refresh_background = true; +} + +// 状態をチェック。 +// 前回値からどれかでも変更があれば 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, gPower->IsPower()); + MODIFY(powerled, gPower->GetPowerLED()); + + // パフォーマンスカウンタ + MODIFY(perf_mode, gSync->GetRunMode()); + MODIFY(perf_rate, gSync->GetPerfCounter()); + + // 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, hostnet->GetTXPkts())); + 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; +} + +// 描画本体 +void +WXStatusPanel::Draw() +{ + std::string text; + + // 指定された時だけ背景を再描画 + if (refresh_background) { + refresh_background = false; + bitmap.Fill(BGPANEL); + } + + // なんとなく見栄えのため上下に薄い線を引く。 + // 四方を線で囲むとオーバーレイウィンドウのように見えるので上下だけ。 + wxSize size = GetSize(); + bitmap.DrawLineH(UD_GREY, 0, 0, size.x); + bitmap.DrawLineH(UD_GREY, 0, size.y - 1, size.x); + + // 各インジケータを描画 + for (auto ind : indicators) { + (this->*(ind->draw))(ind); + } } // パフォーマンスカウンタ void -WXStatusPanel::DrawPerf(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawPerf(const Indicator *ind) { - const wxRect& rect = ind.rect; + const Rect& rect = ind->rect; + char text[16]; + const char *ffmark; // 枠 - dc.SetPen(wxPen(UD_GREY)); - dc.SetBrush(wxBrush(BGPANEL)); - dc.DrawRectangle(rect); + bitmap.DrawRect(UD_GREY, rect); // 電源オフならここまで if (ispower == false) { + bitmap.FillRect(BGPANEL, + rect.x + 1, rect.y + 1, rect.w - 2, rect.h - 2); return; } - // 高速モードアイコン - // UI による高速モード指示なら >> (2つ) - // そのうち実際に高速動作なら >>> (3つ) - if ((perf_mode & Scheduler::SCHED_SYNC) == 0) { - SetTextForeColor(UD_RED); - DrawBitmap(dc, rect.x + 2, rect.y + 2, *ffmark); - DrawBitmap(dc, rect.x + 2 + font_width, rect.y + 2, *ffmark); + // 高速モードか通常モードかでいろいろ違う。 + // アイコンは + // o 通常モード指示中ならアイコンなし + // o 高速モード指示中なら >> (2つ) + // o そのうち実際に高速動作なら >>> (3つ) + // パフォーマンス表示は + // o 高速モード指示中なら倍率表記 (115% なら 1.2x) + // o 通常モード指示中ならパーセント表記 + if ((perf_mode & Sync::SCHED_SYNC) == 0) { + // 高速モード指示 if (perf_mode == 0) { - DrawBitmap(dc, rect.x + 2 + font_width * 2, rect.y + 2, *ffmark); + // 高速走行中 + ffmark = "\x01\x01\x01"; + } else { + ffmark = "\x01\x01 "; } - SetTextForeColor(*wxBLACK); + + // 倍率表記は1桁少ないので、表示用に最下位桁を四捨五入 + int rate = perf_rate + 5; + snprintf(text, sizeof(text), "%2d.%1dx", + (rate / 100), (rate % 100) / 10); + } else { + // 通常モード指示 + ffmark = " "; + snprintf(text, sizeof(text), "%4d%%", perf_rate); } - // パフォーマンス表示 - std::string text = string_format("%4d%%", perf_rate); - DrawStringSJIS(dc, rect.x + 2 + font_width * 3, rect.y + 2, text.c_str()); + DrawStringSJIS(UD_RED, rect.x + 2, rect.y + 2, ffmark); + DrawStringSJIS(rect.x + 2 + font_width * 3, rect.y + 2, text); } // SCSI デバイス側のアクセスランプ // 本体ではなく各 SCSI デバイス側 (外付け HDD の前面とか) にあるやつ。 void -WXStatusPanel::DrawSCSI(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawSCSI(const Indicator *ind) { - wxColour bg; + Color fg; + Color bg; + int scsiid; + uint idmask; + + scsiid = ind->GetSCSIID(); + assertmsg(scsiid >= 0, "id=%d", ind->id); + idmask = 1U << scsiid; + + if ((scsi_loaded & idmask) != 0) { + fg = UD_BLACK; + } else { + fg = UD_GREY; + } - if ((scsi_bsy & (1U << ind.userdata)) != 0) { - bg = UD_RED; + if ((scsi_bsy & idmask) != 0) { + if (scsi_out) { + bg = UD_RED; + } else { + bg = UD_YELLOW_GREEN; + } } else { bg = BGPANEL; } - DrawTextLED(dc, ind, *wxBLACK, bg); + DrawTextLED(ind, fg, bg); } // ネットワーク(LAN) void -WXStatusPanel::DrawNet(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawNet(const Indicator *ind) { - wxColour fg; - wxColour bg = BGPANEL; + Color fg; + Color bg = BGPANEL; + const Rect& rect = ind->rect; if (net_ok) { - fg = *wxBLACK; + fg = UD_BLACK; + if (net_tx_active || net_rx_active) { + bg = UD_YELLOW_GREEN; + } } else { fg = UD_GREY; } - DrawTextLED(dc, ind, fg, bg); + // 枠 + bitmap.FillRect(bg, rect); + bitmap.DrawRect(UD_GREY, rect); + + int x = rect.x + 2; + int y = rect.y + 2; + + for (int i = 0; i < ind->text.size(); i++) { + auto c = ind->text[i]; + switch (c) { + case '\x1e': // 上向き矢印 + if (net_tx_active) { + fg = UD_BLACK; + } else { + fg = UD_GREY; + } + break; + case '\x1f': // 下向き矢印 + if (net_rx_active) { + fg = UD_BLACK; + } else { + fg = UD_GREY; + } + break; + } + SetTextColor(fg, bg); + DrawChar1(x, y, c); + x += font_width; + } + ResetTextColor(); +} + +// 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); } -// 電源LED? +/*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(wxDC& dc, const Indicator& ind) +WXStatusPanel::DrawPower(const Indicator *ind) { - DrawTextLED(dc, ind, *wxBLACK, 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); } // 共通の描画処理。 -// ind.text を前景色 fg、背景色 bg で描画するだけの場合。 +// ind->text を前景色 fg、背景色 bg で描画するだけの場合。 void -WXStatusPanel::DrawTextLED(wxDC& dc, const Indicator& ind, - const wxColour& fg, const wxColour& bg) +WXStatusPanel::DrawTextLED(const Indicator *ind, Color fg, Color bg) { - const wxRect& rect = ind.rect; + const Rect& rect = ind->rect; // 枠 - dc.SetPen(wxPen(UD_GREY)); - dc.SetBrush(wxBrush(bg)); - dc.DrawRectangle(rect); + bitmap.FillRect(bg, rect); + bitmap.DrawRect(UD_GREY, rect); SetTextColor(fg, bg); - DrawStringSJIS(dc, rect.x + 2, rect.y + 2, ind.text.c_str()); + DrawStringSJIS(rect.x + 2, rect.y + 2, ind->text.c_str()); ResetTextColor(); } @@ -450,10 +706,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.x, pt.y) && ind->dclick != NULL) { + (this->*(ind->dclick))(ind); break; } } @@ -461,62 +717,226 @@ WXStatusPanel::OnLeftDClick(wxMouseEvent // パフォーマンスカウンタ枠へのダブルクリック void -WXStatusPanel::DClickPerf(const Indicator& ind) +WXStatusPanel::DClickPerf(const Indicator *ind) { // モードを切り替える - gScheduler->SetFullSpeed(!gScheduler->GetFullSpeed()); + gSync->RequestFullSpeed(!gSync->GetFullSpeed()); } -// 高速モードアイコンの三角一個分、半角文字と同じ大きさ。 -// XBM データは左右鏡像(反転前に左詰め)で保持する。 -static constexpr uint8 FFMark12[] = { - _rev8(0b000000'00), // 0 - _rev8(0b100000'00), // 1 - _rev8(0b110000'00), // 2 - _rev8(0b111000'00), // 3 - _rev8(0b111100'00), // 4 - _rev8(0b111110'00), // 5 - _rev8(0b111111'00), // 6 - _rev8(0b111110'00), // 7 - _rev8(0b111100'00), // 8 - _rev8(0b111000'00), // 9 - _rev8(0b110000'00), // a - _rev8(0b100000'00), // b -}; -static constexpr uint8 FFMark16[] = { - _rev8(0b00000000), // 0 - _rev8(0b10000000), // 1 - _rev8(0b11000000), // 2 - _rev8(0b11100000), // 3 - _rev8(0b11110000), // 4 - _rev8(0b11111000), // 5 - _rev8(0b11111100), // 6 - _rev8(0b11111110), // 7 - _rev8(0b11111111), // 8 - _rev8(0b11111110), // 9 - _rev8(0b11111100), // a - _rev8(0b11111000), // b - _rev8(0b11110000), // c - _rev8(0b11100000), // d - _rev8(0b11000000), // e - _rev8(0b10000000), // f -}; +// コンテキストメニューイベント +void +WXStatusPanel::OnContextMenu(wxContextMenuEvent& event) +{ + wxPoint pos = ScreenToClient(event.GetPosition()); + + for (auto ind : indicators) { + // コンテキストメニューを処理できる枠内なら + if (ind->contextmenu && ind->rect.Contains(pos.x, pos.y)) { + (this->*(ind->contextmenu))(ind); + break; + } + } +} + +// CD 枠でのコンテキストメニュー +void +WXStatusPanel::ContextMenuCD(const Indicator *ind) +{ + int scsiid = ind->GetSCSIID(); + assertmsg(scsiid >= 0, "id=%d", ind->id); + + auto menu = gMainFrame->CreateSCSIRemovableMenu(scsiid); + if (menu) { + PopupMenu(menu); + } +} -// 高速モードアイコン(の1文字分)を現在のフォントサイズに合わせて作り直す +// FD 枠でのコンテキストメニュー void -WXStatusPanel::UpdateFFMark() +WXStatusPanel::ContextMenuFD(const Indicator *ind) { - const uint8 *xbm; + int unit = ind->GetFDUnit(); + assertmsg(unit >= 0, "id=%d", ind->id); - switch (fontid) { - case FontId::_6x12: - xbm = FFMark12; + auto menu = gMainFrame->CreateFDMenu(unit); + if (menu) { + PopupMenu(menu); + } +} + +// SCSI メディア変更イベント (UIMessage イベント) +void +WXStatusPanel::OnSCSIMediaChanged(wxCommandEvent& event) +{ + Indicator *ind = NULL; + uint8 new_loaded; + + // 該当の Indicator を探す (見付かるはず) + int scsiid = event.GetInt(); + int id = Indicator::SCSI0 + scsiid; + for (auto i : indicators) { + if (i->id == id) { + ind = i; + break; + } + } + assert(ind); + const auto disk = dynamic_cast(ind->dev); + + // メディアの状態を一旦クリア + new_loaded = scsi_loaded; + new_loaded &= ~(1U << scsiid); + + // ツールチップを設定 + SCSI::DevType devtype = disk->GetDevType(); + std::string tip = string_format("%s%d: ", + SCSI::GetDevTypeName(devtype), scsiid); + const std::string& path = disk->GetPathName(); + // 同時にメディア状態もセット + if (path.empty()) { + tip += "-"; + } else { + tip += path; + new_loaded |= (1U << scsiid); + } + 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; + } + 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"; + } + } + + // メディアのロード状態が変われば (再配置して) 再描画。 + // 通常周期のタイマーによる更新チェックではメディア状態はチェックして + // いないので、ここで自発的に行う。 + if (scsi_loaded != new_loaded) { + scsi_loaded = new_loaded; + + LayoutIndicators(); + 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 FontId::_8x16: - xbm = FFMark16; + case FDDDevice::RW::WriteIgnore: + tip += "\n(Write Ignored)"; break; default: - assert(false); + 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; } - ffmark.reset(new wxBitmap((const char *)xbm, font_width, font_height, 1)); }