File:  [Isaki's NoNo m68k/m88k emulator] / nono / wx / wxtextscreen.cpp
Revision 1.1.1.15 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:57 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// テキストスクリーンを描画するパネル
//

#include "wxtextscreen.h"
#include "fontmanager.h"
#include "wxmainframe.h"
#include "sjis.h"
PRAGMA_PUSH_WARNINGS
#include <wx/clipbrd.h>
PRAGMA_POP_WARNINGS

// イベントテーブル
wxBEGIN_EVENT_TABLE(WXTextScreen, inherited)
	EVT_SIZE(WXTextScreen::OnSize)
	EVT_MENU(wxID_COPY, WXTextScreen::OnCopy)
wxEND_EVENT_TABLE()

// コンストラクタ
WXTextScreen::WXTextScreen(wxWindow *parent, const nnSize& screensize)
	: inherited(parent)
{
	SetName("WXTextScreen");

	pad_left   = DefaultPadding;
	pad_top    = DefaultPadding;
	pad_right  = DefaultPadding;
	pad_bottom = DefaultPadding;

	Init(screensize.width, screensize.height);

	FontChanged();

	// コンテキストメニューイベントを動的に追加
	// (コンテキストメニューを表示したくない人があとから外せるようにするため)
	ConnectContextMenu();
}

// デストラクタ
WXTextScreen::~WXTextScreen()
{
}

// (再)初期化
void
WXTextScreen::Init(int col, int row)
{
	screen.Init(col, row);
	prevbuf.resize(col * row);
	InvalidateText();
}

// テキスト画面全体が次回再描画されるようにする
void
WXTextScreen::InvalidateText()
{
	std::fill(prevbuf.begin(), prevbuf.end(), 0xffff);
}

// フォントサイズ変更 (外部インタフェース)
void
WXTextScreen::FontChanged()
{
	inherited::FontChanged();

	// 再描画を強制する。
	// テキストの桁数行数は変わってないので再確保はしなくていい。
	InvalidateText();

	// コントロールの大きさを変更
	Fit();
}

void
WXTextScreen::Fit()
{
	// 指定の (row, col) にパディングを加味した大きさにする、
	// なので GetSize() ではない。
	wxSize size(GetScreenWidth(GetCol()), GetScreenHeight(GetRow()));

	// バックバッファのサイズを固定。
	SetMinBitmapSize(size);

	SetSize(size);
	SetMinSize(size);
}

void
WXTextScreen::OnSize(wxSizeEvent& event)
{
	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;
	}

	if (col != screen.GetCol() || row != screen.GetRow()) {
		Init(col, row);
	} else {
		// テキストバッファを再構成しなくても全域を再描画する必要がある
		InvalidateText();
	}
	// サイズが変わったらどちらにしてもパディングも塗り直す。
	draw_padding = true;

	event.Skip();
}

// テキストバッファの内容をパネルのバックグラウンドバッファに描画する。
void
WXTextScreen::Draw()
{
	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) {
		int px = pad_left;
		for (int x = 0; x < col; ) {
			uint16 data = *text;
			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(px, py, chr, attr);

					if (SJIS::IsZenkaku(*prev) && x < col - 1) {
						// 変更前が全角文字だったら 2 バイト目も消す
						prev[1] = 0;
					}
					*prev = data;
				}
				prev += 1;
				text += 1;
				x += 1;
				px += font_width * 1;
			} else {
				// 全角文字
				// 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 += 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<uint16>& src = screen.GetBuf();
	auto s = src.begin();

	std::vector<uint8> 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
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.