File:  [Isaki's NoNo m68k/m88k emulator] / nono / wx / wxsoftkey.cpp
Revision 1.1.1.17 (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) 2020 nono project
// Licensed under nono-license.txt
//

//
// ソフトウェアキーボード
//

#include "wxsoftkey.h"
#include "fontmanager.h"
#include "wxmainframe.h"
#include "wxmainview.h"
#include "wxuimessage.h"
#include "sram.h"

//
// ソフトウェアキーボード (パネル) 共通部分
//

// エンター記号。
// 描画の際にキー刻印文字列の半角文字数から描画幅を決定しており、
// エンター記号は半角2文字幅で描いているので実際の文字列も2文字にしておく。
// そうすると "\\n" が意味的にもちょうどいい。
// これは幅を算出するのに使うだけで表示はされない。
#define EnterMark	"\\n"

// イベントテーブル
wxBEGIN_EVENT_TABLE(WXSoftKeyPanel, inherited)
	EVT_LEFT_DOWN(WXSoftKeyPanel::OnLeftDown)
	EVT_LEFT_UP(WXSoftKeyPanel::OnLeftUp)
	EVT_LEFT_DCLICK(WXSoftKeyPanel::OnLeftDClick)
	EVT_RIGHT_DOWN(WXSoftKeyPanel::OnRightDown)
	EVT_TIMER(wxID_ANY, WXSoftKeyPanel::OnTimer)
wxEND_EVENT_TABLE()

// コンストラクタ
WXSoftKeyPanel::WXSoftKeyPanel(wxWindow *parent,
	const std::vector<keydata_t>& keydata_, int u_width_, int u_height_)
	: inherited(parent)
	, keydata(keydata_)
{
	// デバイスを取得
	keyboard = GetKeyboard();

	timer.SetOwner(this);

	u_width  = u_width_;
	u_height = u_height_;

	FontChanged();

	// トグル動作にするキー
	toggle.emplace(KC_SHIFT, 0);
	toggle.emplace(KC_SHIFT_L, 0);
	toggle.emplace(KC_SHIFT_R, 0);
	toggle.emplace(KC_CTRL, 0);

	// 残光の初期化。
	// persistence は keydata 分の配列ではなく共通キーコード分の配列。
	persistence.resize(KC_max);
	std::fill(persistence.begin(), persistence.end(), 0);

	// このパネルへのキー入力はメインビューへのキー入力と同じ。
	auto mainframe = dynamic_cast<WXMainFrame*>(GetParent()->GetParent());
	auto mainview = mainframe->GetMainView();
	Bind(wxEVT_CHAR,     &WXMainView::OnChar,    mainview);
	Bind(wxEVT_KEY_UP,   &WXMainView::OnKeyUp,   mainview);
	Bind(wxEVT_KEY_DOWN, &WXMainView::OnKeyDown, mainview);

	// VM からの通知を受け取る
	WXUIMessage::Connect(UIMessage::KEYBOARD, this,
		wxCommandEventHandler(WXSoftKeyPanel::OnKeyChanged));

	// 初期タイトルを保存しといて..
	title = dynamic_cast<wxFrame*>(parent)->GetTitle();
	// キーボードの接続状況でタイトルを更新する
	UpdateTitle();
}

// デストラクタ
WXSoftKeyPanel::~WXSoftKeyPanel()
{
	// ウィンドウクローズでトグルキーのワンショットは開放する
	// (長押しは開放しない)
	for (const auto& k : toggle) {
		auto code = k.second;
		if (code) {
			KeyUp(code);
		}
	}

	timer.Stop();

	WXUIMessage::Disconnect(UIMessage::KEYBOARD, this,
		wxCommandEventHandler(WXSoftKeyPanel::OnKeyChanged));
}

// キーボードの接続状況によって親フレームのタイトルを更新する。
// ついでにメンバ変数を更新する。
void
WXSoftKeyPanel::UpdateTitle()
{
	auto parent = dynamic_cast<WXSoftKeyWindow *>(GetParent());
	assert(parent);

	// 次回比較用にメンバ変数に覚えておく
	connected = keyboard->IsConnected();

	if (connected) {
		parent->SetTitle(title);
	} else {
		parent->SetTitle(title + _(" (disconnected)"));
	}
}

// color.Keytop の初期化(補間)。
// num はキートップの種類の数。1 か 2
void
WXSoftKeyPanel::InitColorKeytop(int num)
{
	Color pressed = UD_BLUE;

	auto f = [](int a, int b, int x) {
		return a * x / 100 + b * (100 - x) / 100;
	};

	// Keytop[i][9] は押されたときの色
	// Keytop[i][0] は押されていないときの色
	for (int i = 0; i < num; i++) {
		for (int j = 1; j < 10; j++) {
			int n = j * 100 / 9;
			color.Keytop[i][j] = Color(
				f(pressed.r, color.Keytop[i][0].r, n),
				f(pressed.g, color.Keytop[i][0].g, n),
				f(pressed.b, color.Keytop[i][0].b, n));
		}
	}
}

// フォントサイズ変更
void
WXSoftKeyPanel::FontChanged()
{
	inherited::FontChanged();

	// このパネル内の描画の基本単位
	unit = font_height / 4;

	// エンターキーだけ矩形でないのでここでリージョンを計算。
	// unit が確定したところで一回行う。
	// ただし SHIFT_R のキーコードはちょっと使いづらいので、ここでは SHIFT_R
	// を使わず [_] キーで代用する。
	//
	//   |   ^   |   \   |   BS  |
	//   |       |       |       |
	// --+---+---+--(A)--+------(B)
	//       |       |           |
	//       |   [   |   Enter   |
	//       |       |           |
	// --+---+---+--(F)-(E) - - (G)
	//   |       |       |       |
	//   |   :   |   ]   |       |
	//   |       |       |       |
	// --+---+---+------(D)-----(C)
	//       |                   |
	//   _   |      SHIFT        |
	//
	// Enter キーの上半分 A-B-G-F を enter_top。F-G の線分を含む。
	// Enter キーの下半分 E-G-C-D を enter_btm。E-G の線分を含む。
	const keydata_t& keybs = FindKeydata(KC_BS);
	const keydata_t& keyleft = FindKeydata(KC_bracketleft);
	const keydata_t& keyright = FindKeydata(KC_bracketright);
	const keydata_t& keyscore = FindKeydata(KC_underscore);
	enter_top.SetLTRB(
		keyleft.right() * unit, keyleft.y * unit,
		keybs.right() * unit, keyright.y * unit);
	enter_btm.SetLTRB(
		keyright.right() * unit, keyright.y * unit,
		keybs.right() * unit, keyscore.y * unit);

	// アイコンを変更。参照順序の関係で下のほうにおいてある…。
	InitIcon();

	// バックバッファの下限を固定する
	wxSize size(u_width * unit, u_height * unit);
	SetMinBitmapSize(size);

	// コントロールの大きさを変更
	SetSize(size);
	SetMinSize(size);
}

void
WXSoftKeyPanel::OnTimer(wxTimerEvent& event)
{
	Refresh();
}

// キー状態変更イベント (UIMessage)
//
// キー状態変更イベントは、キーの状態が変わるたびに都度通知される。
// ソフトウェアキーボード入力とパススルーモードの入力では、どのキーに変化
// (押下 or 開放)があったかの情報は付属しない。各キーの押下状態は全キーを
// 逐一調査することで判定する。人間時間で起きる事象なのでこの計算量は許容。
// Char 入力モードの場合は、イベントのパラメータ引数 (GetInt() で取得可) に
// 入力のあったキーの共通キーコードが入っている (複数のキーが同時に押される
// 場合はキーの回数分のイベントが飛んでくる)。Char 入力モードにはキー開放
// という概念はない。
void
WXSoftKeyPanel::OnKeyChanged(wxCommandEvent& event)
{
	// Char 入力モードでキーが押された場合は、共通キーコードが送られてくるので、
	// persistence[] をここでセットする。
	// Char 入力モードでは、キーの Make/Break が一瞬で終わるため、このイベント
	// を受け取って Refresh() を発行 -> OnPaint() -> Draw() 内で IsPressed()
	// を調べた頃にはタイミング問題でもうキーが離されている可能性があるので、
	// 描画時に全キーを調べる方法は使えない。
	uint keycode = event.GetInt();
	if (keycode != 0) {
		assert(keycode < persistence.size());
		// 9 が減光時の初期値 (10 は減光しない点灯)
		persistence[keycode] = 9;
	}

	// トグルキーが開放されていたらトグル情報をクリアする。
	// ハードウェアキーボードを含む VM 側で開放された場合と、
	// ソフトウェアキーボードの KeyUp() で開放された場合のどちらも
	// ここでクリア。
	for (auto& k : toggle) {
		if (keyboard->IsPressed(k.first) == false) {
			k.second = 0;
		}
	}

	// 接続状況が変わっていたらタイトルを更新。
	if (keyboard->IsConnected() != connected) {
		UpdateTitle();
	}

	Refresh();
}

// 描画本体
void
WXSoftKeyPanel::Draw()
{
	bool timer_start = false;

	// キー刻印の表示更新用に修飾キーの状態を更新。
	// 本来は IsShift() だけで判定すべきだが、Char 入力モードでいずれかの
	// SHIFT キーが減光中は SHIFT 刻印を継続してみるのはどうか。
	// 0 まで待つとうちの環境だと消灯した少し後に刻印が戻るように見えるため、
	// ある程度暗くなったところでもう切り替えてみる。閾値は適当。
	if ((2 < persistence[KC_SHIFT_L] && persistence[KC_SHIFT_L] < 10) ||
	    (2 < persistence[KC_SHIFT_R] && persistence[KC_SHIFT_R] < 10) ||
		(2 < persistence[KC_SHIFT]   && persistence[KC_SHIFT]   < 10) ||
		keyboard->IsShift())
	{
		modifier = ModShift;
	} else {
		modifier = ModNormal;
	}

	auto text_color = color.Text;

	// キーボードが接続されていないときは刻印をグレーにしておく
	if (keyboard->IsConnected() == false) {
		text_color = UD_GREY;
	}
	SetTextForeColor(text_color);

	for (const auto& key : keydata) {
		int c = persistence[key.keycode];
		if (0 < c && c < 10) {
			// Char 入力モードで減光中
			c--;
			timer_start = true;
		} else {
			// そうでない場合に限り、現在の状態を問い合わせる
			bool is_pressed = keyboard->IsPressed(key.keycode);
			if (is_pressed) {
				// 押されている
				c = 10;
			} else {
				// 押されていない
				c = 0;
			}
		}
		// persistence[] は 10 が(減光しない)点灯、9..1 が減光中を示す。
		persistence[key.keycode] = c;
		// ただし色配列は 0..9 で 9 が最大点灯色
		if (c > 9) {
			c = 9;
		}

		SetTextBackColor(color.Keytop[key.coltype][c]);

		// エンターキーとブザー表示は描き方が違うので別処理
		if (__predict_false(key.keycode == KC_enter)) {
			DrawEnterKey();
		} else if (__predict_false(key.keycode == KC_buzzer)) {
			DrawBuzzer(key);
		} else {
			DrawKey(key);
		}
	}

	if (timer_start) {
		timer.Start(25);
	} else {
		timer.Stop();
	}
}

// 指定のキーを一つ (枠から刻印まで) 描画する。
// palette[FG] はテキスト色 (キーボード非接続ならグレー)。
// palette[BG] はこのキーの背景色 (押されているか、減光中か、
// 押されていない時の二色あるかも知れないキートップ色)。
void
WXSoftKeyPanel::DrawKey(const keydata_t& key)
{
	uint attr = TA::Normal;

	// 枠
	DrawKeyBox(key, palette[0], color.Text);

	// 刻印を選択
	const char *disp = key.disp[modifier];
	if (disp == NULL) {
		disp = key.disp[0];
	}

	// トグルキーのワンショット押しは太字で表示を区別したい
	const auto it = toggle.find(key.keycode);
	if (it != toggle.end()) {
		if (it->second) {
			attr = TA::Em;
		}
	}

	// Shift_JIS に変換
	wxString utfstr(disp, wxConvUTF8);
	// mb_str() をキャストせずに一度保持しておく必要がある (scan-build 対策)
	auto mbstr = utfstr.mb_str(conv);
	const char *sjis = (const char *)mbstr;
	// 改行があれば2行に分割
	char sjis1[8] {};
	char sjis2[8] {};
	int sjislen;
	int row;
	const char *lf = strchr(sjis, '\n');
	if (lf == NULL) {
		// 1行なら、Shift_JIS での文字数は半角換算の文字数と一致する。
		sjislen = strlen(sjis);
		row = 1;
	} else {
		// 2行に分割。センタリングは1行目の長さだけで調べる。
		strncpy(sjis1, sjis, lf - sjis);
		strlcpy(sjis2, lf + 1, sizeof(sjis2));
		sjislen = strlen(sjis1);
		row = 2;
	}

	// X 方向のセンタリング。
	// unit 単位の計算では精度が足りないのでドット単位で計算する。
	int x = key.x * unit + (key.w - sjislen * 2) * unit / 2 + 1;

	// Y 方向のセンタリングは、端数が出ないのと LED 用オフセットが
	// unit 単位なので、unit 単位のまま計算して、後でドット単位にする。
	int y = key.y + (key.h - row * 4) / 2;
	// LED を描画 (機種によって色々違うので個別クラス側で処理)
	if (key.led >= 0) {
		y += DrawLED(key);
	}
	y = y * unit + 1;

	// 文字を描画
	if (strcmp(disp, EnterMark) == 0) {
		bitmap.DrawBitmapI1(x, y, *enter_icon, palette);
	} else {
		if (lf == NULL) {
			// 1行の場合
			DrawStringSJIS(x, y, sjis, attr);
		} else {
			// 2行の場合
			DrawStringSJIS(x, y, sjis1);
			y += 4 * unit;
			DrawStringSJIS(x, y, sjis2);
		}
	}
}

// キーの枠と背景色を描画する。
void
WXSoftKeyPanel::DrawKeyBox(const keydata_t& key, Color bg, Color bd)
{
	Rect rect(
		key.x * unit,     key.y * unit,
		key.w * unit + 1, key.h * unit + 1);
	bitmap.FillRect(bg, rect);
	bitmap.DrawRect(bd, rect);
}

// エンターキー
void
WXSoftKeyPanel::DrawEnterKey()
{
	// 矩形の内側を背景色で塗りつぶす。
	bitmap.FillRect(palette[0], enter_top);
	bitmap.FillRect(palette[0], enter_btm);

	// 周囲の線分
	// (A)-(B)
	bitmap.DrawLineH(color.Text,
		enter_top.x, enter_top.y, enter_top.GetRight());
	// (B)-(C)
	bitmap.DrawLineV(color.Text,
		enter_top.GetRight(), enter_top.y, enter_btm.GetBottom());
	// (C)-(D)
	bitmap.DrawLineH(color.Text,
		enter_btm.GetRight(), enter_btm.GetBottom(), enter_btm.x);
	// (D)-(E)
	bitmap.DrawLineV(color.Text,
		enter_btm.x, enter_btm.GetBottom(), enter_btm.y);
	// (E)-(F)
	bitmap.DrawLineH(color.Text,
		enter_btm.x, enter_top.GetBottom(), enter_top.x);
	// (F)-(A)
	bitmap.DrawLineV(color.Text,
		enter_top.x, enter_top.GetBottom(), enter_top.y);

	// エンター記号を描画
	bitmap.DrawBitmapI1(
		(enter_top.x + enter_top.GetRight())  / 2 - (4 / 2),
		(enter_top.y + enter_btm.GetBottom()) / 2 - (4 / 2),
		*enter_icon, palette);
}

// ブザー表示(キーに相当)を描画
void
WXSoftKeyPanel::DrawBuzzer(const keydata_t& key)
{
	Color fg;

	// 枠。キーではないので薄くしておく。
	DrawKeyBox(key, palette[0], UD_GREY);

	// テキスト色は本来呼び出し元の DrawKey() で設定してあるが、
	// ここはキーではないので非発声時の文字色だけ薄くしたい。
	// 発声時はキー押下と同じにする。
	fg = GetTextForeColor();
	if (persistence[key.keycode] == 0) {
		SetTextForeColor(UD_GREY);
	}

	// 刻印 (モディファイヤの影響を受けないと分かっているので常に [0])
	const char *disp = key.disp[0];
	assert(disp);

	// センタリング
	int x = key.x + (key.w - strlen(disp) * 2) / 2;
	int y = key.y + (key.h - 4) / 2;

	// 文字を描画
	x = x * unit + 1;
	y = y * unit + 1;
	DrawStringSJIS(x, y, disp, TA::Normal);

	// 前景色を元に戻す
	SetTextForeColor(fg);
}

#define B(x)	(0b##x)
#define B2(x)	(B(x) >> 8), (B(x) & 0xff)
#define B3(x)	(B(x) >> 16), ((B(x) >> 8) & 0xff), (B(x) & 0xff)

static const uint8 EnterIcon12[] = {	// 12x12
	B2(000000000000'0000),	// 0
	B2(000000001110'0000),	// 1
	B2(000000001010'0000),	// 2
	B2(000000001010'0000),	// 3
	B2(000000001010'0000),	// 4
	B2(001100001010'0000),	// 5
	B2(010111110010'0000),	// 6
	B2(100000000010'0000),	// 7
	B2(010111111100'0000),	// 8
	B2(001100000000'0000),	// 9
	B2(000000000000'0000),	// a
	B2(000000000000'0000),	// b
};

static const uint8 EnterIcon16[] = {	// 16x16
	B2(0000000000111110),	// 0
	B2(0000000000100010),	// 1
	B2(0000000000100010),	// 2
	B2(0000000000100010),	// 3
	B2(0000000000100010),	// 4
	B2(0000000000100010),	// 5
	B2(0000000000100010),	// 6
	B2(0000110000100010),	// 7
	B2(0001010000100010),	// 8
	B2(0010011111000010),	// 9
	B2(0100000000000010),	// a
	B2(1000000000000010),	// b
	B2(0100000000000010),	// c
	B2(0010011111111100),	// d
	B2(0001010000000000),	// e
	B2(0000110000000000),	// f
};

static const uint8 EnterIcon24[] = {	// 24x24
	B3(00000000'00000001'11111100),	// 0
	B3(00000000'00000001'11111100),	// 1
	B3(00000000'00000001'10001100),	// 2
	B3(00000000'00000001'10001100),	// 3
	B3(00000000'00000001'10001100),	// 4
	B3(00000000'00000001'10001100),	// 5
	B3(00000000'00000001'10001100),	// 6
	B3(00000000'00000001'10001100),	// 7
	B3(00000000'00000001'10001100),	// 8
	B3(00000000'11000001'10001100),	// 9
	B3(00000001'11000001'10001100),	// 10
	B3(00000011'11000001'10001100),	// 11
	B3(00000110'11000001'10001100),	// 12
	B3(00001100'11111111'10001100),	// 13
	B3(00011000'11111111'10001100),	// 14
	B3(00110000'00000000'00001100),	// 15
	B3(01100000'00000000'00001100),	// 16
	B3(00110000'00000000'00001100),	// 17
	B3(00011000'11111111'11111100),	// 18
	B3(00001100'11111111'11111000),	// 19
	B3(00000110'11000000'00000000),	// 20
	B3(00000011'11000000'00000000),	// 21
	B3(00000001'11000000'00000000),	// 22
	B3(00000000'11000000'00000000),	// 23
};

// アイコンを初期化する。FontChanged() から呼ばれる。
// ソース先頭のほうにデータの羅列を置きたくないが、
// 容量不詳の配列の解決が面倒なのでこれだけ下のほうのここに置いてある。
void
WXSoftKeyPanel::InitIcon()
{
	const int width  = font_width;
	const int height = font_height;
	const uint8 *icon;

	switch (height) {
	 case 12:
		icon = EnterIcon12;
		break;
	 case 16:
		icon = EnterIcon16;
		break;
	 case 24:
		icon = EnterIcon24;
		break;
	 default:
		PANIC("Unexpected font_height=%d", height);
	}

	enter_icon.reset(new BitmapI1(icon, width * 2, height));
}

// keycode に対応する keydata を返す
const keydata_t&
WXSoftKeyPanel::FindKeydata(uint keycode) const
{
	for (const auto& key : keydata) {
		if (key.keycode == keycode) {
			return key;
		}
	}
	// 見付からないはずはない
	PANIC("keycode %d not found", keycode);
}

// マウス左クリック (Down) イベント
void
WXSoftKeyPanel::OnLeftDown(wxMouseEvent& event)
{
	const wxPoint& pt = event.GetPosition();
	const keydata_t *key = GetKeydataFromPoint(pt);

	if (key != NULL) {
		DoLeftDown(key->keycode);
	}
}

// マウス左クリック (Down) 処理
void
WXSoftKeyPanel::DoLeftDown(uint code)
{
	auto it = toggle.find(code);
	if (it != toggle.end()) {
		// トグルキーはここで完結。LeftUp では何もしない。
		// second のクリアは KeyUp() からコールバックされる OnKeyChanged()
		// で行っている。
		if (keyboard->IsPressed(code)) {
			KeyUp(code);
		} else {
			KeyDown(code);
			it->second = code;
		}
		leftdown_key = KC_none;
	} else {
		// トグルでない通常キーはここで押下して LeftUp のために覚えておく
		KeyDown(code);
		leftdown_key = code;
	}
}

// マウス左クリック (Up) イベント
void
WXSoftKeyPanel::OnLeftUp(wxMouseEvent& event)
{
	// Up 時は、これが起きた地点のキーコードではなく
	// 直近に LeftDown したキーコードをターゲットにする。
	uint code = leftdown_key;

	if (code != KC_none) {
		KeyUp(code);

		// 通常キーが離されたら、トグルキーはすべて開放
		for (const auto& k : toggle) {
			auto tcode = k.second;
			if (tcode) {
				KeyUp(tcode);
			}
		}
	}

	leftdown_key = KC_none;
}

// マウス左ダブルクリックイベント
// ダブルクリックという操作に特別何かを割り当ててはいないが、
// キーを連打するとその間隔によってはダブルクリックと扱われるため、
// 受け取ってここで処理する。
//
// DClick イベントは LeftDown、LeftUp、LeftDClick、LeftUp の順で
// 飛んでくるので、ここでは2回目の押下だけ処理すればよい。というか
// 処理自体は LeftDown とまったく同じになるので、イベントテーブルで
// 直接 EVT_LEFT_DCLICK => OnLeftDown に割り付けることも出来そうだが
// デバッグが紛らわしくなりそうなので独立したイベントとしておく。
void
WXSoftKeyPanel::OnLeftDClick(wxMouseEvent& event)
{
	const wxPoint& pt = event.GetPosition();
	const keydata_t *key = GetKeydataFromPoint(pt);

	if (key != NULL) {
		DoLeftDown(key->keycode);
	}
}

// マウス右クリック (Down) イベント
// 長押し開始/解除。
// 右クリックのほうは Down だけで Up は使わない。
void
WXSoftKeyPanel::OnRightDown(wxMouseEvent& event)
{
	const wxPoint& pt = event.GetPosition();
	const keydata_t *key = GetKeydataFromPoint(pt);

	if (key != NULL) {
		uint code = key->keycode;
		if (keyboard->IsPressed(code)) {
			// 押されていたら開放
			KeyUp(code);
		} else {
			// 押されてなければ長押し開始
			KeyDown(code);
		}
	}
}

// 座標からキーデータを返す。
// キーに当たっていなければ NULL を返す。
const keydata_t *
WXSoftKeyPanel::GetKeydataFromPoint(const wxPoint& pt) const
{
	// エンターキーだけ先に別判定
	if (enter_top.Contains(pt.x, pt.y) || enter_btm.Contains(pt.x, pt.y)) {
		return &FindKeydata(KC_enter);
	}

	for (const auto& key : keydata) {
		if (key.x < 0) {
			continue;
		}
		// 始点と幅の +1/-1 は罫線の分
		wxRect rect(
			(key.x * unit) + 1,
			(key.y * unit) + 1,
			(key.w * unit) - 1,
			(key.h * unit) - 1);
		if (rect.Contains(pt)) {
			return &key;
		}
	}
	return NULL;
}

// このキーが押された処理
void
WXSoftKeyPanel::KeyDown(uint code)
{
	// VM に通知 (Keyboard がホストに通知する)
	keyboard->MakeKey(code);
}

// このキーが放された処理
void
WXSoftKeyPanel::KeyUp(uint code)
{
	// VM に通知 (Keyboard がホストに通知する)
	keyboard->BreakKey(code);
}

// LUNA-I、LUNA-88K のコンストラクタ共通部分。
// クラス階層を増やすのも手間なので。
void
WXSoftKeyPanel::InitLunaCommon()
{
	// LUNA では
	// BS キーの左隣 (0x5c) は円記号ではなくバックスラッシュ。
	// それを SHIFT したやつ(0x7c) は破線でないパイプ。
	// そのもう一つ左隣 (0x7e) はオーバーラインではなくチルダ。
	gFontManager->SetGlyph5C(true);
	gFontManager->SetGlyph7E(true);

	// 白黒モデル共通
	color.Border = UD_BLACK;
	color.LED[0] = UD_RED;

	// キートップの濃淡はユニバーサルデザインの推奨色ではないけど
	// ここは見分けを付けるためではなくただのデザインなので。
	if (1) {
		// 白モデル
		color.Background	= UD_LIGHT_GREY;	// 現物新品は何色だったんだろう
		color.Text			= UD_BLACK;
		color.Keytop[0][0]	= UD_WHITE;
		color.Keytop[1][0]	= Color(200, 200, 203);
	} else {
		// 黒モデル
		color.Background	= UD_BLACK;
		color.Text			= UD_WHITE;
		color.Keytop[0][0]	= Color(50, 50, 53);
		color.Keytop[1][0]	= UD_BLACK;
	}
	InitColorKeytop(2);

	SetBitmapBGColor(color.Background);
	Fill();
}


//
// LUNA-I ソフトウェアキーボード (パネル)
//

// コンストラクタ
WXLuna1SoftKeyPanel::WXLuna1SoftKeyPanel(wxWindow *parent)
	: inherited(parent, keydata, 226, 54)
{
	InitLunaCommon();
}

// デストラクタ
WXLuna1SoftKeyPanel::~WXLuna1SoftKeyPanel()
{
}

// LED を描画して刻印描画のための y offset を返す。
int
WXLuna1SoftKeyPanel::DrawLED(const keydata_t& key)
{
	const std::vector<bool>& led = keyboard->GetLED();
	Color fg;
	Color bg;

	if (led[key.led]) {
		// 点灯時は赤、枠は黒
		fg = color.Border;
		bg = color.LED[0];
	} else {
		// 消灯時は元のキートップの色で、枠は薄めにしたい
		// (枠の色は今の所全モデル共通なので ColorPalette に含めていない)
		fg = UD_GREY;
		bg = color.Keytop[key.coltype][0];
	}

	// LED を左肩に描く
	Rect rect(key.x * unit + 2, key.y * unit + 2, 4 * unit - 2, 3 * unit - 2);
	bitmap.FillRect(bg, rect);
	bitmap.DrawRect(fg, rect);

	// そのため文字は少し下げる
	return +1;
}

// LUNA-I のファンクションキーは ESC 行との間が少し空いている。
// LUNA-88K のファンクションキー行は ESC 行とぴったりついていて、キーの
// 縦の長さも他のキーと同じ感じ。
// X68030 のファンクションキーは ESC 行との間が空いていて、キーの長さは
// 他より短い。
//
//  2 - +--------+
//  4 - | LUNA-I | +-------+ +--------+
//  6 - |        | | 88K   | | X68030 |
//  8 - |        | |       | |        |
// 10 - +--------+ |       | +--------+
// 12 - +--------+ +-------+ +--------+  <- ESC 行の上辺
// 14 - |  ESC   | |   1   | |   2    |
//
#define H	 (8)	// キーの高さ
#define LH	 (2)	// ファンクションキーの行 (ESC 行との間に空き)
#define L0	 (4)	// ファンクションキーは少し低い(横長い)
#define L1	(12)	// ESC の行
#define L2	(20)	// TAB の行
#define L3	(28)	// CTRL の行
#define L4	(36)	// SHIFT の行
#define L5	(44)	// space の行

// キーデータ
/*static*/ std::vector<keydata_t>
WXLuna1SoftKeyPanel::keydata {
	//                +---- キートップの色(1:濃)
	//                |  +- 0 以上なら LED 番号
	//  X   Y   W  H  C  L	KeyCode				通常	SHIFT
	{   2, LH, 15, H, 1,-1,	KC_F1,				{ "PF1" } },
	{  17, LH, 15, H, 1,-1,	KC_F2,				{ "PF2" } },
	{  32, LH, 15, H, 1,-1,	KC_F3,				{ "PF3" } },
	{  47, LH, 15, H, 1,-1,	KC_F4,				{ "PF4" } },
	{  62, LH, 15, H, 1,-1,	KC_F5,				{ "PF5" } },
	{  81, LH, 15, H, 1,-1,	KC_F6,				{ "PF6" } },
	{  96, LH, 15, H, 1,-1,	KC_F7,				{ "PF7" } },
	{ 111, LH, 15, H, 1,-1,	KC_F8,				{ "PF8" } },
	{ 126, LH, 15, H, 1,-1,	KC_F9,				{ "PF9" } },
	{ 141, LH, 15, H, 1,-1,	KC_F10,				{ "PF10" } },
	{ 210, LH+1, 14, H-2, 1,-1, KC_buzzer,		{ "Buzzer" } },

	{   2, L1, 12, H, 1,-1,	KC_ESC,				{ "ESC" } },
	{  14, L1, 10, H, 0,-1,	KC_1,				{ "1",	"!" } },
	{  24, L1, 10, H, 0,-1,	KC_2,				{ "2",	"\"" } },
	{  34, L1, 10, H, 0,-1,	KC_3,				{ "3",	"#" } },
	{  44, L1, 10, H, 0,-1,	KC_4,				{ "4",	"$" } },
	{  54, L1, 10, H, 0,-1,	KC_5,				{ "5",	"%" } },
	{  64, L1, 10, H, 0,-1,	KC_6,				{ "6",	"&" } },
	{  74, L1, 10, H, 0,-1,	KC_7,				{ "7",	"'" } },
	{  84, L1, 10, H, 0,-1,	KC_8,				{ "8",	"(" } },
	{  94, L1, 10, H, 0,-1,	KC_9,				{ "9",	")" } },
	{ 104, L1, 10, H, 0,-1,	KC_0,				{ "0",	" " } },
	{ 114, L1, 10, H, 0,-1,	KC_minus,			{ "-",	"=" } },
	{ 124, L1, 10, H, 0,-1,	KC_circum,			{ "^",	"~" } },
	{ 134, L1, 10, H, 0,-1,	KC_backslash,		{ "\\",	"|" } },
	{ 144, L1, 12, H, 1,-1,	KC_BS,				{ "BS" } },

	{   2, L2, 17, H, 1,-1,	KC_TAB,				{ "TAB" } },
	{  19, L2, 10, H, 0,-1,	KC_Q,				{ "q",	"Q" } },
	{  29, L2, 10, H, 0,-1,	KC_W,				{ "w",	"W" } },
	{  39, L2, 10, H, 0,-1,	KC_E,				{ "e",	"E" } },
	{  49, L2, 10, H, 0,-1,	KC_R,				{ "r",	"R" } },
	{  59, L2, 10, H, 0,-1,	KC_T,				{ "t",	"T" } },
	{  69, L2, 10, H, 0,-1,	KC_Y,				{ "y",	"Y" } },
	{  79, L2, 10, H, 0,-1,	KC_U,				{ "u",	"U" } },
	{  89, L2, 10, H, 0,-1,	KC_I,				{ "i",	"I" } },
	{  99, L2, 10, H, 0,-1,	KC_O,				{ "o",	"O" } },
	{ 109, L2, 10, H, 0,-1,	KC_P,				{ "p",	"P" } },
	{ 119, L2, 10, H, 0,-1,	KC_at,				{ "@",	"`" } },
	{ 129, L2, 10, H, 0,-1,	KC_bracketleft,		{ "[",	"{" } },

	{   2, L3, 20, H, 1,-1,	KC_CTRL,			{ "CTRL" } },
	{  22, L3, 10, H, 0,-1,	KC_A,				{ "a",	"A" } },
	{  32, L3, 10, H, 0,-1,	KC_S,				{ "s",	"S" } },
	{  42, L3, 10, H, 0,-1,	KC_D,				{ "d",	"D" } },
	{  52, L3, 10, H, 0,-1,	KC_F,				{ "f",	"F" } },
	{  62, L3, 10, H, 0,-1,	KC_G,				{ "g",	"G" } },
	{  72, L3, 10, H, 0,-1,	KC_H,				{ "h",	"H" } },
	{  82, L3, 10, H, 0,-1,	KC_J,				{ "j",	"J" } },
	{  92, L3, 10, H, 0,-1,	KC_K,				{ "k",	"K" } },
	{ 102, L3, 10, H, 0,-1,	KC_L,				{ "l",	"L" } },
	{ 112, L3, 10, H, 0,-1,	KC_semicolon,		{ ";",	"+" } },
	{ 122, L3, 10, H, 0,-1,	KC_colon,			{ ":",	"*" } },
	{ 132, L3, 10, H, 0,-1,	KC_bracketright,	{ "]",	"}" } },

	{   2, L4, 25, H, 1,-1,	KC_SHIFT_L,			{ "SHIFT" } },
	{  27, L4, 10, H, 0,-1,	KC_Z,				{ "z",	"Z" } },
	{  37, L4, 10, H, 0,-1,	KC_X,				{ "x",	"X" } },
	{  47, L4, 10, H, 0,-1,	KC_C,				{ "c",	"C" } },
	{  57, L4, 10, H, 0,-1,	KC_V,				{ "v",	"V" } },
	{  67, L4, 10, H, 0,-1,	KC_B,				{ "b",	"B" } },
	{  77, L4, 10, H, 0,-1,	KC_N,				{ "n",	"N" } },
	{  87, L4, 10, H, 0,-1,	KC_M,				{ "m",	"M" } },
	{  97, L4, 10, H, 0,-1,	KC_comma,			{ ",",	"<" } },
	{ 107, L4, 10, H, 0,-1,	KC_period,			{ ".",	">" } },
	{ 117, L4, 10, H, 0,-1,	KC_slash,			{ "/",	"?" } },
	{ 127, L4, 10, H, 0,-1,	KC_underscore,		{ " ",	"_" } },
	{ 137, L4, 19, H, 1,-1,	KC_SHIFT_R,			{ "SHIFT" } },

	{   2, L5, 20, H, 1,-1,	KC_VALID,			{ "確定" } },
	{  22, L5, 10, H, 1,-1,	KC_SF,				{ "前面" } },
	{  32, L5, 10, H, 1, 1,	KC_CAPS,			{ "CAP" } },
	{  42, L5, 80, H, 0,-1,	KC_space,			{ " " } },
	{ 122, L5, 10, H, 1, 0,	KC_kana,			{ "かな" } },
	{ 132, L5, 24, H, 1,-1,	KC_XFER,			{ "変換" } },

	// カーソルキー島
	{ 160, L1, 10, H, 1,-1,	KC_PF11,			{ "消去" } },
	{ 170, L1, 10, H, 1,-1,	KC_PF12,			{ "呼出" } },
	{ 160, L2, 10, H, 1,-1,	KC_PF13,			{ "文節\n←" } },
	{ 170, L2, 10, H, 1,-1,	KC_PF14,			{ "文節\n→" } },
	{ 160, L3, 20, H, 1,-1,	KC_up,				{ "↑" } },
	{ 160, L4, 10, H, 1,-1,	KC_left,			{ "←" } },
	{ 170, L4, 10, H, 1,-1,	KC_right,			{ "→" } },
	{ 160, L5, 20, H, 1,-1,	KC_down,			{ "↓" } },

	// テンキー島
	{ 184, L1, 10, H, 1,-1,	KC_DEL,				{ "DEL" } },
	{ 194, L1, 10, H, 1,-1,	KC_PAD_plus,		{ "+" } },
	{ 204, L1, 10, H, 1,-1,	KC_PAD_minus,		{ "-" } },
	{ 214, L1, 10, H, 1,-1,	KC_PAD_multiply,	{ "*" } },

	{ 184, L2, 10, H, 0,-1,	KC_PAD_7,			{ "7" } },
	{ 194, L2, 10, H, 0,-1,	KC_PAD_8,			{ "8" } },
	{ 204, L2, 10, H, 0,-1,	KC_PAD_9,			{ "9" } },
	{ 214, L2, 10, H, 1,-1,	KC_PAD_divide,		{ "/" } },

	{ 184, L3, 10, H, 0,-1,	KC_PAD_4,			{ "4" } },
	{ 194, L3, 10, H, 0,-1,	KC_PAD_5,			{ "5" } },
	{ 204, L3, 10, H, 0,-1,	KC_PAD_6,			{ "6" } },
	{ 214, L3, 10, H, 1,-1,	KC_PAD_equal,		{ "=" } },

	{ 184, L4, 10, H, 0,-1,	KC_PAD_1,			{ "1" } },
	{ 194, L4, 10, H, 0,-1,	KC_PAD_2,			{ "2" } },
	{ 204, L4, 10, H, 0,-1,	KC_PAD_3,			{ "3" } },
	{ 214, L4, 10, H, 1,-1,	KC_PAD_comma,		{ "," } },

	{ 184, L5, 10, H, 0,-1,	KC_PAD_0,			{ "0" } },
	{ 194, L5, 10, H, 0,-1,	KC_PAD_decimal,		{ "." } },
	{ 204, L5, 20, H, 1,-1,	KC_PAD_enter,		{ EnterMark } },

	// Enter だけ矩形でないので別処理するが
	// この構造体には全部のキー情報が統一的に並んでいるほうがよかろう。
	{ -1, -1, -1, -1, 1,-1,	KC_enter,			{ EnterMark } },
};


//
// LUNA-88K ソフトウェアキーボード (パネル)
//

// コンストラクタ
WXLuna88kSoftKeyPanel::WXLuna88kSoftKeyPanel(wxWindow *parent)
	: inherited(parent, keydata, 226, 54)
{
	InitLunaCommon();
}

// デストラクタ
WXLuna88kSoftKeyPanel::~WXLuna88kSoftKeyPanel()
{
}

// LED を描画する。
// 独立 LED なので戻り値は使わないので 0 でいい。
int
WXLuna88kSoftKeyPanel::DrawLED(const keydata_t& basekey)
{
	const std::vector<bool>& led = keyboard->GetLED();
	Color fg_backup;
	Color bg_backup;
	Color fg;
	TA attr;

	fg_backup = GetTextForeColor();
	bg_backup = GetTextBackColor();

	if (led[basekey.led]) {
		// 点灯時、文字は LED 色でボールド。
		fg = color.LED[0];
		attr = TA::Bold;
	} else {
		// 消灯時、文字はグレーで通常体。
		fg = UD_GREY;
		attr = TA::Normal;
	}
	SetTextForeColor(fg);
	// 背景はキーの押し下げ状態ではなく常にキー開放色のままとする。
	SetTextBackColor(color.Keytop[1][0]);

	// LUNA-88K キーボードは LED 入りキーではなく、別のところに
	// LED だけがあるタイプ。(今どきの PC キーボードに近い)
	static const keydata_t led_caps = {
		184, L0+1, 10, H-2, 1, -1, 0, { "Caps" }
	};
	static const keydata_t led_kana = {
		194, L0+1, 10, H-2, 1, -1, 0, { "Kana" }
	};
	const keydata_t& key = (basekey.keycode == KC_CAPS)
		? led_caps
		: led_kana;

	// 枠を描画。
	// 2つの LED で枠が重なるため状態によって色を変えられない。
	DrawKeyBox(key, color.Keytop[1][0], UD_GREY);

	// ラベルをセンタリング。
	const char *disp = key.disp[0];
	int x = key.x + (key.w - strlen(disp) * 2) / 2;
	int y = key.y + (key.h - 4) / 2;

	// ラベルを描画。
	x = x * unit + 1;
	y = y * unit + 1;
	DrawStringSJIS(x, y, disp, attr);

	// 前景色、背景色を元に戻す。
	SetTextForeColor(fg_backup);
	SetTextBackColor(bg_backup);

	// 元のキーの文字描画オフセットは無し。
	return 0;
}


// キーデータ
/*static*/ std::vector<keydata_t>
WXLuna88kSoftKeyPanel::keydata {
	//                +---- キートップの色(1:濃)
	//                |  +- 0 以上なら LED 番号
	//  X   Y   W  H  C  L	KeyCode				通常	SHIFT
	{   2, L0, 10, H, 1,-1,	KC_F1,				{ "PF1" } },
	{  12, L0, 10, H, 1,-1,	KC_F2,				{ "PF2" } },
	{  22, L0, 10, H, 1,-1,	KC_F3,				{ "PF3" } },
	{  32, L0, 10, H, 1,-1,	KC_F4,				{ "PF4" } },
	{  42, L0, 10, H, 1,-1,	KC_F5,				{ "PF5" } },
	{  52, L0, 10, H, 1,-1,	KC_F6,				{ "PF6" } },
	{  62, L0, 10, H, 1,-1,	KC_F7,				{ "PF7" } },
	{  72, L0, 10, H, 1,-1,	KC_F8,				{ "PF8" } },
	{  82, L0, 10, H, 1,-1,	KC_F9,				{ "PF9" } },
	{  92, L0, 10, H, 1,-1,	KC_F10,				{ "PF10" } },
	{ 102, L0, 10, H, 1,-1,	KC_PF11,			{ "消去" } },
	{ 112, L0, 10, H, 1,-1,	KC_PF12,			{ "呼出" } },
	{ 122, L0, 10, H, 1,-1,	KC_PF13,			{ "文節\n←" } },
	{ 132, L0, 10, H, 1,-1,	KC_PF14,			{ "文節\n→" } },
	{ 142, L0, 14, H, 1,-1,	KC_BS,				{ "BS" } },

	{ 210, L0+1, 14, H-2, 1,-1, KC_buzzer,		{ "Buzzer" } },

	{   2, L1, 12, H, 1,-1,	KC_ESC,				{ "ESC" } },
	{  14, L1, 10, H, 0,-1,	KC_1,				{ "1",	"!" } },
	{  24, L1, 10, H, 0,-1,	KC_2,				{ "2",	"\"" } },
	{  34, L1, 10, H, 0,-1,	KC_3,				{ "3",	"#" } },
	{  44, L1, 10, H, 0,-1,	KC_4,				{ "4",	"$" } },
	{  54, L1, 10, H, 0,-1,	KC_5,				{ "5",	"%" } },
	{  64, L1, 10, H, 0,-1,	KC_6,				{ "6",	"&" } },
	{  74, L1, 10, H, 0,-1,	KC_7,				{ "7",	"'" } },
	{  84, L1, 10, H, 0,-1,	KC_8,				{ "8",	"(" } },
	{  94, L1, 10, H, 0,-1,	KC_9,				{ "9",	")" } },
	{ 104, L1, 10, H, 0,-1,	KC_0,				{ "0",	" " } },
	{ 114, L1, 10, H, 0,-1,	KC_minus,			{ "-",	"=" } },
	{ 124, L1, 10, H, 0,-1,	KC_circum,			{ "^",	"~" } },
	{ 134, L1, 10, H, 0,-1,	KC_backslash,		{ "\\",	"|" } },
	{ 144, L1, 12, H, 1,-1,	KC_DEL,				{ "DEL" } },

	{   2, L2, 17, H, 1,-1,	KC_TAB,				{ "TAB" } },
	{  19, L2, 10, H, 0,-1,	KC_Q,				{ "q",	"Q" } },
	{  29, L2, 10, H, 0,-1,	KC_W,				{ "w",	"W" } },
	{  39, L2, 10, H, 0,-1,	KC_E,				{ "e",	"E" } },
	{  49, L2, 10, H, 0,-1,	KC_R,				{ "r",	"R" } },
	{  59, L2, 10, H, 0,-1,	KC_T,				{ "t",	"T" } },
	{  69, L2, 10, H, 0,-1,	KC_Y,				{ "y",	"Y" } },
	{  79, L2, 10, H, 0,-1,	KC_U,				{ "u",	"U" } },
	{  89, L2, 10, H, 0,-1,	KC_I,				{ "i",	"I" } },
	{  99, L2, 10, H, 0,-1,	KC_O,				{ "o",	"O" } },
	{ 109, L2, 10, H, 0,-1,	KC_P,				{ "p",	"P" } },
	{ 119, L2, 10, H, 0,-1,	KC_at,				{ "@",	"`" } },
	{ 129, L2, 10, H, 0,-1,	KC_bracketleft,		{ "[",	"{" } },

	{   2, L3, 20, H, 1,-1,	KC_CTRL,			{ "CTRL" } },
	{  22, L3, 10, H, 0,-1,	KC_A,				{ "a",	"A" } },
	{  32, L3, 10, H, 0,-1,	KC_S,				{ "s",	"S" } },
	{  42, L3, 10, H, 0,-1,	KC_D,				{ "d",	"D" } },
	{  52, L3, 10, H, 0,-1,	KC_F,				{ "f",	"F" } },
	{  62, L3, 10, H, 0,-1,	KC_G,				{ "g",	"G" } },
	{  72, L3, 10, H, 0,-1,	KC_H,				{ "h",	"H" } },
	{  82, L3, 10, H, 0,-1,	KC_J,				{ "j",	"J" } },
	{  92, L3, 10, H, 0,-1,	KC_K,				{ "k",	"K" } },
	{ 102, L3, 10, H, 0,-1,	KC_L,				{ "l",	"L" } },
	{ 112, L3, 10, H, 0,-1,	KC_semicolon,		{ ";",	"+" } },
	{ 122, L3, 10, H, 0,-1,	KC_colon,			{ ":",	"*" } },
	{ 132, L3, 10, H, 0,-1,	KC_bracketright,	{ "]",	"}" } },

	{   2, L4, 25, H, 1,-1,	KC_SHIFT_L,			{ "SHIFT" } },
	{  27, L4, 10, H, 0,-1,	KC_Z,				{ "z",	"Z" } },
	{  37, L4, 10, H, 0,-1,	KC_X,				{ "x",	"X" } },
	{  47, L4, 10, H, 0,-1,	KC_C,				{ "c",	"C" } },
	{  57, L4, 10, H, 0,-1,	KC_V,				{ "v",	"V" } },
	{  67, L4, 10, H, 0,-1,	KC_B,				{ "b",	"B" } },
	{  77, L4, 10, H, 0,-1,	KC_N,				{ "n",	"N" } },
	{  87, L4, 10, H, 0,-1,	KC_M,				{ "m",	"M" } },
	{  97, L4, 10, H, 0,-1,	KC_comma,			{ ",",	"<" } },
	{ 107, L4, 10, H, 0,-1,	KC_period,			{ ".",	">" } },
	{ 117, L4, 10, H, 0,-1,	KC_slash,			{ "/",	"?" } },
	{ 127, L4, 10, H, 0,-1,	KC_underscore,		{ " ",	"_" } },
	{ 137, L4, 19, H, 1,-1,	KC_SHIFT_R,			{ "SHIFT" } },

	{   2, L5, 10, H, 1,-1,	KC_SF,				{ "META" } },
	{  12, L5, 20, H, 1,-1,	KC_VALID,			{ "確定" } },
	{  32, L5, 10, H, 1, 1,	KC_CAPS,			{ "CAPS" } },
	{  42, L5, 80, H, 0,-1,	KC_space,			{ " " } },
	{ 122, L5, 10, H, 1, 0,	KC_kana,			{ "かな" } },
	{ 132, L5, 24, H, 1,-1,	KC_XFER,			{ "変換" } },

	// カーソルキー島
	{ 159, L1, 11, H, 1,-1,	KC_INS,				{ "INS" } },
	{ 170, L1, 11, H, 1,-1,	KC_COPY,			{ "COPY" } },
	{ 159, L2, 11, H, 1,-1,	KC_CUT,				{ "CUT" } },
	{ 170, L2, 11, H, 1,-1,	KC_PASTE,			{ "PASTE" } },
	{ 159, L3, 22, H, 1,-1,	KC_up,				{ "↑" } },
	{ 159, L4, 11, H, 1,-1,	KC_left,			{ "←" } },
	{ 170, L4, 11, H, 1,-1,	KC_right,			{ "→" } },
	{ 159, L5, 22, H, 1,-1,	KC_down,			{ "↓" } },

	// テンキー島
	{ 184, L1, 10, H, 1,-1,	KC_HOME,			{ "HOME" } },
	{ 194, L1, 10, H, 1,-1,	KC_PAD_plus,		{ "+" } },
	{ 204, L1, 10, H, 1,-1,	KC_PAD_minus,		{ "-" } },
	{ 214, L1, 10, H, 1,-1,	KC_PAD_multiply,	{ "*" } },

	{ 184, L2, 10, H, 0,-1,	KC_PAD_7,			{ "7" } },
	{ 194, L2, 10, H, 0,-1,	KC_PAD_8,			{ "8" } },
	{ 204, L2, 10, H, 0,-1,	KC_PAD_9,			{ "9" } },
	{ 214, L2, 10, H, 1,-1,	KC_PAD_divide,		{ "/" } },

	{ 184, L3, 10, H, 0,-1,	KC_PAD_4,			{ "4" } },
	{ 194, L3, 10, H, 0,-1,	KC_PAD_5,			{ "5" } },
	{ 204, L3, 10, H, 0,-1,	KC_PAD_6,			{ "6" } },
	{ 214, L3, 10, H, 1,-1,	KC_PAD_equal,		{ "=" } },

	{ 184, L4, 10, H, 0,-1,	KC_PAD_1,			{ "1" } },
	{ 194, L4, 10, H, 0,-1,	KC_PAD_2,			{ "2" } },
	{ 204, L4, 10, H, 0,-1,	KC_PAD_3,			{ "3" } },
	{ 214, L4, 10, H, 1,-1,	KC_PAD_comma,		{ "," } },

	{ 184, L5, 10, H, 0,-1,	KC_PAD_0,			{ "0" } },
	{ 194, L5, 10, H, 0,-1,	KC_PAD_decimal,		{ "." } },
	{ 204, L5, 20, H, 1,-1,	KC_PAD_enter,		{ EnterMark } },

	// Enter だけ矩形でないので別処理するが
	// この構造体には全部のキー情報が統一的に並んでいるほうがよかろう。
	{ -1, -1, -1, -1, 1,-1,	KC_enter,			{ EnterMark } },
};


//
// X68k ソフトウェアキーボード (パネル)
//

// コンストラクタ
WXX68kSoftKeyPanel::WXX68kSoftKeyPanel(wxWindow *parent)
	: inherited(parent, keydata, 235, 54)
{
	// X68k では SRAM 設定に同期させる。
	const auto sram = GetSRAMDevice();
	uint8 sw = sram->Peek1(0xed0059);
	gFontManager->SetGlyph5C((sw & 0x01));
	gFontManager->SetGlyph7E((sw & 0x02));
	gFontManager->SetGlyph7C((sw & 0x04));

	// とりあえず黒のみ
	color.Background	= Color(50, 50, 53);
	color.Text			= UD_WHITE;
	color.Border		= UD_BLACK;
	color.LED[0]		= UD_RED;
	color.LED[1]		= UD_YELLOW_GREEN;
	color.Keytop[0][0]	= UD_BLACK;
	InitColorKeytop(1);

	SetBitmapBGColor(color.Background);
	Fill();
}

// デストラクタ
WXX68kSoftKeyPanel::~WXX68kSoftKeyPanel()
{
}

// LED を描画して刻印描画のための y offset を返す。
int
WXX68kSoftKeyPanel::DrawLED(const keydata_t& key)
{
	const std::vector<bool>& led = keyboard->GetLED();
	Color fg;
	Color bg;

	// 色を選択
	// idx	刻印	色
	// ----------------
	//  0	かな	赤
	//  1	ローマ	赤
	//  2	コード	赤
	//  3	CAPS	赤
	//  4	INS		赤
	//  5	ひら	緑
	//  6	全角	緑
	if (led[key.led]) {
		int n = (key.led < 5) ? 0 : 1;
		fg = color.LED[n];
		bg = color.LED[n];
	} else {
		// 消灯時は元のキートップの色で、枠は薄めにしたい
		// (枠の色は今の所全モデル共通なので ColorPalette に含めていない)
		fg = UD_GREY;
		bg = color.Keytop[key.coltype][0];
	}

	// LED は中央前方(画面でいうと下)
	Rect rect((key.x + 3) * unit, (key.y + key.h - 2) * unit,
		4 * unit, 2 * unit);
	bitmap.FillRect(bg, rect);
	bitmap.DrawRect(fg, rect);

	// そのため文字は少し上げる
	return -1;
}

#define HT (H * 2)		// 縦2倍角のキー
/*static*/ std::vector<keydata_t>
WXX68kSoftKeyPanel::keydata {
	//                   +- 0以上なら LED 番号
	//  X   Y   W  H  C  L	KeyCode				通常	SHIFT
	{   2, LH, 10, H, 0,-1,	KC_BREAK,			{ "\x4\x5\x6\x7" } },
	{  15, LH, 10, H, 0,-1,	KC_COPY,			{ "COPY" } },
	{  29, L0, 12, 6, 0,-1,	KC_F1,				{ "F1" } },
	{  41, L0, 12, 6, 0,-1,	KC_F2,				{ "F2" } },
	{  53, L0, 12, 6, 0,-1,	KC_F3,				{ "F3" } },
	{  65, L0, 12, 6, 0,-1,	KC_F4,				{ "F4" } },
	{  77, L0, 13, 6, 0,-1,	KC_F5,				{ "F5" } },
	{  94, L0, 13, 6, 0,-1,	KC_F6,				{ "F6" } },
	{ 106, L0, 12, 6, 0,-1,	KC_F7,				{ "F7" } },
	{ 118, L0, 12, 6, 0,-1,	KC_F8,				{ "F8" } },
	{ 130, L0, 12, 6, 0,-1,	KC_F9,				{ "F9" } },
	{ 142, L0, 13, 6, 0,-1,	KC_F10,				{ "F10" } },

	{   2, L1, 10, H, 0,-1,	KC_ESC,				{ "ESC" } },
	{  12, L1, 10, H, 0,-1,	KC_1,				{ "1",	"!" } },
	{  22, L1, 10, H, 0,-1,	KC_2,				{ "2",	"\"" } },
	{  32, L1, 10, H, 0,-1,	KC_3,				{ "3",	"#" } },
	{  42, L1, 10, H, 0,-1,	KC_4,				{ "4",	"$" } },
	{  52, L1, 10, H, 0,-1,	KC_5,				{ "5",	"%" } },
	{  62, L1, 10, H, 0,-1,	KC_6,				{ "6",	"&" } },
	{  72, L1, 10, H, 0,-1,	KC_7,				{ "7",	"'" } },
	{  82, L1, 10, H, 0,-1,	KC_8,				{ "8",	"(" } },
	{  92, L1, 10, H, 0,-1,	KC_9,				{ "9",	")" } },
	{ 102, L1, 10, H, 0,-1,	KC_0,				{ "0",	" " } },
	{ 112, L1, 10, H, 0,-1,	KC_minus,			{ "-",	"=" } },
	{ 122, L1, 10, H, 0,-1,	KC_circum,			{ "^",	"~" } },
	{ 132, L1, 10, H, 0,-1,	KC_backslash,		{ "\\",	"|" } },
	{ 142, L1, 13, H, 0,-1,	KC_BS,				{ "BS" } },

	{   2, L2, 16, H, 0,-1,	KC_TAB,				{ "TAB" } },
	{  18, L2, 10, H, 0,-1,	KC_Q,				{ "q",	"Q" } },
	{  28, L2, 10, H, 0,-1,	KC_W,				{ "w",	"W" } },
	{  38, L2, 10, H, 0,-1,	KC_E,				{ "e",	"E" } },
	{  48, L2, 10, H, 0,-1,	KC_R,				{ "r",	"R" } },
	{  58, L2, 10, H, 0,-1,	KC_T,				{ "t",	"T" } },
	{  68, L2, 10, H, 0,-1,	KC_Y,				{ "y",	"Y" } },
	{  78, L2, 10, H, 0,-1,	KC_U,				{ "u",	"U" } },
	{  88, L2, 10, H, 0,-1,	KC_I,				{ "i",	"I" } },
	{  98, L2, 10, H, 0,-1,	KC_O,				{ "o",	"O" } },
	{ 108, L2, 10, H, 0,-1,	KC_P,				{ "p",	"P" } },
	{ 118, L2, 10, H, 0,-1,	KC_at,				{ "@",	"`" } },
	{ 128, L2, 10, H, 0,-1,	KC_bracketleft,		{ "[",	"{" } },

	{   2, L3, 19, H, 0,-1,	KC_CTRL,			{ "CTRL" } },
	{  21, L3, 10, H, 0,-1,	KC_A,				{ "a",	"A" } },
	{  31, L3, 10, H, 0,-1,	KC_S,				{ "s",	"S" } },
	{  41, L3, 10, H, 0,-1,	KC_D,				{ "d",	"D" } },
	{  51, L3, 10, H, 0,-1,	KC_F,				{ "f",	"F" } },
	{  61, L3, 10, H, 0,-1,	KC_G,				{ "g",	"G" } },
	{  71, L3, 10, H, 0,-1,	KC_H,				{ "h",	"H" } },
	{  81, L3, 10, H, 0,-1,	KC_J,				{ "j",	"J" } },
	{  91, L3, 10, H, 0,-1,	KC_K,				{ "k",	"K" } },
	{ 101, L3, 10, H, 0,-1,	KC_L,				{ "l",	"L" } },
	{ 111, L3, 10, H, 0,-1,	KC_semicolon,		{ ";",	"+" } },
	{ 121, L3, 10, H, 0,-1,	KC_colon,			{ ":",	"*" } },
	{ 131, L3, 10, H, 0,-1,	KC_bracketright,	{ "]",	"}" } },

	{   2, L4, 24, H, 0,-1,	KC_SHIFT,			{ "SHIFT" } },
	{  26, L4, 10, H, 0,-1,	KC_Z,				{ "z",	"Z" } },
	{  36, L4, 10, H, 0,-1,	KC_X,				{ "x",	"X" } },
	{  46, L4, 10, H, 0,-1,	KC_C,				{ "c",	"C" } },
	{  56, L4, 10, H, 0,-1,	KC_V,				{ "v",	"V" } },
	{  66, L4, 10, H, 0,-1,	KC_B,				{ "b",	"B" } },
	{  76, L4, 10, H, 0,-1,	KC_N,				{ "n",	"N" } },
	{  86, L4, 10, H, 0,-1,	KC_M,				{ "m",	"M" } },
	{  96, L4, 10, H, 0,-1,	KC_comma,			{ ",",	"<" } },
	{ 106, L4, 10, H, 0,-1,	KC_period,			{ ".",	">" } },
	{ 116, L4, 10, H, 0,-1,	KC_slash,			{ "/",	"?" } },
	{ 126, L4, 10, H, 0,-1,	KC_underscore,		{ " ",	"_" } },
	{ 136, L4, 19, H, 0,-1,	KC_SHIFT,			{ "SHIFT" } },

	{  21, L5, 10, H, 0, 5,	KC_hira,			{ "ひら" } },
	{  31, L5, 12, H, 0,-1,	KC_XF1,				{ "XF1" } },
	{  43, L5, 13, H, 0,-1,	KC_XF2,				{ "XF2" } },
	{  56, L5, 36, H, 0,-1,	KC_space,			{ " " } },
	{  92, L5, 12, H, 0,-1,	KC_XF3,				{ "XF3" } },
	{ 104, L5, 12, H, 0,-1,	KC_XF4,				{ "XF4" } },
	{ 116, L5, 12, H, 0,-1,	KC_XF5,				{ "XF5" } },
	{ 128, L5, 10, H, 0, 6,	KC_zenkaku,			{ "全角" } },

	// カーソルキー島
	{ 159, L0, 10, H, 0, 0,	KC_kana,			{ "かな" } },
	{ 169, L0, 10, H, 0, 1,	KC_romaji,			{ "ローマ" } },
	{ 179, L0, 10, H, 0, 2,	KC_code,			{ "コード" } },
	{ 159, L1, 10, H, 0,-1,	KC_HOME,			{ "HOME" } },
	{ 169, L1, 10, H, 0, 4,	KC_INS,				{ "INS" } },
	{ 179, L1, 10, H, 0,-1,	KC_DEL,				{ "DEL" } },
	{ 159, L2, 10, H, 0,-1,	KC_ROLLUP,			{ "ROLL\n UP" } },
	{ 169, L2, 10, H, 0,-1,	KC_ROLLDOWN,		{ "ROLL\nDOWN" } },
	{ 179, L2, 10, H, 0,-1,	KC_UNDO,			{ "UNDO" } },
	{ 169, L3, 10, H, 0,-1,	KC_up,				{ "↑" } },
	{ 159, L3, 10, HT,0,-1,	KC_left,			{ "←" } },
	{ 179, L3, 10, HT,0,-1,	KC_right,			{ "→" } },
	{ 169, L4, 10, H, 0,-1,	KC_down,			{ "↓" } },
	{ 159, L5, 15, H, 0,-1,	KC_OPT1,			{ "OPT.1" } },
	{ 174, L5, 15, H, 0,-1,	KC_OPT2,			{ "OPT.2" } },

	// テンキー島
	{ 193, L0, 10, H, 0, 3,	KC_CAPS,			{ "CAPS" } },
	{ 203, L0, 10, H, 0,-1,	KC_kigou,			{ "記号" } },
	{ 213, L0, 10, H, 0,-1,	KC_touroku,			{ "登録" } },
	{ 223, L0, 10, H, 0,-1,	KC_HELP,			{ "HELP" } },
	{ 193, L1, 10, H, 0,-1,	KC_PAD_CLR,			{ "CLR" } },
	{ 203, L1, 10, H, 0,-1,	KC_PAD_divide,		{ "/" } },
	{ 213, L1, 10, H, 0,-1,	KC_PAD_multiply,	{ "*" } },
	{ 223, L1, 10, H, 0,-1,	KC_PAD_minus,		{ "-" } },
	{ 193, L2, 10, H, 0,-1,	KC_PAD_7,			{ "7" } },
	{ 203, L2, 10, H, 0,-1,	KC_PAD_8,			{ "8" } },
	{ 213, L2, 10, H, 0,-1,	KC_PAD_9,			{ "9" } },
	{ 223, L2, 10, H, 0,-1,	KC_PAD_plus,		{ "+" } },
	{ 193, L3, 10, H, 0,-1,	KC_PAD_4,			{ "4" } },
	{ 203, L3, 10, H, 0,-1,	KC_PAD_5,			{ "5" } },
	{ 213, L3, 10, H, 0,-1,	KC_PAD_6,			{ "6" } },
	{ 223, L3, 10, H, 0,-1,	KC_PAD_equal,		{ "=" } },
	{ 193, L4, 10, H, 0,-1,	KC_PAD_1,			{ "1" } },
	{ 203, L4, 10, H, 0,-1,	KC_PAD_2,			{ "2" } },
	{ 213, L4, 10, H, 0,-1,	KC_PAD_3,			{ "3" } },
	{ 223, L4, 10, HT,0,-1,	KC_PAD_enter,		{ "\xb\xc\xe\xf" } },
	{ 193, L5, 10, H, 0,-1,	KC_PAD_0,			{ "0" } },
	{ 203, L5, 10, H, 0,-1,	KC_PAD_comma,		{ "," } },
	{ 213, L5, 10, H, 0,-1,	KC_PAD_decimal,		{ "." } },

	// Enter だけ矩形でないので別処理するが
	// この構造体には全部のキー情報が統一的に並んでいるほうがよかろう。
	{ -1, -1, -1, -1, 0,-1,	KC_enter,			{ EnterMark } },
};


//
// ソフトウェアキーボード (ウィンドウ)
//

// コンストラクタ
WXSoftKeyWindow::WXSoftKeyWindow(wxWindow *parent, VMType vmtype)
	: inherited(parent, wxID_ANY, _("Software Keyboard"))
{
	switch (vmtype) {
	 case VMType::X68030:
		new WXX68kSoftKeyPanel(this);
		break;
	 case VMType::LUNA1:
		new WXLuna1SoftKeyPanel(this);
		break;
	 case VMType::LUNA88K:
		new WXLuna88kSoftKeyPanel(this);
		break;
	 default:
		assert(false);
	}

	Fit();
}

// デストラクタ
WXSoftKeyWindow::~WXSoftKeyWindow()
{
}

unix.superglobalmegacorp.com

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