--- nono/wx/wxtextscreen.cpp 2026/04/29 17:04:53 1.1.1.5 +++ nono/wx/wxtextscreen.cpp 2026/04/29 17:05:08 1.1.1.8 @@ -4,36 +4,45 @@ // Licensed under nono-license.txt // +// +// テキストスクリーンを描画するパネル +// + #include "wxtextscreen.h" +#include "fontmanager.h" #include "wxcolor.h" #include "wxmainframe.h" -#include "bitrev.h" -#include "cgrom.h" #include "sjis.h" - -// -// テキストスクリーンを表示するパネル -// +PRAGMA_PUSH_WARNINGS +#include +PRAGMA_POP_WARNINGS // イベントテーブル wxBEGIN_EVENT_TABLE(WXTextScreen, inherited) - EVT_PAINT(WXTextScreen::OnPaint) EVT_SIZE(WXTextScreen::OnSize) EVT_TIMER(wxID_ANY, WXTextScreen::OnTimer) + EVT_MENU(wxID_COPY, WXTextScreen::OnCopy) wxEND_EVENT_TABLE() // コンストラクタ -WXTextScreen::WXTextScreen(wxWindow *parent, IMonitor& monitor_) +WXTextScreen::WXTextScreen(wxWindow *parent, Monitor& monitor_) : inherited(parent) , monitor(monitor_) { + pad_left = DefaultPadding; + pad_top = DefaultPadding; + pad_right = DefaultPadding; + pad_bottom = DefaultPadding; + // モニタサイズから初期サイズを決定 - nnSize ms = monitor.GetMonitorSize(); + nnSize ms = monitor.GetSize(); Init(ms.width, ms.height); - wxSize size = wxSize(ms.width * font_width, ms.height * font_height); - SetClientSize(size); - SetMinClientSize(size); + FontChanged(); + + // コンテキストメニューイベントを動的に追加 + // (コンテキストメニューを表示したくない人があとから外せるようにするため) + ConnectContextMenu(); timer.SetOwner(this); @@ -41,7 +50,7 @@ WXTextScreen::WXTextScreen(wxWindow *par SetRate(gMainFrame->GetMonitorRate()); // 最初に一回描画を起こす - wxTimerEvent dummy; + wxTimerEvent dummy(timer); AddPendingEvent(dummy); } @@ -56,6 +65,13 @@ WXTextScreen::Init(int col, int row) { ts.Init(col, row); prevbuf.resize(col * row); + InvalidateText(); +} + +// テキスト画面全体が次回再描画されるようにする +void +WXTextScreen::InvalidateText() +{ std::fill(prevbuf.begin(), prevbuf.end(), 0xffff); } @@ -68,22 +84,27 @@ WXTextScreen::SetRate(int hz) // フォントサイズ変更 (外部インタフェース) void -WXTextScreen::SetFontSize(FontId new_fontid) +WXTextScreen::FontChanged() { - // 同じなら何もしない - if (fontid == new_fontid) { - return; - } - - // フォントを更新 - UpdateFont(new_fontid); + inherited::FontChanged(); // 再描画を強制する。 // テキストの桁数行数は変わってないので再確保はしなくていい。 - std::fill(prevbuf.begin(), prevbuf.end(), 0xffff); + InvalidateText(); // コントロールの大きさを変更 - wxSize size = wxSize(ts.GetCol() * font_width, ts.GetRow() * font_height); + DoSize(); +} + +// (自発的な)サイズ変更。 +// ts の行数桁数、font_{width,height}、四方のパディングのいずれかが +// 変わった場合に呼ぶこと。 +void +WXTextScreen::DoSize() +{ + wxSize size = wxSize( + ts.GetCol() * font_width + pad_left + pad_right, + ts.GetRow() * font_height + pad_top + pad_bottom); SetClientSize(size); SetMinClientSize(size); } @@ -99,21 +120,27 @@ WXTextScreen::OnSize(wxSizeEvent& event) return; } + // 親クラス + inherited::OnSize(event); + // テキストサイズが変わった時だけテキストバッファを再構成。 // (フォントサイズを変えると桁数×行数が変わらず画面サイズが変わる // ということは起きることに注意) - int col = size.x / font_width; - int row = size.y / font_height; + int sw = size.x - pad_left - pad_right; + int sh = size.y - pad_top - pad_bottom; + if (__predict_false(sw < 0)) { + sw = 0; + } + if (__predict_false(sh < 0)) { + sh = 0; + } + int col = sw / font_width; + int row = sh / font_height; if (col != ts.GetCol() || row != ts.GetRow()) { Init(col, row); - } - - // ビットマップサイズが変わった時だけビットマップバッファを再構成。 - wxSize bmpsize = bitmap.GetSize(); - wxSize newsize = wxSize(col * font_width, row * font_height); - if (bmpsize != newsize) { - // ビットマップ作成 - bitmap.Create(newsize); + } else { + // テキストバッファを再構成しなくても全域を再描画する必要がある + InvalidateText(); } } @@ -121,51 +148,38 @@ WXTextScreen::OnSize(wxSizeEvent& event) void WXTextScreen::OnTimer(wxTimerEvent& event) { - // モニタースクリーンを更新して再描画。 - monitor.MonitorUpdate(ts); - RefreshIfUpdated(); + DoRefresh(); } -// 必要なら画面を更新して再描画する +// 画面を更新して再描画する。 +// +// 通常はタイマーイベントにより更新間隔ごとに呼ばれるが、 +// ユーザ操作により画面の更新が必要ならその都度直接これを呼ぶ。 void -WXTextScreen::RefreshIfUpdated() +WXTextScreen::DoRefresh() { + // モニタースクリーンを更新して + MONITOR_UPDATE(monitor, ts); + // 必要なら再描画 if (ts.GetBuf() != prevbuf) { Refresh(); } } -// 描画イベント (UI スレッドから呼ばれる) -void -WXTextScreen::OnPaint(wxPaintEvent& event) -{ - // メモリ DC に描画 - wxMemoryDC memDC; - memDC.SelectObject(bitmap); - // 基本的には RefreshIfUpdated() により更新があった時だけ呼ばれる - Draw(memDC); - - // 実画面 DC にコピー - wxPaintDC dstDC(this); - wxSize size = bitmap.GetSize(); - dstDC.Blit(0, 0, size.x, size.y, &memDC, 0, 0); -} - // テキストバッファの内容をパネルのバックグラウンドバッファに描画する。 void -WXTextScreen::Draw(wxDC& dc) +WXTextScreen::Draw() { auto text = ts.GetBuf().begin(); auto prev = prevbuf.begin(); int col = ts.GetCol(); int row = ts.GetRow(); - int px = 0; - int py = 0; uint prevattr = (uint)-1; + int py = pad_top; for (int y = 0; y < row; y++, py += font_height) { - px = 0; + int px = pad_left; for (int x = 0; x < col; ) { uint16 data = *text; uint attr = (data & 0xff00); @@ -175,7 +189,10 @@ WXTextScreen::Draw(wxDC& dc) if (__predict_false(attr != prevattr)) { switch (attr) { case TA::On: - SetTextColor(BGPANEL, *wxBLACK); + SetTextColor(BGPANEL, UD_BLACK); + break; + case TA::Off: + SetTextColor(UD_BLACK, UD_LIGHT_GREY); break; case TA::Disable: SetTextColor(UD_GREY, BGPANEL); @@ -190,7 +207,8 @@ WXTextScreen::Draw(wxDC& dc) if (__predict_true(SJIS::IsHankaku(chr))) { // 半角文字 if (*prev != data) { - DrawChar1(dc, px, py, chr, attr); + DrawChar1(px, py, chr, attr); + if (SJIS::IsZenkaku(*prev) && x < col - 1) { // 変更前が全角文字だったら 2 バイト目も消す prev[1] = 0; @@ -203,13 +221,14 @@ WXTextScreen::Draw(wxDC& dc) px += font_width * 1; } else { // 全角文字 - // XXX: 最右カラムだったら? + // XXX: 最右カラムだったら? if (*prev != data || prev[1] != text[1]) { uint code = (chr << 8) | (text[1] & 0xff); - DrawChar2(dc, px, py, code, attr); + DrawChar2(px, py, code, attr); + if (SJIS::IsASCII(*prev) && SJIS::IsZenkaku(prev[1]) && x < col - 2) { - // 変更前が 'Aあ' だったら'あ'の2バイト目も消す + // 変更前が 'Aあ' だったら'あ'の2バイト目も消す prev[2] = 0; } prev[0] = data; @@ -223,3 +242,118 @@ WXTextScreen::Draw(wxDC& dc) } } } + +// パディングサイズ変更。 +// 負数なら変更しない。 +void +WXTextScreen::SetPadding(int l, int t, int r, int b) +{ + if (l >= 0) { + pad_left = l; + } + if (t >= 0) { + pad_top = t; + } + if (r >= 0) { + pad_right = r; + } + if (b >= 0) { + pad_bottom = b; + } + + DoSize(); +} + +// クライアント座標 pos をテキスト座標(桁、行)に変換して返す +// パディング(上と左)領域では戻り値が負数になることに注意。 +wxPoint +WXTextScreen::GetTextPosition(const wxPoint& pos) const +{ + wxPoint tpos; + + tpos.x = (pos.x - pad_left) / GetFontWidth(); + tpos.y = (pos.y - pad_top) / GetFontHeight(); + + return tpos; +} + +// コンテキストメニューイベントを接続 +void +WXTextScreen::ConnectContextMenu() +{ + Connect(wxEVT_CONTEXT_MENU, + wxContextMenuEventHandler(WXTextScreen::OnContextMenu), NULL, this); +} + +// コンテキストメニューイベントを接続解除 +void +WXTextScreen::DisconnectContextMenu() +{ + Disconnect(wxEVT_CONTEXT_MENU, + wxContextMenuEventHandler(WXTextScreen::OnContextMenu), NULL, this); +} + +// コンテキストメニューイベント +void +WXTextScreen::OnContextMenu(wxContextMenuEvent& ev) +{ + wxMenu *menu = new wxMenu(); + + menu->Append(wxID_COPY, _T("&Copy")); + + PopupMenu(menu); +} + +// コンテキストメニュー「コピー」イベント +void +WXTextScreen::OnCopy(wxCommandEvent& ev) +{ + int col = ts.GetCol(); + int row = ts.GetRow(); + + const std::vector& src = ts.GetBuf(); + auto s = src.begin(); + + std::vector buf; + + for (int y = 0; y < row; y++) { + for (int x = 0; x < col; x++) { + // 下位バイトだけ取り出すと SJIS 文字列になる + buf.push_back((*s++) & 0xff); + } + // 末尾の空白を取り除く + while (buf.back() == ' ') { + buf.pop_back(); + } + buf.push_back('\n'); + } + buf.push_back('\0'); + +#if 0 + // Shift_JIS を UTF-8 に変換 + // XXX 今はまだ Shift_JIS 出してる人がいない +#else + wxString text(buf.data()); +#endif + + // クリップボードへ転送 + if (wxTheClipboard->Open()) { + auto obj = new wxTextDataObject(text); + wxTheClipboard->UsePrimarySelection(false); + wxTheClipboard->SetData(obj); + wxTheClipboard->Close(); + } +#if defined(__WXGTK__) + // wxGTK なら X11 のクリップボードにも同じものを入れる。 + // UsePrimarySelection(false) だと GTK のクリップボードに入り、 + // UsePrimarySelection(true) だと X11 のクリップボードに入る。 + // X11 用語では「カットバッファ/セレクション」というらしく、 + // 実は8つくらいあってその1つ目なので PrimarySelection らしい。 + if (wxTheClipboard->Open()) { + auto obj = new wxTextDataObject(text); + wxTheClipboard->UsePrimarySelection(true); + wxTheClipboard->SetData(obj); + wxTheClipboard->Close(); + } +#endif +}