--- nono/wx/wxlogmonitor.cpp 2026/04/29 17:04:56 1.1.1.7 +++ nono/wx/wxlogmonitor.cpp 2026/04/29 17:05:57 1.1.1.15 @@ -4,84 +4,60 @@ // Licensed under nono-license.txt // +// +// ログモニター +// + #include "wxlogmonitor.h" #include "logger.h" +#include "mainapp.h" +#include "monitor.h" #include "sjis.h" -// -// ログモニター -// +//#define WINDOW_DEBUG 1 -// イベントテーブル -wxBEGIN_EVENT_TABLE(WXLogMonitor, inherited) - EVT_SIZE(WXLogMonitor::OnSize) - EVT_SCROLL(WXLogMonitor::OnScroll) -wxEND_EVENT_TABLE() +#if defined(WINDOW_DEBUG) +#define DPRINTF(fmt...) printf(fmt) +#else +#define DPRINTF(fmt...) /**/ +#endif // コンストラクタ WXLogMonitor::WXLogMonitor(wxWindow *parent) - : inherited(parent, wxID_ANY, _("Log")) + : inherited(parent, wxID_ANY, _("Log"), DEFAULT_STYLE | wxRESIZE_BORDER) { - col = 80; - row = 40; + DPRINTF("%s begin\n", __method__); + + // +-----------------+---------+ + // | LogMonitorPanel | VScroll | + // +-----------------+---------+ - monitor.obj = &log; - monitor.func = (MonitorCallback_t)&LogMonitor::MonitorUpdate; - monitor.SetSize(col, row); - // プライベート利用なので Regist() は不要 - - log.Init(this); - - // → - // +--+-----------------------+--+--+ - // | |↓+------------------+ | | | - // | | | 上余白(padding1) | | | | - // |左| +------------------+ |右| | - // |余| | TextScreen | |余| <- VScroll - // |白| +------------------+ |白| | - // | | | 下余白(padding2) | | | | - // | | +------------------+ | | | - // +--+-----------------------+--+--+ - - topsizer = new wxBoxSizer(wxHORIZONTAL); - // 左余白 - topsizer->Add(new WXPaddingPanel(this), 0, wxEXPAND); - // 中列の縦 sizer - auto *vbox = new wxBoxSizer(wxVERTICAL); - topsizer->Add(vbox, 0, wxEXPAND); - - // パディングパネル(上下) - // ウィンドウを1行ずつ伸縮させるためには、クライアント領域がフォント高さの - // 整数倍になってないといけない。TextScreen はそれ自身が行の整数倍の大きさ - // だが、上下の余白はその時点のフォントサイズによって変更する必要がある。 - // 先にサイズ不定のパネルだけ置いといて、フォントサイズ確定後に大きさを - // 確定させる。DoSize() 参照。 - padding1 = new WXPaddingPanel(this); - padding2 = new WXPaddingPanel(this); - screen = new WXTextScreen(this, monitor); - vbox->Add(padding1, 0, wxEXPAND); - vbox->Add(screen, 1, wxEXPAND); - vbox->Add(padding2, 0, wxEXPAND); + // モニタ + logger = gMainApp.GetLogger(); + screen = new LogMonitorPanel(this, logger->GetMonitor()); - // 右余白 - topsizer->Add(new WXPaddingPanel(this), 0, wxEXPAND); // スクロールバー - vscroll = new wxScrollBar(this, wxID_ANY, wxDefaultPosition, - wxDefaultSize, wxSB_VERTICAL); - topsizer->Add(vscroll, 0, wxEXPAND); + vscroll = new WXScrollBar(this, wxID_ANY, wxSB_VERTICAL); - SetSizer(topsizer); + // 自前レイアウト。 + SetMyLayout(); // ウィンドウサイズを確定させる - DoSize(); + FontChanged(); // スクロールのために (パネル領域での) MouseWheel イベントもここで - // 受け取りたい。スクロールバー上のマウスホイール操作は最初から Scroll - // イベントとして処理されているので問題ないが、パネル領域でのマウス - // ホイール操作はここ(wxFrame)ではなくパネルに対して飛んでくる。 + // 受け取りたい。スクロールバー上のマウスホイール操作はスクロールバーが + // 処理しているので問題ないが、パネル領域でのマウスホイール操作はここ + // (wxFrame)ではなくパネルに対して飛んでくる。 // ここで一緒に処理したほうが楽なので、こちらに回す。 - screen->Connect(wxEVT_MOUSEWHEEL, - wxMouseEventHandler(WXLogMonitor::OnMouseWheel), NULL, this); + screen->Bind(wxEVT_MOUSEWHEEL, &WXLogMonitor::OnMouseWheel, this); + + // スクロールバーからの位置変更通知イベントは表示パネルへ回す。 + vscroll->Bind(NONO_EVT_SCROLL, &WXLogMonitor::OnScroll, this); + + // XXX ログウィンドウのコピーはまだ動いてない + screen->DisconnectContextMenu(); + DPRINTF("%s done\n", __method__); } // デストラクタ @@ -89,44 +65,90 @@ WXLogMonitor::~WXLogMonitor() { } -// フォントサイズが確定したら、それによってウィンドウサイズを再計算する。 void -WXLogMonitor::DoSize() +WXLogMonitor::FontChanged() { - CalcVSize(0); -} + screen->FontChanged(); + vscroll->FontChanged(); + DPRINTF("%s %d\n", __method__, screen->GetFontHeight()); + + Fit(); +} + +// 自前レイアウト方式のサイズに関する情報を返す。 +bool +WXLogMonitor::GetMySizeHints(wxSize *newp, wxSize *minp, wxSize *maxp, + wxSize *incp) +{ + auto ssize = screen->GetSize(); + auto vsize = vscroll->GetSize(); + + 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; +} + +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; + } -// サイズ変更イベント -void -WXLogMonitor::OnSize(wxSizeEvent& event) -{ - inherited::OnSize(event); + screen->SetSize(0, 0, panel_width, panel_height); + vscroll->SetSize(panel_width, 0, vsize.x, panel_height); - // スクロールバーを再設定 - SetScroll(); + // スクロールバーを再設定 + 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 (lines <= row) { + if (displines <= row) { pos = 0; range = row; } else { - pos = (lines - row) - vpos; - range = lines; + pos = (displines - row) - screen->GetUserData(); + range = displines; } thumbsize = row; pagesize = thumbsize - 1; - vscroll->SetScrollbar(pos, thumbsize, range, pagesize); + vscroll->SetScrollParam(pos, thumbsize, range, pagesize); } // マウスホイールイベント (WXTextScreen 上で起きたやつをこっちに回してある) @@ -146,13 +168,14 @@ WXLogMonitor::OnMouseWheel(wxMouseEvent& pos = maxpos; DoScroll(pos); + // スクロールバーの位置を追従 + vscroll->SetThumbPosition(pos); } -// スクロールイベント (全部入り) +// スクロールバーからの通知イベント void WXLogMonitor::OnScroll(wxScrollEvent& event) { - // Thumb の上端の位置 (0 なら Thumb が上端に接している) DoScroll(event.GetPosition()); } @@ -162,14 +185,14 @@ WXLogMonitor::DoScroll(int pos) { // pos 即ちスクロールバーの Thumb の位置は上端の位置。 // 対してログウィンドウでは下端を基準にしたほうが処理しやすい。 - // vpos = 0 だと Thumb が下に接していてパネルの最下行が最新行、 - // vpos = 1 だと Thumb が下から1段階分上にありパネルの最下行は最新行の - // 一つ前の行、となる。 + // userdata = 0 だと Thumb が下に接していてパネルの最下行が最新行、 + // userdata = 1 だと Thumb が下から1段階分上にありパネルの最下行は + // 最新行の一つ前の行、となる。 // // Range=6 Range=6 // Pos=4 Pos=0 // ThumbSz=2 ThumbSz=2 - // vpos=0 vpos=4 + // userdata=0 userdata=4 // △ △ // □ ■ // □ ■ @@ -181,167 +204,32 @@ WXLogMonitor::DoScroll(int pos) // パネル最下行に表示する行の最新行からのオフセット // (0 なら Thumb が下端に接している) - vpos = vscroll->GetRange() - vscroll->GetThumbSize() - pos; - - // Thumb の位置を更新 - vscroll->SetThumbPosition(pos); + int vpos = vscroll->GetRange() - vscroll->GetThumbSize() - pos; + screen->SetUserData(vpos); } + // // モニターオブジェクト // // コンストラクタ -LogMonitor::LogMonitor() - : inherited("LogMonitor") -{ -} - -// デストラクタ -LogMonitor::~LogMonitor() -{ -} - -void -LogMonitor::Init(WXLogMonitor *parent_) +LogMonitorPanel::LogMonitorPanel(WXLogMonitor *parent_, Monitor *monitor_) + : inherited(parent_, monitor_) { parent = parent_; - - // バックログは今の所固定長 - logbuf.Init(parent->col, backlog); - logbuf.Mode = TextScreen::Ring; } -// モニターテキストを更新 -void -LogMonitor::MonitorUpdate(Monitor *, TextScreen& screen) -{ - char buf[1024]; - - // どこでどうするかはあるけど、 - // とりあえず row は常に現在の TextScreen の高さということにしておく - parent->row = screen.GetRow(); - - // XXX: 生成時にやるべき - screen.Mode = TextScreen::Ring; - - // キューから読めるだけバックログに書き込む。 - // 読めるだけと言っても while ループで読めるだけ読んでしまうと、 - // 流速が早すぎた場合にこのループから抜けるタイミングがなくなる可能性が - // (可能性としては) あるので、適当に1画面分くらいで打ち切る。 - // 取り込み残した分は次回取り込まれるし、その流速も上回るようだと - // どのみち gLogger の固定長キューで落ちる。 - for (int i = 0; i < parent->row; i++) { - if (gLogger.Read(buf, sizeof(buf)) == false) - break; - Append(buf); - } - - screen.Clear(); - - // スクロールバー(vpos)による表示位置計算 - // vpos は行数、v は logbuf の Y 座標 - int v = cursor - parent->vpos; - if (v < 0) { - v += backlog; - } - - // 表示位置(v)の Row 行分手前から順に表示 - int srcY = v - parent->row; - if (srcY < 0) { - srcY += backlog; - } - - // 画面コピー - logbuf.Locate(0, srcY); - for (int i = 0; i < parent->col * parent->row; i++) { - screen.Putc(logbuf.Getc()); - } -} - -// 1行追加された時の共通処理 -void -LogMonitor::IncLines() +// デストラクタ +LogMonitorPanel::~LogMonitorPanel() { - // スクロールバーが下端にない時(バックログを見ている時)は - // ログが追加されても現在位置をキープする。 - if (parent->vpos > 0) { - // 上端に達したら増加させない - if (parent->vpos < backlog - parent->vscroll->GetThumbSize()) { - parent->vpos++; - } - } - - // 有効行数を増やす - if (parent->lines < backlog) { - parent->lines++; - - // 行数が変わったのでスクロールバーを更新 - parent->SetScroll(); - } } -// バックログに行を追加。 -// -// 引数の utfbuf は UTF-8 で改行コードのない1行ずつ。 -// これをバックログの最新位置に書き込む。古い行は順次上書きされる。 -// -// バックログは col * backlog バイトの Shift_JIS バッファ。 -// 属性は持たず文字コードのみ。漢字は2バイト幅をとる。 -// 横幅 col が固定なので書き込む時点で行数は表示用のそれと一致する。 -// -// cursor はこれから書き込む位置の行頭。cursor は必ず行頭だけをポイント -// しながら移動する。 -// -// logbuf +------------------------ -// | : -// | 前回書き込んだ行 -// cursor -> | 今から書きこむ行 (保存されていた一番古い行) -// : -// | -// +------------------------ void -LogMonitor::Append(const char *utfbuf) +LogMonitorPanel::DoRefresh() { - const char *sjis; - const char *s; - int x; - - // 文字コードの変換 - wxString utfstr(utfbuf, wxConvUTF8); - sjis = (const char *)utfstr.mb_str(conv); - - logbuf.Locate(0, cursor); - - for (s = sjis; *s; ) { - // 最終桁で2バイト文字の1バイト目が来たら - // 代わりに1文字空白を入れる (それによってこの後改行が起きる) - if (SJIS::IsZenkaku(*s)) { - // 漢字ならば 2 バイトセットで扱う - if (logbuf.GetX() >= parent->col - 1) { - // 最終桁には出せないので空白で次の行に送る - logbuf.Putc(' '); - IncLines(); - } - logbuf.Putc(*s++); - // 万が一に備える - if (__predict_true(*s)) { - logbuf.Putc(*s++); - } - } else { - // そうでなければそのまま出力 - logbuf.Putc(*s++); - } - } + inherited::DoRefresh(); - // 行の終わりまでは空白でクリアする - x = logbuf.GetX(); - if (x > 0) { - for (; x < logbuf.GetCol(); x++) { - logbuf.Putc(' '); - } - IncLines(); - } - // 次回書き込みの Y 座標 - cursor = logbuf.GetY(); + // スクロールバーを再設定。 + parent->SetScroll(); }