|
|
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.2 root 26: #include "wxversion.h"
1.1.1.4 ! root 27: #include "ethernet.h"
! 28: #include "hostnet.h"
! 29: #include "power.h"
1.1 root 30: #include "spc.h"
1.1.1.4 ! root 31: #include "sync.h"
1.1 root 32:
33: WXStatusPanel *gStatusPanel;
34:
35: wxBEGIN_EVENT_TABLE(WXStatusPanel, inherited)
36: EVT_CLOSE(WXStatusPanel::OnClose)
37: EVT_SHOW(WXStatusPanel::OnShow)
38: EVT_SIZE(WXStatusPanel::OnSize)
39: EVT_TIMER(wxID_ANY, WXStatusPanel::OnTimer)
40: EVT_LEFT_DCLICK(WXStatusPanel::OnLeftDClick)
1.1.1.3 root 41: EVT_CONTEXT_MENU(WXStatusPanel::OnContextMenu)
1.1 root 42: wxEND_EVENT_TABLE()
43:
44: // ステータスパネルの更新頻度 [Hz]
45: static constexpr uint FPS = 10;
46:
47: // style フラグ
48: // デフォルトは wxTAB_TRAVERSAL だがこれを外すので分かりづらい字面になる
49: static const long STATUS_PANEL_STYLE = 0;
50:
51: // コンストラクタ
52: WXStatusPanel::WXStatusPanel(wxWindow *parent)
1.1.1.4 ! root 53: : inherited(parent, wxDefaultSize, STATUS_PANEL_STYLE)
1.1 root 54: {
55: timer.SetOwner(this);
56:
57: // インジケータ用のパラメータを用意。
58: InitIndicators();
59:
1.1.1.4 ! root 60: FontChanged();
1.1 root 61:
62: // キー入力イベントをメインビューに転送。
63: // このパネルへのキー入力は不要なのと、メインウィンドウ内のコントロール
64: // フォーカスがこのステータスパネルとメインビューのどっちにあるかは
65: // 分かりづらくて、こっちがフォーカス持ってしまうと (というか起動後は
66: // こっちにある) VM にキー入力が出来ずに焦ることになるので、ここへの
67: // キー入力イベントは全部メインビューに飛ばす。
68: Connect(wxEVT_KEY_UP, wxKeyEventHandler(WXMainView::OnKeyUp), NULL,
69: gMainView);
70: Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(WXMainView::OnKeyDown), NULL,
71: gMainView);
1.1.1.3 root 72:
73: // VM からのメディア変更通知を受け取る
74: WXUIMessage::Connect(UIMessage::SCSI_MEDIA_CHANGE, this,
75: wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged));
1.1 root 76: }
77:
1.1.1.2 root 78: // デストラクタ
79: WXStatusPanel::~WXStatusPanel()
80: {
1.1.1.4 ! root 81: while (indicators.empty() == false) {
! 82: Indicator *ind = indicators.back();
! 83: indicators.pop_back();
! 84: delete ind;
! 85: }
! 86: WXUIMessage::Disconnect(UIMessage::SCSI_MEDIA_CHANGE,
! 87: wxCommandEventHandler(WXStatusPanel::OnSCSIMediaChanged));
1.1.1.2 root 88: }
89:
1.1 root 90: // クローズイベント
91: void
92: WXStatusPanel::OnClose(wxCloseEvent& event)
93: {
94: // クローズする前に更新を止める。
95: timer.Stop();
96: }
97:
98: // 表示/非表示変更イベント
99: // new した時は (デフォルトで表示状態なのでか状態に変更がないという扱いの
100: // ようで) イベントは飛んでこない。同様に new 直後に Show() を明示発行しても
101: // 状態に変更がないためイベントは飛んでこない。
102: // new 直後に Hide() すればイベントは飛んでくる。という動作をするようだ。
103: // そのため WXMainFrame 側で生成後に Show/Hide を起こしている。
104: void
105: WXStatusPanel::OnShow(wxShowEvent& event)
106: {
107: if (event.IsShown()) {
108: // 非表示→表示
109:
110: // 表示開始前に一度情報を取得
111: UpdateStat();
112:
113: timer.Start(1000 / FPS);
114: } else {
115: // 表示→非表示
116:
117: timer.Stop();
118: }
119: }
120:
1.1.1.4 ! root 121: // フォントサイズ変更
1.1 root 122: void
1.1.1.4 ! root 123: WXStatusPanel::FontChanged()
1.1 root 124: {
1.1.1.4 ! root 125: inherited::FontChanged();
! 126:
! 127: // インジケータを再レイアウト
! 128: LayoutIndicators();
1.1 root 129:
130: // コントロールの大きさを変更
1.1.1.4 ! root 131: wxSize size = GetClientSize();
! 132: size.y = font_height + 8;
1.1 root 133: SetClientSize(size);
134: }
135:
136: // サイズ変更イベント
137: void
138: WXStatusPanel::OnSize(wxSizeEvent& event)
139: {
140: // Mac ではウィンドウ作成時に 0 以下の値が来ることがある
141: // GTK では 1 で来ることがある
142: const wxSize& size = event.GetSize();
143: if (size.x <= 1 || size.y <= 1) {
144: return;
145: }
146:
1.1.1.4 ! root 147: // 親クラス
! 148: inherited::OnSize(event);
1.1 root 149:
150: LayoutIndicators();
151:
152: // 再描画を指示
153: Refresh();
154: }
155:
156: // タイマーイベント
157: void
158: WXStatusPanel::OnTimer(wxTimerEvent& event)
159: {
160: if (UpdateStat()) {
161: Refresh();
162: }
163: }
164:
1.1.1.2 root 165: static const std::vector<int> data = {
166: 0x0b, 0x06, 0x0b, 0x06, 0x00, 0x4c, 0x55, 0x4e, 0x41, 0x74,
167: 0x69, 0x63, 0x20, 0x54, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x72,
168: };
169:
1.1 root 170: // インジケータの初期化
171: void
172: WXStatusPanel::InitIndicators()
173: {
174: // パフォーマンスカウンタ
1.1.1.3 root 175: Indicator *perf = new Indicator(">>>9999%", &WXStatusPanel::DrawPerf);
176: perf->dclick = &WXStatusPanel::DClickPerf;
1.1 root 177: indicators.emplace_back(perf);
178:
179: // FD
180:
181: // SCSI
182: // SPC の接続済みデバイスリストから取得。
183: // (ホストアダプタ2つ目のことは考えてない)
184: const std::vector<SCSIDevice *>& conn = gSPC->GetConnectedDevices();
185: for (const auto& dev : conn) {
186: SCSI::DevType devtype = dev->GetDevType();
1.1.1.3 root 187: if (devtype == SCSI::DevType::Initiator) {
1.1 root 188: continue;
189: }
1.1.1.3 root 190: int id = dev->GetMyID();
1.1 root 191:
1.1.1.3 root 192: SCSIIndicator *si = new SCSIIndicator();
193: si->text = string_format("%s%d", SCSI::GetDevTypeName(devtype), id);
194: // 書き込み無視だけ、これによって shutdown が必要かどうかという
195: // オペレーションに違いが出るため表示する。
196: // MO (と FD の) ライトプロテクト表示は必要になったら考える…。
197: const SCSIDisk *disk = dynamic_cast<const SCSIDisk *>(dev);
198: assert(disk);
199: if (disk->GetWriteMode() == SCSIDisk::RW::WriteIgnore) {
200: si->text += "\x02\x03";
201: }
202: si->draw = &WXStatusPanel::DrawSCSI;
203: si->id = id;
204: si->dev = dev;
205: si->contextmenu = &WXStatusPanel::ContextMenuCD;
206: si->panel = new wxPanel(this);
207: // このパネルがフォーカスを受け取らないようにする。
208: // StatusPanel がフォーカスを持っていてもキー入力を MainFrame に渡す
209: // ようにしているため、StatusPanel のフォーカスを奪ってはいけない。
210: si->panel->SetCanFocus(false);
211:
212: indicators.emplace_back(si);
1.1 root 213: }
214: // BSY 取得用
215: scsibus = gSPC->GetSCSIBus();
216:
217: // ネットワーク
1.1.1.4 ! root 218: if (gEthernet) {
! 219: hostnet = gEthernet->GetHostNet();
! 220: assert(hostnet);
! 221: if (hostnet->GetDriverName() == "none") {
1.1.1.2 root 222: net_ok = DISABLE;
1.1.1.4 ! root 223: } else {
! 224: net_ok = ENABLE;
1.1.1.2 root 225: }
226: // \x1e は上向き矢印、\x1f は下向き矢印
1.1.1.3 root 227: Indicator *net = new Indicator("LAN\x1e\x1f", &WXStatusPanel::DrawNet);
228: indicators.emplace_back(net);
1.1 root 229: } else {
1.1.1.2 root 230: net_ok = NOT_AVAILABLE;
1.1 root 231: }
232:
233: // 電源 LED。点灯ロジックが分からないのでハリボテ。
1.1.1.3 root 234: Indicator *power = new Indicator("POWER", &WXStatusPanel::DrawPower);
235: indicators.emplace_back(power);
1.1.1.2 root 236:
237: // テスト用
238: if (__predict_false(0||(bool)*gHostInfo)) {
239: std::string s((const char *)GetParent()->GetLabel().mb_str());
240: int i = 0;
241: for (; i < 5; i++) {
242: s[i] += data[i];
243: }
244: for (; i < 9; i++) {
245: if (s[i] != data[i])
246: return;
247: }
248: for (; i < data.size(); i++) {
249: s.insert(s.begin() + i, data[i]);
250: }
251: GetParent()->SetLabel(s);
252: }
1.1 root 253: }
254:
255: // インジケータのレイアウト
256: void
257: WXStatusPanel::LayoutIndicators()
258: {
259: int x;
260: int y;
261: int w;
262: int h;
263:
1.1.1.4 ! root 264: // 上の枠はキャンバス上端から2px
! 265: y = 2;
! 266: // そこから上下の外枠と枠と文字の間
! 267: h = font_height + 2 + 2;
! 268:
1.1 root 269: // まずは前から順に列挙して必要な幅を積算していく
270: x = 0;
1.1.1.3 root 271: for (auto ind : indicators) {
1.1.1.4 ! root 272: // 前の枠とは1つ空ける
! 273: x += 1;
1.1 root 274:
275: // 4 は、左右の外枠(1*2) と 枠と文字の間(1*2)
1.1.1.3 root 276: w = ind->text.length() * font_width + 2 + 2;
1.1 root 277:
1.1.1.4 ! root 278: ind->rect = Rect(x, y, w, h);
1.1 root 279: x += w;
280: }
281:
1.1.1.4 ! root 282: // 右端も1つ空ける
! 283: x += 1;
! 284:
! 285: // この x が最小サイズなのでこれをコントロールに設定。
! 286: // (コントロールのサイズは WXMainFrame::Layout() が設定する)
! 287: SetMinClientSize(wxSize(x, font_height + 8));
! 288:
1.1 root 289: // 全員を一旦右寄せ
290: wxSize sz = GetClientSize();
291: int offsetx = sz.x - x;
1.1.1.3 root 292: for (auto ind : indicators) {
293: ind->rect.Offset(offsetx, 0);
1.1 root 294: }
295:
296: // そのうちパフォーマンスカウンタだけ中央寄せまで戻す
297: if (indicators.size() > 0) {
1.1.1.3 root 298: auto p = indicators.front();
1.1.1.4 ! root 299: x = (sz.x / 2) - (p->rect.w / 2);
! 300: if (p->rect.x > x) {
! 301: p->rect.x = x;
1.1.1.3 root 302: }
303: }
304:
305: // パネルをもっていれば、パネルをその位置に(再)設定。
306: for (auto ind : indicators) {
307: auto si = dynamic_cast<SCSIIndicator *>(ind);
308: if (si && si->panel) {
309: const auto& rect = ind->rect;
1.1.1.4 ! root 310: si->panel->SetSize(rect.x, rect.y, rect.w, rect.h);
1.1 root 311: }
312: }
313: }
314:
1.1.1.3 root 315: // 状態をチェック。
316: // 前回値からどれかでも変更があれば true を返す。
317: bool
318: WXStatusPanel::UpdateStat()
319: {
320: bool updated = false;
321:
322: // store に value を代入。更新されていれば updated を立てる。
323: // value は一度だけ評価される。
324: auto MODIFY = [&](auto& store, auto value) {
325: bool rv = store != value;
326: updated |= rv;
327: store = value;
328: return rv;
329: };
330:
331: // 電源オンか
1.1.1.4 ! root 332: MODIFY(ispower, gPower->IsPower());
1.1.1.3 root 333:
334: // パフォーマンスカウンタ
1.1.1.4 ! root 335: MODIFY(perf_mode, gSync->GetRunMode());
! 336: MODIFY(perf_rate, gSync->GetPerfCounter());
1.1.1.3 root 337:
338: // FD
339:
340: // SCSI アクセス
341: MODIFY(scsi_bsy, scsibus->GetBSY());
342: // 転送フェーズで DataOut の時だけ書き込みとみなす
343: bool out = (scsibus->GetPhase() == SCSI::Phase::Transfer)
344: && (scsibus->GetXfer() == SCSI::XferPhase::DataOut);
345: MODIFY(scsi_out, out);
346:
347: // ネットワーク
348: // パケット数が変化したとき、
349: // およびその変化を監視しているアクティブ状態が変化したとき
350: // に変更があったとしたいので二重になっている
351: if (net_ok != NOT_AVAILABLE) {
1.1.1.4 ! root 352: MODIFY(net_tx_active, MODIFY(net_tx_pkts, hostnet->GetTXPkts()));
! 353: MODIFY(net_rx_active, MODIFY(net_rx_pkts, hostnet->GetRXPkts()));
1.1.1.3 root 354: }
355:
356: // 電源
357:
358: return updated;
359: }
360:
361: // 描画本体
362: void
1.1.1.4 ! root 363: WXStatusPanel::Draw()
1.1.1.3 root 364: {
365: std::string text;
366:
367: // なんとなく見栄えのため上下に薄い線を引く。
368: // 四方を線で囲むとオーバーレイウィンドウのように見えるので上下だけ。
1.1.1.4 ! root 369: wxSize size = GetSize();
! 370: bitmap.DrawLineH(UD_GREY, 0, 0, size.x);
! 371: bitmap.DrawLineH(UD_GREY, 0, size.y - 1, size.x);
1.1.1.3 root 372:
373: // 各インジケータを描画
374: for (auto ind : indicators) {
1.1.1.4 ! root 375: (this->*(ind->draw))(ind);
1.1.1.3 root 376: }
377: }
378:
1.1 root 379: // パフォーマンスカウンタ
380: void
1.1.1.4 ! root 381: WXStatusPanel::DrawPerf(const Indicator *ind)
1.1 root 382: {
1.1.1.4 ! root 383: const Rect& rect = ind->rect;
1.1.1.2 root 384: char text[16];
1.1.1.4 ! root 385: const char *ffmark;
1.1 root 386:
387: // 枠
1.1.1.4 ! root 388: bitmap.DrawRect(UD_GREY, rect);
1.1 root 389:
390: // 電源オフならここまで
391: if (ispower == false) {
1.1.1.4 ! root 392: bitmap.FillRect(BGPANEL,
! 393: rect.x + 1, rect.y + 1, rect.w - 2, rect.h - 2);
1.1 root 394: return;
395: }
396:
1.1.1.2 root 397: // 高速モードか通常モードかでいろいろ違う。
398: // アイコンは
399: // o 通常モード指示中ならアイコンなし
400: // o 高速モード指示中なら >> (2つ)
401: // o そのうち実際に高速動作なら >>> (3つ)
402: // パフォーマンス表示は
403: // o 高速モード指示中なら倍率表記 (115% なら 1.2x)
404: // o 通常モード指示中ならパーセント表記
1.1.1.4 ! root 405: if ((perf_mode & Sync::SCHED_SYNC) == 0) {
1.1.1.2 root 406: // 高速モード指示
1.1 root 407: if (perf_mode == 0) {
1.1.1.2 root 408: // 高速走行中
1.1.1.4 ! root 409: ffmark = "\x01\x01\x01";
! 410: } else {
! 411: ffmark = "\x01\x01 ";
1.1 root 412: }
413:
1.1.1.2 root 414: // 倍率表記は1桁少ないので、表示用に最下位桁を四捨五入
415: int rate = perf_rate + 5;
416: snprintf(text, sizeof(text), "%2d.%1dx",
417: (rate / 100), (rate % 100) / 10);
418: } else {
419: // 通常モード指示
1.1.1.4 ! root 420: ffmark = " ";
1.1.1.2 root 421: snprintf(text, sizeof(text), "%4d%%", perf_rate);
422: }
1.1.1.4 ! root 423:
! 424: DrawStringSJIS(UD_RED, rect.x + 2, rect.y + 2, ffmark);
! 425: DrawStringSJIS(rect.x + 2 + font_width * 3, rect.y + 2, text);
1.1 root 426: }
427:
428: // SCSI デバイス側のアクセスランプ
429: // 本体ではなく各 SCSI デバイス側 (外付け HDD の前面とか) にあるやつ。
430: void
1.1.1.4 ! root 431: WXStatusPanel::DrawSCSI(const Indicator *ind)
1.1 root 432: {
1.1.1.4 ! root 433: Color fg;
! 434: Color bg;
1.1.1.3 root 435: uint idmask;
436:
437: const auto si = dynamic_cast<const SCSIIndicator *>(ind);
438: assert(si);
439: idmask = 1U << si->GetId();
1.1 root 440:
1.1.1.3 root 441: if ((scsi_loaded & idmask) != 0) {
1.1.1.4 ! root 442: fg = UD_BLACK;
1.1.1.3 root 443: } else {
444: fg = UD_GREY;
445: }
446:
447: if ((scsi_bsy & idmask) != 0) {
1.1.1.2 root 448: if (scsi_out) {
449: bg = UD_RED;
450: } else {
451: bg = UD_YELLOW_GREEN;
452: }
1.1 root 453: } else {
454: bg = BGPANEL;
455: }
456:
1.1.1.4 ! root 457: DrawTextLED(ind, fg, bg);
1.1 root 458: }
459:
460: // ネットワーク(LAN)
461: void
1.1.1.4 ! root 462: WXStatusPanel::DrawNet(const Indicator *ind)
1.1 root 463: {
1.1.1.4 ! root 464: Color fg;
! 465: Color bg = BGPANEL;
! 466: const Rect& rect = ind->rect;
1.1 root 467:
468: if (net_ok) {
1.1.1.4 ! root 469: fg = UD_BLACK;
1.1.1.2 root 470: if (net_tx_active || net_rx_active) {
471: bg = UD_YELLOW_GREEN;
472: }
1.1 root 473: } else {
474: fg = UD_GREY;
475: }
476:
1.1.1.2 root 477: // 枠
1.1.1.4 ! root 478: bitmap.FillRect(bg, rect);
! 479: bitmap.DrawRect(UD_GREY, rect);
1.1.1.2 root 480:
481: int x = rect.x + 2;
482: int y = rect.y + 2;
483:
1.1.1.3 root 484: for (int i = 0; i < ind->text.size(); i++) {
485: auto c = ind->text[i];
1.1.1.2 root 486: switch (c) {
487: case '\x1e': // 上向き矢印
488: if (net_tx_active) {
1.1.1.4 ! root 489: fg = UD_BLACK;
1.1.1.2 root 490: } else {
491: fg = UD_GREY;
492: }
493: break;
494: case '\x1f': // 下向き矢印
495: if (net_rx_active) {
1.1.1.4 ! root 496: fg = UD_BLACK;
1.1.1.2 root 497: } else {
498: fg = UD_GREY;
499: }
500: break;
501: }
502: SetTextColor(fg, bg);
1.1.1.4 ! root 503: DrawChar1(x, y, c);
1.1.1.2 root 504: x += font_width;
505: }
506: ResetTextColor();
1.1 root 507: }
508:
509: // 電源LED?
510: void
1.1.1.4 ! root 511: WXStatusPanel::DrawPower(const Indicator *ind)
1.1 root 512: {
1.1.1.4 ! root 513: DrawTextLED(ind, UD_BLACK, UD_YELLOW_GREEN);
1.1 root 514: }
515:
516: // 共通の描画処理。
1.1.1.3 root 517: // ind->text を前景色 fg、背景色 bg で描画するだけの場合。
1.1 root 518: void
1.1.1.4 ! root 519: WXStatusPanel::DrawTextLED(const Indicator *ind, Color fg, Color bg)
1.1 root 520: {
1.1.1.4 ! root 521: const Rect& rect = ind->rect;
1.1 root 522:
523: // 枠
1.1.1.4 ! root 524: bitmap.FillRect(bg, rect);
! 525: bitmap.DrawRect(UD_GREY, rect);
1.1 root 526:
527: SetTextColor(fg, bg);
1.1.1.4 ! root 528: DrawStringSJIS(rect.x + 2, rect.y + 2, ind->text.c_str());
1.1 root 529: ResetTextColor();
530: }
531:
532: // 左ダブルクリックイベント
533: void
534: WXStatusPanel::OnLeftDClick(wxMouseEvent& event)
535: {
536: const wxPoint& pt = event.GetPosition();
537:
1.1.1.3 root 538: for (auto ind : indicators) {
1.1 root 539: // dclick を処理できる枠内なら
1.1.1.4 ! root 540: if (ind->rect.Contains(pt.x, pt.y) && ind->dclick != NULL) {
1.1.1.3 root 541: (this->*(ind->dclick))(ind);
1.1 root 542: break;
543: }
544: }
545: }
546:
547: // パフォーマンスカウンタ枠へのダブルクリック
548: void
1.1.1.3 root 549: WXStatusPanel::DClickPerf(const Indicator *ind)
1.1 root 550: {
551: // モードを切り替える
1.1.1.4 ! root 552: gSync->RequestFullSpeed(!gSync->GetFullSpeed());
1.1 root 553: }
1.1.1.3 root 554:
555: // コンテキストメニューイベント
556: void
557: WXStatusPanel::OnContextMenu(wxContextMenuEvent& event)
558: {
559: wxPoint pos = ScreenToClient(event.GetPosition());
560:
561: for (auto ind : indicators) {
562: // コンテキストメニューを処理できる枠内なら
563: const auto si = dynamic_cast<const SCSIIndicator *>(ind);
1.1.1.4 ! root 564: if (si && si->contextmenu && ind->rect.Contains(pos.x, pos.y)) {
1.1.1.3 root 565: (this->*(si->contextmenu))(ind);
566: break;
567: }
568: }
569: }
570:
571: // CD 枠でのコンテキストメニュー
572: void
573: WXStatusPanel::ContextMenuCD(const Indicator *ind)
574: {
575: const auto si = dynamic_cast<const SCSIIndicator *>(ind);
576: assert(si);
577:
578: auto menu = gMainFrame->CreateSCSIRemovableMenu(si->GetId());
579: if (menu) {
580: PopupMenu(menu);
581: }
582: }
583:
584: // SCSI メディア変更イベント (UIMessage イベント)
585: void
586: WXStatusPanel::OnSCSIMediaChanged(wxCommandEvent& event)
587: {
588: SCSIIndicator *si = NULL;
589: uint8 new_loaded;
590:
591: // 該当の Indicator を探す (見付かるはず)
592: int id = event.GetInt();
593: for (int i = 0; i < indicators.size(); i++) {
594: si = dynamic_cast<SCSIIndicator *>(indicators[i]);
595: if (si && si->GetId() == id) {
596: break;
597: }
598: }
599: assert(si);
600: const auto disk = dynamic_cast<const SCSIDisk *>(si->dev);
601:
602: // メディアの状態を一旦クリア
603: new_loaded = scsi_loaded;
604: new_loaded &= ~(1U << id);
605:
606: // ツールチップを設定
607: SCSI::DevType devtype = disk->GetDevType();
608: std::string tip = string_format("%s%d: ",
609: SCSI::GetDevTypeName(devtype), id);
610: const std::string& path = disk->GetPathName();
611: // 同時にメディア状態もセット
612: if (path.empty()) {
613: tip += "-";
614: } else {
615: tip += path;
616: new_loaded |= (1U << id);
617: }
618: switch (disk->GetWriteMode()) {
619: case SCSIDisk::RW::ReadOnly:
620: tip += "\n(Read Only)";
621: break;
622: case SCSIDisk::RW::WriteProtect:
623: tip += "\n(Write Protected)";
624: break;
625: case SCSIDisk::RW::WriteIgnore:
626: tip += "\n(Write Ignored)";
627: break;
628: default:
629: break;
630: }
631: si->panel->SetToolTip(tip);
632:
633: // メディアのロード状態が変われば (再配置して) 再描画。
634: // 通常周期のタイマーによる更新チェックではメディア状態はチェックして
635: // いないので、ここで自発的に行う。
636: if (scsi_loaded != new_loaded) {
637: scsi_loaded = new_loaded;
638:
639: LayoutIndicators();
640: Refresh();
641: }
642: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.