File:  [Isaki's NoNo m68k/m88k emulator] / nono / wx / wxstatuspanel.cpp
Revision 1.1.1.16 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:57 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2021 nono project
// Licensed under nono-license.txt
//

//
// ステータスパネル
//

// 描画領域に対してフォントの開始位置は外枠から +4、内枠からなら +2。
// 横も同様。
//
// +0 ------ 外枠
// +1        アキ
// +2  +---- 内枠
// +3  |     アキ
// +4  | ここからフォント

#include "wxstatuspanel.h"
#include "fontmanager.h"
#include "wxmainframe.h"
#include "wxmainview.h"
#include "wxuimessage.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()
{
}

// 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_TIMER(wxID_ANY, WXStatusPanel::OnTimer)
	EVT_LEFT_DCLICK(WXStatusPanel::OnLeftDClick)
	EVT_CONTEXT_MENU(WXStatusPanel::OnContextMenu)
wxEND_EVENT_TABLE()

// ステータスパネルの更新頻度 [Hz]
static constexpr uint FPS = 10;

// style フラグ
// デフォルトは wxTAB_TRAVERSAL だがこれを外すので分かりづらい字面になる
static const long STATUS_PANEL_STYLE = 0;

// コンストラクタ
WXStatusPanel::WXStatusPanel(wxWindow *parent)
	: inherited(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
		STATUS_PANEL_STYLE)
{
	SetName("WXStatusPanel");

	timer.SetOwner(this);

	power = GetPowerDevice();
	syncer = GetSyncer();

	// インジケータ用のパラメータを用意。
	InitIndicators();

	SetAutoLayout(true);
	FontChanged();

	// キー入力イベントをメインビューに転送。
	// このパネルへのキー入力は不要なのと、メインウィンドウ内のコントロール
	// フォーカスがこのステータスパネルとメインビューのどっちにあるかは
	// 分かりづらくて、こっちがフォーカス持ってしまうと (というか起動後は
	// こっちにある) VM にキー入力が出来ずに焦ることになるので、ここへの
	// キー入力イベントは全部メインビューに飛ばす。
	auto mainframe = dynamic_cast<WXMainFrame*>(GetParent());
	auto mainview = mainframe->GetMainView();
	Bind(wxEVT_KEY_UP,   &WXMainView::OnKeyUp,   mainview);
	Bind(wxEVT_KEY_DOWN, &WXMainView::OnKeyDown, mainview);
	Bind(wxEVT_CHAR,     &WXMainView::OnChar,    mainview);

	// 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::Init()
{
	// ここで最初に一回、全メディアの状態更新を能動的に行う。
	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::OnClose(wxCloseEvent& event)
{
	// クローズする前に更新を止める。
	timer.Stop();
}

// 表示/非表示変更イベント
// new した時は (デフォルトで表示状態なのでか状態に変更がないという扱いの
// ようで) イベントは飛んでこない。同様に new 直後に Show() を明示発行しても
// 状態に変更がないためイベントは飛んでこない。
// new 直後に Hide() すればイベントは飛んでくる。という動作をするようだ。
// そのため WXMainFrame 側で生成後に Show/Hide を起こしている。
void
WXStatusPanel::OnShow(wxShowEvent& event)
{
	if (event.IsShown()) {
		// 非表示→表示

		// 表示開始前に一度情報を取得
		UpdateStat();

		timer.Start(1000 / FPS);
	} else {
		// 表示→非表示

		timer.Stop();
	}
}

// フォントサイズ変更
void
WXStatusPanel::FontChanged()
{
	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));

	Fit();
}

void
WXStatusPanel::Fit()
{
	wxSize size = LayoutIndicators();

	// バックバッファのサイズを固定。
	SetMinBitmapSize(size);

	SetMinSize(size);
	SetSize(size);
}

// タイマーイベント
void
WXStatusPanel::OnTimer(wxTimerEvent& event)
{
	if (UpdateStat()) {
		Refresh();
	}
}

// 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<RWStatus, 8> 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<uint32, 16> 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<uint32, 8 * 2> 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()
{
	Status *stat;
	Indicator *ind;

	// パフォーマンスカウンタ
	// 0123456789012345
	// >>> 99.9x 100MHz
	auto mpu = gMainApp.FindObject<MainMPUDevice>(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
	// 接続済みデバイスリストから取得。
	auto scsibus = gMainApp.FindObject<SCSIBus>(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();

			std::string label = string_format("%s%d",
				SCSI::GetDevTypeName(devtype), scsiid);
			const auto disk = dynamic_cast<SCSIDisk *>(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);
		}
	}

	// VirtIOBlock
	for (int i = 0; i < 8; i++) {
		auto dev = gMainApp.FindObject<VirtIOBlockDevice>(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);
		}
	}

	// VirtIOSCSI
	auto vscsi = gMainApp.FindObject<VirtIOSCSIDevice>(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<SCSIDisk *>(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);

			indicators.emplace_back(ind);
		}
	}

	// ネットワーク
	for (int i = 0; i < 2; i++) {
		auto hostnet = gMainApp.FindObject<HostNetDevice>(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);
		}
	}

	// FD
	for (int unit = 0; unit < FDCDevice::MAX_DRIVE; unit++) {
		auto fdd = gMainApp.FindObject<FDDDevice>(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);
		}
	}

	// NEWS の LED (2->1 の順で並べる)
	auto newsctlr = gMainApp.FindObject<NewsCtlrDevice>(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;
	int y;
	int w;
	int h;

	// 上の枠はキャンバス上端から2px
	y = 2;
	// そこから上下の外枠と枠と文字の間
	h = font_height + 2 + 2;

	// まずは前から順に列挙して必要な幅を積算していく
	x = 0;
	for (auto ind : indicators) {
		// 前の枠とは1つ空ける
		x += 1;

		// 4 は、左右の外枠(1*2) と 枠と文字の間(1*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;
	}

	// 右端も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);
	}

	// そのうちパフォーマンスカウンタだけ中央寄せまで戻す
	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) {
		if (ind->panel) {
			const auto& rect = ind->rect;
			ind->panel->SetSize(rect.x, rect.y, rect.w, rect.h);
		}
	}

	refresh_background = true;
	return true;
}

