--- nono/wx/wxmainview.cpp 2026/04/29 17:04:56 1.1.1.9 +++ nono/wx/wxmainview.cpp 2026/04/29 17:05:08 1.1.1.11 @@ -4,11 +4,17 @@ // Licensed under nono-license.txt // +// +// メイン画面パネル +// + #include "wxmainview.h" +#include "wxuimessage.h" +#include "bitmap.h" +#include "config.h" +#include "event.h" #include "keyboard.h" #include "renderer.h" -#include "scheduler.h" -#include #include //#define KEYDEBUG 1 @@ -42,16 +48,14 @@ #define DONT_USE_REFRESH 1 #endif -static void wxmainview_refresh(); - WXMainView *gMainView; wxBEGIN_EVENT_TABLE(WXMainView, inherited) EVT_PAINT(WXMainView::OnPaint) EVT_KEY_DOWN(WXMainView::OnKeyDown) EVT_KEY_UP(WXMainView::OnKeyUp) + EVT_CHAR(WXMainView::OnChar) EVT_MOUSE_EVENTS(WXMainView::OnMouse) - EVT_TIMER(wxID_ANY, WXMainView::OnTimer) wxEND_EVENT_TABLE() WXMainView::WXMainView(wxWindow *parent) @@ -61,19 +65,17 @@ WXMainView::WXMainView(wxWindow *parent) rendwidth = gRenderer->GetWidth(); rendheight = gRenderer->GetHeight(); - // レンダラバッファは常に仮想画面サイズ - int buflen = rendwidth * rendheight * 3; - ImageBuf.reset(new uint8_t[buflen]); - memset(&ImageBuf[0], 0x70, buflen); - // レンダラに通知 - gRenderer->SetBuf(ImageBuf.get(), wxmainview_refresh); + gRenderer->Enable(); + + // VM からの通知を受け取る + WXUIMessage::Connect(UIMessage::RENDER, this, + wxCommandEventHandler(WXMainView::OnRender)); // 実画面 (ウィンドウの大きさ) DoResize(screen_scale); - // 描画を律速するためのタイマー - timer.SetOwner(this); - timer.Start(33); + // 更新間隔 + gRenderer->SetRefreshInterval(33_msec); // 最初に一度描画 Refresh(); @@ -81,19 +83,46 @@ WXMainView::WXMainView(wxWindow *parent) WXMainView::~WXMainView() { + // マウスをリリースしないと Assert される + if (HasCapture()) { + ReleaseMouse(); + } + WXUIMessage::Disconnect(UIMessage::RENDER, + wxCommandEventHandler(WXMainView::OnRender)); } -// リフレッシュ要求コールバック。 -// VM から呼ばれるので一旦グローバル関数を経由する。 +// (設定反映などの) 初期化 +bool +WXMainView::Init() +{ + // キーボードの入力モードの初期値設定 + const ConfigItem& item_input = gConfig->Find("hostkbd-input"); + const std::string input_mode = item_input.AsString(); + if (input_mode == "char") { + SetCharInputMode(true); + } else if (input_mode == "jp") { + // 今は二択しかないのでちょっと噛み合ってないが + SetCharInputMode(false); + } else { + item_input.Err(); + return false; + } + + return true; +} + +// 終了用に描画を停止する void -wxmainview_refresh(void) +WXMainView::Close() { - gMainView->DoRefresh(); + // これで OnPaint() が何もせず帰るようになる。 + viewwidth = 0; + viewheight = 0; } -// リフレッシュ要求コールバック。こっちが実体。 +// リフレッシュ要求イベント void -WXMainView::DoRefresh() +WXMainView::OnRender(wxCommandEvent& event) { #if defined(DONT_USE_REFRESH) // Refresh{,Rect}() が使えないので、再描画範囲は常に全体とする @@ -102,65 +131,36 @@ WXMainView::DoRefresh() // XXX のはずだが、今の所全体 RefreshRect(wxRect(0, 0, viewwidth, viewheight), false); #endif - - // ここは VM Renderer が画面を更新した時に呼ばれるので、本来なら - // ここで単に Refresh() を呼んで再描画を指示すればいいのだが、 - // VM 高速モードになると、VM Renderer が高速で回ってここも高速で - // 呼ばれてしまい、その結果 Refresh() を発行しすぎて Mac では - // システムイベントが拾えなくなってしまう。 - // それを避けるため、ここではフラグを立てるだけにしておいて、 - // こちらで管理している実時間タイマーで Refresh() を律速する。 - paint_request = true; -} - -// タイマーイベント -void -WXMainView::OnTimer(wxTimerEvent& event) -{ - bool req = paint_request.exchange(false); - if (req) { - // 実際に再描画する - Refresh(); - } + // 実際に再描画する + Refresh(); + // TODO: ビデオモニタにも通知する } // 描画イベント void WXMainView::OnPaint(wxPaintEvent& event) { - if (viewwidth == 0 || viewheight == 0) + if (__predict_false(viewwidth == 0 || viewheight == 0)) { return; + } + + const BitmapRGB& bitmap0 = gRenderer->GetBitmap(); + BitmapRGB bitmap1; + uint8 *p = const_cast(bitmap0.GetBuf()); - wxImage image(rendwidth, rendheight, ImageBuf.get(), true); if (viewwidth != rendwidth || viewheight != rendheight) { - // wxIMAGE_QUALITY_NORMAL だと速いが、ここは品質優先。 -#if _BYTE_ORDER == _BIG_ENDIAN - // XXX ..のはずだったが wxWidget 3.0.4 の QUALITY_HIGH は - // BigEndian だとバグってるようだ。QUALITY_NORMAL のほうは大丈夫。 - auto quality = wxIMAGE_QUALITY_NORMAL; -#else - auto quality = wxIMAGE_QUALITY_HIGH; -#endif - image.Rescale(viewwidth, viewheight, quality); + bitmap1.Create(viewwidth, viewheight); + Rect rect(0, 0, viewwidth, viewheight); + bitmap1.StretchDraw(rect, bitmap0); + p = bitmap1.GetBuf(); } - wxBitmap bitmap(image); - wxMemoryDC memDC; - memDC.SelectObject(bitmap); - - wxPaintDC dc(this); - wxRegion region(0, 0, viewwidth, viewheight); - wxRegionIterator ri(region); - for (; ri; ri++) { - int dx, dy, dw, dh; - dx = ri.GetX(); - dy = ri.GetY(); - dw = ri.GetWidth(); - dh = ri.GetHeight(); - int sx = dx; - int sy = dy; - dc.Blit(dx, dy, dw, dh, &memDC, sx, sy, wxCOPY); - } + wxImage image(viewwidth, viewheight, p, true); + wxBitmap bmp(image); + + // 実画面 DC にコピー + wxPaintDC dstDC(this); + dstDC.DrawBitmap(bmp, 0, 0); } // リサイズ (UI スレッドから呼ぶこと) @@ -359,7 +359,7 @@ WXMainView::OnActivate(wxActivateEvent& // wxKeyEvent でキーコードを得るためのメソッドは // GetKeyCode()、GetRawKeyCode()、GetRawKeyFlags() の3つが用意されていて -// wxWigets の port (wxGTK、wxOSX とか) によってそれぞれ内容の定義が違う。 +// wxWidgets の port (wxGTK、wxOSX とか) によってそれぞれ内容の定義が違う。 // // wxGTK の場合 // GetKeyCode() が論理キーコードなのでこれをメインに使う。ただし左右 [SHIFT] @@ -403,27 +403,114 @@ WXMainView::OnActivate(wxActivateEvent& // GetRawKeyFlags() は状態フラグか何かのようだ。 // +// 入力モード変更 +void +WXMainView::SetCharInputMode(bool enable) +{ + is_charinput = enable; +} + // キーを押した void WXMainView::OnKeyDown(wxKeyEvent& event) { + // メニューのアクセラレータのために必要 + event.Skip(); + + if (is_charinput) { + return; + } + DKPRINTF("KeyDown "); int keycode = GetKeyCode(event); gKeyboard->MakeKey(keycode); - - // メニューのアクセラレータのために必要 - event.Skip(); } // キーを離した void WXMainView::OnKeyUp(wxKeyEvent& event) { + if (is_charinput) { + return; + } + DKPRINTF("KeyUp "); int keycode = GetKeyCode(event); gKeyboard->BreakKey(keycode); } +// 文字による入力モード +void +WXMainView::OnChar(wxKeyEvent& event) +{ + // メニューのアクセラレータのために必要 + event.Skip(); + + if (!is_charinput) { + return; + } + + uint ukey = event.GetUnicodeKey(); + uint ukey_orig = ukey; + DKPRINTF("Char uni=0x%02x", ukey); + if (0x20 <= ukey && ukey < 0x7f) { + DKPRINTF(" '%c'", ukey); + } + + bool ctrl = event.ControlDown(); + if (ctrl) { + DKPRINTF(" CTRL"); + + // Ctrl + A .. Ctrl + Z までは 0x01(^A) .. 0x1a(^Z) の文字が来るが、 + // Ctrl + 記号は記号の文字コードだけが送られてくるようだ。 + // Ctrl + [ は 0x1b('^[') ではなく 0x5b('[') のような感じ。 + // そこでこの場合は Ctrl キーが押されているかどうかを別途判定する。 + if (0x40 <= ukey && ukey <= 0x5f) { + ukey -= 0x40; + // Ctrl+@ ('\0') はそのまま通せないので、別文字にエスケープ。 + if (ukey == 0) { + ukey = CC_NUL; + } + } + } + // 文字コードが拾えなければキーコードから + if (ukey == WXK_NONE) { + uint wxkey = event.GetKeyCode(); + DKPRINTF(" key=%3u(0x%03x)", wxkey, wxkey); + // 一部の特殊キーをここで対応。 + // キーボード国籍による違いはないだろう。 + switch (wxkey) { + case WXK_LEFT: ukey = CC_left; break; + case WXK_UP: ukey = CC_up; break; + case WXK_RIGHT: ukey = CC_right; break; + case WXK_DOWN: ukey = CC_down; break; + case WXK_F1: ukey = CC_F1; break; + case WXK_F2: ukey = CC_F2; break; + case WXK_F3: ukey = CC_F3; break; + case WXK_F4: ukey = CC_F4; break; + case WXK_F5: ukey = CC_F5; break; + case WXK_F6: ukey = CC_F6; break; + case WXK_F7: ukey = CC_F7; break; + case WXK_F8: ukey = CC_F8; break; + case WXK_F9: ukey = CC_F9; break; + case WXK_F10: ukey = CC_F10; break; + default: + ukey = 0; + break; + } + } + if (ukey != ukey_orig) { + DKPRINTF(" -> 0x%02x", ukey); + } + DKPRINTF("\n"); + if (ukey == 0) { + return; + } + ukey &= 0xff; + + gKeyboard->CharInput(ukey); +} + // 共通キーコードを取得する。 // 対応するキーがない場合は -1 を返す。 int @@ -723,7 +810,7 @@ WXMainView::keycode_table[0x190] = { 0, // 310 WXK_PAUSE 0, // 311 WXK_CAPITAL 0, // 312 WXK_END - 0, // 313 WXK_HOME + KC_HOME, // 313 WXK_HOME KC_left, // 314 WXK_LEFT KC_up, // 315 WXK_UP KC_right, // 316 WXK_RIGHT @@ -803,7 +890,7 @@ WXMainView::keycode_table[0x190] = { 0, // 383 WXK_NUMPAD_BEGIN 0, // 384 WXK_NUMPAD_INSERT 0, // 385 WXK_NUMPAD_DELETE - 0, // 386 WXK_NUMPAD_EQUAL + KC_PAD_equal, // 386 WXK_NUMPAD_EQUAL KC_PAD_multiply,// 387 WXK_NUMPAD_MULTIPLY KC_PAD_plus, // 388 WXK_NUMPAD_ADD 0, // 389 WXK_NUMPAD_SEPARATOR