--- nono/wx/wxmonitor.cpp 2026/04/29 17:04:39 1.1.1.3 +++ nono/wx/wxmonitor.cpp 2026/04/29 17:05:44 1.1.1.10 @@ -4,36 +4,94 @@ // Licensed under nono-license.txt // +// +// モニター +// + #include "wxmonitor.h" -#include "wxtextscreen.h" +#include "wxmainframe.h" +#include "monitor.h" + +//#define WINDOW_DEBUG 1 + +#if defined(WINDOW_DEBUG) +#define DPRINTF(fmt...) printf(fmt) +#else +#define DPRINTF(fmt...) /**/ +#endif // -// モニターウィンドウ +// モニターパネル // +// イベントテーブル +wxBEGIN_EVENT_TABLE(WXMonitorPanel, inherited) + EVT_TIMER(wxID_ANY, WXMonitorPanel::OnTimer) +wxEND_EVENT_TABLE() + // コンストラクタ -WXMonitorWindow::WXMonitorWindow(wxWindow *parent, wxWindowID id, - const wxString& name, IMonitor& monitor_) - : inherited(parent, id, name) -{ - // ↓+------------------+ - // | 上余白 | - // |左+------------+右| - // |余| TextScreen |余| - // |白+------------+白| - // | 下余白 | - // +------------------+ - - wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL); - - // テキストスクリーン - screen = new WXTextScreen(this, monitor_); - topsizer->Add(screen, 0, wxEXPAND | wxALL, DefaultPadding); +WXMonitorPanel::WXMonitorPanel(wxWindow *parent, Monitor *monitor_) + : inherited(parent, monitor_->GetSize()) + , monitor(monitor_) +{ + timer.SetOwner(this); + + // 現在の設定値を適用 + auto mainframe = dynamic_cast(GetParent()->GetParent()); + SetRate(mainframe->GetMonitorRate()); + + // 最初に一回描画を起こす + wxTimerEvent dummy(timer); + AddPendingEvent(dummy); +} - SetSizer(topsizer); +// デストラクタ +WXMonitorPanel::~WXMonitorPanel() +{ +} + +// タイマーイベント +void +WXMonitorPanel::OnTimer(wxTimerEvent& event) +{ + DoRefresh(); +} + +// 画面を更新して再描画する。 +// +// 通常はタイマーイベントにより更新間隔ごとに呼ばれるが、 +// ユーザ操作により画面の更新が必要ならその都度直接これを呼ぶ。 +void +WXMonitorPanel::DoRefresh() +{ + // モニタースクリーンを更新して + MONITOR_UPDATE(monitor, screen); + + // 必要なら再描画 + if (screen.GetBuf() != prevbuf) { + Refresh(); + } +} - // 大きさをそれに固定 - Layout(); +// 画面更新頻度を Hz で設定する。 +void +WXMonitorPanel::SetRate(int hz) +{ + timer.Start(1000 / hz); +} + + +// +// モニターウィンドウ +// + +// コンストラクタ +WXMonitorWindow::WXMonitorWindow(wxWindow *parent, const wxString& name, + Monitor *monitor_) + : inherited(parent, wxID_ANY, name) +{ + // モニタパネル + monpanel = new WXMonitorPanel(this, monitor_); Fit(); } @@ -41,3 +99,225 @@ WXMonitorWindow::WXMonitorWindow(wxWindo WXMonitorWindow::~WXMonitorWindow() { } + + +// +// 縦スクロールバー付きモニタウィンドウ +// + +// コンストラクタ +WXScrollMonitorWindow::WXScrollMonitorWindow(wxWindow *parent, + const wxString& name, Monitor *monitor_) + : inherited(parent, wxID_ANY, name, DEFAULT_STYLE | wxRESIZE_BORDER) +{ + DPRINTF("%s begin\n", __method__); + + // +--------------+---------+ + // | MonitorPanel | VScroll | + // +--------------+---------+ + + // モニタパネル + monpanel = new WXMonitorPanel(this, monitor_); + + // スクロールバー + vscroll = new WXScrollBar(this, wxID_ANY, wxSB_VERTICAL); + + // Sizer 使わず自前でレイアウトする。 + SetAutoLayout(true); + + // ウィンドウサイズを確定させる + FontChanged(); + + // スクロールのために (パネル領域での) MouseWheel イベントもここで + // 受け取りたい。スクロールバー上のマウスホイール操作はスクロールバーが + // 処理しているので問題ないが、パネル領域でのマウスホイール操作はここ + // (wxFrame)ではなくパネルに対して飛んでくる。 + // ここで一緒に処理したほうが楽なので、こちらに回す。 + monpanel->Connect(wxEVT_MOUSEWHEEL, + wxMouseEventHandler(WXScrollMonitorWindow::OnMouseWheel), NULL, this); + + // スクロールバーからの位置変更通知 + vscroll->Connect(NONO_EVT_SCROLL, + wxScrollEventHandler(WXScrollMonitorWindow::OnScroll), NULL, this); + DPRINTF("%s done\n", __method__); +} + +// デストラクタ +WXScrollMonitorWindow::~WXScrollMonitorWindow() +{ +} + +void +WXScrollMonitorWindow::FontChanged() +{ + monpanel->FontChanged(); + vscroll->FontChanged(); + DPRINTF("%s %d\n", __method__, monpanel->GetFontHeight()); + + Fit(); +} + +void +WXScrollMonitorWindow::Fit() +{ + // 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 +WXScrollMonitorWindow::Layout() +{ + // inherited::Layout() は(実質何もしないので)、呼ばずに自力で行う。 + + wxSize client = GetClientSize(); + const wxSize win = GetSize(); + const wxSize margin = win - client; + const wxSize vsize = vscroll->GetSize(); + const wxSize monsize = monpanel->GetSize(); +#if defined(WINDOW_DEBUG) + const wxSize min = GetMinClientSize(); + int py = client.y + - monpanel->GetPaddingTop() - monpanel->GetPaddingBottom(); + int height = monpanel->GetFontHeight(); + int row = (height != 0) ? (py / height) : 0; + int res = (height != 0) ? (py % height) : 0; + wxSize minwin = GetMinSize(); + printf("%s begin: margin=(%d,%d) shown=%d\n", __method__, + margin.x, margin.y, IsShown()); + printf("%s WinSize =(%d,%d) MinWin =(%d,%d)\n", __method__, + win.x, win.y, minwin.x, minwin.y); + printf("%s ClientSize=(%d,%d) MinClient=(%d,%d)\n", __method__, + client.x, client.y, min.x, min.y); + printf("%s mon=(%d,%d) vscroll=(%d,%d), row=%d%s\n", __method__, + monsize.x, monsize.y, vsize.x, vsize.y, + row, (res == 0 ? "" : "+")); +#endif + + if (oldmargin != margin) { + // ウィンドウマネージャ管轄のサイズが変わったので最小サイズを再設定。 + // ここでは出来ないのでこの関数を抜けた後 CallAfter 内で設定する。 + layout_pass2 = true; + oldmargin = margin; + CallAfter([this]() { + DoSizeHints(); + }); + DPRINTF("%s done(pass1)\n", __method__); + return true; + } else if (layout_pass2) { + // SetSizeHints() 後に呼ばれるところ。 + // なぜか設定したサイズと変わっている(ことがある?)ので、その場合は + // 仕方ないのでここで元の要求サイズに再設定する。 + // ウィンドウマネージャによっては表示サイズが変わらない(?)ので + // Hide()/Show() で叩き起こすと直るようだ??。 + layout_pass2 = false; + client.y = monsize.y; + DPRINTF("%s SetClientSize (%d,%d)\n", __method__, client.x, client.y); + SetClientSize(client); + Hide(); + Show(); + DPRINTF("%s done(pass2)\n", __method__); + return true; + } + + // コントロールを配置。 + int mon_width = client.x - vsize.x; + int mon_height = client.y; + // 最低でも 1px ないと GTK とかに怒られる。 + if (mon_width < 1) { + mon_width = 1; + } + if (mon_height < 1) { + mon_height = 1; + } + + monpanel->SetSize(0, 0, mon_width, mon_height); + vscroll->SetSize(mon_width, 0, vsize.x, mon_height); + + // スクロールバーを再設定。 + // 今の所ヘッダが常に1行ある。 + static int HEADLINES = 1; + int pos = (int)monpanel->GetUserData(); + int thumbsize = monpanel->GetRow() - HEADLINES; + int range = monpanel->GetMonitor()->GetMaxHeight() - HEADLINES; + int pagesize = thumbsize - 1; + if (pos > range - thumbsize) { + pos = range - thumbsize; + DoScroll(pos); + } + vscroll->SetScrollParam(pos, thumbsize, range, pagesize); + + DPRINTF("%s done\n", __method__); + + return true; +} + +// クライアントサイズと最大/最小サイズを計算する。 +// SetSizeHints() を実行し、設定すべきクライアントサイズを返す。 +wxSize +WXScrollMonitorWindow::DoSizeHints() +{ + auto psize = monpanel->GetSize(); + auto vsize = vscroll->GetSize(); + + // 幅は monpanel 幅 + スクロールバーの幅、高さは monpanel の高さで決まる。 + // monpanel には四辺に Padding があることに注意。 + int x = psize.x + vsize.x; + int min_y = std::max(vscroll->GetMinSize().y, + monpanel->GetScreenHeight(1)); + int new_y = std::max(psize.y, min_y); + wxSize minsize(x, min_y); + wxSize maxsize(x, GetMaxClientSize().y); + wxSize incsize(0, monpanel->GetFontHeight()); + wxSize minwin = ClientToWindowSize(minsize); + wxSize maxwin = ClientToWindowSize(maxsize); + wxSize newsize(x, new_y); + DPRINTF("%s MinWin=(%d,%d) RecommendClient=(%d,%d)\n", __method__, + minwin.x, minwin.y, newsize.x, newsize.y); + SetSizeHints(minwin, maxwin, incsize); + + return newsize; +} + +// マウスホイールイベント (WXTextScreen 上で起きたやつをこっちに回してある) +void +WXScrollMonitorWindow::OnMouseWheel(wxMouseEvent& event) +{ + // GetWheelRotation() は上(奥)向きに回すと +120、下(手前)に回すと -120。 + // どこの決まりか知らんけど、スクロールバーのほうはホイール1段階で3行 + // スクロールするのでそれに合わせる。 + int pos = (int)monpanel->GetUserData(); + 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 +WXScrollMonitorWindow::OnScroll(wxScrollEvent& event) +{ + DoScroll(event.GetPosition()); +} + +// スクロール処理 (OnScroll() と OnMouseWheel() から呼ばれる) +void +WXScrollMonitorWindow::DoScroll(int pos) +{ + // userdata が表示開始位置になっている + monpanel->SetUserData(pos); + monpanel->DoRefresh(); +}