|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2021 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: // ステータスパネル
8: //
9: // 描画領域に対してフォントの開始位置は外枠から +4、内枠からなら +2。
10: // 横も同様。
11: //
12: // +0 ------ 外枠
13: // +1 アキ
14: // +2 +---- 内枠
15: // +3 | アキ
16: // +4 | ここからフォント
17:
18: #include "wxstatuspanel.h"
19: #include "wxcolor.h"
20: #include "wxmainview.h"
21: #include "bitrev.h"
22: #include "netdriver.h"
23: #include "scheduler.h"
24: #include "spc.h"
25:
26: WXStatusPanel *gStatusPanel;
27:
28: wxBEGIN_EVENT_TABLE(WXStatusPanel, inherited)
29: EVT_CREATE(WXStatusPanel::OnCreate)
30: EVT_CLOSE(WXStatusPanel::OnClose)
31: EVT_SHOW(WXStatusPanel::OnShow)
32: EVT_SIZE(WXStatusPanel::OnSize)
33: EVT_TIMER(wxID_ANY, WXStatusPanel::OnTimer)
34: EVT_PAINT(WXStatusPanel::OnPaint)
35: EVT_LEFT_DCLICK(WXStatusPanel::OnLeftDClick)
36: wxEND_EVENT_TABLE()
37:
38: // ステータスパネルの更新頻度 [Hz]
39: static constexpr uint FPS = 10;
40:
41: // style フラグ
42: // デフォルトは wxTAB_TRAVERSAL だがこれを外すので分かりづらい字面になる
43: static const long STATUS_PANEL_STYLE = 0;
44:
45: // コンストラクタ
46: WXStatusPanel::WXStatusPanel(wxWindow *parent)
47: : inherited(parent, STATUS_PANEL_STYLE)
48: {
49: timer.SetOwner(this);
50:
51: // インジケータ用のパラメータを用意。
52: InitIndicators();
53:
54: DoSize();
55:
56: // メインビューにイベントを接続したいがこの時点ではまだ gMainView が
57: // ないので OnCreate イベントで処理する。
58: POST_CREATE();
59: }
60:
61: // デストラクタ
62: WXStatusPanel::~WXStatusPanel()
63: {
64: }
65:
66: // ウィンドウ作成イベント
67: void
68: WXStatusPanel::OnCreate(wxCommandEvent& event)
69: {
70: // キー入力イベントをメインビューに転送。
71: // このパネルへのキー入力は不要なのと、メインウィンドウ内のコントロール
72: // フォーカスがこのステータスパネルとメインビューのどっちにあるかは
73: // 分かりづらくて、こっちがフォーカス持ってしまうと (というか起動後は
74: // こっちにある) VM にキー入力が出来ずに焦ることになるので、ここへの
75: // キー入力イベントは全部メインビューに飛ばす。
76: Connect(wxEVT_KEY_UP, wxKeyEventHandler(WXMainView::OnKeyUp), NULL,
77: gMainView);
78: Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL,
79: gMainView);
80: }
81:
82: // クローズイベント
83: void
84: WXStatusPanel::OnClose(wxCloseEvent& event)
85: {
86: // クローズする前に更新を止める。
87: timer.Stop();
88: }
89:
90: // フォントサイズ変更 (外部インタフェース)
91: void
92: WXStatusPanel::SetFontSize(FontId new_fontid)
93: {
94: // 同じなら何もしない
95: if (fontid == new_fontid) {
96: return;
97: }
98:
99: // フォントを変更
100: UpdateFont(new_fontid);
101:
102: // サイズ変更処理
103: DoSize();
104: }
105:
106: // 表示/非表示変更イベント
107: // new した時は (デフォルトで表示状態なのでか状態に変更がないという扱いの
108: // ようで) イベントは飛んでこない。同様に new 直後に Show() を明示発行しても
109: // 状態に変更がないためイベントは飛んでこない。
110: // new 直後に Hide() すればイベントは飛んでくる。という動作をするようだ。
111: // そのため WXMainFrame 側で生成後に Show/Hide を起こしている。
112: void
113: WXStatusPanel::OnShow(wxShowEvent& event)
114: {
115: if (event.IsShown()) {
116: // 非表示→表示
117:
118: // 表示開始前に一度情報を取得
119: UpdateStat();
120:
121: timer.Start(1000 / FPS);
122: } else {
123: // 表示→非表示
124:
125: timer.Stop();
126: }
127: }
128:
129: // サイズ変更処理
130: // サイズを変更したい時に呼ぶ (サイズが変更されたからではない)。
131: // ここではサイズは fontsize にのみ依存しているので、フォントサイズが
132: // 変化/確定した時に呼び出すこと。
133: void
134: WXStatusPanel::DoSize()
135: {
136: assert(fontid != FontId::None);
137:
138: // ここで高速モードアイコンを作る、あるいは変更する。
139: // どこでやるか悩ましいけど、ここがフォントサイズが変わった時と
140: // 起動時の他のイベントより先に呼ばれるので。
141: UpdateFFMark();
142:
143: // コントロールの大きさを変更
144: wxSize size(wxDefaultCoord, font_height + 8);
145: SetClientSize(size);
146: SetMinClientSize(size);
147: }
148:
149: // サイズ変更イベント
150: void
151: WXStatusPanel::OnSize(wxSizeEvent& event)
152: {
153: // Mac ではウィンドウ作成時に 0 以下の値が来ることがある
154: // GTK では 1 で来ることがある
155: const wxSize& size = event.GetSize();
156: if (size.x <= 1 || size.y <= 1) {
157: return;
158: }
159:
160: // ビットマップ作成
161: bitmap.Create(size.x, size.y);
162:
163: LayoutIndicators();
164:
165: // 再描画を指示
166: Refresh();
167: }
168:
169: // タイマーイベント
170: void
171: WXStatusPanel::OnTimer(wxTimerEvent& event)
172: {
173: if (UpdateStat()) {
174: Refresh();
175: }
176: }
177:
178: // 状態をチェック。
179: // 前回値からどれかでも変更があれば true を返す。
180: bool
181: WXStatusPanel::UpdateStat()
182: {
183: bool updated = false;
184:
185: // 電源オンか
186: ispower = gScheduler->IsPower();
187: if (last_ispower != ispower) {
188: last_ispower = ispower;
189: updated = true;
190: }
191:
192: // パフォーマンスカウンタ
193: perf_mode = gScheduler->GetRunMode();
194: if (last_perf_mode != perf_mode) {
195: last_perf_mode = perf_mode;
196: updated = true;
197: }
198: perf_rate = gScheduler->GetPerfCounter();
199: if (last_perf_rate != perf_rate) {
200: last_perf_rate = perf_rate;
201: updated = true;
202: }
203:
204: // FD
205:
206: // SCSI アクセス
207: scsi_bsy = scsibus->GetBSY();
208: if (scsi_bsy != last_scsi_bsy) {
209: last_scsi_bsy = scsi_bsy;
210: updated = true;
211: }
212:
213: // ネットワーク
214:
215: // 電源
216:
217: return updated;
218: }
219:
220: // 描画イベント
221: void
222: WXStatusPanel::OnPaint(wxPaintEvent& event)
223: {
224: // メモリ DC に描画
225: wxMemoryDC memDC;
226: memDC.SelectObject(bitmap);
227: Draw(memDC);
228:
229: // 実画面 DC にコピー
230: wxPaintDC dstDC(this);
231: wxSize size = bitmap.GetSize();
232: dstDC.Blit(0, 0, size.x, size.y, &memDC, 0, 0);
233: }
234:
235: // 描画本体
236: void
237: WXStatusPanel::Draw(wxDC& dc)
238: {
239: std::string text;
240:
241: // 背景はいつものグレー。
242: wxSize size = GetSize();
243: dc.SetPen(wxPen(BGPANEL));
244: dc.SetBrush(wxBrush(BGPANEL));
245: dc.DrawRectangle(wxPoint(0, 0), size);
246:
247: // なんとなく見栄えのため上下に薄い線を引く。
248: // 四方を線で囲むとオーバーレイウィンドウのように見えるので上下だけ。
249: dc.SetPen(wxPen(UD_GREY));
250: dc.DrawLine(0, 0, size.x - 1, 0);
251: dc.DrawLine(0, size.y - 1, size.x - 1, size.y - 1);
252:
253: // 各インジケータを描画
254: for (auto& ind : indicators) {
255: (this->*(ind.draw))(dc, ind);
256: }
257: }
258:
259: // インジケータの初期化
260: void
261: WXStatusPanel::InitIndicators()
262: {
263: // パフォーマンスカウンタ
264: Indicator perf(">>>9999%", &WXStatusPanel::DrawPerf);
265: perf.dclick = &WXStatusPanel::DClickPerf;
266: indicators.emplace_back(perf);
267:
268: // FD
269:
270: // SCSI
271: // SPC の接続済みデバイスリストから取得。
272: // (ホストアダプタ2つ目のことは考えてない)
273: const std::vector<SCSIDevice *>& conn = gSPC->GetConnectedDevices();
274: for (const auto& dev : conn) {
275: int id = dev->GetMyID();
276: SCSI::DevType devtype = dev->GetDevType();
277:
278: std::string disp;
279: switch (devtype) {
280: case SCSI::DevType::Initiator:
281: continue;
282: case SCSI::DevType::HD:
283: disp = "HD";
284: break;
285: default:
286: disp = "??";
287: break;
288: }
289: disp += string_format("%d", id);
290:
291: indicators.emplace_back(disp, &WXStatusPanel::DrawSCSI, id);
292: }
293: // BSY 取得用
294: scsibus = gSPC->GetSCSIBus();
295:
296: // ネットワーク
297: #if 0 // XXX まだアクセスランプどうするか要調査
298: if (gNetDriver->IsOpen()) {
299: net_ok = true;
300: } else {
301: net_ok = false;
302: }
303: indicators.emplace_back("LAN", &WXStatusPanel::DrawNet);
304: #endif
305:
306: // 電源 LED。点灯ロジックが分からないのでハリボテ。
307: // ロジックが分かった時のために一応 3 文字にしておく。
308: indicators.emplace_back("PWR", &WXStatusPanel::DrawPower);
309: }
310:
311: // インジケータのレイアウト
312: void
313: WXStatusPanel::LayoutIndicators()
314: {
315: int x;
316: int y;
317: int w;
318: int h;
319:
320: // まずは前から順に列挙して必要な幅を積算していく
321: x = 0;
322: for (auto& ind : indicators) {
323: // 前の枠とは1つ開ける
324: if (x != 0) {
325: x += 1;
326: }
327:
328: // 上の枠はキャンバス上端から2px
329: y = 2;
330: // 4 は、左右の外枠(1*2) と 枠と文字の間(1*2)
331: w = ind.text.length() * font_width + 2 + 2;
332: // そこから上下の外枠と枠と文字の間
333: h = font_height + 2 + 2;
334:
335: ind.rect = wxRect(x, y, w, h);
336: x += w;
337: }
338:
339: // 全員を一旦右寄せ
340: wxSize sz = GetClientSize();
341: int offsetx = sz.x - x;
342: for (auto& ind : indicators) {
343: ind.rect.Offset(offsetx, 0);
344: }
345:
346: // そのうちパフォーマンスカウンタだけ中央寄せまで戻す
347: if (indicators.size() > 0) {
348: auto& p = indicators.front();
349: x = (sz.x / 2) - (p.rect.GetWidth() / 2);
350: if (p.rect.GetX() > x) {
351: p.rect.SetX(x);
352: }
353: }
354: }
355:
356: // パフォーマンスカウンタ
357: void
358: WXStatusPanel::DrawPerf(wxDC& dc, const Indicator& ind)
359: {
360: const wxRect& rect = ind.rect;
361:
362: // 枠
363: dc.SetPen(wxPen(UD_GREY));
364: dc.SetBrush(wxBrush(BGPANEL));
365: dc.DrawRectangle(rect);
366:
367: // 電源オフならここまで
368: if (ispower == false) {
369: return;
370: }
371:
372: // 高速モードアイコン
373: // UI による高速モード指示なら >> (2つ)
374: // そのうち実際に高速動作なら >>> (3つ)
375: if ((perf_mode & Scheduler::SCHED_SYNC) == 0) {
376: SetTextForeColor(UD_RED);
377: DrawBitmap(dc, rect.x + 2, rect.y + 2, *ffmark);
378: DrawBitmap(dc, rect.x + 2 + font_width, rect.y + 2, *ffmark);
379: if (perf_mode == 0) {
380: DrawBitmap(dc, rect.x + 2 + font_width * 2, rect.y + 2, *ffmark);
381: }
382: SetTextForeColor(*wxBLACK);
383: }
384:
385: // パフォーマンス表示
386: std::string text = string_format("%4d%%", perf_rate);
387: DrawStringSJIS(dc, rect.x + 2 + font_width * 3, rect.y + 2, text.c_str());
388: }
389:
390: // SCSI デバイス側のアクセスランプ
391: // 本体ではなく各 SCSI デバイス側 (外付け HDD の前面とか) にあるやつ。
392: void
393: WXStatusPanel::DrawSCSI(wxDC& dc, const Indicator& ind)
394: {
395: wxColour bg;
396:
397: if ((scsi_bsy & (1U << ind.userdata)) != 0) {
398: bg = UD_RED;
399: } else {
400: bg = BGPANEL;
401: }
402:
403: DrawTextLED(dc, ind, *wxBLACK, bg);
404: }
405:
406: // ネットワーク(LAN)
407: void
408: WXStatusPanel::DrawNet(wxDC& dc, const Indicator& ind)
409: {
410: wxColour fg;
411: wxColour bg = BGPANEL;
412:
413: if (net_ok) {
414: fg = *wxBLACK;
415: } else {
416: fg = UD_GREY;
417: }
418:
419: DrawTextLED(dc, ind, fg, bg);
420: }
421:
422: // 電源LED?
423: void
424: WXStatusPanel::DrawPower(wxDC& dc, const Indicator& ind)
425: {
426: DrawTextLED(dc, ind, *wxBLACK, UD_YELLOW_GREEN);
427: }
428:
429: // 共通の描画処理。
430: // ind.text を前景色 fg、背景色 bg で描画するだけの場合。
431: void
432: WXStatusPanel::DrawTextLED(wxDC& dc, const Indicator& ind,
433: const wxColour& fg, const wxColour& bg)
434: {
435: const wxRect& rect = ind.rect;
436:
437: // 枠
438: dc.SetPen(wxPen(UD_GREY));
439: dc.SetBrush(wxBrush(bg));
440: dc.DrawRectangle(rect);
441:
442: SetTextColor(fg, bg);
443: DrawStringSJIS(dc, rect.x + 2, rect.y + 2, ind.text.c_str());
444: ResetTextColor();
445: }
446:
447: // 左ダブルクリックイベント
448: void
449: WXStatusPanel::OnLeftDClick(wxMouseEvent& event)
450: {
451: const wxPoint& pt = event.GetPosition();
452:
453: for (auto& ind : indicators) {
454: // dclick を処理できる枠内なら
455: if (ind.rect.Contains(pt) && ind.dclick != NULL) {
456: (this->*(ind.dclick))(ind);
457: break;
458: }
459: }
460: }
461:
462: // パフォーマンスカウンタ枠へのダブルクリック
463: void
464: WXStatusPanel::DClickPerf(const Indicator& ind)
465: {
466: // モードを切り替える
467: gScheduler->SetFullSpeed(!gScheduler->GetFullSpeed());
468: }
469:
470: // 高速モードアイコンの三角一個分、半角文字と同じ大きさ。
471: // XBM データは左右鏡像(反転前に左詰め)で保持する。
472: static constexpr uint8 FFMark12[] = {
473: _rev8(0b000000'00), // 0
474: _rev8(0b100000'00), // 1
475: _rev8(0b110000'00), // 2
476: _rev8(0b111000'00), // 3
477: _rev8(0b111100'00), // 4
478: _rev8(0b111110'00), // 5
479: _rev8(0b111111'00), // 6
480: _rev8(0b111110'00), // 7
481: _rev8(0b111100'00), // 8
482: _rev8(0b111000'00), // 9
483: _rev8(0b110000'00), // a
484: _rev8(0b100000'00), // b
485: };
486: static constexpr uint8 FFMark16[] = {
487: _rev8(0b00000000), // 0
488: _rev8(0b10000000), // 1
489: _rev8(0b11000000), // 2
490: _rev8(0b11100000), // 3
491: _rev8(0b11110000), // 4
492: _rev8(0b11111000), // 5
493: _rev8(0b11111100), // 6
494: _rev8(0b11111110), // 7
495: _rev8(0b11111111), // 8
496: _rev8(0b11111110), // 9
497: _rev8(0b11111100), // a
498: _rev8(0b11111000), // b
499: _rev8(0b11110000), // c
500: _rev8(0b11100000), // d
501: _rev8(0b11000000), // e
502: _rev8(0b10000000), // f
503: };
504:
505: // 高速モードアイコン(の1文字分)を現在のフォントサイズに合わせて作り直す
506: void
507: WXStatusPanel::UpdateFFMark()
508: {
509: const uint8 *xbm;
510:
511: switch (fontid) {
512: case FontId::_6x12:
513: xbm = FFMark12;
514: break;
515: case FontId::_8x16:
516: xbm = FFMark16;
517: break;
518: default:
519: assert(false);
520: }
521: ffmark.reset(new wxBitmap((const char *)xbm, font_width, font_height, 1));
522: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.