--- nono/wx/wxlogmonitor.cpp 2026/04/29 17:04:36 1.1.1.3 +++ nono/wx/wxlogmonitor.cpp 2026/04/29 17:05:48 1.1.1.14 @@ -4,50 +4,62 @@ // Licensed under nono-license.txt // -#include "wxlogmonitor.h" -#include "logger.h" - // // ログモニター // -// イベントテーブル -wxBEGIN_EVENT_TABLE(WXLogMonitor, inherited) - EVT_TIMER(wxID_ANY, WXLogMonitor::OnTimer) -wxEND_EVENT_TABLE() +#include "wxlogmonitor.h" +#include "logger.h" +#include "mainapp.h" +#include "monitor.h" +#include "sjis.h" + +//#define WINDOW_DEBUG 1 + +#if defined(WINDOW_DEBUG) +#define DPRINTF(fmt...) printf(fmt) +#else +#define DPRINTF(fmt...) /**/ +#endif // コンストラクタ -WXLogMonitor::WXLogMonitor(wxWindow *parent, wxWindowID id) - : inherited(parent, id, _("Log")) +WXLogMonitor::WXLogMonitor(wxWindow *parent) + : inherited(parent, wxID_ANY, _("Log"), DEFAULT_STYLE | wxRESIZE_BORDER) { - col = 80; - row = 40; - - logbuf.reset(new uint8 [row * col]); - memset(&logbuf[0], 0x20, row * col); - text.Init(col, row); - - wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL); + DPRINTF("%s begin\n", __method__); - // テキストスクリーン - screen = new WXTextScreen(this, col, row, text.GetBuf()); - topsizer->Add(screen, BorderFlags); - - SetSizer(topsizer); - - // 大きさを固定 - Layout(); - Fit(); - - // 最初に一度描画しておく。 - // ウィンドウが表示されてもまだログが出てない場合、タイマーイベントが - // おきても更新が起きず、グレーの背景が見えてしまうため。 - // (ウィンドウの表示が早い場合や、ログレベルが低い場合は有り得る) - screen->Refresh(); - - // 更新タイマーは適当な短さにしておく - timer.SetOwner(this); - timer.Start(10); + // +-----------------+---------+ + // | LogMonitorPanel | VScroll | + // +-----------------+---------+ + + // モニタ + logger = gMainApp.GetLogger(); + screen = new LogMonitorPanel(this, logger->GetMonitor()); + + // スクロールバー + vscroll = new WXScrollBar(this, wxID_ANY, wxSB_VERTICAL); + + // 自前レイアウト。 + SetMyLayout(); + + // ウィンドウサイズを確定させる + FontChanged(); + + // スクロールのために (パネル領域での) MouseWheel イベントもここで + // 受け取りたい。スクロールバー上のマウスホイール操作はスクロールバーが + // 処理しているので問題ないが、パネル領域でのマウスホイール操作はここ + // (wxFrame)ではなくパネルに対して飛んでくる。 + // ここで一緒に処理したほうが楽なので、こちらに回す。 + screen->Connect(wxEVT_MOUSEWHEEL, + wxMouseEventHandler(WXLogMonitor::OnMouseWheel), NULL, this); + + // スクロールバーからの位置変更通知イベントは表示パネルへ回す。 + vscroll->Connect(NONO_EVT_SCROLL, + wxScrollEventHandler(WXLogMonitor::OnScroll), NULL, this); + + // XXX ログウィンドウのコピーはまだ動いてない + screen->DisconnectContextMenu(); + DPRINTF("%s done\n", __method__); } // デストラクタ @@ -55,109 +67,171 @@ WXLogMonitor::~WXLogMonitor() { } -// タイマーイベント void -WXLogMonitor::OnTimer(wxTimerEvent& event) +WXLogMonitor::FontChanged() { - char buf[1024]; - bool updated = false; + screen->FontChanged(); + vscroll->FontChanged(); + DPRINTF("%s %d\n", __method__, screen->GetFontHeight()); - // キューから読めるだけ全部ログバッファに書き込む - while (gLogger.Read(buf, sizeof(buf))) { - Append(buf); - updated = true; - } + Fit(); +} - // ログの追加がなければここまで - if (!updated) { - return; - } +// 自前レイアウト方式のサイズに関する情報を返す。 +bool +WXLogMonitor::GetMySizeHints(wxSize *newp, wxSize *minp, wxSize *maxp, + wxSize *incp) +{ + auto ssize = screen->GetSize(); + auto vsize = vscroll->GetSize(); - // ここは TextScreen の内部構造に直接書き込むことにする。 - uint16 *textbuf = text.GetRawBuf(); + int x = ssize.x + vsize.x; + int min_y = std::max(vscroll->GetMinSize().y, screen->GetScreenHeight(1)); + int new_y = std::max(ssize.y, min_y); + wxSize newsize(x, new_y); + wxSize minsize(x, min_y); + wxSize maxsize(x, GetMaxClientSize().y); + wxSize incsize(0, screen->GetFontHeight()); + + if (newp) *newp = newsize; + if (minp) *minp = minsize; + if (maxp) *maxp = maxsize; + if (incp) *incp = incsize; + return true; +} - // ログバッファ(循環バッファ)をテキストバッファに再構成。 - // target は logbuf のうち今表示されるべき一番古い行の先頭を指している - // のでこれがテキストバッファの先頭になる。そこから logbuf の末尾まで。 - // target == 0 の時に限りこの1回のコピーで終わり。それ以外の場合は必ず - // logbuf の先頭から target までの後半部分のコピーが発生する。 - for (int i = 0; i < col * row - target; i++) { - textbuf[i] = logbuf[target + i]; - } - if (target != 0) { - for (int i = 0; i < target; i++) { - textbuf[col * row - target + i] = logbuf[i]; +bool +WXLogMonitor::Layout() +{ + if (MyLayout() == false) { + // コントロールを配置。 + wxSize client = GetClientSize(); + const wxSize vsize = vscroll->GetSize(); + + int panel_width = client.x - vsize.x; + int panel_height = client.y; + // 最低でも 1px ないと GTK とかに怒られる。今は起きないはず? + if (__predict_false(panel_width < 1)) { + panel_width = 1; } + if (__predict_false(panel_height < 1)) { + panel_height = 1; + } + + screen->SetSize(0, 0, panel_width, panel_height); + vscroll->SetSize(panel_width, 0, vsize.x, panel_height); + + // スクロールバーを再設定 + SetScroll(); + } + return true; +} + +// スクロールバーを再設定 +void +WXLogMonitor::SetScroll() +{ + int displines; + int row; + int pos; + int thumbsize; + int range; + int pagesize; + + displines = logger->GetDispLines(); + row = screen->GetRow(); + + if (displines <= row) { + pos = 0; + range = row; + } else { + pos = (displines - row) - screen->GetUserData(); + range = displines; } + thumbsize = row; + pagesize = thumbsize - 1; + vscroll->SetScrollParam(pos, thumbsize, range, pagesize); +} - // 更新指示 - screen->Refresh(); +// マウスホイールイベント (WXTextScreen 上で起きたやつをこっちに回してある) +void +WXLogMonitor::OnMouseWheel(wxMouseEvent& event) +{ + // GetWheelRotation() は上(奥)向きに回すと +120、下(手前)に回すと -120。 + // どこの決まりか知らんけど、スクロールバーのほうはホイール1段階で3行 + // スクロールするのでそれに合わせる。 + int pos = vscroll->GetThumbPosition(); + pos = pos - event.GetWheelRotation() / 40; + + int maxpos = vscroll->GetRange() - vscroll->GetThumbSize(); + if (pos < 0) + pos = 0; + if (pos > maxpos) + pos = maxpos; + + DoScroll(pos); + // スクロールバーの位置を追従 + vscroll->SetThumbPosition(pos); } -// フォントサイズ変更 +// スクロールバーからの通知イベント void -WXLogMonitor::SetFontSize(fontsize_t fontsize) +WXLogMonitor::OnScroll(wxScrollEvent& event) { - screen->SetFontSize(fontsize); - // ログの再描画は自前で行う必要がある - screen->Refresh(); - // 大きさを変更 - Layout(); - Fit(); + DoScroll(event.GetPosition()); } -// 表示用のログバッファに buf を追加 -// -// logbuf は textbuf(, prevbuf) とかと同じ col * row バイトの単純バッファ -// 形式。ただしこれを行ごとの循環バッファとして使っていて開始位置が target。 -// target は logbuf からの距離 (logbuf[] のインデックス) で表し、行は必ず -// ウィンドウの行頭から追加することにすると target は col の倍数をとる。 +// スクロール処理 (OnScroll() と OnMouseWheel() から呼ばれる) +void +WXLogMonitor::DoScroll(int pos) +{ + // pos 即ちスクロールバーの Thumb の位置は上端の位置。 + // 対してログウィンドウでは下端を基準にしたほうが処理しやすい。 + // userdata = 0 だと Thumb が下に接していてパネルの最下行が最新行、 + // userdata = 1 だと Thumb が下から1段階分上にありパネルの最下行は + // 最新行の一つ前の行、となる。 + // + // Range=6 Range=6 + // Pos=4 Pos=0 + // ThumbSz=2 ThumbSz=2 + // userdata=0 userdata=4 + // △ △ + // □ ■ + // □ ■ + // □ □ + // □ □ + // ■ □ + // ■ □ + // ▽ ▽ + + // パネル最下行に表示する行の最新行からのオフセット + // (0 なら Thumb が下端に接している) + int vpos = vscroll->GetRange() - vscroll->GetThumbSize() - pos; + screen->SetUserData(vpos); +} + + // -// +---------------------------+ -// logbuf -> | | -// |前行 | -// target -> |次に書く行 (今表示されてる一番古い行) -// | | -// | | -// | | -// +---------------------------+ +// モニターオブジェクト // -// ついでに utfbuf は UTF-8 だが、表示用バッファ(textbuf) は Shift_JIS -// なのでここで変換する。 -void -WXLogMonitor::Append(const char *utfbuf) + +// コンストラクタ +LogMonitorPanel::LogMonitorPanel(WXLogMonitor *parent_, Monitor *monitor_) + : inherited(parent_, monitor_) { - const char *sjis; - int sjislen; - int spos; - - // 文字コードの変換 - wxString utfstr(utfbuf, wxConvUTF8); - sjis = (const char *)utfstr.mb_str(conv); - sjislen = strlen(sjis); - - // XXX 行末残り1桁で漢字が来た場合は? - - spos = 0; - for (;;) { - // この行に書き込む長さ - int len = std::min(sjislen - spos, col); - if (len < 1) { - break; - } + parent = parent_; +} - // コピー。残りは空白で埋める - memcpy(&logbuf[target], &sjis[spos], len); - if (len < col) { - memset(&logbuf[target + len], 0x20, col - len); - } +// デストラクタ +LogMonitorPanel::~LogMonitorPanel() +{ +} - spos += len; - target += col; +void +LogMonitorPanel::DoRefresh() +{ + inherited::DoRefresh(); - // 折り返し - if (target >= col * row) { - target = 0; - } - } + // スクロールバーを再設定。 + parent->SetScroll(); }