--- nono/wx/wxmainview.cpp 2026/04/29 17:05:04 1.1.1.10 +++ nono/wx/wxmainview.cpp 2026/04/29 17:05:53 1.1.1.19 @@ -4,12 +4,17 @@ // Licensed under nono-license.txt // +// +// メイン画面パネル +// + #include "wxmainview.h" +#include "wxmainframe.h" +#include "wxuimessage.h" #include "config.h" +#include "device.h" #include "keyboard.h" #include "renderer.h" -#include "scheduler.h" -#include #include //#define KEYDEBUG 1 @@ -43,9 +48,7 @@ #define DONT_USE_REFRESH 1 #endif -static void wxmainview_refresh(); - -WXMainView *gMainView; +wxDEFINE_EVENT(NONO_EVT_MOUSEMODE, wxCommandEvent); wxBEGIN_EVENT_TABLE(WXMainView, inherited) EVT_PAINT(WXMainView::OnPaint) @@ -53,29 +56,40 @@ wxBEGIN_EVENT_TABLE(WXMainView, inherite EVT_KEY_UP(WXMainView::OnKeyUp) EVT_CHAR(WXMainView::OnChar) EVT_MOUSE_EVENTS(WXMainView::OnMouse) - EVT_TIMER(wxID_ANY, WXMainView::OnTimer) + EVT_MOUSE_CAPTURE_LOST(WXMainView::OnMouseCaptureLost) wxEND_EVENT_TABLE() WXMainView::WXMainView(wxWindow *parent) : inherited(parent) { - // 仮想画面(VM が描画する画面) - 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); + SetName("WXMainView"); + + // デバイスを取得。 + renderer = GetVideoRenderer(); + + // rich dumb + // -------- ------- + // NULL NULL キー入力機能なし + // NULL avail シリアル入力機能のみ + // avail avail キー入力機能あり (2つは同じポインタ) + dumbkbd = gMainApp.FindObject(OBJ_KEYBOARD); + if (dumbkbd) { + richkbd = dynamic_cast(dumbkbd); + } + + // マウスモード変更イベント + Connect(NONO_EVT_MOUSEMODE, + wxCommandEventHandler(WXMainView::OnMouseMode), NULL, this); + + // VM からの通知を受け取る + WXUIMessage::Connect(UIMessage::RENDER, this, + wxCommandEventHandler(WXMainView::OnRender)); // 実画面 (ウィンドウの大きさ) DoResize(screen_scale); - // 描画を律速するためのタイマー - timer.SetOwner(this); - timer.Start(33); + // 更新間隔 + renderer->SetRefreshInterval(33_msec); // 最初に一度描画 Refresh(); @@ -87,39 +101,47 @@ WXMainView::~WXMainView() if (HasCapture()) { ReleaseMouse(); } + WXUIMessage::Disconnect(UIMessage::RENDER, this, + wxCommandEventHandler(WXMainView::OnRender)); } // (設定反映などの) 初期化 bool WXMainView::Init() { - // キーボードの入力モードの初期値設定 - const ConfigItem& item_input = gConfig->Find("hostkbd-input"); - const std::string input_mode = item_input.AsString(); - if (input_mode == "char") { + if (richkbd) { + // キーボードの入力モードの初期値設定 + 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; + } + } else if (dumbkbd) { + // シリアルキーボードのみなら常に文字入力。 SetCharInputMode(true); - } else if (input_mode == "jp") { - // 今は二択しかないのでちょっと噛み合ってないが - SetCharInputMode(false); - } else { - item_input.Err(); - return false; } return true; } -// リフレッシュ要求コールバック。 -// VM から呼ばれるので一旦グローバル関数を経由する。 +// 終了用に描画を停止する 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}() が使えないので、再描画範囲は常に全体とする @@ -128,65 +150,30 @@ 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; - - 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); } - 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); + BitmapRGB *bitmap24 = renderer->GetBitmap(); + if (__predict_false(bitmap24 == NULL)) { + return; } + + wxImage image(viewwidth, viewheight, bitmap24->GetBuf(), true); + wxBitmap bmp(image); + + // 実画面 DC にコピー + wxPaintDC dstDC(this); + dstDC.DrawBitmap(bmp, 0, 0); } // リサイズ (UI スレッドから呼ぶこと) @@ -194,14 +181,18 @@ void WXMainView::DoResize(double scale) { screen_scale = scale; - int width = (double)rendwidth * scale; - int height = (double)rendheight * scale; + int width = (double)renderer->GetWidth() * scale; + int height = (double)renderer->GetHeight() * scale; if (viewwidth != width || viewheight != height) { - // ウィンドウサイズ変更 viewwidth = width; viewheight = height; - SetClientSize(viewwidth, viewheight); - SetMinClientSize(wxSize(viewwidth, viewheight)); + + // レンダラにリサイズ指示。 + renderer->ResizeView(viewwidth, viewheight); + + // ウィンドウサイズ変更 + SetSize(viewwidth, viewheight); + SetMinSize(wxSize(viewwidth, viewheight)); } } @@ -221,6 +212,13 @@ WXMainView::GetScale() const // マウス入力 // +// マウスモード変更要求イベント +void +WXMainView::OnMouseMode(wxCommandEvent& event) +{ + SetMouseMode(event.GetInt()); +} + // マウスモード設定 void WXMainView::SetMouseMode(bool mode) @@ -235,25 +233,33 @@ WXMainView::SetMouseMode(bool mode) // マウスカーソル OFF ::wxSetCursor(wxCursor(wxCURSOR_BLANK)); - CaptureMouse(); + if (!HasCapture()) { + CaptureMouse(); + } // このウィンドウから外れないように移動を検出し続けるため、 // マウスカーソルをウィンドウ中央に移す。 // ウィンドウとメニューバーが分離してる wxOSX のために必要。 - MyWarpPointer(GetClientSize() / 2); + MyWarpPointer(GetSize() / 2); } else { // マウスモードを OFF にする DMPRINTF("SetMouseMode OFF\n"); - ReleaseMouse(); + if (HasCapture()) { + ReleaseMouse(); + } // 分かりやすさのため、マウスカーソルを中央へ戻してから復帰 - wxSize pt = GetClientSize() / 2; + wxSize pt = GetSize() / 2; WarpPointer(pt.x, pt.y); // マウスカーソル ON ::wxSetCursor(wxNullCursor); } + + // マウスモードなら、タイトルにマウスモードの抜け方を表示したい。 + auto frame = dynamic_cast(GetParent()); + frame->SetMyTitle(mousemode); } // マウスカーソルをクライアント座標 (x, y) に移動 @@ -310,8 +316,14 @@ WXMainView::OnMouse(wxMouseEvent& event) #endif if (leaving) { - // タスクスイッチした時とかのための自動リリース - SetMouseMode(false); + // タスクスイッチした時とかのための自動リリース。 + // メインウィンドウの上にサブウィンドウが乗っているときも + // leaving が発生するので、メインウィンドウの外に出た場合のみ + // リリースする。 + auto sz = GetSize(); + if (pos.x < 0 || pos.x > sz.x || pos.y < 0 || pos.y > sz.y) { + SetMouseMode(false); + } } if (moving || dragging) { @@ -329,7 +341,7 @@ WXMainView::OnMouse(wxMouseEvent& event) // マウスカーソルを中心に戻す if (dx != 0 || dy != 0) { - MyWarpPointer(GetClientSize() / 2); + MyWarpPointer(GetSize() / 2); } } } @@ -347,7 +359,9 @@ WXMainView::OnMouse(wxMouseEvent& event) (mb ? 'M' : '-'), (rb ? 'R' : '-')); - gKeyboard->MouseInput(dx, dy, rb, lb, mb); + if (richkbd) { + richkbd->MouseInput(dx, dy, rb, lb, mb); + } } prevlb = lb; @@ -359,6 +373,14 @@ WXMainView::OnMouse(wxMouseEvent& event) event.Skip(); } +// マウスキャプチャロスト +void +WXMainView::OnMouseCaptureLost(wxMouseCaptureLostEvent& event) +{ + // 外部要因でマウスキャプチャをロストしたので、マウスモードを抜ける。 + SetMouseMode(false); +} + // アクティベートイベント。 // この(トップレベル)ウィンドウにフォーカスが当たるか外れるかで飛んでくる。 // 実際には WXMainFrame 宛てに飛んできたイベントをこちらに転送してある。 @@ -404,6 +426,8 @@ WXMainView::OnActivate(wxActivateEvent& // // GetRawKeyFlags() は物理キー番号ようだ。GDK の hardware keycode らしいが、 // それ以上の詳細は不明。 +// o 環境 (OS か X かライブラリ?) によって異なるようだ。 +// 少なくとも NetBSD9 と Ubuntu20.04 では一部の値が異なる。 // o [SHIFT] キーの影響を受けない。 // GetKeyCode() では影響を受けた例外キーもここでは影響を受けない。 // o [SHIFT], [CTRL], [ALT] の各左右キーは違うコードになる。 @@ -449,7 +473,9 @@ WXMainView::OnKeyDown(wxKeyEvent& event) DKPRINTF("KeyDown "); int keycode = GetKeyCode(event); - gKeyboard->MakeKey(keycode); + if (richkbd) { + richkbd->MakeKey(keycode); + } } // キーを離した @@ -462,13 +488,18 @@ WXMainView::OnKeyUp(wxKeyEvent& event) DKPRINTF("KeyUp "); int keycode = GetKeyCode(event); - gKeyboard->BreakKey(keycode); + if (richkbd) { + richkbd->BreakKey(keycode); + } } // 文字による入力モード void WXMainView::OnChar(wxKeyEvent& event) { + // メニューのアクセラレータのために必要 + event.Skip(); + if (!is_charinput) { return; } @@ -517,6 +548,7 @@ WXMainView::OnChar(wxKeyEvent& event) case WXK_F8: ukey = CC_F8; break; case WXK_F9: ukey = CC_F9; break; case WXK_F10: ukey = CC_F10; break; + case WXK_F11: ukey = CC_F10; break; default: ukey = 0; break; @@ -531,7 +563,9 @@ WXMainView::OnChar(wxKeyEvent& event) } ukey &= 0xff; - gKeyboard->CharInput(ukey); + if (dumbkbd) { + dumbkbd->CharInput(ukey); + } } // 共通キーコードを取得する。 @@ -582,6 +616,14 @@ WXMainView::GetKeyCode(wxKeyEvent& event #if defined(__WXGTK__) // wxGTK では日本語キーボードの一部のキーが論理キーコードだけでは // 判定できないようだ + // その上 raw キーコードも OS (か何か) によってところどころ体系が + // 異なるようだ。orz + // + // OS [^] [0] [_] [\] + // --------------- ----- ----- ----- ----- + // NetBSD9 0x15 0x13 0xd3 0x85 + // OpenBSD7.1 0x15 0x13 0xd3 0x85 + // Ubuntu20.04 0x15 0x13 0x61 0x84 // [0] と [SHIFT]+[^] が同じキーコードになるようだ if (keycode == KC_0) { @@ -591,12 +633,17 @@ WXMainView::GetKeyCode(wxKeyEvent& event keycode = KC_0; } } - // [\] と [_] が同じキーコードになるようだ + // [\] と [_] が同じキーコードになるようだ。 + // その時の raw は OS によって(?)異なるようだ。 if (keycode == KC_backslash) { - if (raw == 0xd3) { + switch (raw) { + case 0xd3: // NetBSD9 + case 0x61: // Ubuntu20.04 keycode = KC_underscore; - } else { + break; + default: keycode = KC_backslash; + break; } } #endif @@ -832,8 +879,8 @@ WXMainView::keycode_table[0x190] = { 0, // 310 WXK_PAUSE 0, // 311 WXK_CAPITAL - 0, // 312 WXK_END - 0, // 313 WXK_HOME + KC_UNDO, // 312 WXK_END + KC_HOME, // 313 WXK_HOME KC_left, // 314 WXK_LEFT KC_up, // 315 WXK_UP KC_right, // 316 WXK_RIGHT @@ -874,7 +921,7 @@ WXMainView::keycode_table[0x190] = { KC_F9, // 348 WXK_F9 KC_F10, // 349 WXK_F10 - 0, // 350 WXK_F11 + KC_F10, // 350 WXK_F11 0, // 351 WXK_F12 0, // 352 WXK_F13 0, // 353 WXK_F14 @@ -891,8 +938,8 @@ WXMainView::keycode_table[0x190] = { 0, // 363 WXK_F24 0, // 364 WXK_NUMLOCK 0, // 365 WXK_SCROLL - 0, // 366 WXK_PAGEUP - 0, // 367 WXK_PAGEDOWN + KC_ROLLUP, // 366 WXK_PAGEUP + KC_ROLLDOWN, // 367 WXK_PAGEDOWN 0, // 368 WXK_NUMPAD_SPACE 0, // 369 WXK_NUMPAD_TAB