--- nono/wx/wxlogmonitor.cpp 2026/04/29 17:04:56 1.1.1.7 +++ nono/wx/wxlogmonitor.cpp 2026/04/29 17:05:44 1.1.1.13 @@ -4,84 +4,70 @@ // 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) { + DPRINTF("%s begin\n", __method__); col = 80; row = 40; - monitor.obj = &log; - monitor.func = (MonitorCallback_t)&LogMonitor::MonitorUpdate; - monitor.SetSize(col, row); - // プライベート利用なので Regist() は不要 + // プライベート利用なので Regist() しない。 + monitor.reset(new Monitor(-1, &log)); + monitor->func = ToMonitorCallback(&LogMonitor::MonitorUpdate); + monitor->SetSize(col, row); 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); + // +------------+---------+ + // | TextScreen | VScroll | + // +------------+---------+ + + // テキストスクリーン + screen = new WXMonitorPanel(this, monitor.get()); - // 右余白 - 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); + // Sizer 使わず自前でレイアウトする。 + SetAutoLayout(true); // ウィンドウサイズを確定させる - DoSize(); + FontChanged(); // スクロールのために (パネル領域での) MouseWheel イベントもここで - // 受け取りたい。スクロールバー上のマウスホイール操作は最初から Scroll - // イベントとして処理されているので問題ないが、パネル領域でのマウス - // ホイール操作はここ(wxFrame)ではなくパネルに対して飛んでくる。 + // 受け取りたい。スクロールバー上のマウスホイール操作はスクロールバーが + // 処理しているので問題ないが、パネル領域でのマウスホイール操作はここ + // (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__); } // デストラクタ @@ -89,21 +75,100 @@ WXLogMonitor::~WXLogMonitor() { } -// フォントサイズが確定したら、それによってウィンドウサイズを再計算する。 void -WXLogMonitor::DoSize() +WXLogMonitor::FontChanged() { - CalcVSize(0); + screen->FontChanged(); + vscroll->FontChanged(); + DPRINTF("%s %d\n", __method__, screen->GetFontHeight()); + + Fit(); } -// サイズ変更イベント void -WXLogMonitor::OnSize(wxSizeEvent& event) +WXLogMonitor::Fit() { - inherited::OnSize(event); + // inherited::Fit() は呼ばずに自力で行う。 + + wxSize newsize = DoSizeHints(); +#if defined(WINDOW_DEBUG) + wxSize newwin = ClientToWindowSize(newsize); + printf("%s (%d,%d) client=(%d,%d)\n", __method__, + newwin.x, newwin.y, newsize.x, newsize.y); +#endif + SetClientSize(newsize); +} + +bool +WXLogMonitor::Layout() +{ + // inherited::Layout() は(実質何もしないので)、呼ばずに自力で行う。 + + wxSize client = GetClientSize(); + const wxSize win = GetSize(); + const wxSize margin = win - client; + const wxSize vsize = vscroll->GetSize(); + const wxSize monsize = screen->GetSize(); + + // wxmonitor.cpp の Layout() 内のコメント参照。 + if (oldmargin != margin) { + layout_pass2 = true; + oldmargin = margin; + CallAfter([this]() { + DoSizeHints(); + }); + return true; + } else if (layout_pass2) { + layout_pass2 = false; + client.y = monsize.y; + SetClientSize(client); + Hide(); + Show(); + return true; + } + + // コントロールを配置。 + int panel_width = client.x - vsize.x; + int panel_height = client.y; + // 最低でも 1px ないと GTK とかに怒られる。 + if (panel_width < 1) { + panel_width = 1; + } + if (panel_height < 1) { + panel_height = 1; + } + + screen->SetSize(0, 0, panel_width, panel_height); + vscroll->SetSize(panel_width, 0, vsize.x, panel_height); // スクロールバーを再設定 SetScroll(); + + DPRINTF("%s done\n", __method__); + + return true; +} + +// クライアントサイズと最大/最小サイズを計算する。 +// SetSizeHints() を実行し、設定すべきクライアントサイズを返す。 +wxSize +WXLogMonitor::DoSizeHints() +{ + auto ssize = screen->GetSize(); + auto vsize = vscroll->GetSize(); + DPRINTF("%s screen=(%d,%d) vscroll=(%d,%d)\n", __method__, + ssize.x, ssize.y, vsize.x, vsize.y); + + 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 minsize(x, min_y); + wxSize maxsize(x, GetMaxClientSize().y); + wxSize incsize(0, screen->GetFontHeight()); + SetSizeHints(ClientToWindowSize(minsize), ClientToWindowSize(maxsize), + incsize); + + return wxSize(x, new_y); } // スクロールバーを再設定 @@ -126,7 +191,7 @@ WXLogMonitor::SetScroll() } thumbsize = row; pagesize = thumbsize - 1; - vscroll->SetScrollbar(pos, thumbsize, range, pagesize); + vscroll->SetScrollParam(pos, thumbsize, range, pagesize); } // マウスホイールイベント (WXTextScreen 上で起きたやつをこっちに回してある) @@ -146,13 +211,14 @@ WXLogMonitor::OnMouseWheel(wxMouseEvent& pos = maxpos; DoScroll(pos); + // スクロールバーの位置を追従 + vscroll->SetThumbPosition(pos); } -// スクロールイベント (全部入り) +// スクロールバーからの通知イベント void WXLogMonitor::OnScroll(wxScrollEvent& event) { - // Thumb の上端の位置 (0 なら Thumb が上端に接している) DoScroll(event.GetPosition()); } @@ -182,18 +248,16 @@ WXLogMonitor::DoScroll(int pos) // パネル最下行に表示する行の最新行からのオフセット // (0 なら Thumb が下端に接している) vpos = vscroll->GetRange() - vscroll->GetThumbSize() - pos; - - // Thumb の位置を更新 - vscroll->SetThumbPosition(pos); } + // // モニターオブジェクト // // コンストラクタ LogMonitor::LogMonitor() - : inherited("LogMonitor") + : inherited(OBJ_LOGMONITOR) { } @@ -207,6 +271,8 @@ LogMonitor::Init(WXLogMonitor *parent_) { parent = parent_; + logger = gMainApp.GetLogger(); + // バックログは今の所固定長 logbuf.Init(parent->col, backlog); logbuf.Mode = TextScreen::Ring; @@ -230,9 +296,9 @@ LogMonitor::MonitorUpdate(Monitor *, Tex // 流速が早すぎた場合にこのループから抜けるタイミングがなくなる可能性が // (可能性としては) あるので、適当に1画面分くらいで打ち切る。 // 取り込み残した分は次回取り込まれるし、その流速も上回るようだと - // どのみち gLogger の固定長キューで落ちる。 + // どのみち Logger の固定長キューで落ちる。 for (int i = 0; i < parent->row; i++) { - if (gLogger.Read(buf, sizeof(buf)) == false) + if (logger->Read(buf, sizeof(buf)) == false) break; Append(buf); } @@ -255,7 +321,9 @@ LogMonitor::MonitorUpdate(Monitor *, Tex // 画面コピー logbuf.Locate(0, srcY); for (int i = 0; i < parent->col * parent->row; i++) { - screen.Putc(logbuf.Getc()); + uint16 ch = logbuf.Getc(); + logbuf.Ascend(); + screen.Putc(ch); } } @@ -303,17 +371,17 @@ LogMonitor::IncLines() void LogMonitor::Append(const char *utfbuf) { - const char *sjis; const char *s; int x; // 文字コードの変換 wxString utfstr(utfbuf, wxConvUTF8); - sjis = (const char *)utfstr.mb_str(conv); + // mb_str() をキャストせずに一度保持しておく必要がある (scan-build 対策) + auto sjis = utfstr.mb_str(conv); logbuf.Locate(0, cursor); - for (s = sjis; *s; ) { + for (s = (const char *)sjis; *s; ) { // 最終桁で2バイト文字の1バイト目が来たら // 代わりに1文字空白を入れる (それによってこの後改行が起きる) if (SJIS::IsZenkaku(*s)) {