--- nono/wx/wxstatuspanel.cpp 2026/04/29 17:05:00 1.1.1.3 +++ nono/wx/wxstatuspanel.cpp 2026/04/29 17:05:48 1.1.1.14 @@ -4,8 +4,10 @@ // Licensed under nono-license.txt // +// // ステータスパネル // + // 描画領域に対してフォントの開始位置は外枠から +4、内枠からなら +2。 // 横も同様。 // @@ -16,23 +18,156 @@ // +4 | ここからフォント #include "wxstatuspanel.h" +#include "fontmanager.h" #include "wxcolor.h" #include "wxmainframe.h" #include "wxmainview.h" #include "wxuimessage.h" -#include "wxversion.h" -#include "netdriver.h" -#include "scheduler.h" -#include "spc.h" +#include "fdc.h" +#include "hostnet.h" +#include "mpu.h" +#include "newsctlr.h" +#include "power.h" +#include "scsi.h" +#include "scsidomain.h" +#include "syncer.h" +#include "virtio_block.h" +#include "virtio_scsi.h" + +// +// インジケータ情報 (ほぼ構造体) +// +class Indicator +{ + public: + // インジケータの識別子 + enum { + NONE = 0, + PERF, + POWER, + SCSI0, + SCSI1, + SCSI2, + SCSI3, + SCSI4, + SCSI5, + SCSI6, + SCSI7, + VBLK0, + VBLK1, + VBLK2, + VBLK3, + VBLK4, + VBLK5, + VBLK6, + VBLK7, + LAN0, + LAN1, + FDD0, + FDD1, + FDD2, + FDD3, + NEWSLED1, + NEWSLED2, + }; + protected: + using dispfunc_t = void (WXStatusPanel::*)(const Indicator *); + using eventfunc_t = void (WXStatusPanel::*)(const Indicator *); + public: + Indicator() { } + // 文字列の長さだけ指定する場合 + Indicator(int id_, dispfunc_t draw_, int minlen_, Status *stat_) { + id = id_; + draw = draw_; + stat = stat_; + minlen = minlen_; + } + // 文字列で指定する場合 + Indicator(int id_, dispfunc_t draw_, const std::string& text_, + Status *stat_) + : Indicator(id_, draw_, text_.length(), stat_) + { + text = text_; + } + virtual ~Indicator(); + + // インジケータ識別子 + int id {}; + + // 対応する状態 + Status *stat {}; + + // 枠内の最小文字列長。 + // 枠の大きさはこの文字列長と text.length() の長いほうで描画される。 + int minlen {}; + + // 表示する文字列。 + std::string text {}; + + // 描画関数 (必須) + dispfunc_t draw {}; + + // ダブルクリックの処理関数 (必要なら) + eventfunc_t dclick {}; + + // コンテキストメニューの処理関数 (必要なら) + eventfunc_t contextmenu {}; + + // 対応するデバイス (必要なら) + Device *dev {}; + + // ToolTip を表示するためのダミーパネル + wxPanel *panel {}; + + // 枠の位置と大きさは panel.GetRect() で取得できる (実行時に計算する) + Rect rect {}; + + public: + // SCSI なら SCSI ID を返す。そうでなければ -1 を返す。 + int GetSCSIID() const; + + // FDD ならユニット番号を返す。そうでなければ -1 を返す。 + int GetFDUnit() const; +}; + +// デストラクタ +Indicator::~Indicator() +{ +} -WXStatusPanel *gStatusPanel; +// 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; + } +} + + +// +// ステータスパネル +// wxBEGIN_EVENT_TABLE(WXStatusPanel, inherited) 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() @@ -46,14 +181,21 @@ static const long STATUS_PANEL_STYLE = 0 // コンストラクタ WXStatusPanel::WXStatusPanel(wxWindow *parent) - : inherited(parent, STATUS_PANEL_STYLE) + : inherited(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, + STATUS_PANEL_STYLE) { + SetName("WXStatusPanel"); + timer.SetOwner(this); + power = GetPowerDevice(); + syncer = GetSyncer(); + // インジケータ用のパラメータを用意。 InitIndicators(); - DoSize(); + SetAutoLayout(true); + FontChanged(); // キー入力イベントをメインビューに転送。 // このパネルへのキー入力は不要なのと、メインウィンドウ内のコントロール @@ -61,43 +203,69 @@ WXStatusPanel::WXStatusPanel(wxWindow *p // 分かりづらくて、こっちがフォーカス持ってしまうと (というか起動後は // こっちにある) VM にキー入力が出来ずに焦ることになるので、ここへの // キー入力イベントは全部メインビューに飛ばす。 + auto mainframe = dynamic_cast(GetParent()); + auto mainview = mainframe->GetMainView(); Connect(wxEVT_KEY_UP, wxKeyEventHandler(WXMainView::OnKeyUp), NULL, - gMainView); + mainview); Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL, - gMainView); + mainview); + Connect(wxEVT_CHAR, wxKeyEventHandler(WXMainView::OnChar), NULL, + mainview); - // 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)); } // デストラクタ WXStatusPanel::~WXStatusPanel() { + while (indicators.empty() == false) { + Indicator *ind = indicators.back(); + indicators.pop_back(); + delete ind; + } + + WXUIMessage::Disconnect(UIMessage::SCSI_MEDIA_CHANGE, this, + wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged)); + WXUIMessage::Disconnect(UIMessage::FDD_MEDIA_CHANGE, this, + wxCommandEventHandler(WXStatusPanel::OnFDDMediaChanged)); + WXUIMessage::Disconnect(UIMessage::LED, this, + wxCommandEventHandler(WXStatusPanel::OnLEDChanged)); } -// クローズイベント +// 初期化 void -WXStatusPanel::OnClose(wxCloseEvent& event) +WXStatusPanel::Init() { - // クローズする前に更新を止める。 - timer.Stop(); + // ここで最初に一回、全メディアの状態更新を能動的に行う。 + for (const auto ind : indicators) { + int id; + id = ind->GetSCSIID(); + if (id >= 0) { + wxCommandEvent event(NONO_EVT_UIMESSAGE); + event.SetInt(id); + OnSCSIMediaChanged(event); + } + id = ind->GetFDUnit(); + if (id >= 0) { + wxCommandEvent event(NONO_EVT_UIMESSAGE); + event.SetInt(id); + OnFDDMediaChanged(event); + } + } } -// フォントサイズ変更 (外部インタフェース) +// クローズイベント void -WXStatusPanel::SetFontSize(FontId new_fontid) +WXStatusPanel::OnClose(wxCloseEvent& event) { - // 同じなら何もしない - if (fontid == new_fontid) { - return; - } - - // フォントを変更 - UpdateFont(new_fontid); - - // サイズ変更処理 - DoSize(); + // クローズする前に更新を止める。 + timer.Stop(); } // 表示/非表示変更イベント @@ -123,39 +291,42 @@ WXStatusPanel::OnShow(wxShowEvent& event } } -// サイズ変更処理 -// サイズを変更したい時に呼ぶ (サイズが変更されたからではない)。 -// ここではサイズは fontsize にのみ依存しているので、フォントサイズが -// 変化/確定した時に呼び出すこと。 +// フォントサイズ変更 void -WXStatusPanel::DoSize() +WXStatusPanel::FontChanged() { - assert(fontid != FontId::None); + inherited::FontChanged(); + + // アクセスマークをサイズに応じて作り直す + const uint8 *src; + switch (font_height) { + case 12: + src = AccessMark12; + break; + case 16: + src = AccessMark16; + break; + case 24: + src = AccessMark24; + break; + default: + PANIC("Unexpected font_height=%d", font_height); + } + accmark.reset(new BitmapI8(src, font_height, font_height)); - // コントロールの大きさを変更 - wxSize size(wxDefaultCoord, font_height + 8); - SetClientSize(size); - SetMinClientSize(size); + Fit(); } -// サイズ変更イベント void -WXStatusPanel::OnSize(wxSizeEvent& event) +WXStatusPanel::Fit() { - // Mac ではウィンドウ作成時に 0 以下の値が来ることがある - // GTK では 1 で来ることがある - const wxSize& size = event.GetSize(); - if (size.x <= 1 || size.y <= 1) { - return; - } - - // ビットマップ作成 - bitmap.Create(size.x, size.y); + wxSize size = LayoutIndicators(); - LayoutIndicators(); + // バックバッファのサイズを固定。 + SetMinBitmapSize(size); - // 再描画を指示 - Refresh(); + SetMinSize(size); + SetSize(size); } // タイマーイベント @@ -167,96 +338,449 @@ WXStatusPanel::OnTimer(wxTimerEvent& eve } } -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, +// store に value を代入。更新されていれば true を返す。 +// value は引数に渡す時に一度だけ評価される。 +#define MODIFY(store, value_) ({ \ + bool mod_; \ + decltype(value_) value = value_; \ + if (store != value) { \ + store = value; \ + mod_ = true; \ + } else { \ + mod_ = false; \ + } \ + mod_; /* return value */ \ +}) + +// +// インジケータ用の情報 +// +class Status +{ + public: + virtual ~Status(); + + // 情報取得メソッド + virtual bool Poll() = 0; +}; + +// デストラクタ +Status::~Status() +{ +} + +// Read/Write が別の3値タイプのアクセスインジケータ用 +enum class RWStatus : uint8 { + None = 0, + Read = 1, + Write = 2, +}; + +// +// SCSI 情報 (Bus/VirtIO 共通) +// +class StatusSCSI : public Status +{ + public: + std::array active {}; +}; + +// +// SCSIBus 情報 +// +class StatusSCSIBus : public StatusSCSI +{ + public: + StatusSCSIBus(SCSIBus *scsibus_) { + scsibus = scsibus_; + } + ~StatusSCSIBus() override; + + bool Poll() override { + uint8 bsy = scsibus->GetBSY(); + // 転送フェーズで DataOut の時だけ書き込みとみなす + bool out = (scsibus->GetPhase() == SCSI::Phase::Transfer) + && (scsibus->GetXfer() == SCSI::XferPhase::DataOut); + + bool mod = false; + for (uint id = 0; id < 8; id++) { + RWStatus newactive; + if (__predict_false((bsy & (1U << id)) != 0)) { + if (out) { + newactive = RWStatus::Write; + } else { + newactive = RWStatus::Read; + } + } else { + newactive = RWStatus::None; + } + mod |= MODIFY(active[id], newactive); + } + return mod; + } + + private: + SCSIBus *scsibus {}; +}; + +// デストラクタ +StatusSCSIBus::~StatusSCSIBus() +{ +} + +// +// VirtIOSCSI 情報 +// +class StatusVSCSI : public StatusSCSI +{ + public: + StatusVSCSI(VirtIOSCSIDevice *dev_) { + dev = dev_; + } + ~StatusVSCSI() override = default; + + bool Poll() override { + std::array newacc; + dev->GetAccess(&newacc); + bool mod = false; + for (uint id = 0; id < 8; id++) { + bool rmod = MODIFY(access[id * 2 + 0], newacc[id * 2 + 0]); + bool wmod = MODIFY(access[id * 2 + 1], newacc[id * 2 + 1]); + + RWStatus newactive; + if (wmod) { + newactive = RWStatus::Write; + } else if (rmod) { + newactive = RWStatus::Read; + } else { + newactive = RWStatus::None; + } + mod |= MODIFY(active[id], newactive); + } + return mod; + } + + std::array access {}; + + private: + VirtIOSCSIDevice *dev {}; +}; + +// +// VirtIOBlock 情報 +// +class StatusVBlk : public Status +{ + public: + StatusVBlk(VirtIOBlockDevice *dev_) { + dev = dev_; + } + ~StatusVBlk() override; + + bool Poll() override { + bool rmod = MODIFY(access_read, dev->GetAccessRead()); + bool wmod = MODIFY(access_write, dev->GetAccessWrite()); + RWStatus newactive; + if (wmod) { + newactive = RWStatus::Write; + } else if (rmod) { + newactive = RWStatus::Read; + } else { + newactive = RWStatus::None; + } + return MODIFY(active, newactive); + } + + // アクセス状況 + uint32 access_read {}; + uint32 access_write {}; + RWStatus active {}; + + private: + VirtIOBlockDevice *dev {}; +}; + +// デストラクタ +StatusVBlk::~StatusVBlk() +{ +} + +// +// ネットワーク情報 +// +class StatusNet : public Status +{ + public: + StatusNet(HostNetDevice *hostnet_) { + hostnet = hostnet_; + } + ~StatusNet() override; + + bool Poll() override { + // パケット数が変化したとき、および + // その変化を監視しているアクティブ状態が変化したときに + // 変更があったとしたいので二重になっている。 + bool tx_mod = MODIFY(tx_pkts, hostnet->GetTXPkts()); + bool rx_mod = MODIFY(rx_pkts, hostnet->GetRXPkts()); + + bool mod = false; + mod |= MODIFY(tx_active, tx_mod); + mod |= MODIFY(rx_active, rx_mod); + return mod; + } + + bool enable {}; + uint64 tx_pkts {}; + uint64 rx_pkts {}; + bool tx_active {}; + bool rx_active {}; + + private: + HostNetDevice *hostnet {}; +}; + +// デストラクタ +StatusNet::~StatusNet() +{ +} + +// +// FDD 情報 +// +class StatusFDD : public Status +{ + public: + StatusFDD(FDDDevice *fdd_) { + fdd = fdd_; + } + ~StatusFDD() override; + + bool Poll() override { + bool mod = false; + mod |= MODIFY(access_led, fdd->GetAccessLED()); + mod |= MODIFY(eject_led, fdd->GetEjectLED()); + return mod; + } + + FDDDevice::LED access_led {}; // アクセス LED の状態 + bool eject_led {}; // イジェクト LED 点灯なら true + private: + FDDDevice *fdd {}; +}; + +// デストラクタ +StatusFDD::~StatusFDD() +{ +} + +// +// NEWS の LED 情報 +// +class StatusNEWS : public Status +{ + public: + StatusNEWS(NewsCtlrDevice *newsctlr_) { + newsctlr = newsctlr_; + } + ~StatusNEWS() override; + + bool Poll() override { + return MODIFY(led, newsctlr->GetLED()); + } + + uint led {}; + private: + NewsCtlrDevice *newsctlr {}; }; +// デストラクタ +StatusNEWS::~StatusNEWS() +{ +} + + // インジケータの初期化 void WXStatusPanel::InitIndicators() { - // パフォーマンスカウンタ - Indicator *perf = new Indicator(">>>9999%", &WXStatusPanel::DrawPerf); - perf->dclick = &WXStatusPanel::DClickPerf; - indicators.emplace_back(perf); + Status *stat; + Indicator *ind; - // FD + // パフォーマンスカウンタ + // 0123456789012345 + // >>> 99.9x 100MHz + auto mpu = gMainApp.FindObject(OBJ_MPU); + assert(mpu); + clock_khz = mpu->GetClockSpeed(); + ind = new Indicator(Indicator::PERF, &WXStatusPanel::DrawPerf, 15, NULL); + ind->dclick = &WXStatusPanel::DClickPerf; + indicators.emplace_back(ind); // SCSI - // SPC の接続済みデバイスリストから取得。 - // (ホストアダプタ2つ目のことは考えてない) - const std::vector& conn = gSPC->GetConnectedDevices(); - for (const auto& dev : conn) { - SCSI::DevType devtype = dev->GetDevType(); - if (devtype == SCSI::DevType::Initiator) { - continue; - } - int id = 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; - si->contextmenu = &WXStatusPanel::ContextMenuCD; - si->panel = new wxPanel(this); - // このパネルがフォーカスを受け取らないようにする。 - // StatusPanel がフォーカスを持っていてもキー入力を MainFrame に渡す - // ようにしているため、StatusPanel のフォーカスを奪ってはいけない。 - si->panel->SetCanFocus(false); + // 接続済みデバイスリストから取得。 + auto scsibus = gMainApp.FindObject(OBJ_SCSIBUS); + if (scsibus) { + stat = new StatusSCSIBus(scsibus); + stats.emplace_back(stat); + + const auto& conn = scsibus->GetDomain()->GetConnectedDevices(); + for (const auto& dev : conn) { + SCSI::DevType devtype = dev->GetDevType(); + if (devtype == SCSI::DevType::Initiator) { + continue; + } + int scsiid = dev->GetMyID(); - indicators.emplace_back(si); + std::string label = string_format("%s%d", + SCSI::GetDevTypeName(devtype), scsiid); + const auto disk = dynamic_cast(dev); + if (disk && disk->GetWriteMode() == SCSIDisk::RW::WriteIgnore) { + label += "\x02\x03"; + } + ind = new Indicator(Indicator::SCSI0 + scsiid, + &WXStatusPanel::DrawSCSI, label, stat); + ind->contextmenu = &WXStatusPanel::ContextMenuCD; + ind->dev = dev; + ind->panel = new wxPanel(this); + ind->panel->SetName("WXStatusPanel.SCSI"); + // このパネルがフォーカスを受け取らないようにする。 + // StatusPanel がフォーカスを持っていてもキー入力を MainFrame に渡す + // ようにしているため、StatusPanel のフォーカスを奪ってはいけない。 + ind->panel->SetCanFocus(false); + + indicators.emplace_back(ind); + } } - // BSY 取得用 - scsibus = gSPC->GetSCSIBus(); - // ネットワーク - if ((bool)gNetDriver) { - if (gNetDriver->IsOpen()) { - net_ok = ENABLE; - } else { - net_ok = DISABLE; + // VirtIOBlock + for (int i = 0; i < 8; i++) { + auto dev = gMainApp.FindObject(OBJ_VIRTIO_BLOCK(i)); + if (dev) { + stat = new StatusVBlk(dev); + stats.emplace_back(stat); + + std::string label = string_format("VB%u", i); + if (dev->GetWriteMode() == SCSIDisk::RW::WriteIgnore) { + label += "\x02\x03"; + } + ind = new Indicator(Indicator::VBLK0 + i, &WXStatusPanel::DrawVBlk, + label, stat); + ind->dev = dev; + indicators.emplace_back(ind); } - // \x1e は上向き矢印、\x1f は下向き矢印 - Indicator *net = new Indicator("LAN\x1e\x1f", &WXStatusPanel::DrawNet); - indicators.emplace_back(net); - } else { - net_ok = NOT_AVAILABLE; } - // 電源 LED。点灯ロジックが分からないのでハリボテ。 - Indicator *power = new Indicator("POWER", &WXStatusPanel::DrawPower); - indicators.emplace_back(power); + // VirtIOSCSI + auto vscsi = gMainApp.FindObject(OBJ_VIRTIO_SCSI); + if (vscsi) { + stat = new StatusVSCSI(vscsi); + stats.emplace_back(stat); + + const auto& conn = vscsi->GetDomain()->GetConnectedDevices(); + for (const auto& dev : conn) { + SCSI::DevType devtype = dev->GetDevType(); + int scsiid = dev->GetMyID(); + + std::string label = string_format("%s%d", + SCSI::GetDevTypeName(devtype), scsiid); + const auto disk = dynamic_cast(dev); + if (disk && disk->GetWriteMode() == SCSIDisk::RW::WriteIgnore) { + label += "\x02\x03"; + } + ind = new Indicator(Indicator::SCSI0 + scsiid, + &WXStatusPanel::DrawSCSI, label, stat); + ind->contextmenu = &WXStatusPanel::ContextMenuCD; + ind->dev = dev; + ind->panel = new wxPanel(this); + ind->panel->SetName("WXStatusPanel.VSCSI"); + // このパネルがフォーカスを受け取らないようにする。 + // StatusPanel がフォーカスを持っていてもキー入力を MainFrame に渡す + // ようにしているため、StatusPanel のフォーカスを奪ってはいけない。 + ind->panel->SetCanFocus(false); - // テスト用 - 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]; + indicators.emplace_back(ind); } - for (; i < 9; i++) { - if (s[i] != data[i]) - return; + } + + // ネットワーク + for (int i = 0; i < 2; i++) { + auto hostnet = gMainApp.FindObject(OBJ_HOSTNET(i)); + if (hostnet) { + auto statnet = new StatusNet(hostnet); + stat = statnet; + stats.emplace_back(stat); + + if (hostnet->GetDriverName() == "none") { + statnet->enable = false; + } else { + statnet->enable = true; + } + // \x1e は上向き矢印、\x1f は下向き矢印。 + // ラベルは結局機種で変わる… + std::string label; + if (gMainApp.IsX68030()) { + label = string_format("LAN%d\x1e\x1f", i); + } else { + label = "LAN\x1e\x1f"; + } + ind = new Indicator(Indicator::LAN0 + i, &WXStatusPanel::DrawNet, + label, stat); + indicators.emplace_back(ind); } - for (; i < data.size(); i++) { - s.insert(s.begin() + i, data[i]); + } + + // FD + for (int unit = 0; unit < FDCDevice::MAX_DRIVE; unit++) { + auto fdd = gMainApp.FindObject(OBJ_FDD(unit)); + if (fdd) { + stat = new StatusFDD(fdd); + stats.emplace_back(stat); + + // 後ろの4文字分のスペースは LED アイコン用の場所取り。 + std::string label = string_format("FD%d ", fdd->GetUnitNo()); + if (fdd->GetWriteMode() == FDDDevice::RW::WriteIgnore) { + label += "\x02\x03"; + } + + // 文字列は OnFDDMediaChanged() でセットする。 + ind = new Indicator(Indicator::FDD0 + unit, + &WXStatusPanel::DrawFD, label, stat); + ind->contextmenu = &WXStatusPanel::ContextMenuFD; + ind->dev = fdd; + ind->panel = new wxPanel(this); + ind->panel->SetName("WXStatusPanel.FD"); + // フォーカスを受け取らない。少し上の CD のところ参照。 + ind->panel->SetCanFocus(false); + + indicators.emplace_back(ind); } - GetParent()->SetLabel(s); } -} -// インジケータのレイアウト -void + // NEWS の LED (2->1 の順で並べる) + auto newsctlr = gMainApp.FindObject(OBJ_NEWSCTLR); + if (newsctlr) { + stat = new StatusNEWS(newsctlr); + stats.emplace_back(stat); + + ind = new Indicator(Indicator::NEWSLED2, &WXStatusPanel::DrawNewsLED2, + "LED2", stat); + indicators.emplace_back(ind); + + ind = new Indicator(Indicator::NEWSLED1, &WXStatusPanel::DrawNewsLED1, + "LED1", stat); + indicators.emplace_back(ind); + } + + // 電源 LED + ind = new Indicator(Indicator::POWER, &WXStatusPanel::DrawPower, + "POWER", NULL); + indicators.emplace_back(ind); +} + +// インジケータの配置位置を求める。 +// ind->rect を更新するがここで描画とかはしない。 +// 戻り値はインジケータの配置に必要な最小サイズ。 +wxSize WXStatusPanel::LayoutIndicators() { int x; @@ -264,50 +788,74 @@ 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; - } + // 前の枠とは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; } - // 全員を一旦右寄せ - wxSize sz = GetClientSize(); - int offsetx = sz.x - x; + // 右端も1つ空ける + x += 1; + + // この x が最小サイズなのでこれをコントロールに設定。 + wxSize size(x, h + 2 + 2); + return size; +} + +// レイアウト +bool +WXStatusPanel::Layout() +{ + if (indicators.empty()) { + return false; + } + + // レイアウトの計算はビットマップサイズに対して行う。 + // 起動直後に何かの拍子で随分小さいサイズにリサイズされることがあったり + // また実画面が小さくてそもそも入り切らないような場合でも、 + // ビットマップは常に最低限描画できるサイズを保証してあるので。 + // それを画面に転送しても見た目は事故るがとりあえず死にはしないので。 + wxSize size(bitmap.GetWidth(), bitmap.GetHeight()); + + // 配置情報をリセット。(左寄せ相当になる) + wxSize minsize = LayoutIndicators(); + + // 全員を一旦右寄せ。 + int offsetx = size.x - minsize.x; 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 perf = indicators.front(); + int x = (size.x / 2) - (perf->rect.w / 2); + if (perf->rect.x > x) { + perf->rect.x = x; } // パネルをもっていれば、パネルをその位置に(再)設定。 for (auto ind : indicators) { - auto si = dynamic_cast(ind); - if (si && si->panel) { + if (ind->panel) { const auto& rect = ind->rect; - si->panel->SetPosition(wxPoint(rect.GetX(), rect.GetY())); - si->panel->SetClientSize(wxSize(rect.GetWidth(), rect.GetWidth())); + ind->panel->SetSize(rect.x, rect.y, rect.w, rect.h); } } + + refresh_background = true; + return true; } // 状態をチェック。 @@ -315,100 +863,63 @@ WXStatusPanel::LayoutIndicators() 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; - }; + bool mod = false; - // 電源オンか - MODIFY(ispower, gScheduler->IsPower()); + // 電源オン + mod |= MODIFY(ispower, power->IsPower()); + mod |= MODIFY(powerled, power->GetPowerLED()); // パフォーマンスカウンタ - MODIFY(perf_mode, gScheduler->GetRunMode()); - MODIFY(perf_rate, gScheduler->GetPerfCounter()); + mod |= MODIFY(perf_mode, syncer->GetRunMode()); + mod |= MODIFY(perf_rate, syncer->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())); + // それ以外 + for (const auto stat : stats) { + mod |= stat->Poll(); } - // 電源 - - 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); + return mod; } // 描画本体 void -WXStatusPanel::Draw(wxDC& dc) +WXStatusPanel::Draw() { 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); + // 指定された時だけ背景を再描画 + 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))(dc, ind); + (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; } @@ -420,72 +931,104 @@ WXStatusPanel::DrawPerf(wxDC& dc, const // パフォーマンス表示は // o 高速モード指示中なら倍率表記 (115% なら 1.2x) // o 通常モード指示中ならパーセント表記 - if ((perf_mode & Scheduler::SCHED_SYNC) == 0) { + if ((perf_mode & Syncer::SYNCMODE_SYNC) == 0) { // 高速モード指示 - SetTextForeColor(UD_RED); - DrawStringSJIS(dc, rect.x + 2, rect.y + 2, "\x01"); - DrawStringSJIS(dc, rect.x + 2 + font_width, rect.y + 2, "\x01"); if (perf_mode == 0) { // 高速走行中 - DrawStringSJIS(dc, rect.x + 2 + font_width * 2, rect.y + 2, "\x01"); + 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); + uint rate = perf_rate + 5; + if (rate < 100 * 100) { + // 2桁倍までなら "99.9x" のように小数以下1桁まで。 + snprintf(text, sizeof(text), "%2u.%1ux", + (rate / 100), (rate % 100) / 10); + } else if (rate < 10000 * 100) { + // 3桁倍、4桁倍だと整数部のみ。"9999x" + snprintf(text, sizeof(text), "%4ux", rate / 100); + } else { + // どうする? + strlcpy(text, "9999x", sizeof(text)); + } } else { // 通常モード指示 - - snprintf(text, sizeof(text), "%4d%%", perf_rate); + ffmark = " "; + snprintf(text, sizeof(text), "%4u%%", perf_rate); } - DrawStringSJIS(dc, rect.x + 2 + font_width * 3, rect.y + 2, text); + + // クロックスピード(MHz)も四捨五入。 + uint eff_khz = (clock_khz * perf_rate + 50) / 100; + snprintf(text + 5, sizeof(text) - 5, "%4uMHz", (eff_khz + 500) / 1000); + + 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 fg; - wxColour bg; - uint idmask; - - const auto si = dynamic_cast(ind); - assert(si); - idmask = 1U << si->GetId(); + const StatusSCSI *stat = dynamic_cast(ind->stat); + Color fg; + Color bg; + int scsiid; - if ((scsi_loaded & idmask) != 0) { - fg = *wxBLACK; + scsiid = ind->GetSCSIID(); + assertmsg(scsiid >= 0, "id=%d", ind->id); + + if (scsi_loaded[scsiid]) { + fg = UD_BLACK; } else { fg = UD_GREY; } - if ((scsi_bsy & idmask) != 0) { - if (scsi_out) { - bg = UD_RED; - } else { - bg = UD_YELLOW_GREEN; - } + if (stat->active[scsiid] == RWStatus::Write) { + bg = UD_RED; + } else if (stat->active[scsiid] == RWStatus::Read) { + bg = UD_YELLOW_GREEN; } else { bg = BGPANEL; } - DrawTextLED(dc, ind, fg, bg); + DrawTextLED(ind, fg, bg); +} + +// VirtIOBlock デバイスのアクセスランプ。 +void +WXStatusPanel::DrawVBlk(const Indicator *ind) +{ + const StatusVBlk *stat = dynamic_cast(ind->stat); + Color fg; + Color bg; + + fg = UD_BLACK; + if (stat->active == RWStatus::Write) { + bg = UD_RED; + } else if (stat->active == RWStatus::Read) { + bg = UD_YELLOW_GREEN; + } else { + bg = BGPANEL; + } + + DrawTextLED(ind, fg, bg); } // ネットワーク(LAN) void -WXStatusPanel::DrawNet(wxDC& dc, const Indicator *ind) +WXStatusPanel::DrawNet(const Indicator *ind) { - wxColour fg; - wxColour bg = BGPANEL; - const wxRect& rect = ind->rect; - - if (net_ok) { - fg = *wxBLACK; - if (net_tx_active || net_rx_active) { + const StatusNet *stat = dynamic_cast(ind->stat); + const Rect& rect = ind->rect; + Color fg; + Color bg = BGPANEL; + + if (stat->enable) { + fg = UD_BLACK; + if (stat->tx_active || stat->rx_active) { bg = UD_YELLOW_GREEN; } } else { @@ -493,9 +1036,8 @@ WXStatusPanel::DrawNet(wxDC& dc, const I } // 枠 - dc.SetPen(wxPen(UD_GREY)); - dc.SetBrush(wxBrush(bg)); - dc.DrawRectangle(rect); + bitmap.FillRect(bg, rect); + bitmap.DrawRect(UD_GREY, rect); int x = rect.x + 2; int y = rect.y + 2; @@ -504,49 +1046,218 @@ WXStatusPanel::DrawNet(wxDC& dc, const I auto c = ind->text[i]; switch (c) { case '\x1e': // 上向き矢印 - if (net_tx_active) { - fg = *wxBLACK; + if (stat->tx_active) { + fg = UD_BLACK; } else { fg = UD_GREY; } break; case '\x1f': // 下向き矢印 - if (net_rx_active) { - fg = *wxBLACK; + if (stat->rx_active) { + fg = UD_BLACK; } else { fg = UD_GREY; } break; } SetTextColor(fg, bg); - DrawChar1(dc, x, y, c); + DrawChar1(x, y, c); x += font_width; } ResetTextColor(); } -// 電源LED? +// FD void -WXStatusPanel::DrawPower(wxDC& dc, const Indicator *ind) +WXStatusPanel::DrawFD(const Indicator *ind) { - DrawTextLED(dc, ind, *wxBLACK, UD_YELLOW_GREEN); + const StatusFDD *stat = dynamic_cast(ind->stat); + const Rect& rect = ind->rect; + Rect iconrect; + Color pal[3]; + int unit; + enum { + BG = 0, + FG, + IN, + }; + + unit = ind->GetFDUnit(); + + 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 (stat->eject_led) { + iconrect.x += 1; + iconrect.y += 1; + iconrect.w -= 2; + iconrect.h -= 2; + bitmap.FillRect(UD_YELLOW_GREEN, iconrect); + } + + // その後で 3,4文字目の空き地に丸いインジケータを描画。 + int dx = rect.x + 2 + font_width * 3; + int dy = rect.y + 2; + + // アクセス LED はディスク非挿入時に点滅するケースがあって、 + // その場合、フチがグレーのまま中を黄緑で塗りつぶすのは見えづらいので + // 中を塗りつぶす時はフチを濃くしたほうがいい。 + switch (stat->access_led) { + 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.DrawBitmapI8(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, +}; +/*static*/ const uint8 +WXStatusPanel::AccessMark24[] = { + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1, 1,2,2,2,2,2,2,1, 1,0,0,0,0,0,0,0, + 0,0,0,0,0,1,1,2, 2,2,2,2,2,2,2,2, 2,1,1,0,0,0,0,0, + 0,0,0,0,1,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,1,0,0,0,0, + 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0, + 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0, + 0,0,1,2,2,2,2,2, 2,2,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,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,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,2, 2,2,2,2,2,2,1,0, + 0,1,2,2,2,2,2,2, 2,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,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,2, 2,2,2,2,2,2,1,0, + 0,1,2,2,2,2,2,2, 2,2,2,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,2,2, 2,2,2,2,2,1,0,0, + 0,0,1,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,1,0,0, + 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0, + 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0, + 0,0,0,0,1,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,1,0,0,0,0, + 0,0,0,0,0,1,1,2, 2,2,2,2,2,2,2,2, 2,1,1,0,0,0,0,0, + 0,0,0,0,0,0,0,1, 1,2,2,2,2,2,2,1, 1,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,1,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,0,0,0, 0,0,0,0,0,0,0,0, +}; + +// NEWS LED1 +void +WXStatusPanel::DrawNewsLED1(const Indicator *ind) +{ + const StatusNEWS *stat = dynamic_cast(ind->stat); + Color bg; + + if ((stat->led & NewsCtlrDevice::LED_ORANGE) != 0) { + bg = UD_ORANGE; + } else { + bg = BGPANEL; + } + DrawTextLED(ind, UD_BLACK, bg); +} + +// NEWS LED2 +void +WXStatusPanel::DrawNewsLED2(const Indicator *ind) +{ + const StatusNEWS *stat = dynamic_cast(ind->stat); + Color bg; + + if ((stat->led & NewsCtlrDevice::LED_GREEN) != 0) { + bg = UD_GREEN; + } else { + bg = BGPANEL; + } + DrawTextLED(ind, UD_BLACK, bg); +} + +// 電源 LED +void +WXStatusPanel::DrawPower(const Indicator *ind) +{ + 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 で描画するだけの場合。 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(); } @@ -558,7 +1269,7 @@ WXStatusPanel::OnLeftDClick(wxMouseEvent for (auto ind : indicators) { // dclick を処理できる枠内なら - if (ind->rect.Contains(pt) && ind->dclick != NULL) { + if (ind->rect.Contains(pt.x, pt.y) && ind->dclick != NULL) { (this->*(ind->dclick))(ind); break; } @@ -570,7 +1281,7 @@ void WXStatusPanel::DClickPerf(const Indicator *ind) { // モードを切り替える - gScheduler->SetFullSpeed(!gScheduler->GetFullSpeed()); + syncer->RequestFullSpeed(!syncer->GetFullSpeed()); } // コンテキストメニューイベント @@ -581,9 +1292,8 @@ WXStatusPanel::OnContextMenu(wxContextMe for (auto ind : indicators) { // コンテキストメニューを処理できる枠内なら - const auto si = dynamic_cast(ind); - if (si && si->contextmenu && ind->rect.Contains(pos)) { - (this->*(si->contextmenu))(ind); + if (ind->contextmenu && ind->rect.Contains(pos.x, pos.y)) { + (this->*(ind->contextmenu))(ind); break; } } @@ -593,10 +1303,25 @@ 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(si->GetId()); + auto mainframe = dynamic_cast(GetParent()); + auto menu = mainframe->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 mainframe = dynamic_cast(GetParent()); + auto menu = mainframe->CreateFDMenu(unit); if (menu) { PopupMenu(menu); } @@ -606,35 +1331,35 @@ WXStatusPanel::ContextMenuCD(const Indic void WXStatusPanel::OnSCSIMediaChanged(wxCommandEvent& event) { - SCSIIndicator *si = NULL; - uint8 new_loaded; + Indicator *ind = NULL; + bool 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 = false; // ツールチップを設定 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 = true; } switch (disk->GetWriteMode()) { case SCSIDisk::RW::ReadOnly: @@ -649,15 +1374,78 @@ WXStatusPanel::OnSCSIMediaChanged(wxComm default: break; } - si->panel->SetToolTip(tip); + ind->panel->SetToolTip(tip); // メディアのロード状態が変われば (再配置して) 再描画。 // 通常周期のタイマーによる更新チェックではメディア状態はチェックして // いないので、ここで自発的に行う。 - if (scsi_loaded != new_loaded) { - scsi_loaded = new_loaded; + if (scsi_loaded[scsiid] != new_loaded) { + scsi_loaded[scsiid] = new_loaded; + 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); - LayoutIndicators(); + // メディアのロード状態が変われば (再配置して) 再描画。 + // 通常周期のタイマーによる更新チェックではメディア状態はチェックして + // いないので、ここで自発的に行う。 + if (fd_loaded[unit] != new_loaded) { + fd_loaded[unit] = new_loaded; + Refresh(); + } +} + +// LED 状態変更イベント (UIMessage イベント)。 +// +// 状態変更があったことをプッシュ通知する。主に、通常の更新頻度では +// 間に合わない X68030 の電源 LED の 16Hz 点滅用。 +void +WXStatusPanel::OnLEDChanged(wxCommandEvent& event) +{ + if (UpdateStat()) { Refresh(); } }