|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2021 nono project
4: // Licensed under nono-license.txt
5: //
6:
1.1.1.4 root 7: //
1.1 root 8: // ステータスパネル
9: //
1.1.1.4 root 10:
1.1 root 11: // 描画領域に対してフォントの開始位置は外枠から +4、内枠からなら +2。
12: // 横も同様。
13: //
14: // +0 ------ 外枠
15: // +1 アキ
16: // +2 +---- 内枠
17: // +3 | アキ
18: // +4 | ここからフォント
19:
20: #include "wxstatuspanel.h"
1.1.1.4 root 21: #include "fontmanager.h"
1.1 root 22: #include "wxcolor.h"
1.1.1.3 root 23: #include "wxmainframe.h"
1.1 root 24: #include "wxmainview.h"
1.1.1.3 root 25: #include "wxuimessage.h"
1.1.1.5 root 26: #include "fdc.h"
1.1.1.4 root 27: #include "hostnet.h"
1.1.1.6 root 28: #include "newsctlr.h"
1.1.1.4 root 29: #include "power.h"
1.1 root 30: #include "spc.h"
1.1.1.6 root 31: #include "syncer.h"
1.1.1.8 ! root 32: #include "virtio_block.h"
1.1 root 33:
34: wxBEGIN_EVENT_TABLE(WXStatusPanel, inherited)
35: EVT_CLOSE(WXStatusPanel::OnClose)
36: EVT_SHOW(WXStatusPanel::OnShow)
37: EVT_SIZE(WXStatusPanel::OnSize)
38: EVT_TIMER(wxID_ANY, WXStatusPanel::OnTimer)
39: EVT_LEFT_DCLICK(WXStatusPanel::OnLeftDClick)
1.1.1.3 root 40: EVT_CONTEXT_MENU(WXStatusPanel::OnContextMenu)
1.1 root 41: wxEND_EVENT_TABLE()
42:
43: // ステータスパネルの更新頻度 [Hz]
44: static constexpr uint FPS = 10;
45:
46: // style フラグ
47: // デフォルトは wxTAB_TRAVERSAL だがこれを外すので分かりづらい字面になる
48: static const long STATUS_PANEL_STYLE = 0;
49:
50: // コンストラクタ
51: WXStatusPanel::WXStatusPanel(wxWindow *parent)
1.1.1.6 root 52: : inherited(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
53: STATUS_PANEL_STYLE)
1.1 root 54: {
55: timer.SetOwner(this);
56:
1.1.1.7 root 57: power = GetPowerDevice();
58: syncer = GetSyncer();
59:
1.1 root 60: // インジケータ用のパラメータを用意。
61: InitIndicators();
62:
1.1.1.4 root 63: FontChanged();
1.1 root 64:
65: // キー入力イベントをメインビューに転送。
66: // このパネルへのキー入力は不要なのと、メインウィンドウ内のコントロール
67: // フォーカスがこのステータスパネルとメインビューのどっちにあるかは
68: // 分かりづらくて、こっちがフォーカス持ってしまうと (というか起動後は
69: // こっちにある) VM にキー入力が出来ずに焦ることになるので、ここへの
70: // キー入力イベントは全部メインビューに飛ばす。
1.1.1.6 root 71: auto mainframe = dynamic_cast<WXMainFrame*>(GetParent());
72: auto mainview = mainframe->GetMainView();
1.1 root 73: Connect(wxEVT_KEY_UP, wxKeyEventHandler(WXMainView::OnKeyUp), NULL,
1.1.1.6 root 74: mainview);
1.1 root 75: Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL,
1.1.1.6 root 76: mainview);
1.1.1.3 root 77:
1.1.1.5 root 78: // VM からの通知を受け取る
1.1.1.3 root 79: WXUIMessage::Connect(UIMessage::SCSI_MEDIA_CHANGE, this,
80: wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged));
1.1.1.5 root 81: WXUIMessage::Connect(UIMessage::FDD_MEDIA_CHANGE, this,
82: wxCommandEventHandler(WXStatusPanel::OnFDDMediaChanged));
83: WXUIMessage::Connect(UIMessage::LED, this,
84: wxCommandEventHandler(WXStatusPanel::OnLEDChanged));
1.1 root 85: }
86:
1.1.1.2 root 87: // デストラクタ
88: WXStatusPanel::~WXStatusPanel()
89: {
1.1.1.4 root 90: while (indicators.empty() == false) {
91: Indicator *ind = indicators.back();
92: indicators.pop_back();
93: delete ind;
94: }
1.1.1.5 root 95:
1.1.1.6 root 96: WXUIMessage::Disconnect(UIMessage::SCSI_MEDIA_CHANGE, this,
1.1.1.4 root 97: wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged));
1.1.1.6 root 98: WXUIMessage::Disconnect(UIMessage::FDD_MEDIA_CHANGE, this,
1.1.1.5 root 99: wxCommandEventHandler(WXStatusPanel::OnFDDMediaChanged));
1.1.1.6 root 100: WXUIMessage::Disconnect(UIMessage::LED, this,
1.1.1.5 root 101: wxCommandEventHandler(WXStatusPanel::OnLEDChanged));
1.1.1.2 root 102: }
103:
1.1 root 104: // クローズイベント
105: void
106: WXStatusPanel::OnClose(wxCloseEvent& event)
107: {
108: // クローズする前に更新を止める。
109: timer.Stop();
110: }
111:
112: // 表示/非表示変更イベント
113: // new した時は (デフォルトで表示状態なのでか状態に変更がないという扱いの
114: // ようで) イベントは飛んでこない。同様に new 直後に Show() を明示発行しても
115: // 状態に変更がないためイベントは飛んでこない。
116: // new 直後に Hide() すればイベントは飛んでくる。という動作をするようだ。
117: // そのため WXMainFrame 側で生成後に Show/Hide を起こしている。
118: void
119: WXStatusPanel::OnShow(wxShowEvent& event)
120: {
121: if (event.IsShown()) {
122: // 非表示→表示
123:
124: // 表示開始前に一度情報を取得
125: UpdateStat();
126:
127: timer.Start(1000 / FPS);
128: } else {
129: // 表示→非表示
130:
131: timer.Stop();
132: }
133: }
134:
1.1.1.4 root 135: // フォントサイズ変更
1.1 root 136: void
1.1.1.4 root 137: WXStatusPanel::FontChanged()
1.1 root 138: {
1.1.1.4 root 139: inherited::FontChanged();
140:
1.1.1.6 root 141: // アクセスマークをサイズに応じて作り直す
142: const uint8 *src;
143: switch (font_height) {
144: case 12:
145: src = AccessMark12;
146: break;
147: case 16:
148: src = AccessMark16;
149: break;
150: case 24:
151: src = AccessMark24;
152: break;
153: default:
154: PANIC("Unexpected font_height=%d", font_height);
155: }
156: accmark.reset(new BitmapI8(src, font_height, font_height));
157:
1.1.1.4 root 158: // インジケータを再レイアウト
159: LayoutIndicators();
1.1 root 160:
161: // コントロールの大きさを変更
1.1.1.4 root 162: wxSize size = GetClientSize();
163: size.y = font_height + 8;
1.1 root 164: SetClientSize(size);
165: }
166:
167: // サイズ変更イベント
168: void
169: WXStatusPanel::OnSize(wxSizeEvent& event)
170: {
1.1.1.5 root 171: // Mac ではウィンドウ作成時に 0 以下の値が来ることがある。
172: // GTK では 1 で来ることがある。
1.1 root 173: const wxSize& size = event.GetSize();
174: if (size.x <= 1 || size.y <= 1) {
175: return;
176: }
177:
1.1.1.4 root 178: // 親クラス
179: inherited::OnSize(event);
1.1 root 180:
181: LayoutIndicators();
182:
183: // 再描画を指示
184: Refresh();
185: }
186:
187: // タイマーイベント
188: void
189: WXStatusPanel::OnTimer(wxTimerEvent& event)
190: {
191: if (UpdateStat()) {
192: Refresh();
193: }
194: }
195:
1.1.1.7 root 196: // store に value を代入。更新されていれば updated を立てる。
197: // value は引数に渡す時に一度だけ評価される。
198: #define MODIFY(store, value_) ({ \
199: bool mod_; \
200: decltype(value_) value = value_; \
201: if (store != value) { \
202: store = value; \
203: mod_ = true; \
204: } else { \
205: mod_ = false; \
206: } \
207: mod_; /* return value */ \
208: })
209:
210: //
211: // インジケータ用の情報
212: //
213: class Status
214: {
215: public:
216: virtual ~Status() = default;
217:
218: // 情報取得メソッド
219: virtual bool Poll() = 0;
220: };
221:
222: //
223: // SCSI 情報
224: //
225: class StatusSCSI : public Status
226: {
227: public:
228: StatusSCSI(SCSIBus *scsibus_) {
229: scsibus = scsibus_;
230: }
231: virtual ~StatusSCSI() override = default;
232:
233: bool Poll() override {
234: bool mod = false;
235: mod |= MODIFY(bsy, scsibus->GetBSY());
236: // 転送フェーズで DataOut の時だけ書き込みとみなす
237: bool out = (scsibus->GetPhase() == SCSI::Phase::Transfer)
238: && (scsibus->GetXfer() == SCSI::XferPhase::DataOut);
239: mod |= MODIFY(dataout, out);
240: return mod;
241: }
242:
243: uint8 bsy {}; // BSY (ID0-7)
244: bool dataout {}; // DataOut フェーズなら true
245:
246: private:
247: SCSIBus *scsibus {};
248: };
249:
250: //
1.1.1.8 ! root 251: // VirtIOBlock 情報
! 252: //
! 253: class StatusVBlk : public Status
! 254: {
! 255: public:
! 256: StatusVBlk(VirtIOBlockDevice *dev_) {
! 257: dev = dev_;
! 258: }
! 259: virtual ~StatusVBlk() override = default;
! 260:
! 261: bool Poll() override {
! 262: bool rmod = MODIFY(access_read, dev->GetAccessRead());
! 263: bool wmod = MODIFY(access_write, dev->GetAccessWrite());
! 264:
! 265: bool mod = false;
! 266: mod |= MODIFY(read_active, rmod);
! 267: mod |= MODIFY(write_active, wmod);
! 268: return mod;
! 269: }
! 270:
! 271: // アクセス状況
! 272: uint32 access_read {};
! 273: uint32 access_write {};
! 274: bool read_active {};
! 275: bool write_active {};
! 276:
! 277: private:
! 278: VirtIOBlockDevice *dev {};
! 279: };
! 280:
! 281: //
1.1.1.7 root 282: // ネットワーク情報
283: //
284: class StatusNet : public Status
285: {
286: public:
287: StatusNet(HostNetDevice *hostnet_) {
288: hostnet = hostnet_;
289: }
290: virtual ~StatusNet() override = default;
291:
292: bool Poll() override {
293: // パケット数が変化したとき、および
294: // その変化を監視しているアクティブ状態が変化したときに
295: // 変更があったとしたいので二重になっている。
296: bool tx_mod = MODIFY(tx_pkts, hostnet->GetTXPkts());
297: bool rx_mod = MODIFY(rx_pkts, hostnet->GetRXPkts());
298:
299: bool mod = false;
300: mod |= MODIFY(tx_active, tx_mod);
301: mod |= MODIFY(rx_active, rx_mod);
302: return mod;
303: }
304:
305: bool enable {};
306: uint64 tx_pkts {};
307: uint64 rx_pkts {};
308: bool tx_active {};
309: bool rx_active {};
310:
311: private:
312: HostNetDevice *hostnet {};
313: };
314:
315: //
316: // FDD 情報
317: //
318: class StatusFDD : public Status
319: {
320: public:
321: StatusFDD(FDDDevice *fdd_) {
322: fdd = fdd_;
323: }
324: virtual ~StatusFDD() override = default;
325:
326: bool Poll() override {
327: bool mod = false;
328: mod |= MODIFY(access_led, fdd->GetAccessLED());
329: mod |= MODIFY(eject_led, fdd->GetEjectLED());
330: return mod;
331: }
332:
333: FDDDevice::LED access_led {}; // アクセス LED の状態
334: bool eject_led {}; // イジェクト LED 点灯なら true
335: private:
336: FDDDevice *fdd {};
337: };
338:
339: //
340: // NEWS の LED 情報
341: //
342: class StatusNEWS : public Status
343: {
344: public:
345: StatusNEWS(NewsCtlrDevice *newsctlr_) {
346: newsctlr = newsctlr_;
347: }
348: virtual ~StatusNEWS() override = default;
349:
350: bool Poll() override {
351: return MODIFY(led, newsctlr->GetLED());
352: }
353:
354: uint led {};
355: private:
356: NewsCtlrDevice *newsctlr {};
357: };
358:
359:
1.1 root 360: // インジケータの初期化
361: void
362: WXStatusPanel::InitIndicators()
363: {
1.1.1.7 root 364: Status *stat;
1.1.1.6 root 365: Indicator *ind;
366:
1.1 root 367: // パフォーマンスカウンタ
1.1.1.7 root 368: ind = new Indicator(Indicator::PERF, &WXStatusPanel::DrawPerf,
369: 8/* >>>9999% */, NULL);
1.1.1.6 root 370: ind->dclick = &WXStatusPanel::DClickPerf;
371: indicators.emplace_back(ind);
1.1 root 372:
373: // SCSI
374: // SPC の接続済みデバイスリストから取得。
375: // (ホストアダプタ2つ目のことは考えてない)
1.1.1.6 root 376: auto spc = gMainApp.FindObject<SPCDevice>(OBJ_SPC);
377: if (spc) {
1.1.1.7 root 378: stat = new StatusSCSI(spc->GetSCSIBus());
379: stats.emplace_back(stat);
380:
1.1.1.6 root 381: const std::vector<SCSIDevice *>& conn = spc->GetConnectedDevices();
382: for (const auto& dev : conn) {
383: SCSI::DevType devtype = dev->GetDevType();
384: if (devtype == SCSI::DevType::Initiator) {
385: continue;
386: }
387: int scsiid = dev->GetMyID();
388:
389: // 文字列は OnSCSIMediaChanged() でセットする。
1.1.1.7 root 390: ind = new Indicator(Indicator::SCSI0 + scsiid,
391: &WXStatusPanel::DrawSCSI, 3, stat);
1.1.1.6 root 392: ind->contextmenu = &WXStatusPanel::ContextMenuCD;
393: ind->dev = dev;
394: ind->panel = new wxPanel(this);
395: // このパネルがフォーカスを受け取らないようにする。
396: // StatusPanel がフォーカスを持っていてもキー入力を MainFrame に渡す
397: // ようにしているため、StatusPanel のフォーカスを奪ってはいけない。
398: ind->panel->SetCanFocus(false);
1.1.1.3 root 399:
1.1.1.6 root 400: indicators.emplace_back(ind);
401: }
1.1 root 402: }
403:
1.1.1.8 ! root 404: // VirtIOBlock
! 405: for (int i = 0; i < 8; i++) {
! 406: auto dev = gMainApp.FindObject<VirtIOBlockDevice>(OBJ_VIRTIO_BLOCK(i));
! 407: if (dev) {
! 408: stat = new StatusVBlk(dev);
! 409: stats.emplace_back(stat);
! 410:
! 411: std::string label = string_format("VB%u", i);
! 412: if (dev->GetWriteMode() == SCSIDisk::RW::WriteIgnore) {
! 413: label += "\x02\x03";
! 414: }
! 415: ind = new Indicator(Indicator::VBLK0 + i, &WXStatusPanel::DrawVBlk,
! 416: label, stat);
! 417: ind->dev = dev;
! 418: indicators.emplace_back(ind);
! 419: }
! 420: }
! 421:
1.1 root 422: // ネットワーク
1.1.1.7 root 423: for (int i = 0; i < 2; i++) {
424: auto hostnet = gMainApp.FindObject<HostNetDevice>(OBJ_HOSTNET(i));
425: if (hostnet) {
426: auto statnet = new StatusNet(hostnet);
427: stat = statnet;
428: stats.emplace_back(stat);
429:
430: if (hostnet->GetDriverName() == "none") {
431: statnet->enable = false;
432: } else {
433: statnet->enable = true;
434: }
435: // \x1e は上向き矢印、\x1f は下向き矢印。
436: // ラベルは結局機種で変わる…
437: std::string label;
438: if (gMainApp.IsX68030()) {
439: label = string_format("LAN%d\x1e\x1f", i);
440: } else {
441: label = "LAN\x1e\x1f";
442: }
443: ind = new Indicator(Indicator::LAN0 + i, &WXStatusPanel::DrawNet,
444: label, stat);
445: indicators.emplace_back(ind);
1.1.1.2 root 446: }
1.1 root 447: }
448:
1.1.1.5 root 449: // FD
1.1.1.6 root 450: for (int unit = 0; unit < FDCDevice::MAX_DRIVE; unit++) {
451: auto fdd = gMainApp.FindObject<FDDDevice>(OBJ_FDD(unit));
452: if (fdd) {
1.1.1.7 root 453: stat = new StatusFDD(fdd);
454: stats.emplace_back(stat);
1.1.1.6 root 455:
456: // 文字列は OnFDDMediaChanged() でセットする。
1.1.1.7 root 457: ind = new Indicator(Indicator::FDD0 + unit,
458: &WXStatusPanel::DrawFD, 7, stat);
1.1.1.6 root 459:
460: ind->contextmenu = &WXStatusPanel::ContextMenuFD;
461: ind->dev = fdd;
462: ind->panel = new wxPanel(this);
463: // フォーカスを受け取らない。少し上の CD のところ参照。
464: ind->panel->SetCanFocus(false);
465:
466: indicators.emplace_back(ind);
1.1.1.5 root 467: }
468: }
469:
1.1.1.6 root 470: // NEWS の LED (2->1 の順で並べる)
1.1.1.7 root 471: auto newsctlr = gMainApp.FindObject<NewsCtlrDevice>(OBJ_NEWSCTLR);
1.1.1.6 root 472: if (newsctlr) {
1.1.1.7 root 473: stat = new StatusNEWS(newsctlr);
474: stats.emplace_back(stat);
475:
476: ind = new Indicator(Indicator::NEWSLED2, &WXStatusPanel::DrawNewsLED2,
477: "LED2", stat);
1.1.1.6 root 478: indicators.emplace_back(ind);
479:
1.1.1.7 root 480: ind = new Indicator(Indicator::NEWSLED1, &WXStatusPanel::DrawNewsLED1,
481: "LED1", stat);
1.1.1.6 root 482: indicators.emplace_back(ind);
483: }
484:
1.1.1.5 root 485: // 電源 LED
1.1.1.7 root 486: ind = new Indicator(Indicator::POWER, &WXStatusPanel::DrawPower,
487: "POWER", NULL);
1.1.1.6 root 488: indicators.emplace_back(ind);
1.1 root 489: }
490:
491: // インジケータのレイアウト
492: void
493: WXStatusPanel::LayoutIndicators()
494: {
495: int x;
496: int y;
497: int w;
498: int h;
499:
1.1.1.4 root 500: // 上の枠はキャンバス上端から2px
501: y = 2;
502: // そこから上下の外枠と枠と文字の間
503: h = font_height + 2 + 2;
504:
1.1 root 505: // まずは前から順に列挙して必要な幅を積算していく
506: x = 0;
1.1.1.3 root 507: for (auto ind : indicators) {
1.1.1.4 root 508: // 前の枠とは1つ空ける
509: x += 1;
1.1 root 510:
511: // 4 は、左右の外枠(1*2) と 枠と文字の間(1*2)
1.1.1.5 root 512: int len = std::max(ind->minlen, (int)ind->text.length());
513: w = len * font_width + 2 + 2;
1.1 root 514:
1.1.1.4 root 515: ind->rect = Rect(x, y, w, h);
1.1 root 516: x += w;
517: }
518:
1.1.1.4 root 519: // 右端も1つ空ける
520: x += 1;
521:
522: // この x が最小サイズなのでこれをコントロールに設定。
523: // (コントロールのサイズは WXMainFrame::Layout() が設定する)
524: SetMinClientSize(wxSize(x, font_height + 8));
525:
1.1 root 526: // 全員を一旦右寄せ
527: wxSize sz = GetClientSize();
528: int offsetx = sz.x - x;
1.1.1.3 root 529: for (auto ind : indicators) {
530: ind->rect.Offset(offsetx, 0);
1.1 root 531: }
532:
533: // そのうちパフォーマンスカウンタだけ中央寄せまで戻す
534: if (indicators.size() > 0) {
1.1.1.3 root 535: auto p = indicators.front();
1.1.1.4 root 536: x = (sz.x / 2) - (p->rect.w / 2);
537: if (p->rect.x > x) {
538: p->rect.x = x;
1.1.1.3 root 539: }
540: }
541:
542: // パネルをもっていれば、パネルをその位置に(再)設定。
543: for (auto ind : indicators) {
1.1.1.5 root 544: if (ind->panel) {
1.1.1.3 root 545: const auto& rect = ind->rect;
1.1.1.5 root 546: ind->panel->SetSize(rect.x, rect.y, rect.w, rect.h);
1.1 root 547: }
548: }
1.1.1.5 root 549:
550: refresh_background = true;
1.1 root 551: }
552:
1.1.1.3 root 553: // 状態をチェック。
554: // 前回値からどれかでも変更があれば true を返す。
555: bool
556: WXStatusPanel::UpdateStat()
557: {
1.1.1.7 root 558: bool mod = false;
1.1.1.3 root 559:
1.1.1.7 root 560: // 電源オン
561: mod |= MODIFY(ispower, power->IsPower());
562: mod |= MODIFY(powerled, power->GetPowerLED());
1.1.1.3 root 563:
564: // パフォーマンスカウンタ
1.1.1.7 root 565: mod |= MODIFY(perf_mode, syncer->GetRunMode());
566: mod |= MODIFY(perf_rate, syncer->GetPerfCounter());
1.1.1.3 root 567:
1.1.1.7 root 568: // それ以外
569: for (const auto stat : stats) {
570: mod |= stat->Poll();
1.1.1.3 root 571: }
572:
1.1.1.7 root 573: return mod;
1.1.1.3 root 574: }
575:
576: // 描画本体
577: void
1.1.1.4 root 578: WXStatusPanel::Draw()
1.1.1.3 root 579: {
580: std::string text;
581:
1.1.1.5 root 582: // 指定された時だけ背景を再描画
583: if (refresh_background) {
584: refresh_background = false;
585: bitmap.Fill(BGPANEL);
586: }
587:
1.1.1.3 root 588: // なんとなく見栄えのため上下に薄い線を引く。
589: // 四方を線で囲むとオーバーレイウィンドウのように見えるので上下だけ。
1.1.1.4 root 590: wxSize size = GetSize();
591: bitmap.DrawLineH(UD_GREY, 0, 0, size.x);
592: bitmap.DrawLineH(UD_GREY, 0, size.y - 1, size.x);
1.1.1.3 root 593:
594: // 各インジケータを描画
595: for (auto ind : indicators) {
1.1.1.4 root 596: (this->*(ind->draw))(ind);
1.1.1.3 root 597: }
598: }
599:
1.1 root 600: // パフォーマンスカウンタ
601: void
1.1.1.4 root 602: WXStatusPanel::DrawPerf(const Indicator *ind)
1.1 root 603: {
1.1.1.4 root 604: const Rect& rect = ind->rect;
1.1.1.2 root 605: char text[16];
1.1.1.4 root 606: const char *ffmark;
1.1 root 607:
608: // 枠
1.1.1.4 root 609: bitmap.DrawRect(UD_GREY, rect);
1.1 root 610:
611: // 電源オフならここまで
612: if (ispower == false) {
1.1.1.4 root 613: bitmap.FillRect(BGPANEL,
614: rect.x + 1, rect.y + 1, rect.w - 2, rect.h - 2);
1.1 root 615: return;
616: }
617:
1.1.1.2 root 618: // 高速モードか通常モードかでいろいろ違う。
619: // アイコンは
620: // o 通常モード指示中ならアイコンなし
621: // o 高速モード指示中なら >> (2つ)
622: // o そのうち実際に高速動作なら >>> (3つ)
623: // パフォーマンス表示は
624: // o 高速モード指示中なら倍率表記 (115% なら 1.2x)
625: // o 通常モード指示中ならパーセント表記
1.1.1.6 root 626: if ((perf_mode & Syncer::SCHED_SYNC) == 0) {
1.1.1.2 root 627: // 高速モード指示
1.1 root 628: if (perf_mode == 0) {
1.1.1.2 root 629: // 高速走行中
1.1.1.4 root 630: ffmark = "\x01\x01\x01";
631: } else {
632: ffmark = "\x01\x01 ";
1.1 root 633: }
634:
1.1.1.2 root 635: // 倍率表記は1桁少ないので、表示用に最下位桁を四捨五入
636: int rate = perf_rate + 5;
637: snprintf(text, sizeof(text), "%2d.%1dx",
638: (rate / 100), (rate % 100) / 10);
639: } else {
640: // 通常モード指示
1.1.1.4 root 641: ffmark = " ";
1.1.1.2 root 642: snprintf(text, sizeof(text), "%4d%%", perf_rate);
643: }
1.1.1.4 root 644:
645: DrawStringSJIS(UD_RED, rect.x + 2, rect.y + 2, ffmark);
646: DrawStringSJIS(rect.x + 2 + font_width * 3, rect.y + 2, text);
1.1 root 647: }
648:
649: // SCSI デバイス側のアクセスランプ
650: // 本体ではなく各 SCSI デバイス側 (外付け HDD の前面とか) にあるやつ。
651: void
1.1.1.4 root 652: WXStatusPanel::DrawSCSI(const Indicator *ind)
1.1 root 653: {
1.1.1.7 root 654: const StatusSCSI *stat = dynamic_cast<const StatusSCSI*>(ind->stat);
1.1.1.4 root 655: Color fg;
656: Color bg;
1.1.1.5 root 657: int scsiid;
1.1.1.3 root 658: uint idmask;
659:
1.1.1.5 root 660: scsiid = ind->GetSCSIID();
661: assertmsg(scsiid >= 0, "id=%d", ind->id);
662: idmask = 1U << scsiid;
1.1 root 663:
1.1.1.7 root 664: if (scsi_loaded[scsiid]) {
1.1.1.4 root 665: fg = UD_BLACK;
1.1.1.3 root 666: } else {
667: fg = UD_GREY;
668: }
669:
1.1.1.7 root 670: if ((stat->bsy & idmask) != 0) {
671: if (stat->dataout) {
1.1.1.2 root 672: bg = UD_RED;
673: } else {
674: bg = UD_YELLOW_GREEN;
675: }
1.1 root 676: } else {
677: bg = BGPANEL;
678: }
679:
1.1.1.4 root 680: DrawTextLED(ind, fg, bg);
1.1 root 681: }
682:
1.1.1.8 ! root 683: // VirtIOBlock デバイスのアクセスランプ。
! 684: void
! 685: WXStatusPanel::DrawVBlk(const Indicator *ind)
! 686: {
! 687: const StatusVBlk *stat = dynamic_cast<const StatusVBlk*>(ind->stat);
! 688: Color fg;
! 689: Color bg;
! 690:
! 691: fg = UD_BLACK;
! 692: if (stat->write_active) {
! 693: bg = UD_RED;
! 694: } else if (stat->read_active) {
! 695: bg = UD_YELLOW_GREEN;
! 696: } else {
! 697: bg = BGPANEL;
! 698: }
! 699:
! 700: DrawTextLED(ind, fg, bg);
! 701: }
! 702:
1.1 root 703: // ネットワーク(LAN)
704: void
1.1.1.4 root 705: WXStatusPanel::DrawNet(const Indicator *ind)
1.1 root 706: {
1.1.1.7 root 707: const StatusNet *stat = dynamic_cast<const StatusNet*>(ind->stat);
708: const Rect& rect = ind->rect;
1.1.1.4 root 709: Color fg;
710: Color bg = BGPANEL;
1.1 root 711:
1.1.1.7 root 712: if (stat->enable) {
1.1.1.4 root 713: fg = UD_BLACK;
1.1.1.7 root 714: if (stat->tx_active || stat->rx_active) {
1.1.1.2 root 715: bg = UD_YELLOW_GREEN;
716: }
1.1 root 717: } else {
718: fg = UD_GREY;
719: }
720:
1.1.1.2 root 721: // 枠
1.1.1.4 root 722: bitmap.FillRect(bg, rect);
723: bitmap.DrawRect(UD_GREY, rect);
1.1.1.2 root 724:
725: int x = rect.x + 2;
726: int y = rect.y + 2;
727:
1.1.1.3 root 728: for (int i = 0; i < ind->text.size(); i++) {
729: auto c = ind->text[i];
1.1.1.2 root 730: switch (c) {
731: case '\x1e': // 上向き矢印
1.1.1.7 root 732: if (stat->tx_active) {
1.1.1.4 root 733: fg = UD_BLACK;
1.1.1.2 root 734: } else {
735: fg = UD_GREY;
736: }
737: break;
738: case '\x1f': // 下向き矢印
1.1.1.7 root 739: if (stat->rx_active) {
1.1.1.4 root 740: fg = UD_BLACK;
1.1.1.2 root 741: } else {
742: fg = UD_GREY;
743: }
744: break;
745: }
746: SetTextColor(fg, bg);
1.1.1.4 root 747: DrawChar1(x, y, c);
1.1.1.2 root 748: x += font_width;
749: }
750: ResetTextColor();
1.1 root 751: }
752:
1.1.1.5 root 753: // FD
754: void
755: WXStatusPanel::DrawFD(const Indicator *ind)
756: {
1.1.1.7 root 757: const StatusFDD *stat = dynamic_cast<const StatusFDD*>(ind->stat);
758: const Rect& rect = ind->rect;
1.1.1.5 root 759: Rect iconrect;
760: Color pal[3];
1.1.1.7 root 761: int unit;
1.1.1.5 root 762: enum {
763: BG = 0,
764: FG,
765: IN,
766: };
767:
768: unit = ind->GetFDUnit();
769:
770: pal[BG] = BGPANEL;
1.1.1.7 root 771: if (fd_loaded[unit]) {
1.1.1.5 root 772: pal[FG] = UD_BLACK;
773: } else {
774: pal[FG] = UD_GREY;
775: }
776:
777: // まず枠とテキストを通常描画
778: DrawTextLED(ind, pal[FG], pal[BG]);
779:
780: // 続いて 5,6文字目の空き地に四角いインジケータを描画。
781: // 同じ色で済むので先に描画する。
782: iconrect.x = rect.x + 2 + font_width * 5 + 1;
783: iconrect.y = rect.y + 2 + 1;
784: iconrect.w = font_width * 2 - 2;
785: iconrect.h = font_height - 2;
786: bitmap.DrawRect(pal[FG], iconrect);
787: // ディスク挿入(取り出し可)なら黄緑点灯
1.1.1.7 root 788: if (stat->eject_led) {
1.1.1.5 root 789: iconrect.x += 1;
790: iconrect.y += 1;
791: iconrect.w -= 2;
792: iconrect.h -= 2;
793: bitmap.FillRect(UD_YELLOW_GREEN, iconrect);
794: }
795:
796: // その後で 3,4文字目の空き地に丸いインジケータを描画。
797: int dx = rect.x + 2 + font_width * 3;
798: int dy = rect.y + 2;
799:
800: // アクセス LED はディスク非挿入時に点滅するケースがあって、
801: // その場合、フチがグレーのまま中を黄緑で塗りつぶすのは見えづらいので
802: // 中を塗りつぶす時はフチを濃くしたほうがいい。
1.1.1.7 root 803: switch (stat->access_led) {
1.1.1.5 root 804: case FDDDevice::LED::GREEN:
805: pal[FG] = UD_BLACK;
806: pal[IN] = UD_YELLOW_GREEN;
807: break;
808: case FDDDevice::LED::RED:
809: pal[FG] = UD_BLACK;
810: pal[IN] = UD_RED;
811: break;
812: default:
813: pal[IN] = pal[BG];
814: break;
815: }
1.1.1.6 root 816: bitmap.DrawBitmap(dx, dy, *accmark, pal);
1.1.1.5 root 817: }
818:
819: /*static*/ const uint8
820: WXStatusPanel::AccessMark12[] = {
821: 0,0,0,0,1,1,1,0,0,0,0,0,
822: 0,0,1,1,2,2,2,1,1,0,0,0,
823: 0,1,2,2,2,2,2,2,2,1,0,0,
824: 0,1,2,2,2,2,2,2,2,1,0,0,
825: 1,2,2,2,2,2,2,2,2,2,1,0,
826: 1,2,2,2,2,2,2,2,2,2,1,0,
827: 1,2,2,2,2,2,2,2,2,2,1,0,
828: 0,1,2,2,2,2,2,2,2,1,0,0,
829: 0,1,2,2,2,2,2,2,2,1,0,0,
830: 0,0,1,1,2,2,2,1,1,0,0,0,
831: 0,0,0,0,1,1,1,0,0,0,0,0,
832: 0,0,0,0,0,0,0,0,0,0,0,0,
833: };
834: /*static*/ const uint8
835: WXStatusPanel::AccessMark16[] = {
836: 0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
837: 0,0,0,1,1,2,2,2,2,2,1,1,0,0,0,0,
838: 0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0,
839: 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,
840: 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,
841: 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,
842: 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,
843: 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,
844: 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,
845: 1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,
846: 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,
847: 0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,
848: 0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0,
849: 0,0,0,1,1,2,2,2,2,2,1,1,0,0,0,0,
850: 0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
851: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
852: };
1.1.1.6 root 853: /*static*/ const uint8
854: WXStatusPanel::AccessMark24[] = {
855: 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
856: 0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0,
857: 0,0,0,0,0,0,0,1, 1,2,2,2,2,2,2,1, 1,0,0,0,0,0,0,0,
858: 0,0,0,0,0,1,1,2, 2,2,2,2,2,2,2,2, 2,1,1,0,0,0,0,0,
859: 0,0,0,0,1,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,1,0,0,0,0,
860: 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0,
861: 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0,
862: 0,0,1,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,1,0,0,
863: 0,0,1,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,1,0,0,
864: 0,1,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,1,0,
865: 0,1,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,1,0,
866: 0,1,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,1,0,
867: 0,1,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,1,0,
868: 0,1,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,1,0,
869: 0,1,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,1,0,
870: 0,0,1,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,1,0,0,
871: 0,0,1,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,1,0,0,
872: 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0,
873: 0,0,0,1,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,1,0,0,0,
874: 0,0,0,0,1,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,1,0,0,0,0,
875: 0,0,0,0,0,1,1,2, 2,2,2,2,2,2,2,2, 2,1,1,0,0,0,0,0,
876: 0,0,0,0,0,0,0,1, 1,2,2,2,2,2,2,1, 1,0,0,0,0,0,0,0,
877: 0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0,
878: 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
879: };
880:
881: // NEWS LED1
882: void
883: WXStatusPanel::DrawNewsLED1(const Indicator *ind)
884: {
1.1.1.7 root 885: const StatusNEWS *stat = dynamic_cast<const StatusNEWS*>(ind->stat);
1.1.1.6 root 886: Color bg;
887:
1.1.1.7 root 888: if ((stat->led & NewsCtlrDevice::LED_ORANGE) != 0) {
1.1.1.6 root 889: bg = UD_ORANGE;
890: } else {
891: bg = BGPANEL;
892: }
893: DrawTextLED(ind, UD_BLACK, bg);
894: }
895:
896: // NEWS LED2
897: void
898: WXStatusPanel::DrawNewsLED2(const Indicator *ind)
899: {
1.1.1.7 root 900: const StatusNEWS *stat = dynamic_cast<const StatusNEWS*>(ind->stat);
1.1.1.6 root 901: Color bg;
902:
1.1.1.7 root 903: if ((stat->led & NewsCtlrDevice::LED_GREEN) != 0) {
1.1.1.6 root 904: bg = UD_GREEN;
905: } else {
906: bg = BGPANEL;
907: }
908: DrawTextLED(ind, UD_BLACK, bg);
909: }
1.1.1.5 root 910:
911: // 電源 LED
1.1 root 912: void
1.1.1.4 root 913: WXStatusPanel::DrawPower(const Indicator *ind)
1.1 root 914: {
1.1.1.5 root 915: Color bg;
916:
917: if (ispower) {
918: if (powerled) {
919: bg = UD_YELLOW_GREEN;
920: } else {
921: bg = UD_GREY;
922: }
923: } else {
924: // XXX 電源オフが出来たら X68030 なら赤
925: bg = BGPANEL;
926: }
927:
928: DrawTextLED(ind, UD_BLACK, bg);
1.1 root 929: }
930:
931: // 共通の描画処理。
1.1.1.3 root 932: // ind->text を前景色 fg、背景色 bg で描画するだけの場合。
1.1 root 933: void
1.1.1.4 root 934: WXStatusPanel::DrawTextLED(const Indicator *ind, Color fg, Color bg)
1.1 root 935: {
1.1.1.4 root 936: const Rect& rect = ind->rect;
1.1 root 937:
938: // 枠
1.1.1.4 root 939: bitmap.FillRect(bg, rect);
940: bitmap.DrawRect(UD_GREY, rect);
1.1 root 941:
942: SetTextColor(fg, bg);
1.1.1.4 root 943: DrawStringSJIS(rect.x + 2, rect.y + 2, ind->text.c_str());
1.1 root 944: ResetTextColor();
945: }
946:
947: // 左ダブルクリックイベント
948: void
949: WXStatusPanel::OnLeftDClick(wxMouseEvent& event)
950: {
951: const wxPoint& pt = event.GetPosition();
952:
1.1.1.3 root 953: for (auto ind : indicators) {
1.1 root 954: // dclick を処理できる枠内なら
1.1.1.4 root 955: if (ind->rect.Contains(pt.x, pt.y) && ind->dclick != NULL) {
1.1.1.3 root 956: (this->*(ind->dclick))(ind);
1.1 root 957: break;
958: }
959: }
960: }
961:
962: // パフォーマンスカウンタ枠へのダブルクリック
963: void
1.1.1.3 root 964: WXStatusPanel::DClickPerf(const Indicator *ind)
1.1 root 965: {
966: // モードを切り替える
1.1.1.6 root 967: syncer->RequestFullSpeed(!syncer->GetFullSpeed());
1.1 root 968: }
1.1.1.3 root 969:
970: // コンテキストメニューイベント
971: void
972: WXStatusPanel::OnContextMenu(wxContextMenuEvent& event)
973: {
974: wxPoint pos = ScreenToClient(event.GetPosition());
975:
976: for (auto ind : indicators) {
977: // コンテキストメニューを処理できる枠内なら
1.1.1.5 root 978: if (ind->contextmenu && ind->rect.Contains(pos.x, pos.y)) {
979: (this->*(ind->contextmenu))(ind);
1.1.1.3 root 980: break;
981: }
982: }
983: }
984:
985: // CD 枠でのコンテキストメニュー
986: void
987: WXStatusPanel::ContextMenuCD(const Indicator *ind)
988: {
1.1.1.5 root 989: int scsiid = ind->GetSCSIID();
990: assertmsg(scsiid >= 0, "id=%d", ind->id);
991:
1.1.1.6 root 992: auto mainframe = dynamic_cast<WXMainFrame*>(GetParent());
993: auto menu = mainframe->CreateSCSIRemovableMenu(scsiid);
1.1.1.5 root 994: if (menu) {
995: PopupMenu(menu);
996: }
997: }
998:
999: // FD 枠でのコンテキストメニュー
1000: void
1001: WXStatusPanel::ContextMenuFD(const Indicator *ind)
1002: {
1003: int unit = ind->GetFDUnit();
1004: assertmsg(unit >= 0, "id=%d", ind->id);
1.1.1.3 root 1005:
1.1.1.6 root 1006: auto mainframe = dynamic_cast<WXMainFrame*>(GetParent());
1007: auto menu = mainframe->CreateFDMenu(unit);
1.1.1.3 root 1008: if (menu) {
1009: PopupMenu(menu);
1010: }
1011: }
1012:
1013: // SCSI メディア変更イベント (UIMessage イベント)
1014: void
1015: WXStatusPanel::OnSCSIMediaChanged(wxCommandEvent& event)
1016: {
1.1.1.5 root 1017: Indicator *ind = NULL;
1.1.1.7 root 1018: bool new_loaded;
1.1.1.3 root 1019:
1020: // 該当の Indicator を探す (見付かるはず)
1.1.1.5 root 1021: int scsiid = event.GetInt();
1022: int id = Indicator::SCSI0 + scsiid;
1023: for (auto i : indicators) {
1024: if (i->id == id) {
1025: ind = i;
1.1.1.3 root 1026: break;
1027: }
1028: }
1.1.1.5 root 1029: assert(ind);
1030: const auto disk = dynamic_cast<const SCSIDisk *>(ind->dev);
1.1.1.3 root 1031:
1032: // メディアの状態を一旦クリア
1.1.1.7 root 1033: new_loaded = false;
1.1.1.3 root 1034:
1035: // ツールチップを設定
1036: SCSI::DevType devtype = disk->GetDevType();
1037: std::string tip = string_format("%s%d: ",
1.1.1.5 root 1038: SCSI::GetDevTypeName(devtype), scsiid);
1.1.1.3 root 1039: const std::string& path = disk->GetPathName();
1040: // 同時にメディア状態もセット
1041: if (path.empty()) {
1042: tip += "-";
1043: } else {
1044: tip += path;
1.1.1.7 root 1045: new_loaded = true;
1.1.1.3 root 1046: }
1047: switch (disk->GetWriteMode()) {
1048: case SCSIDisk::RW::ReadOnly:
1049: tip += "\n(Read Only)";
1050: break;
1051: case SCSIDisk::RW::WriteProtect:
1052: tip += "\n(Write Protected)";
1053: break;
1054: case SCSIDisk::RW::WriteIgnore:
1055: tip += "\n(Write Ignored)";
1056: break;
1057: default:
1058: break;
1059: }
1.1.1.5 root 1060: ind->panel->SetToolTip(tip);
1061:
1062: // 書き込み無視だけ、これによって shutdown が必要かどうかという
1063: // オペレーションに違いが出るため表示する。
1064: // 無駄に幅を取らないためメディア挿入時のみ表示。
1065: // MO のライトプロテクト表示は必要になったら考える…。
1066: ind->text = string_format("%s%d", SCSI::GetDevTypeName(devtype), scsiid);
1.1.1.7 root 1067: if (new_loaded) {
1.1.1.5 root 1068: if (disk->GetWriteMode() == SCSIDisk::RW::WriteIgnore) {
1069: ind->text += "\x02\x03";
1070: }
1071: }
1.1.1.3 root 1072:
1073: // メディアのロード状態が変われば (再配置して) 再描画。
1074: // 通常周期のタイマーによる更新チェックではメディア状態はチェックして
1075: // いないので、ここで自発的に行う。
1.1.1.7 root 1076: if (scsi_loaded[scsiid] != new_loaded) {
1077: scsi_loaded[scsiid] = new_loaded;
1.1.1.3 root 1078:
1079: LayoutIndicators();
1080: Refresh();
1081: }
1082: }
1.1.1.5 root 1083:
1084: // FDD メディア変更イベント (UIMessage イベント)
1085: void
1086: WXStatusPanel::OnFDDMediaChanged(wxCommandEvent& event)
1087: {
1088: Indicator *ind = NULL;
1089: bool new_loaded;
1090:
1091: // 該当の Indicator を探す (見付かるはず)
1092: int unit = event.GetInt();
1093: int id = Indicator::FDD0 + unit;
1094: for (auto i : indicators) {
1095: if (i->id == id) {
1096: ind = i;
1097: break;
1098: }
1099: }
1100: assert(ind);
1101: const auto fdd = dynamic_cast<const FDDDevice *>(ind->dev);
1102:
1103: // メディアの状態を一旦クリア
1104: new_loaded = false;
1105:
1106: // ツールチップを設定
1107: std::string tip = string_format("FD%d: ", unit);
1108: const std::string& path = fdd->GetPathName();
1109: // 同時にメディア状態もセット
1110: if (path.empty()) {
1111: tip += "-";
1112: } else {
1113: tip += path;
1114: new_loaded = true;
1115: }
1116: switch (fdd->GetWriteMode()) {
1117: case FDDDevice::RW::WriteProtect:
1118: tip += "\n(Write Protected)";
1119: break;
1120: case FDDDevice::RW::WriteIgnore:
1121: tip += "\n(Write Ignored)";
1122: break;
1123: default:
1124: break;
1125: }
1126: ind->panel->SetToolTip(tip);
1127:
1128: ind->text = string_format("FD%d", fdd->GetUnitNo());
1129: // 書き込み無視だけ表示する。無駄に幅を取らないためメディア挿入時のみ表示。
1130: // 4文字分のマークの後ろに "WI" を追加。
1131: if (new_loaded) {
1132: if (fdd->GetWriteMode() == FDDDevice::RW::WriteIgnore) {
1133: ind->text += " \x02\x03";
1134: }
1135: }
1136:
1137: // メディアのロード状態が変われば (再配置して) 再描画。
1138: // 通常周期のタイマーによる更新チェックではメディア状態はチェックして
1139: // いないので、ここで自発的に行う。
1.1.1.7 root 1140: if (fd_loaded[unit] != new_loaded) {
1141: fd_loaded[unit] = new_loaded;
1.1.1.5 root 1142:
1143: LayoutIndicators();
1144: Refresh();
1145: }
1146: }
1147:
1148: // LED 状態変更イベント (UIMessage イベント)。
1149: //
1150: // 状態変更があったことをプッシュ通知する。主に、通常の更新頻度では
1151: // 間に合わない X68030 の電源 LED の 16Hz 点滅用。
1152: void
1153: WXStatusPanel::OnLEDChanged(wxCommandEvent& event)
1154: {
1155: if (UpdateStat()) {
1156: Refresh();
1157: }
1158: }
1159:
1160:
1161: //
1162: // インジケータ情報
1163: //
1164:
1165: // SCSI なら SCSI ID を返す。そうでなければ -1 を返す。
1166: int
1167: Indicator::GetSCSIID() const
1168: {
1169: int scsiid = id - SCSI0;
1170: if (0 <= scsiid && scsiid <= 7) {
1171: return scsiid;
1172: } else {
1173: return -1;
1174: }
1175: }
1176:
1177: // FDD ならユニット番号を返す。そうでなければ -1 を返す。
1178: int
1179: Indicator::GetFDUnit() const
1180: {
1181: int unit = id - FDD0;
1182: if (0 <= unit && unit <= 3) {
1183: return unit;
1184: } else {
1185: return -1;
1186: }
1187: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.