// 状態をチェック。
// 前回値からどれかでも変更があれば true を返す。
bool
WXStatusPanel::UpdateStat()
{
	bool mod = false;

	// 電源オン
	mod |= MODIFY(ispower, power->IsPower());
	mod |= MODIFY(powerled, power->GetPowerLED());

	// パフォーマンスカウンタ
	mod |= MODIFY(perf_mode, syncer->GetRunMode());
	mod |= MODIFY(perf_rate, syncer->GetPerfCounter());

	// それ以外
	for (const auto stat : stats) {
		mod |= stat->Poll();
	}

	return mod;
}

// 描画本体
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(const Indicator *ind)
{
	const Rect& rect = ind->rect;
	char text[16];
	const char *ffmark;

	// 枠
	bitmap.DrawRect(UD_GREY, rect);

	// 電源オフならここまで
	if (ispower == false) {
		bitmap.FillRect(BGPANEL,
			rect.x + 1, rect.y + 1, rect.w - 2, rect.h - 2);
		return;
	}

	// 高速モードか通常モードかでいろいろ違う。
	// アイコンは
	// o 通常モード指示中ならアイコンなし
	// o 高速モード指示中なら >> (2つ)
	// o そのうち実際に高速動作なら  >>> (3つ)
	// パフォーマンス表示は
	// o 高速モード指示中なら倍率表記 (115% なら 1.2x)
	// o 通常モード指示中ならパーセント表記
	if ((perf_mode & Syncer::SYNCMODE_SYNC) == 0) {
		// 高速モード指示
		if (perf_mode == 0) {
			// 高速走行中
			ffmark = "\x01\x01\x01";
		} else {
			ffmark = "\x01\x01 ";
		}

		// 倍率表記は1桁少ないので、表示用に最下位桁を四捨五入
		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 {
		// 通常モード指示
		ffmark = "   ";
		snprintf(text, sizeof(text), "%4u%%", perf_rate);
	}

	// クロックスピード(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(const Indicator *ind)
{
	const StatusSCSI *stat = dynamic_cast<const StatusSCSI*>(ind->stat);
	Color fg;
	Color bg;
	int scsiid;

	scsiid = ind->GetSCSIID();
	assertmsg(scsiid >= 0, "id=%d", ind->id);

	if (scsi_loaded[scsiid]) {
		fg = UD_BLACK;
	} else {
		fg = UD_GREY;
	}

	if (stat->active[scsiid] == RWStatus::Write) {
		bg = UD_RED;
	} else if (stat->active[scsiid] == RWStatus::Read) {
		bg = UD_YELLOW_GREEN;
	} else {
		bg = BGPANEL;
	}

	DrawTextLED(ind, fg, bg);
}

// VirtIOBlock デバイスのアクセスランプ。
void
WXStatusPanel::DrawVBlk(const Indicator *ind)
{
	const StatusVBlk *stat = dynamic_cast<const StatusVBlk*>(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(const Indicator *ind)
{
	const StatusNet *stat = dynamic_cast<const StatusNet*>(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 {
		fg = UD_GREY;
	}

	// 枠
	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 (stat->tx_active) {
				fg = UD_BLACK;
			} else {
				fg = UD_GREY;
			}
			break;
		 case '\x1f':	// 下向き矢印
			if (stat->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)
{
	const StatusFDD *stat = dynamic_cast<const StatusFDD*>(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<const StatusNEWS*>(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<const StatusNEWS*>(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(const Indicator *ind, Color fg, Color bg)
{
	const Rect& rect = ind->rect;

	// 枠
	bitmap.FillRect(bg, rect);
	bitmap.DrawRect(UD_GREY, rect);

	SetTextColor(fg, bg);
	DrawStringSJIS(rect.x + 2, rect.y + 2, ind->text.c_str());
	ResetTextColor();
}

// 左ダブルクリックイベント
void
WXStatusPanel::OnLeftDClick(wxMouseEvent& event)
{
	const wxPoint& pt = event.GetPosition();

	for (auto ind : indicators) {
		// dclick を処理できる枠内なら
		if (ind->rect.Contains(pt.x, pt.y) && ind->dclick != NULL) {
			(this->*(ind->dclick))(ind);
			break;
		}
	}
}

// パフォーマンスカウンタ枠へのダブルクリック
void
WXStatusPanel::DClickPerf(const Indicator *ind)
{
	// モードを切り替える
	syncer->NotifyFullSpeed(!syncer->GetFullSpeed());
}

// コンテキストメニューイベント
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 mainframe = dynamic_cast<WXMainFrame*>(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<WXMainFrame*>(GetParent());
	auto menu = mainframe->CreateFDMenu(unit);
	if (menu) {
		PopupMenu(menu);
	}
}

// SCSI メディア変更イベント (UIMessage イベント)
void
WXStatusPanel::OnSCSIMediaChanged(wxCommandEvent& event)
{
	Indicator *ind = NULL;
	bool 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<const SCSIDisk *>(ind->dev);

	// メディアの状態を一旦クリア
	new_loaded = false;

	// ツールチップを設定
	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 = true;
	}
	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);

	// メディアのロード状態が変われば (再配置して) 再描画。
	// 通常周期のタイマーによる更新チェックではメディア状態はチェックして
	// いないので、ここで自発的に行う。
	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<const FDDDevice *>(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);

	// メディアのロード状態が変われば (再配置して) 再描画。
	// 通常周期のタイマーによる更新チェックではメディア状態はチェックして
	// いないので、ここで自発的に行う。
	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();
	}
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.