--- nono/wx/wxsoftkey.cpp 2026/04/29 17:04:59 1.1.1.8 +++ nono/wx/wxsoftkey.cpp 2026/04/29 17:05:07 1.1.1.10 @@ -29,6 +29,7 @@ wxBEGIN_EVENT_TABLE(WXSoftKeyPanel, inhe EVT_LEFT_DCLICK(WXSoftKeyPanel::OnLeftDClick) EVT_RIGHT_DOWN(WXSoftKeyPanel::OnRightDown) EVT_PAINT(WXSoftKeyPanel::OnPaint) + EVT_TIMER(wxID_ANY, WXSoftKeyPanel::OnTimer) wxEND_EVENT_TABLE() // コンストラクタ @@ -37,18 +38,27 @@ WXSoftKeyPanel::WXSoftKeyPanel(wxWindow : inherited(parent) , keydata(keydata_) { + timer.SetOwner(this); + u_width = u_width_; u_height = u_height_; - // 比較用のバッファ (初回更新させるため true で埋めておく) - const std::vector& pressed = gKeyboard->GetPressed(); - last_pressed.resize(pressed.size()); - std::fill(last_pressed.begin(), last_pressed.end(), true); - last_modifier = (Modifier)-1; - DoSize(); + // トグル動作にするキー + 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); + // このパネルへのキー入力はメインビューへのキー入力と同じ。 + Connect(wxEVT_CHAR, wxCharEventHandler(WXMainView::OnChar), NULL, + gMainView); Connect(wxEVT_KEY_UP, wxKeyEventHandler(WXMainView::OnKeyUp), NULL, gMainView); Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL, @@ -62,10 +72,45 @@ WXSoftKeyPanel::WXSoftKeyPanel(wxWindow // デストラクタ WXSoftKeyPanel::~WXSoftKeyPanel() { + // ウィンドウクローズでトグルキーのワンショットは開放する + // (長押しは開放しない) + for (const auto& k : toggle) { + auto code = k.second; + if (code) { + KeyUp(code); + } + } + + timer.Stop(); + WXUIMessage::Disconnect(UIMessage::KEYBOARD, wxCommandEventHandler(WXSoftKeyPanel::OnKeyChanged)); } +// color.Keytop の初期化(補間)。 +// num はキートップの種類の数。1 か 2 +void +WXSoftKeyPanel::InitColorKeytop(int num) +{ + wxColour 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] = wxColour( + f(pressed.Red(), color.Keytop[i][0].Red(), n), + f(pressed.Green(), color.Keytop[i][0].Green(), n), + f(pressed.Blue(), color.Keytop[i][0].Blue(), n)); + } + } +} + // フォントサイズ変更 (外部インタフェース) void WXSoftKeyPanel::SetFontSize(FontId new_fontid) @@ -141,16 +186,49 @@ WXSoftKeyPanel::DoSize() Refresh(); } +void +WXSoftKeyPanel::OnTimer(wxTimerEvent& event) +{ + Refresh(); +} + // キー状態変更イベント (UIMessage) +// +// キー状態変更イベントは、キーの状態が変わるたびに都度通知される。 +// ソフトウェアキーボード入力とパススルーモードの入力では、どのキーに変化 +// (押下 or 開放)があったかの情報は付属しない。各キーの押下状態は全キーを +// 逐一調査することで判定する。人間時間で起きる事象なのでこの計算量は許容。 +// Char 入力モードの場合は、イベントのパラメータ引数 (GetInt() で取得可) に +// 入力のあったキーの共通キーコードが入っている (複数のキーが同時に押される +// 場合はキーの回数分のイベントが飛んでくる)。Char 入力モードにはキー開放 +// という概念はない。 void WXSoftKeyPanel::OnKeyChanged(wxCommandEvent& event) { - // 更新があれば再描画を指示 - const std::vector& pressed = gKeyboard->GetPressed(); - UpdateModifier(); - if (pressed != last_pressed || modifier != last_modifier) { - Refresh(); + // 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 (gKeyboard->IsPressed(k.first) == false) { + k.second = 0; + } } + + Refresh(); } // 描画イベント (UI スレッドから呼ばれる) @@ -172,6 +250,23 @@ WXSoftKeyPanel::OnPaint(wxPaintEvent& ev void WXSoftKeyPanel::Draw(wxDC& dc) { + 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) || + gKeyboard->IsShift()) + { + modifier = ModShift; + } else { + modifier = ModNormal; + } + dc.SetPen(wxPen(color.Background)); dc.SetBrush(wxBrush(color.Background)); dc.DrawRectangle(wxPoint(0, 0), GetSize()); @@ -179,44 +274,53 @@ WXSoftKeyPanel::Draw(wxDC& dc) dc.SetPen(color.Text); SetTextForeColor(color.Text); for (const auto& key : keydata) { - bool is_pressed = gKeyboard->IsPressed(key.keycode); - if (is_pressed) { - // 押されている - SetTextBackColor(color.KeyPressed); - dc.SetBrush(color.KeyPressed); + int c = persistence[key.keycode]; + if (0 < c && c < 10) { + // Char 入力モードで減光中 + c--; + timer_start = true; } else { - // 押されていない - SetTextBackColor(color.Keytop[key.coltype]); - dc.SetBrush(wxBrush(color.Keytop[key.coltype])); + // そうでない場合に限り、現在の状態を問い合わせる + bool is_pressed = gKeyboard->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]); + dc.SetBrush(color.Keytop[key.coltype][c]); + // エンターキーだけ枠の描き方が違うので別処理 if (__predict_false(key.x < 0)) { DrawEnterKey(dc); } else { DrawKey(dc, key); } - - // 次回更新チェック用に現状を保存 - last_pressed[key.keycode] = is_pressed; } - // 次回更新チェック用に現状を保存 - last_modifier = modifier; - -#if 0 - // X68k キーボードだとどこに表示するか - SetTextBackColor(color.Background); - wxString utfstr(wxT("右クリックで長押し")); - const char *sjis = (const char *)utfstr.mb_str(conv); - DrawStringSJIS(dc, 190 * unit, 2 * unit, sjis, TA::Disable); -#endif + if (timer_start) { + timer.Start(25); + } else { + timer.Stop(); + } } // 指定のキーを一つ (枠から刻印まで) 描画する。 void WXSoftKeyPanel::DrawKey(wxDC& dc, const keydata_t& key) { + uint attr = TA::Normal; + // 枠 wxPoint pt(key.x * unit, key.y * unit); wxSize sz(key.w * unit + 1, key.h * unit + 1); @@ -228,6 +332,14 @@ WXSoftKeyPanel::DrawKey(wxDC& dc, const 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); const char *sjis = (const char *)utfstr.mb_str(conv); @@ -271,7 +383,7 @@ WXSoftKeyPanel::DrawKey(wxDC& dc, const } else { if (lf == NULL) { // 1行の場合 - DrawStringSJIS(dc, pt.x, pt.y, sjis); + DrawStringSJIS(dc, pt.x, pt.y, sjis, attr); } else { // 2行の場合 DrawStringSJIS(dc, pt.x, pt.y, sjis1); @@ -349,18 +461,31 @@ WXSoftKeyPanel::OnLeftDown(wxMouseEvent& const wxPoint& pt = event.GetPosition(); const keydata_t *key = GetKeydataFromPoint(pt); - DoLeftDown(key); + if (key != NULL) { + DoLeftDown(key->keycode); + } } // マウス左クリック (Down) 処理 void -WXSoftKeyPanel::DoLeftDown(const keydata_t *key) +WXSoftKeyPanel::DoLeftDown(uint code) { - if (key != NULL) { - // 押下 - KeyDown(*key); - // LeftUp のために覚えておく - leftdown_key = key; + auto it = toggle.find(code); + if (it != toggle.end()) { + // トグルキーはここで完結。LeftUp では何もしない。 + // second のクリアは KeyUp() からコールバックされる OnKeyChanged() + // で行っている。 + if (gKeyboard->IsPressed(code)) { + KeyUp(code); + } else { + KeyDown(code); + it->second = code; + } + leftdown_key = KC_none; + } else { + // トグルでない通常キーはここで押下して LeftUp のために覚えておく + KeyDown(code); + leftdown_key = code; } } @@ -370,20 +495,21 @@ WXSoftKeyPanel::OnLeftUp(wxMouseEvent& e { // Up 時は、これが起きた地点のキーコードではなく // 直近に LeftDown したキーコードをターゲットにする。 - const keydata_t *key = leftdown_key; + uint code = leftdown_key; - DoLeftUp(key); -} + if (code != KC_none) { + KeyUp(code); -// マウス左クリック (Up) 処理 -void -WXSoftKeyPanel::DoLeftUp(const keydata_t *key) -{ - if (key != NULL) { - // 開放 - KeyUp(*key); + // 通常キーが離されたら、トグルキーはすべて開放 + for (const auto& k : toggle) { + auto tcode = k.second; + if (tcode) { + KeyUp(tcode); + } + } } - leftdown_key = NULL; + + leftdown_key = KC_none; } // マウス左ダブルクリックイベント @@ -402,7 +528,9 @@ WXSoftKeyPanel::OnLeftDClick(wxMouseEven const wxPoint& pt = event.GetPosition(); const keydata_t *key = GetKeydataFromPoint(pt); - DoLeftDown(key); + if (key != NULL) { + DoLeftDown(key->keycode); + } } // マウス右クリック (Down) イベント @@ -418,10 +546,10 @@ WXSoftKeyPanel::OnRightDown(wxMouseEvent uint code = key->keycode; if (gKeyboard->IsPressed(code)) { // 押されていたら開放 - KeyUp(*key); + KeyUp(code); } else { // 押されてなければ長押し開始 - KeyDown(*key); + KeyDown(code); } } } @@ -453,65 +581,20 @@ WXSoftKeyPanel::GetKeydataFromPoint(cons return NULL; } -// 修飾キーなら true を返す -// (今は SHIFT しかないから修飾キーならなのか SHIFT ならなのか曖昧) -bool -WXSoftKeyPanel::IsModifier(uint code) const -{ - if (code == KC_SHIFT_L || code == KC_SHIFT_R || code == KC_SHIFT) { - return true; - } else { - return false; - } -} - -// 修飾キーの状態を更新。今は SHIFT かどうかしかない -void -WXSoftKeyPanel::UpdateModifier() -{ - if (gKeyboard->IsShift()) { - modifier = ModShift; - } else { - modifier = ModNormal; - } -} - // このキーが押された処理 void -WXSoftKeyPanel::KeyDown(const keydata_t& key) +WXSoftKeyPanel::KeyDown(uint code) { - uint code = key.keycode; - - // VM に通知 (Keyboard がそれをホストに通知するかはまた別) + // VM に通知 (Keyboard がホストに通知する) gKeyboard->MakeKey(code); - - // 修飾キー - if (IsModifier(code)) { - UpdateModifier(); - } - - // キー状態が変わった(かも知れない)ので再描画 - // 押した時にはタイマーを待たずに再描画したほうがよかろう - Refresh(); } // このキーが放された処理 void -WXSoftKeyPanel::KeyUp(const keydata_t& key) +WXSoftKeyPanel::KeyUp(uint code) { - uint code = key.keycode; - - // VM に通知 (Keyboard がそれをホストに通知するかはまた別) + // VM に通知 (Keyboard がホストに通知する) gKeyboard->BreakKey(code); - - // 修飾キー - if (IsModifier(code)) { - UpdateModifier(); - } - - // キー状態が変わった(かも知れない)が、 - // 解放時はタイマーに任せた再描画でよかろう - //Refresh(); } @@ -530,31 +613,26 @@ WXLunaSoftKeyPanel::WXLunaSoftKeyPanel(w SetGlyph5C(true); SetGlyph7E(true); + // 白黒モデル共通 + color.Border = *wxBLACK; + color.LED[0] = UD_RED; + // キートップの濃淡はユニバーサルデザインの推奨色ではないけど // ここは見分けを付けるためではなくただのデザインなので。 if (1) { // 白モデル - color = { - .Background = UD_LIGHT_GREY, // 現物新品は何色だったんだろう - .Text = *wxBLACK, - .Keytop = { *wxWHITE, wxColour(200, 200, 203) }, - // 白黒共通 - .KeyPressed = UD_BLUE, - .Border = *wxBLACK, - .LED = { UD_RED }, - }; + color.Background = UD_LIGHT_GREY; // 現物新品は何色だったんだろう + color.Text = *wxBLACK; + color.Keytop[0][0] = *wxWHITE; + color.Keytop[1][0] = wxColour(200, 200, 203); } else { // 黒モデル - color = { - .Background = *wxBLACK, - .Text = *wxWHITE, - .Keytop = { wxColour(50, 50, 53), *wxBLACK }, - // 白黒共通 - .KeyPressed = UD_BLUE, - .Border = *wxBLACK, - .LED = { UD_RED }, - }; + color.Background = *wxBLACK; + color.Text = *wxWHITE; + color.Keytop[0][0] = wxColour(50, 50, 53); + color.Keytop[1][0] = *wxBLACK; } + InitColorKeytop(2); } // デストラクタ @@ -576,7 +654,7 @@ WXLunaSoftKeyPanel::DrawLED(wxDC& dc, co // 消灯時は元のキートップの色で、枠は薄めにしたい // (枠の色は今の所全モデル共通なので ColorPalette に含めていない) dc.SetPen(UD_GREY); - dc.SetBrush(color.Keytop[key.coltype]); + dc.SetBrush(color.Keytop[key.coltype][0]); } // LED を左肩に描く @@ -733,14 +811,13 @@ WXX68kSoftKeyPanel::WXX68kSoftKeyPanel(w SetGlyph7C((sw & 0x04)); // とりあえず黒のみ - color = { - .Background = wxColour(50, 50, 53), - .Text = *wxWHITE, - .Keytop = { *wxBLACK }, - .KeyPressed = UD_BLUE, - .Border = *wxBLACK, - .LED = { UD_RED, UD_YELLOW_GREEN }, - }; + color.Background = wxColour(50, 50, 53); + color.Text = *wxWHITE; + color.Border = *wxBLACK; + color.LED[0] = UD_RED; + color.LED[1] = UD_YELLOW_GREEN; + color.Keytop[0][0] = *wxBLACK; + InitColorKeytop(1); } // デストラクタ @@ -772,7 +849,7 @@ WXX68kSoftKeyPanel::DrawLED(wxDC& dc, co // 消灯時は元のキートップの色で、枠は薄めにしたい // (枠の色は今の所全モデル共通なので ColorPalette に含めていない) dc.SetPen(UD_GREY); - dc.SetBrush(color.Keytop[key.coltype]); + dc.SetBrush(color.Keytop[key.coltype][0]); } // LED は中央前方(画面でいうと下)