--- nono/wx/wxtextscreen.cpp 2026/04/29 17:04:37 1.1.1.2 +++ nono/wx/wxtextscreen.cpp 2026/04/29 17:05:57 1.1.1.15 @@ -4,166 +4,342 @@ // Licensed under nono-license.txt // -#include "wxtextscreen.h" -#include "bitrev.h" -#include "cgrom.h" - // -// テキストスクリーン風コントロール +// テキストスクリーンを描画するパネル // +#include "wxtextscreen.h" +#include "fontmanager.h" +#include "wxmainframe.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_MENU(wxID_COPY, WXTextScreen::OnCopy) wxEND_EVENT_TABLE() // コンストラクタ -WXTextScreen::WXTextScreen(wxWindow *parent, int col_, int row_, - const uint16 *textbuf_) +WXTextScreen::WXTextScreen(wxWindow *parent, const nnSize& screensize) : inherited(parent) { - col = col_; - row = row_; - textbuf = textbuf_; + SetName("WXTextScreen"); + + pad_left = DefaultPadding; + pad_top = DefaultPadding; + pad_right = DefaultPadding; + pad_bottom = DefaultPadding; + + Init(screensize.width, screensize.height); - SetTextSize(col, row); + FontChanged(); + + // コンテキストメニューイベントを動的に追加 + // (コンテキストメニューを表示したくない人があとから外せるようにするため) + ConnectContextMenu(); } // デストラクタ WXTextScreen::~WXTextScreen() { - bitmap.reset(); - image.reset(); - bmpbuf.reset(); - prevbuf.reset(); } -// このパネルの(テキストでの)大きさを設定する。 +// (再)初期化 void -WXTextScreen::SetTextSize(int col_, int row_) +WXTextScreen::Init(int col, int row) { - col = col_; - row = row_; - int width = col * font_width; - int height = row * font_height; - - if (wxSize(width, height) == wxSize(view_width, view_height)) { - return; - } - - // サイズ変更イベント側でやるか - if ((bool)prevbuf) { - prevbuf.reset(); - } - prevbuf.reset(new uint16 [col * row]); - memset(&prevbuf[0], 0xff, col * row * sizeof(prevbuf[0])); + screen.Init(col, row); + prevbuf.resize(col * row); + InvalidateText(); +} - SetClientSize(width, height); - SetMinSize(wxSize(width, height)); +// テキスト画面全体が次回再描画されるようにする +void +WXTextScreen::InvalidateText() +{ + std::fill(prevbuf.begin(), prevbuf.end(), 0xffff); } // フォントサイズ変更 (外部インタフェース) void -WXTextScreen::SetFontSize(fontsize_t new_fontsize) +WXTextScreen::FontChanged() { - // 同じなら何もしない - if (fontsize == new_fontsize) { - return; - } - - // フォントを更新 - UpdateFont(new_fontsize); + inherited::FontChanged(); // 再描画を強制する。 // テキストの桁数行数は変わってないので再確保はしなくていい。 - assert((bool)prevbuf); - memset(&prevbuf[0], 0xff, col * row * sizeof(prevbuf[0])); + InvalidateText(); // コントロールの大きさを変更 - wxSize size = wxSize(col * font_width, row * font_height); - SetClientSize(size); - SetMinSize(size); + Fit(); } -// サイズ変更イベント void -WXTextScreen::OnSize(wxSizeEvent& event) +WXTextScreen::Fit() { - // Mac ではウィンドウ作成時に 0 以下の値が来ることがある - // GTK では 1 で来ることがある - wxSize size = event.GetSize(); - if (size.x <= 1 || size.y <= 1) { - return; - } - - view_width = size.x; - view_height = size.y; + // 指定の (row, col) にパディングを加味した大きさにする、 + // なので GetSize() ではない。 + wxSize size(GetScreenWidth(GetCol()), GetScreenHeight(GetRow())); - // ビットマップを持っていれば一旦解放 - bitmap.reset(); - image.reset(); - bmpbuf.reset(); + // バックバッファのサイズを固定。 + SetMinBitmapSize(size); - // ビットマップ作成 - // 正しく描かれなかった時に分かりやすいよう初期値は濃いグレーにしておく - bmpbuf.reset(new uint8 [view_width * view_height * 3]); - memset(&bmpbuf[0], 0x70, view_width * view_height * 3); - image.reset(new wxImage(view_width, view_height, bmpbuf.get(), true)); - bitmap.reset(new wxBitmap(*image)); + SetSize(size); + SetMinSize(size); } -// 描画イベント (UI スレッドから呼ばれる) void -WXTextScreen::OnPaint(wxPaintEvent& event) +WXTextScreen::OnSize(wxSizeEvent& event) { - // 更新があればメモリ DC に描画 - wxMemoryDC memDC; - memDC.SelectObject(*bitmap); - if (memcmp(textbuf, &prevbuf[0], col * row * sizeof(prevbuf[0])) != 0) { - Draw(memDC); + const wxSize& size = GetSize(); + + // テキストサイズが変わった時だけテキストバッファを再構成。 + // (フォントサイズを変えると桁数×行数が変わらず画面サイズが変わる + // ということは起きることに注意) + int sw = size.x - pad_left - pad_right; + int sh = size.y - pad_top - pad_bottom; + int col = (font_width != 0) ? (sw / font_width) : 0; + int row = (font_height != 0) ? (sh / font_height) : 0; + if (__predict_false(col < 1 || row < 1)) { + return; } - // 実画面 DC にコピー - wxPaintDC dstDC(this); - dstDC.Blit(0, 0, view_width, view_height, &memDC, 0, 0); + if (col != screen.GetCol() || row != screen.GetRow()) { + Init(col, row); + } else { + // テキストバッファを再構成しなくても全域を再描画する必要がある + InvalidateText(); + } + // サイズが変わったらどちらにしてもパディングも塗り直す。 + draw_padding = true; + + event.Skip(); } // テキストバッファの内容をパネルのバックグラウンドバッファに描画する。 void -WXTextScreen::Draw(wxDC& dc) +WXTextScreen::Draw() { - const uint16 *text = textbuf; - uint16 *prev = &prevbuf[0]; - int px = 0; - int py = 0; + if (__predict_false(draw_padding)) { + draw_padding = false; + auto bgcol = GetTextBackColor(); + // パディングを塗るだけでいいのだが手抜き。 + bitmap.Fill(bgcol); + } + + auto text = screen.GetBuf().begin(); + auto prev = prevbuf.begin(); + + int col = screen.GetCol(); + int row = screen.GetRow(); + uint prevcolor = (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); - uint chr = (data & 0xff); - if (__predict_true(chr < 128)) { - // ASCII + uint attr = (data & 0xf000); + uint color = (data & 0x0f00); + uint chr = (data & 0x00ff); + + // 属性(色)を変更 + if (__predict_false(color != prevcolor)) { + switch (color) { + case TA::Off: + SetTextColor(UD_BLACK, UD_LIGHT_GREY); + break; + case TA::Disable: + SetTextColor(UD_GREY, BGPANEL); + break; + case TA::Reverse: + SetTextColor(BGPANEL, UD_BLACK); + break; + case TA::ReverseRed: + SetTextColor(BGPANEL, UD_RED); + break; + case TA::ReverseGreen: + SetTextColor(UD_BLACK, UD_GREEN); + break; + case TA::ReverseOrange: + SetTextColor(UD_BLACK, UD_ORANGE); + break; + default: + ResetTextColor(); + break; + } + prevcolor = color; + } + + 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; + } *prev = data; } - prev++; - text++; - x++; - px += font_width; + prev += 1; + text += 1; + x += 1; + px += font_width * 1; } else { - // マルチバイト文字は Shift_JIS として処理する - uint code = (chr << 8) | (text[1] & 0xff); - if (*(uint32 *)prev != *(const uint32 *)text) { - DrawChar2(dc, px, py, code, attr); + // 全角文字 + // XXX: 最右カラムだったら? + if (*prev != data || prev[1] != text[1]) { + uint code = (chr << 8) | (text[1] & 0xff); + DrawChar2(px, py, code, attr); + + if (SJIS::IsASCII(*prev) && SJIS::IsZenkaku(prev[1]) + && x < col - 2) { + // 変更前が 'Aあ' だったら'あ'の2バイト目も消す + prev[2] = 0; + } + prev[0] = data; + prev[1] = text[1]; } - *prev++ = *text++; - *prev++ = *text++; + prev += 2; + text += 2; x += 2; px += font_width * 2; } } } } + +// 桁数が col_ の時の横幅 [pixel] を取得。 +int +WXTextScreen::GetScreenWidth(int col_) const +{ + int w = GetFontWidth() * col_ + GetPaddingX(); + return w; +} + +// 行数が row_ の時の高さ [pixel] を取得。 +int +WXTextScreen::GetScreenHeight(int row_) const +{ + int h = GetFontHeight() * row_ + GetPaddingY(); + return h; +} + +// パディングサイズ変更。 +// 負数なら変更しない。 +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; + } + + Fit(); +} + +// クライアント座標 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, _("&Copy All")); + + PopupMenu(menu); +} + +// コンテキストメニュー「コピー」イベント +void +WXTextScreen::OnCopy(wxCommandEvent& ev) +{ + int col = screen.GetCol(); + int row = screen.GetRow(); + + const std::vector& src = screen.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 +}