|
|
1.1 ! root 1: // ! 2: // nono ! 3: // Copyright (C) 2018 [email protected] ! 4: // ! 5: ! 6: #include "wxheader.h" ! 7: #include "keyboard.h" ! 8: #include "renderer.h" ! 9: #include "scheduler.h" ! 10: #include "wxmainview.h" ! 11: #include <wx/region.h> ! 12: #include <wx/event.h> ! 13: #if defined(__NetBSD__) ! 14: #include <sys/param.h> // __NetBSD_Version__ ! 15: #endif ! 16: ! 17: //#define KEYDEBUG 1 ! 18: ! 19: #if defined(KEYDEBUG) ! 20: #define DPRINTF(...) printf(__VA_ARGS__) ! 21: #else ! 22: #define DPRINTF(...) /**/ ! 23: #endif ! 24: ! 25: // 再描画矩形の指定方法について。 ! 26: // DoRefresh() にて、VM スレッドが自発的に画面を更新する際は、 ! 27: // パフォーマンスの観点からも更新矩形だけを再描画すべき。 ! 28: // これを wxWidgets に指示するのが wxWindow::Refresh{,Rect}() であるが ! 29: // 一部のプラットフォームではこれを呼び出すと、アプリケーションウィンドウ ! 30: // 全体がささってしまい、MainView だけでなくメニューバーも含めたすべてが ! 31: // 再描画できなくなってしまう。仕方がないので、これらのプラットフォームでは ! 32: // Refresh{,Rect}() を呼ばず、再描画矩形を画面全体として運用する。 ! 33: // ちょっと無駄なだけなので (動かないことに比べれば) 気にしない。 ! 34: // ! 35: // 今のところ NetBSD 5.0 以上(と Linux (CentOS 6.3/x86_64)) が該当する。 ! 36: #if defined(__NetBSD__) && __NetBSD_Version__ >= 500000000 ! 37: #define DONT_USE_REFRESH 1 ! 38: #endif ! 39: ! 40: #define TIMERID_PAINTTIMER 1 ! 41: ! 42: static void wxmainview_refresh(); ! 43: ! 44: WXMainView *gMainView; ! 45: ! 46: wxBEGIN_EVENT_TABLE(WXMainView, inherited) ! 47: EVT_PAINT(WXMainView::OnPaint) ! 48: EVT_KEY_DOWN(WXMainView::OnKeyDown) ! 49: EVT_KEY_UP(WXMainView::OnKeyUp) ! 50: EVT_TIMER(TIMERID_PAINTTIMER, WXMainView::OnTimer) ! 51: wxEND_EVENT_TABLE() ! 52: ! 53: WXMainView::WXMainView(wxWindow *parent) ! 54: : inherited(parent) ! 55: { ! 56: // 仮想画面(VM が描画する画面) ! 57: rendwidth = gRenderer->GetWidth(); ! 58: rendheight = gRenderer->GetHeight(); ! 59: ! 60: // レンダラバッファは常に仮想画面サイズ ! 61: int buflen = rendwidth * rendheight * 3; ! 62: ImageBuf = new uint8_t[buflen]; ! 63: memset(ImageBuf, 0x70, buflen); ! 64: // レンダラに通知 ! 65: gRenderer->SetBuf(ImageBuf, wxmainview_refresh); ! 66: ! 67: // 実画面 (ウィンドウの大きさ) ! 68: DoResize(screen_scale); ! 69: ! 70: paintTimer.SetOwner(this, TIMERID_PAINTTIMER); ! 71: paintTimer.Start(16); ! 72: paintRequested = true; // first paint request ! 73: } ! 74: ! 75: WXMainView::~WXMainView() ! 76: { ! 77: delete[] ImageBuf; ! 78: } ! 79: ! 80: // リフレッシュ要求コールバック。 ! 81: // VM から呼ばれるので一旦グローバル関数を経由する。 ! 82: void ! 83: wxmainview_refresh(void) ! 84: { ! 85: gMainView->DoRefresh(); ! 86: } ! 87: ! 88: // リフレッシュ要求コールバック。こっちが実体。 ! 89: void ! 90: WXMainView::DoRefresh() ! 91: { ! 92: #if defined(DONT_USE_REFRESH) ! 93: // Refresh{,Rect}() が使えないので、再描画範囲は常に全体とする ! 94: #else ! 95: // 再描画範囲を更新矩形に限定 ! 96: // XXX のはずだが、今の所全体 ! 97: RefreshRect(wxRect(0, 0, viewwidth, viewheight), false); ! 98: #endif ! 99: ! 100: // PAINT イベントを投げる ! 101: // XXX んだけど mac だと投げすぎるとシステムイベントを拾えなくなってしまう ! 102: paintRequested = true; ! 103: } ! 104: ! 105: void ! 106: WXMainView::OnTimer(wxTimerEvent& event) ! 107: { ! 108: bool req = paintRequested.exchange(false); ! 109: if (req) { ! 110: // PAINT イベントを投げる ! 111: wxPaintEvent *ev = new wxPaintEvent(); ! 112: QueueEvent(ev); ! 113: } ! 114: } ! 115: ! 116: // 描画イベント ! 117: void ! 118: WXMainView::OnPaint(wxPaintEvent& event) ! 119: { ! 120: wxClientDC dc(this); ! 121: ! 122: DoPaint(dc); ! 123: } ! 124: ! 125: // 描画実行 ! 126: void ! 127: WXMainView::DoPaint(wxDC& dc) ! 128: { ! 129: if (viewwidth == 0 || viewheight == 0) ! 130: return; ! 131: ! 132: wxImage image(rendwidth, rendheight, ImageBuf, true); ! 133: ! 134: if (viewwidth != rendwidth || viewheight != rendheight) { ! 135: // wxIMAGE_QUALITY_NORMAL だと速いが、品質優先。 ! 136: image.Rescale(viewwidth, viewheight, wxIMAGE_QUALITY_HIGH); ! 137: } ! 138: ! 139: wxBitmap bitmap(image); ! 140: wxMemoryDC memDC; ! 141: memDC.SelectObject(bitmap); ! 142: ! 143: wxRegion region(0, 0, viewwidth, viewheight); ! 144: wxRegionIterator ri(region); ! 145: for (; ri; ri++) { ! 146: int dx, dy, dw, dh; ! 147: dx = ri.GetX(); ! 148: dy = ri.GetY(); ! 149: dw = ri.GetWidth(); ! 150: dh = ri.GetHeight(); ! 151: int sx = dx; ! 152: int sy = dy; ! 153: dc.Blit(dx, dy, dw, dh, &memDC, sx, sy, wxCOPY); ! 154: } ! 155: } ! 156: ! 157: // リサイズ (UI スレッドから呼ぶこと) ! 158: void ! 159: WXMainView::DoResize(double scale) ! 160: { ! 161: screen_scale = scale; ! 162: int width = (double)rendwidth * scale; ! 163: int height = (double)rendheight * scale; ! 164: if (viewwidth != width || viewheight != height) { ! 165: // ウィンドウサイズ変更 ! 166: viewwidth = width; ! 167: viewheight = height; ! 168: SetClientSize(viewwidth, viewheight); ! 169: SetMinSize(wxSize(viewwidth, viewheight)); ! 170: } ! 171: } ! 172: ! 173: // 現在のスケールを返す。 ! 174: // screen_scale は public static なので見えるんだけど一応お行儀で。 ! 175: double ! 176: WXMainView::GetScale() const ! 177: { ! 178: return screen_scale; ! 179: } ! 180: ! 181: // 現在のスケール ! 182: /*static*/ double WXMainView::screen_scale; ! 183: ! 184: ! 185: // ! 186: // キー入力 ! 187: // ! 188: ! 189: // wxKeyEvent でキーコードを得るためのメソッドは ! 190: // GetKeyCode()、GetRawKeyCode()、GetRawKeyFlags() の3つが用意されていて ! 191: // wxWigets の port (wxGTK、wxOSX とか) によってそれぞれ内容の定義が違う。 ! 192: // ! 193: // wxGTK の場合 ! 194: // GetKeyCode() は wxWidgets の WXK_* (/usr/pkg/include/wx-3.0/wx/defs.h)。 ! 195: // これは左右の SHIFT が区別できないが、 ! 196: // それ以外はだいたい使えそうではある。 ! 197: // ! 198: // GetRawKeyCode() は GDK の keycode っぽい? ! 199: // (/usr/pkg/include/gtk-2.0/gdk/gdkkeysyms.h)。 ! 200: // X11 の keysym から来てる? (/usr/X11R7/include/X11/keysymdef.h)。 ! 201: // ただし [A] と [SHIFT]+[A] が違うコードを返すというか、文字コードで ! 202: // 表現出来る時は文字コード、みたいな感じになっているので、ここでは ! 203: // 使いづらい。 ! 204: // 左右の SHIFT は区別可能。 ! 205: // [^] と [SHIFT]+[^] もそもそも文字コードになるので '^' と '~' になる。 ! 206: // ! 207: // GetRawKeyFlags() は GDK の hardware keycode らしいけど、それが何か不明。 ! 208: // 左右の SHIFT は違うコードを返す。 ! 209: // SHIFT を押しながらでもコードは変わらないっぽい。 ! 210: // [^] と [SHIFT]+[^] は同じコードを返す。 ! 211: // ! 212: // wxOSX の場合 ! 213: // GetKeyCode() ! 214: // これは左右の SHIFT が区別できないが、 ! 215: // それ以外はだいたい使えそうではある。 ! 216: // ! 217: // GetRawKeyCode() ! 218: // [A] と [SHIFT]+[A] などは同じコードを返す。 ! 219: // 左右の SHIFT は区別可能。 ! 220: // ! 221: // GetRawKeyFlags() は状態フラグか何かのようだ。 ! 222: // ! 223: ! 224: // キーを押した ! 225: void ! 226: WXMainView::OnKeyDown(wxKeyEvent& event) ! 227: { ! 228: DPRINTF("KeyDown "); ! 229: int keycode = GetKeyCode(event); ! 230: gKeyboard->MakeKey(keycode); ! 231: } ! 232: ! 233: // キーを離した ! 234: void ! 235: WXMainView::OnKeyUp(wxKeyEvent& event) ! 236: { ! 237: DPRINTF("KeyUp "); ! 238: int keycode = GetKeyCode(event); ! 239: gKeyboard->BreakKey(keycode); ! 240: } ! 241: ! 242: // 共通キーコードを取得する。 ! 243: // 対応するキーがない場合は -1 を返す。 ! 244: int ! 245: WXMainView::GetKeyCode(wxKeyEvent& event) ! 246: { ! 247: uint32 wxcode __unused = event.GetKeyCode(); ! 248: uint32 rawcode __unused = event.GetRawKeyCode(); ! 249: uint32 rawflags __unused = event.GetRawKeyFlags(); ! 250: uint32 inp; ! 251: int keycode; ! 252: ! 253: #if defined(__WXGTK__) ! 254: // wxGTK では RawKeyFlags を使う ! 255: DPRINTF("key=0x%03x raw=0x%04x <flags=0x%02x> ", wxcode, rawcode, rawflags); ! 256: inp = rawflags; ! 257: #elif defined(__WXOSX__) ! 258: // wxOSX では RawKeyCode を使う ! 259: DPRINTF("key=0x%03x <raw=0x%02x> flags=0x%06x ", wxcode, rawcode, rawflags); ! 260: inp = rawcode; ! 261: #endif ! 262: ! 263: // どちらにしてもテーブルを引く ! 264: if (inp < countof(keycode_table)) { ! 265: keycode = keycode_table[inp]; ! 266: } else { ! 267: keycode = KC_none; ! 268: } ! 269: DPRINTF("keycode=%d %s\n", keycode, Keyboard::keyname[keycode]); ! 270: return keycode; ! 271: } ! 272: ! 273: #if defined(__WXGTK__) ! 274: // wxGTK の RawKeyFlags を共通キーコードに変換するテーブル。 ! 275: /*static*/ const int ! 276: WXMainView::keycode_table[0xd4] = { ! 277: KC_none, // 0x00 ! 278: KC_none, // 0x01 ! 279: KC_none, // 0x02 ! 280: KC_none, // 0x03 ! 281: KC_none, // 0x04 ! 282: KC_none, // 0x05 ! 283: KC_none, // 0x06 ! 284: KC_none, // 0x07 ! 285: KC_none, // 0x08 ! 286: KC_none, // 0x09 ! 287: KC_1, // 0x0a ! 288: KC_2, // 0x0b ! 289: KC_3, // 0x0c ! 290: KC_4, // 0x0d ! 291: KC_5, // 0x0e ! 292: KC_6, // 0x0f ! 293: ! 294: KC_7, // 0x10 ! 295: KC_8, // 0x11 ! 296: KC_9, // 0x12 ! 297: KC_0, // 0x13 ! 298: KC_minus, // 0x14 ! 299: KC_circum, // 0x15 ! 300: KC_BS, // 0x16 ! 301: KC_TAB, // 0x17 ! 302: KC_Q, // 0x18 ! 303: KC_W, // 0x19 ! 304: KC_E, // 0x1a ! 305: KC_R, // 0x1b ! 306: KC_T, // 0x1c ! 307: KC_Y, // 0x1d ! 308: KC_U, // 0x1e ! 309: KC_I, // 0x1f ! 310: ! 311: KC_O, // 0x20 ! 312: KC_P, // 0x21 ! 313: KC_at, // 0x22 ! 314: KC_bracketleft, // 0x23 ! 315: KC_enter, // 0x24 ! 316: KC_CTRL, // 0x25 (CTRL_L) ! 317: KC_A, // 0x26 ! 318: KC_S, // 0x27 ! 319: KC_D, // 0x28 ! 320: KC_F, // 0x29 ! 321: KC_G, // 0x2a ! 322: KC_H, // 0x2b ! 323: KC_J, // 0x2c ! 324: KC_K, // 0x2d ! 325: KC_L, // 0x2e ! 326: KC_semicolon, // 0x2f ! 327: ! 328: KC_colon, // 0x30 ! 329: KC_ESC, // 0x31 ! 330: KC_SHIFT_L, // 0x32 (SHIFT_L) ! 331: KC_bracketright,// 0x33 ! 332: KC_Z, // 0x34 ! 333: KC_X, // 0x35 ! 334: KC_C, // 0x36 ! 335: KC_V, // 0x37 ! 336: KC_B, // 0x38 ! 337: KC_N, // 0x39 ! 338: KC_M, // 0x3a ! 339: KC_comma, // 0x3b ! 340: KC_period, // 0x3c ! 341: KC_slash, // 0x3d ! 342: KC_SHIFT_R, // 0x3e (SHIFT_R) ! 343: KC_PAD_multiply,// 0x3f ! 344: ! 345: KC_none, // 0x40 ! 346: KC_space, // 0x41 ! 347: KC_none, // 0x42 ! 348: KC_F1, // 0x43 ! 349: KC_F2, // 0x44 ! 350: KC_F3, // 0x45 ! 351: KC_F4, // 0x46 ! 352: KC_F5, // 0x47 ! 353: KC_F6, // 0x48 ! 354: KC_F7, // 0x49 ! 355: KC_F8, // 0x4a ! 356: KC_F9, // 0x4b ! 357: KC_F10, // 0x4c ! 358: KC_none, // 0x4d ! 359: KC_none, // 0x4e ! 360: KC_PAD_7, // 0x4f ! 361: ! 362: KC_PAD_8, // 0x50 ! 363: KC_PAD_9, // 0x51 ! 364: KC_PAD_minus, // 0x52 ! 365: KC_PAD_4, // 0x53 ! 366: KC_PAD_5, // 0x54 ! 367: KC_PAD_6, // 0x55 ! 368: KC_PAD_plus, // 0x56 ! 369: KC_PAD_1, // 0x57 ! 370: KC_PAD_2, // 0x58 ! 371: KC_PAD_3, // 0x59 ! 372: KC_PAD_0, // 0x5a ! 373: KC_PAD_decimal, // 0x5b ! 374: KC_none, // 0x5c ! 375: KC_none, // 0x5d ! 376: KC_none, // 0x5e ! 377: KC_none, // 0x5f ! 378: ! 379: KC_none, // 0x60 ! 380: KC_none, // 0x61 ! 381: KC_up, // 0x62 ! 382: KC_none, // 0x63 ! 383: KC_left, // 0x64 ! 384: KC_none, // 0x65 ! 385: KC_right, // 0x66 ! 386: KC_none, // 0x67 ! 387: KC_down, // 0x68 ! 388: KC_none, // 0x69 ! 389: KC_INS, // 0x6a ! 390: KC_DEL, // 0x6b ! 391: KC_PAD_enter, // 0x6c ! 392: KC_CTRL, // 0x6d (CTRL_R) ! 393: KC_none, // 0x6e ! 394: KC_none, // 0x6f ! 395: ! 396: KC_PAD_divide, // 0x70 ! 397: KC_none, // 0x71 ! 398: KC_none, // 0x72 ! 399: KC_none, // 0x73 ! 400: KC_none, // 0x74 ! 401: KC_none, // 0x75 ! 402: KC_none, // 0x76 ! 403: KC_none, // 0x77 ! 404: KC_none, // 0x78 ! 405: KC_none, // 0x79 ! 406: KC_none, // 0x7a ! 407: KC_none, // 0x7b ! 408: KC_none, // 0x7c ! 409: KC_none, // 0x7d ! 410: KC_none, // 0x7e ! 411: KC_none, // 0x7f ! 412: ! 413: KC_none, // 0x80 ! 414: KC_none, // 0x81 ! 415: KC_none, // 0x82 ! 416: KC_none, // 0x83 ! 417: KC_none, // 0x84 ! 418: KC_backslash, // 0x85 ! 419: KC_none, // 0x86 ! 420: KC_none, // 0x87 ! 421: KC_none, // 0x88 ! 422: KC_none, // 0x89 ! 423: KC_none, // 0x8a ! 424: KC_none, // 0x8b ! 425: KC_none, // 0x8c ! 426: KC_none, // 0x8d ! 427: KC_none, // 0x8e ! 428: KC_none, // 0x8f ! 429: ! 430: KC_none, KC_none, KC_none, KC_none, // 0x90 ! 431: KC_none, KC_none, KC_none, KC_none, ! 432: KC_none, KC_none, KC_none, KC_none, ! 433: KC_none, KC_none, KC_none, KC_none, ! 434: ! 435: KC_none, KC_none, KC_none, KC_none, // 0xa0 ! 436: KC_none, KC_none, KC_none, KC_none, ! 437: KC_none, KC_none, KC_none, KC_none, ! 438: KC_none, KC_none, KC_none, KC_none, ! 439: ! 440: KC_none, KC_none, KC_none, KC_none, // 0xb0 ! 441: KC_none, KC_none, KC_none, KC_none, ! 442: KC_none, KC_none, KC_none, KC_none, ! 443: KC_none, KC_none, KC_none, KC_none, ! 444: ! 445: KC_none, KC_none, KC_none, KC_none, // 0xc0 ! 446: KC_none, KC_none, KC_none, KC_none, ! 447: KC_none, KC_none, KC_none, KC_none, ! 448: KC_none, KC_none, KC_none, KC_none, ! 449: ! 450: KC_none, // 0xd0 ! 451: KC_none, // 0xd1 ! 452: KC_none, // 0xd2 ! 453: KC_underscore, // 0xd3 ! 454: }; ! 455: #endif // __WXGTK__ ! 456: ! 457: #if defined(__WXOSX__) ! 458: // wxOSX の RawKeyCode を共通キーコードに変換するテーブル。 ! 459: // Mac の CAPS は押す離すで一回分の KeyDown しか来ないのでちょっと放置。 ! 460: // [英数] と [かな] もちょっと放置。 ! 461: /*static*/ const int ! 462: WXMainView::keycode_table[0x80] = { ! 463: KC_A, // 0x00 ! 464: KC_S, // 0x01 ! 465: KC_D, // 0x02 ! 466: KC_F, // 0x03 ! 467: KC_H, // 0x04 ! 468: KC_G, // 0x05 ! 469: KC_Z, // 0x06 ! 470: KC_X, // 0x07 ! 471: KC_C, // 0x08 ! 472: KC_V, // 0x09 ! 473: KC_none, // 0x0a ! 474: KC_B, // 0x0b ! 475: KC_Q, // 0x0c ! 476: KC_W, // 0x0d ! 477: KC_E, // 0x0e ! 478: KC_R, // 0x0f ! 479: ! 480: KC_Y, // 0x10 ! 481: KC_T, // 0x11 ! 482: KC_1, // 0x12 ! 483: KC_2, // 0x13 ! 484: KC_3, // 0x14 ! 485: KC_4, // 0x15 ! 486: KC_6, // 0x16 ! 487: KC_5, // 0x17 ! 488: KC_circum, // 0x18 ! 489: KC_9, // 0x19 ! 490: KC_7, // 0x1a ! 491: KC_minus, // 0x1b ! 492: KC_8, // 0x1c ! 493: KC_0, // 0x1d ! 494: KC_bracketleft, // 0x1e ! 495: KC_O, // 0x1f ! 496: ! 497: KC_U, // 0x20 ! 498: KC_at, // 0x21 ! 499: KC_I, // 0x22 ! 500: KC_P, // 0x23 ! 501: KC_enter, // 0x24 ! 502: KC_L, // 0x25 ! 503: KC_J, // 0x26 ! 504: KC_colon, // 0x27 ! 505: KC_K, // 0x28 ! 506: KC_semicolon, // 0x29 ! 507: KC_bracketright,// 0x2a ! 508: KC_comma, // 0x2b ! 509: KC_slash, // 0x2c ! 510: KC_N, // 0x2d ! 511: KC_M, // 0x2e ! 512: KC_period, // 0x2f ! 513: ! 514: KC_TAB, // 0x30 ! 515: KC_space, // 0x31 ! 516: KC_none, // 0x32 ! 517: KC_BS, // 0x33 ! 518: KC_none, // 0x34 ! 519: KC_ESC, // 0x35 ! 520: KC_none, // 0x36 ! 521: KC_none, // 0x37 ! 522: KC_SHIFT_L, // 0x38 ! 523: KC_none, // 0x39 ! 524: KC_none, // 0x3a ! 525: KC_CTRL, // 0x3b ! 526: KC_SHIFT_R, // 0x3c ! 527: KC_none, // 0x3d ! 528: KC_none, // 0x3e ! 529: KC_none, // 0x3f ! 530: ! 531: KC_none, // 0x40 ! 532: KC_none, // 0x41 ! 533: KC_none, // 0x42 ! 534: KC_none, // 0x43 ! 535: KC_none, // 0x44 ! 536: KC_none, // 0x45 ! 537: KC_none, // 0x46 ! 538: KC_none, // 0x47 ! 539: KC_none, // 0x48 ! 540: KC_none, // 0x49 ! 541: KC_none, // 0x4a ! 542: KC_none, // 0x4b ! 543: KC_none, // 0x4c ! 544: KC_none, // 0x4d ! 545: KC_none, // 0x4e ! 546: KC_none, // 0x4f ! 547: ! 548: KC_none, // 0x50 ! 549: KC_none, // 0x51 ! 550: KC_none, // 0x52 ! 551: KC_none, // 0x53 ! 552: KC_none, // 0x54 ! 553: KC_none, // 0x55 ! 554: KC_none, // 0x56 ! 555: KC_none, // 0x57 ! 556: KC_none, // 0x58 ! 557: KC_none, // 0x59 ! 558: KC_none, // 0x5a ! 559: KC_none, // 0x5b ! 560: KC_none, // 0x5c ! 561: KC_backslash, // 0x5d ! 562: KC_underscore, // 0x5e ! 563: KC_none, // 0x5f ! 564: ! 565: KC_F5, // 0x60 ! 566: KC_F6, // 0x61 ! 567: KC_F7, // 0x62 ! 568: KC_F3, // 0x63 ! 569: KC_F8, // 0x64 ! 570: KC_F9, // 0x65 ! 571: KC_none, // 0x66 ! 572: KC_none, // 0x67 F11 ! 573: KC_none, // 0x68 ! 574: KC_none, // 0x69 ! 575: KC_none, // 0x6a ! 576: KC_none, // 0x6b ! 577: KC_none, // 0x6c ! 578: KC_F10, // 0x6d ! 579: KC_none, // 0x6e ! 580: KC_none, // 0x6f F12 ! 581: ! 582: KC_none, // 0x70 ! 583: KC_none, // 0x71 ! 584: KC_none, // 0x72 ! 585: KC_none, // 0x73 ! 586: KC_none, // 0x74 ! 587: KC_none, // 0x75 ! 588: KC_F4, // 0x76 ! 589: KC_none, // 0x77 ! 590: KC_F2, // 0x78 ! 591: KC_none, // 0x79 ! 592: KC_F1, // 0x7a ! 593: KC_left, // 0x7b ! 594: KC_right, // 0x7c ! 595: KC_down, // 0x7d ! 596: KC_up, // 0x7e ! 597: KC_none, // 0x7f ! 598: }; ! 599: #endif // __WXOSX__
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.