--- nono/wx/wxsoftkey.cpp 2026/04/29 17:04:53 1.1.1.6 +++ nono/wx/wxsoftkey.cpp 2026/04/29 17:05:04 1.1.1.9 @@ -8,6 +8,7 @@ #include "wxcolor.h" #include "wxmainview.h" #include "wxtextscreen.h" +#include "wxuimessage.h" #include "sram.h" // @@ -28,7 +29,6 @@ wxBEGIN_EVENT_TABLE(WXSoftKeyPanel, inhe EVT_LEFT_DCLICK(WXSoftKeyPanel::OnLeftDClick) EVT_RIGHT_DOWN(WXSoftKeyPanel::OnRightDown) EVT_PAINT(WXSoftKeyPanel::OnPaint) - EVT_SIZE(WXSoftKeyPanel::OnSize) EVT_TIMER(wxID_ANY, WXSoftKeyPanel::OnTimer) wxEND_EVENT_TABLE() @@ -38,29 +38,67 @@ 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(); + + // 残光の初期化。 + // persistence は keydata 分の配列ではなく共通キーコード分の配列。 + // 使われない領域も多いが、共通キーコードから 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, gMainView); - // 更新間隔は適当 - timer.SetOwner(this); - timer.Start(100); + // VM からの通知を受け取る + WXUIMessage::Connect(UIMessage::KEYBOARD, this, + wxCommandEventHandler(WXSoftKeyPanel::OnKeyChanged)); } // デストラクタ WXSoftKeyPanel::~WXSoftKeyPanel() { + // SHIFT キーのワンショットは解放する? + // (長押しは開放しない) + + 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)); + } + } } // フォントサイズ変更 (外部インタフェース) @@ -130,18 +168,6 @@ WXSoftKeyPanel::DoSize() wxSize size(u_width * unit, u_height * unit); SetClientSize(size); SetMinClientSize(size); -} - -// サイズ変更イベント -void -WXSoftKeyPanel::OnSize(wxSizeEvent& event) -{ - // Mac ではウィンドウ作成時に 0 以下の値が来ることがある - // GTK では 1 で来ることがある - const wxSize& size = event.GetSize(); - if (size.x <= 1 || size.y <= 1) { - return; - } // ビットマップ作成 bitmap.Create(size.x, size.y); @@ -150,16 +176,39 @@ WXSoftKeyPanel::OnSize(wxSizeEvent& even Refresh(); } -// タイマーイベント void WXSoftKeyPanel::OnTimer(wxTimerEvent& event) { - // 更新があれば再描画を指示 - const std::vector& pressed = gKeyboard->GetPressed(); - UpdateModifier(); - if (pressed != last_pressed || modifier != last_modifier) { - Refresh(); + 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; } + + Refresh(); } // 描画イベント (UI スレッドから呼ばれる) @@ -181,6 +230,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()); @@ -188,30 +254,45 @@ 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 (timer_start) { + timer.Start(25); + } else { + timer.Stop(); + } #if 0 // X68k キーボードだとどこに表示するか @@ -351,6 +432,20 @@ WXSoftKeyPanel::FindKeydata(uint keycode assertmsg(0, "keycode %d not found", keycode); } +// keycode が SHIFT キーなら true を返す +// (SHIFT キーが押されているかとかではない) +/*static*/ bool +WXSoftKeyPanel::IsShiftKey(uint keycode) +{ + if (keycode == KC_SHIFT_L || + keycode == KC_SHIFT_R || + keycode == KC_SHIFT) + { + return true; + } + return false; +} + // マウス左クリック (Down) イベント void WXSoftKeyPanel::OnLeftDown(wxMouseEvent& event) @@ -358,17 +453,40 @@ WXSoftKeyPanel::OnLeftDown(wxMouseEvent& const wxPoint& pt = event.GetPosition(); const keydata_t *key = GetKeydataFromPoint(pt); - DoLeftDown(key); + if (key != NULL) { + DoLeftDown(key); + } } // マウス左クリック (Down) 処理 void WXSoftKeyPanel::DoLeftDown(const keydata_t *key) { - if (key != NULL) { - // 押下 + assert(key); + + // SHIFT キーの左クリックはトグル状動作。 + // SHIFT 状態で押すと、すべての SHIFT キーを開放。 + // 非 SHIFT 状態で押すと、次のキー押下までこの SHIFT キーを押下維持。 + if (IsShiftKey(key->keycode)) { + if (gKeyboard->IsShift()) { + // SHIFT 状態で SHIFT キーが押されたのですべての SHIFT キーを開放 + if (key->keycode == KC_SHIFT) { + KeyUp(*key); + } else { + KeyUp(FindKeydata(KC_SHIFT_L)); + KeyUp(FindKeydata(KC_SHIFT_R)); + } + } else { + // 非 SHIFT 状態で SHIFT キーが押されたのでこのキーを押下 + KeyDown(*key); + shift_hold = false; + } + + // SHIFT キーなら LeftUp では何もしない + leftdown_key = NULL; + } else { + // SHIFT 以外のキーは押下して LeftUp のために覚えておく KeyDown(*key); - // LeftUp のために覚えておく leftdown_key = key; } } @@ -381,17 +499,24 @@ WXSoftKeyPanel::OnLeftUp(wxMouseEvent& e // 直近に LeftDown したキーコードをターゲットにする。 const keydata_t *key = leftdown_key; - DoLeftUp(key); -} - -// マウス左クリック (Up) 処理 -void -WXSoftKeyPanel::DoLeftUp(const keydata_t *key) -{ if (key != NULL) { - // 開放 KeyUp(*key); + + // キーが離された時、長押しでない SHIFT キーがあれば開放 + if (gKeyboard->IsShift() && shift_hold == false) { + // XXX どの SHIFT が存在するのか分からないのがなんかアレ + if (gKeyboard->IsPressed(KC_SHIFT_L)) { + KeyUp(FindKeydata(KC_SHIFT_L)); + } + if (gKeyboard->IsPressed(KC_SHIFT_R)) { + KeyUp(FindKeydata(KC_SHIFT_R)); + } + if (gKeyboard->IsPressed(KC_SHIFT)) { + KeyUp(FindKeydata(KC_SHIFT)); + } + } } + leftdown_key = NULL; } @@ -411,7 +536,9 @@ WXSoftKeyPanel::OnLeftDClick(wxMouseEven const wxPoint& pt = event.GetPosition(); const keydata_t *key = GetKeydataFromPoint(pt); - DoLeftDown(key); + if (key != NULL) { + DoLeftDown(key); + } } // マウス右クリック (Down) イベント @@ -431,6 +558,10 @@ WXSoftKeyPanel::OnRightDown(wxMouseEvent } else { // 押されてなければ長押し開始 KeyDown(*key); + + if (IsShiftKey(key->keycode)) { + shift_hold = true; + } } } } @@ -462,46 +593,14 @@ 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) { uint code = key.keycode; - // VM に通知 (Keyboard がそれをホストに通知するかはまた別) + // VM に通知 (Keyboard がホストに通知する) gKeyboard->MakeKey(code); - - // 修飾キー - if (IsModifier(code)) { - UpdateModifier(); - } - - // キー状態が変わった(かも知れない)ので再描画 - // 押した時にはタイマーを待たずに再描画したほうがよかろう - Refresh(); } // このキーが放された処理 @@ -510,17 +609,8 @@ WXSoftKeyPanel::KeyUp(const keydata_t& k { uint code = key.keycode; - // VM に通知 (Keyboard がそれをホストに通知するかはまた別) + // VM に通知 (Keyboard がホストに通知する) gKeyboard->BreakKey(code); - - // 修飾キー - if (IsModifier(code)) { - UpdateModifier(); - } - - // キー状態が変わった(かも知れない)が、 - // 解放時はタイマーに任せた再描画でよかろう - //Refresh(); } @@ -539,33 +629,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; } - - DoSize(); + InitColorKeytop(2); } // デストラクタ @@ -587,7 +670,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 を左肩に描く @@ -744,16 +827,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 }, - }; - - DoSize(); + 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); } // デストラクタ @@ -785,7 +865,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 は中央前方(画面でいうと下) @@ -803,7 +883,7 @@ WXX68kSoftKeyPanel::DrawLED(wxDC& dc, co WXX68kSoftKeyPanel::keydata { // +- 0以上なら LED 番号 // X Y W H C L KeyCode 通常 SHIFT - { 2, L0, 10, H, 0,-1, KC_BREAK, { "BRK" } }, + { 2, L0, 10, H, 0,-1, KC_BREAK, { "\x4\x5\x6\x7" } }, { 15, L0, 10, H, 0,-1, KC_COPY, { "COPY" } }, { 29, LF, 12, 6, 0,-1, KC_F1, { "F1" } }, { 41, LF, 12, 6, 0,-1, KC_F2, { "F2" } }, @@ -920,7 +1000,7 @@ WXX68kSoftKeyPanel::keydata { { 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, { "ENT" } }, + { 223, L4, 10, HT,0,-1, KC_PAD_enter, { "\xb\xc\xd\xe" } }, { 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, { "." } }, @@ -935,16 +1015,20 @@ WXX68kSoftKeyPanel::keydata { // // コンストラクタ -WXSoftKeyWindow::WXSoftKeyWindow(wxWindow *parent, wxWindowID id, - vmtype_t vmtype) - : inherited(parent, id, _("Software Keyboard")) +WXSoftKeyWindow::WXSoftKeyWindow(wxWindow *parent, vmtype_t vmtype) + : inherited(parent, wxID_ANY, _("Software Keyboard")) { + wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL); + if (vmtype == VMTYPE_X68030) { panel = new WXX68kSoftKeyPanel(this); } else { panel = new WXLunaSoftKeyPanel(this); } + topsizer->Add(panel, 0, wxEXPAND); + + SetSizer(topsizer); // 大きさをそれに固定 Fit(); }