OSDN Git Service

アプリケーション名のtypo修正。
[gvonavish/GVONavish.git] / GVONavish / GVONavish / GVONavish.cpp
1 #include "stdafx.h"
2 #include <vector>
3 #include <list>
4
5 // PNG\82Æ\82©\82Ì\93Ç\82Ý\8d\9e\82Ý\97p
6 #include <gdiplus.h>
7 #pragma comment(lib, "gdiplus.lib")
8
9 // PATH API\97p
10 #include <Shlwapi.h>
11 #pragma comment(lib, "shlwapi.lib")
12
13 #include <CommCtrl.h>
14 #pragma comment(lib, "comctl32.lib")
15 #include <CommDlg.h>
16 #pragma comment(lib, "Comdlg32.lib")
17
18
19 #include "GVONavish.h"
20 #include "GVOConfig.h"
21 #include "GVOGameProcess.h"
22 #include "GVOWorldMap.h"
23 #include "GVOShip.h"
24 #include "GVOSpeedMeter.h"
25
26
27
28
29 // \83f\83o\83b\83O\8e\9e\82Ì\95`\89æ\83p\83t\83H\81[\83}\83\93\83X\91ª\92è\97p\81B
30 //#define GVO_PERF_CHECK
31
32
33
34 // \83O\83\8d\81[\83o\83\8b\95Ï\90\94:
35 HINSTANCE g_hinst;                                                                              // \8c»\8dÝ\82Ì\83C\83\93\83^\81[\83t\83F\83C\83X
36 HWND g_hwndMain;
37 HDC g_hdcMain;
38
39
40 // \8aÖ\90\94\83v\83\8d\83g\83^\83C\83v\90é\8c¾
41 static ATOM MyRegisterClass( HINSTANCE hInstance );
42 static BOOL InitInstance( HINSTANCE, int );
43 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
44 static LRESULT s_mainLoop();
45
46
47 // \83\81\83b\83Z\81[\83W\83n\83\93\83h\83\89
48 static bool s_onCreate( HWND, LPCREATESTRUCT );
49 static void s_onMove( HWND, WORD, WORD );
50 static void s_onSize( HWND, UINT, WORD, WORD );
51 static void s_onMouseWheel( HWND, int16_t, UINT, int16_t, int16_t );
52 static void s_onMouseMove( HWND, UINT, int16_t, int16_t );
53 static void s_onMouseLeftButtonDown( HWND, UINT, int16_t, int16_t );
54 static void s_onMouseLeftButtonUp( HWND, UINT, int16_t, int16_t );
55 static void s_onMouseLeftButtonDoubleClick( HWND, UINT, int16_t, int16_t );
56 static void s_onMouseRightButtonUp( HWND, UINT, int16_t, int16_t );
57 static void s_onPaint( HWND );
58
59
60 // \83A\83v\83\8a\8f\88\97\9d
61 static std::wstring s_makeVersionString();
62 static std::wstring s_getMapFileName();
63 static void s_updateFrame(HWND hwnd, HDC hdc);
64 static void s_updateWindowTitle( HWND );
65 static void s_toggleKeepForeground( HWND );
66 static void s_popupMenu( HWND, int16_t, int16_t );
67 static void s_popupCoord( HWND, int16_t, int16_t );
68
69
70 // \83\8d\81[\83J\83\8b\95Ï\90\94
71 static LPCWSTR const k_appName = L"GVONavi\81i\82Á\82Û\82¢\89½\82©\81j";              // \83A\83v\83\8a\83P\81[\83V\83\87\83\93\96¼
72 static LPCWSTR const k_version = L"ver 1.0.1";                                  // \83o\81[\83W\83\87\83\93\94Ô\8d\86
73 static LPCWSTR const k_copyright = L"copyright(c) mandheling";  // \92\98\8dì\8c \95\\8e¦\81i\82¢\82¿\82¨\81[\81j
74
75 static LPCWSTR const k_windowClassName = L"GVONavish";          // \83\81\83C\83\93 \83E\83B\83\93\83h\83\83N\83\89\83X\96¼
76 static const LPCWSTR k_configFileName = L"GVONavish.ini";       // \90Ý\92è\83t\83@\83C\83\8b\96¼
77 static LPCWSTR const k_appMutexName = L"Global\\{7554E265-3247-4FCA-BC60-5AA814658351}";
78 static HANDLE s_appMutex;
79
80 static const std::wstring k_aboutText = s_makeVersionString();  // \83o\81[\83W\83\87\83\93\8fî\95ñ\83e\83L\83X\83g
81
82 static GVOImage s_backbuffer;   //!<@brief \95`\89æ\97p24bit\83C\83\81\81[\83W\83o\83b\83t\83@
83 static HANDLE s_pollingTimerEvent = ::CreateEvent( NULL, TRUE, TRUE, NULL );
84 static UINT s_pollingTimerID = 0;
85
86 static Gdiplus::GdiplusStartupInput s_gdisi;
87 static ULONG_PTR s_gdiToken;
88
89 static GVOConfig s_config( k_configFileName );
90 static GVOGameProcess s_gvoGameProcess;
91 static GVOWorldMap s_worldMap;
92 static GVOShip s_ship;
93 static GVOSpeedMeter s_speedMeter;
94
95 static UINT s_pollingInterval = 1000;   // \8fó\91Ô\8aÄ\8e\8b\8aÔ\8au\81i1\95b\81j
96 static bool s_isUpdated = false;                // \8fó\91Ô\8dX\90V\83t\83\89\83O
97 static bool s_isDragging = false;               // \83h\83\89\83b\83O\8fó\91Ô\83t\83\89\83O
98 static SIZE s_clientSize;                               // \83N\83\89\83C\83A\83\93\83g\97Ì\88æ\82Ì\91å\82«\82³
99 static POINT s_dragOrg;                                 // \83h\83\89\83b\83O\8c´\93_\81i\88Ú\93®\97Ê\8eZ\8fo\97p\81j
100
101
102 #ifndef NDEBUG
103 // \83f\83o\83b\83O\97p\8e©\93®\8dq\8ds\95Ï\90\94
104 static double s_xDebugAutoCruise;
105 static double s_yDebugAutoCruise;
106 static double s_debugAutoCruiseAngle = 0;
107 #endif
108
109
110
111
112 int APIENTRY _tWinMain( _In_ HINSTANCE hInstance,
113         _In_opt_ HINSTANCE hPrevInstance,
114         _In_ LPTSTR    lpCmdLine,
115         _In_ int       nCmdShow )
116 {
117         UNREFERENCED_PARAMETER( hPrevInstance );
118         UNREFERENCED_PARAMETER( lpCmdLine );
119
120         ::SetLastError( NOERROR );
121         s_appMutex = ::CreateMutex( NULL, TRUE, k_appMutexName );
122         if ( ::GetLastError() == ERROR_ALREADY_EXISTS ) {
123                 HWND hwnd = ::FindWindow( k_windowClassName, NULL );
124                 if ( hwnd ) {
125                         ::SetForegroundWindow( hwnd );
126                 }
127                 return 0;
128         }
129
130         ::CoInitialize( NULL );
131         TIMECAPS tc;
132         ::timeGetDevCaps( &tc, sizeof(tc) );
133         ::timeBeginPeriod( tc.wPeriodMin );
134         INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_WIN95_CLASSES };
135         ::InitCommonControlsEx( &icc );
136         GdiplusStartup( &s_gdiToken, &s_gdisi, NULL );
137         s_config.load();
138
139         MyRegisterClass( hInstance );
140
141         // \83A\83v\83\8a\83P\81[\83V\83\87\83\93\82Ì\8f\89\8aú\89»\82ð\8eÀ\8ds\82µ\82Ü\82·:
142         if ( !InitInstance( hInstance, nCmdShow ) ) {
143                 return 0;
144         }
145
146         s_pollingTimerID = ::timeSetEvent( s_config.m_pollingInterval, tc.wPeriodMin, LPTIMECALLBACK( s_pollingTimerEvent ), 0, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET );
147
148         // \83\81\83C\83\93 \83\81\83b\83Z\81[\83\83\8b\81[\83v:
149         const LRESULT retVal = s_mainLoop();
150
151         ::timeKillEvent( s_pollingTimerID );
152
153         s_config.save();
154         Gdiplus::GdiplusShutdown( s_gdiToken );
155         ::timeEndPeriod( tc.wPeriodMin );
156         ::CoUninitialize();
157         return retVal;
158 }
159
160 static ATOM MyRegisterClass( HINSTANCE hInstance )
161 {
162         WNDCLASSEX wcex = { sizeof(wcex) };
163
164         wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC;
165         wcex.lpfnWndProc = WndProc;
166         wcex.hInstance = hInstance;
167         wcex.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDR_MAINFRAME ) );
168         wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
169         wcex.hbrBackground = (HBRUSH)::GetStockObject( BLACK_BRUSH );
170         //wcex.lpszMenuName     = MAKEINTRESOURCE(IDC_GVONAVISH);
171         wcex.lpszClassName = k_windowClassName;
172         wcex.hIconSm = LoadIcon( wcex.hInstance, MAKEINTRESOURCE( IDI_SMALL ) );
173
174         return RegisterClassEx( &wcex );
175 }
176
177 static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
178 {
179         if ( !s_worldMap.loadFromFile( s_config ) ) {
180                 // \8e¸\94s\82·\82ê\82Î\95Û\91\82³\82ê\82È\82¢\82Ì\82Å\91å\8fä\95v\81B
181                 s_config.m_mapFileName = s_getMapFileName();
182                 if ( !s_worldMap.loadFromFile( s_config ) ) {
183                         ::MessageBox( NULL,
184                                 L"\83}\83b\83v\89æ\91\9c\82ð\8aJ\82¯\82Ü\82¹\82ñ\82Å\82µ\82½\81B",
185                                 k_appName,
186                                 MB_ICONERROR | MB_SETFOREGROUND | MB_OK );
187                         return FALSE;
188                 }
189         }
190
191         HWND hwnd;
192
193         g_hinst = hInstance; // \83O\83\8d\81[\83o\83\8b\95Ï\90\94\82É\83C\83\93\83X\83^\83\93\83X\8f\88\97\9d\82ð\8ai\94[\82µ\82Ü\82·\81B
194
195         DWORD exStyle = 0;
196         if ( s_config.m_keepForeground ) {
197                 exStyle |= WS_EX_TOPMOST;
198         }
199         hwnd = CreateWindowEx( exStyle, k_windowClassName, k_appName, WS_OVERLAPPEDWINDOW,
200                 s_config.m_windowPos.x, s_config.m_windowPos.y,
201                 s_config.m_windowSize.cx, s_config.m_windowSize.cy,
202                 NULL, NULL, hInstance, NULL );
203
204         if ( !hwnd ) {
205                 return FALSE;
206         }
207
208         s_pollingInterval = s_config.m_pollingInterval;
209         s_gvoGameProcess.setConfig( s_config );
210         s_worldMap.setConfig( s_config );
211         s_ship.setInitialSurveyCoord( s_config.m_initialSurveyCoord );
212 #ifndef NDEBUG
213         s_xDebugAutoCruise = s_config.m_initialSurveyCoord.x;
214         s_yDebugAutoCruise = s_config.m_initialSurveyCoord.y;
215 #endif
216
217         if ( s_gvoGameProcess.updateState() ) {
218                 s_ship.updateWithSurveyCoord( s_gvoGameProcess.surveyCoord(), s_gvoGameProcess.timeStamp() );
219                 s_worldMap.setShipPosition( s_gvoGameProcess.surveyCoord(), s_config.m_traceShipPositionEnabled );
220         }
221         s_updateWindowTitle( hwnd );
222
223         ShowWindow( hwnd, nCmdShow );
224         UpdateWindow( hwnd );
225         g_hwndMain = hwnd;
226         g_hdcMain = ::GetDC( g_hwndMain );
227
228         return TRUE;
229 }
230
231 static LRESULT s_mainLoop()
232 {
233         MSG msg;
234         HACCEL hAccelTable;
235
236         hAccelTable = LoadAccelerators( g_hinst, MAKEINTRESOURCE( IDC_GVONAVISH ) );
237
238         for ( ;; ) {
239                 if ( ::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
240                         if ( msg.message == WM_QUIT ) {
241                                 break;
242                         }
243                         if ( !TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) ) {
244                                 TranslateMessage( &msg );
245                                 DispatchMessage( &msg );
246                         }
247                         continue;
248                 }
249                 std::vector<HANDLE> handles;
250
251                 // \8aÄ\8e\8b\82·\82é\83n\83\93\83h\83\8b\82ð\92Ç\89Á\82·\82é\81B
252                 if ( s_gvoGameProcess.processHandle() ) {
253                         handles.push_back( s_gvoGameProcess.processHandle() );
254                 }
255                 handles.push_back( s_pollingTimerEvent );
256
257                 if ( handles.empty() ) {
258                         ::WaitMessage();
259                         continue;
260                 }
261
262                 DWORD const waitResult = ::MsgWaitForMultipleObjects( handles.size(), &handles[0], FALSE, INFINITE, QS_ALLINPUT );
263                 if ( handles.size() <= waitResult ) {
264                         continue;
265                 }
266
267                 // \8aÄ\8e\8b\82·\82é\83n\83\93\83h\83\8b\82É\91Î\89\9e\82·\82é\81B
268                 HANDLE const activeHandle = handles[waitResult];
269
270                 if ( activeHandle == s_gvoGameProcess.processHandle() ) {
271                         // \83Q\81[\83\80\83v\83\8d\83Z\83X\82ª\8fI\97¹\82µ\82½\81B
272                         s_gvoGameProcess.clear();
273                         continue;
274                 }
275                 if ( activeHandle == s_pollingTimerEvent ) {
276                         ::ResetEvent( s_pollingTimerEvent );
277                         s_updateFrame( g_hwndMain, g_hdcMain );
278                         continue;
279                 }
280         }
281         return (int)msg.wParam;
282 }
283
284
285 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
286 {
287         int wmId, wmEvent;
288
289         switch ( message ) {
290         case WM_ERASEBKGND:
291                 return TRUE;
292         case WM_PAINT:
293                 s_onPaint( hwnd );
294                 break;
295
296         case WM_MOVE:
297                 s_onMove( hwnd, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
298                 break;
299         case WM_SIZE:
300                 s_onSize( hwnd, wp, LOWORD( lp ), HIWORD( lp ) );
301                 break;
302
303         case WM_COMMAND:
304                 wmId = LOWORD( wp );
305                 wmEvent = HIWORD( wp );
306                 // \91I\91ð\82³\82ê\82½\83\81\83j\83\85\81[\82Ì\89ð\90Í:
307                 switch ( wmId ) {
308                 case IDM_ABOUT:
309                         ::MessageBox( hwnd,
310                                 k_aboutText.c_str(),
311                                 k_appName,
312                                 MB_OK | MB_ICONINFORMATION );
313                         break;
314                 case IDM_EXIT:
315                         DestroyWindow( hwnd );
316                         break;
317                 case IDM_TOGGLE_TRACE_SHIP:
318                         s_config.m_traceShipPositionEnabled = !s_config.m_traceShipPositionEnabled;
319                         break;
320                 case IDM_ERASE_SHIP_ROUTE:
321                         s_worldMap.clearShipRoute();
322                         break;
323                 case IDM_TOGGLE_KEEP_FOREGROUND:
324                         s_toggleKeepForeground( hwnd );
325                         break;
326                 case IDM_TOGGLE_SPEED_METER:
327                         s_config.m_speedMeterEnabled = !s_config.m_speedMeterEnabled;
328                         ::InvalidateRect( hwnd, NULL, FALSE );
329                         break;
330                 case IDM_TOGGLE_VECTOR_LINE:
331                         s_config.m_shipVectorLineEnabled = !s_config.m_shipVectorLineEnabled;
332                         s_worldMap.setVisibleShipRoute( s_config.m_shipVectorLineEnabled );
333                         break;
334                 default:
335                         return DefWindowProc( hwnd, message, wp, lp );
336                 }
337                 break;
338
339         case WM_KEYUP:
340                 switch ( wp ) {
341                 case VK_F1:
342                         s_toggleKeepForeground( hwnd );
343                         break;
344                 case VK_ADD:
345                         if ( s_worldMap.zoomIn() ) {
346                                 s_updateWindowTitle( hwnd );
347                                 ::InvalidateRect( hwnd, NULL, FALSE );
348                         }
349                         break;
350                 case VK_OEM_MINUS:
351                         if ( s_worldMap.zoomOut() ) {
352                                 s_updateWindowTitle( hwnd );
353                                 ::InvalidateRect( hwnd, NULL, FALSE );
354                         }
355                         break;
356                 default:
357                         break;
358                 }
359                 break;
360
361         case WM_MOUSEWHEEL:
362                 s_onMouseWheel( hwnd, HIWORD( wp ), LOWORD( wp ), int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
363                 break;
364         case WM_MOUSEMOVE:
365                 s_onMouseMove( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
366                 break;
367         case WM_LBUTTONDOWN:
368                 s_onMouseLeftButtonDown( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
369                 break;
370         case WM_LBUTTONUP:
371                 s_onMouseLeftButtonUp( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
372                 break;
373         case WM_RBUTTONUP:
374                 s_onMouseRightButtonUp( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
375                 break;
376         case WM_LBUTTONDBLCLK:
377                 s_onMouseLeftButtonDoubleClick( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
378                 break;
379
380         case WM_CREATE:
381                 if ( !s_onCreate( hwnd, reinterpret_cast<LPCREATESTRUCT>(lp) ) ) {
382                         return -1;
383                 }
384                 break;
385         case WM_DESTROY:
386                 PostQuitMessage( 0 );
387                 break;
388         default:
389                 return DefWindowProc( hwnd, message, wp, lp );
390         }
391         return 0;
392 }
393
394
395 static bool s_onCreate( HWND hwnd, LPCREATESTRUCT /*cs*/ )
396 {
397         return true;
398 }
399
400
401 static void s_onMove( HWND hwnd, WORD /*cx*/, WORD /*cy*/ )
402 {
403         const DWORD style = ::GetWindowLong( hwnd, GWL_STYLE );
404         if ( style & WS_MAXIMIZE ) {
405                 return;
406         }
407         RECT rc = { 0 };
408         ::GetWindowRect( hwnd, &rc );
409         s_config.m_windowPos.x = rc.left;
410         s_config.m_windowPos.y = rc.top;
411 }
412
413
414 static void s_onSize( HWND hwnd, UINT state, WORD cx, WORD cy )
415 {
416         RECT rc = { 0 };
417
418         switch ( state ) {
419         case SIZE_RESTORED:
420                 ::GetWindowRect( hwnd, &rc );
421                 s_config.m_windowSize.cx = rc.right - rc.left;
422                 s_config.m_windowSize.cy = rc.bottom - rc.top;
423                 break;
424         case SIZE_MAXIMIZED:
425                 break;
426         default:
427                 return;
428         }
429
430         if ( s_clientSize.cx != cx || s_clientSize.cy != cy ) {
431                 s_clientSize.cx = cx;
432                 s_clientSize.cy = cy;
433                 if ( !s_backbuffer.isCompatible( s_clientSize ) ) {
434                         s_backbuffer.createDIBImage( s_clientSize );
435                 }
436                 s_worldMap.setViewSize( s_clientSize );
437         }
438 }
439
440
441 static void s_onMouseWheel( HWND hwnd, int16_t delta, UINT vkey, int16_t x, int16_t y )
442 {
443         bool isChanged = false;
444
445         if ( 0 < delta ) {
446                 isChanged = s_worldMap.zoomIn();
447         }
448         else {
449                 isChanged = s_worldMap.zoomOut();
450         }
451
452         if ( isChanged ) {
453                 s_updateWindowTitle( hwnd );
454                 ::InvalidateRect( hwnd, NULL, FALSE );
455         }
456 }
457
458
459 static void s_onMouseMove( HWND hwnd, UINT vkey, int16_t x, int16_t y )
460 {
461         if ( s_isDragging ) {
462                 const int dx = x - s_dragOrg.x;
463                 const int dy = y - s_dragOrg.y;
464                 const int threshold = 1;        // \8a´\93x\82ª\97Ç\82·\82¬\82é\82Æ\92Ç\8f]\82ª\8aÈ\92P\82É\90Ø\82ê\82Ä\82µ\82Ü\82¤\82Ì\82Å\93K\93\96\82É\91Î\8dô
465                 if ( s_config.m_traceShipPositionEnabled ) {
466                         if ( ::abs( dx ) <= threshold && ::abs( dy ) < threshold ) {
467                                 return;
468                         }
469                 }
470                 const POINT offset = { -dx, -dy };
471
472                 s_worldMap.offsetFocusInViewCoord( offset );
473                 ::InvalidateRect( hwnd, NULL, FALSE );
474
475                 s_dragOrg.x = x;
476                 s_dragOrg.y = y;
477                 s_config.m_traceShipPositionEnabled = false;
478         }
479         else {
480
481         }
482 }
483
484
485 static void s_onMouseLeftButtonDown( HWND hwnd, UINT vkey, int16_t x, int16_t y )
486 {
487         if ( s_isDragging ) {
488
489         }
490         else {
491                 ::SetCapture( hwnd );
492                 s_isDragging = true;
493                 s_dragOrg.x = x;
494                 s_dragOrg.y = y;
495         }
496 }
497
498
499 static void s_onMouseLeftButtonUp( HWND hwnd, UINT vkey, int16_t x, int16_t y )
500 {
501         if ( s_isDragging ) {
502                 ::ReleaseCapture();
503                 s_isDragging = false;
504                 s_dragOrg.x = 0;
505                 s_dragOrg.y = 0;
506         }
507         else {
508
509         }
510 }
511
512
513 static void s_onMouseLeftButtonDoubleClick( HWND hwnd, UINT vkey, int16_t x, int16_t y )
514 {
515         if ( s_isDragging ) {
516
517         }
518         else {
519                 s_popupCoord( hwnd, x, y );
520         }
521 }
522
523
524 static void s_onMouseRightButtonUp( HWND hwnd, UINT vkey, int16_t x, int16_t y )
525 {
526         if ( s_isDragging ) {
527
528         }
529         else {
530                 s_popupMenu( hwnd, x, y );
531         }
532 }
533
534
535 // \83_\83u\83\8b\83o\83b\83t\83@\83\8a\83\93\83O\82Å\82¿\82ç\82Â\82«\96h\8e~\81B
536 static void s_onPaint( HWND hwnd )
537 {
538 #ifdef GVO_PERF_CHECK
539         int64_t perfBegin = 0, perfEnd = 0;
540         ::QueryPerformanceCounter((LARGE_INTEGER*)&perfBegin);
541 #endif
542         if ( !s_backbuffer.bitmapHandle() ) {
543                 s_backbuffer.createDIBImage( s_clientSize );
544         }
545
546         PAINTSTRUCT ps;
547         HDC hdc = BeginPaint( hwnd, &ps );
548         HDC hdcBackbuffer = ::CreateCompatibleDC( hdc );
549         ::SaveDC( hdcBackbuffer );
550         ::SelectObject( hdcBackbuffer, s_backbuffer.bitmapHandle() );
551         RECT rc = { 0, 0, s_clientSize.cx, s_clientSize.cy };
552         ::FillRect( hdcBackbuffer, &rc, (HBRUSH)::GetStockObject( BLACK_BRUSH ) );
553
554         // \95`\89æ\82ðhdcBackbuffer\82É\91Î\82µ\82Ä\8ds\82¤\81B
555         s_worldMap.drawMap( hdcBackbuffer, s_ship );
556
557         // \91¬\93x\8cv\82ð\95`\89æ
558         if ( s_config.m_speedMeterEnabled ) {
559                 const double velocity = s_speedMeter.velocityByKnot();
560                 wchar_t buf[4096] = { 0 };
561                 swprintf( buf, _countof( buf ), L"%.2f kt", velocity );
562
563                 RECT rc = { 0 };
564                 ::DrawText( hdcBackbuffer, buf, -1, &rc, DT_SINGLELINE | DT_RIGHT | DT_TOP | DT_CALCRECT );
565                 const int width = rc.right - rc.left;
566                 rc.left = s_clientSize.cx - width;
567                 rc.right = rc.left + width;
568                 ::DrawText( hdcBackbuffer, buf, -1, &rc, DT_SINGLELINE | DT_RIGHT | DT_TOP );
569         }
570
571 #ifndef NDEBUG
572         // \91ª\97Ê\8dÀ\95W\82ð\95`\89æ
573         {
574                 const GVOImage& surveyCoordImage = s_gvoGameProcess.surveyCoordImage();
575                 HDC hdcSurvey = ::CreateCompatibleDC( hdcBackbuffer );
576                 ::SaveDC( hdcSurvey );
577                 ::SelectObject( hdcSurvey, surveyCoordImage.bitmapHandle() );
578                 ::BitBlt( hdcBackbuffer, 0, 0, surveyCoordImage.size().cx, surveyCoordImage.size().cy,
579                         hdcSurvey, 0, 0, SRCCOPY );
580                 ::RestoreDC( hdcSurvey, -1 );
581                 ::DeleteDC( hdcSurvey );
582         }
583 #endif
584
585 #ifndef NDEBUG
586         if ( s_config.m_debugAutoCruiseEnabled ) {
587                 const double rad = ((s_debugAutoCruiseAngle)* M_PI) / 180;
588                 const double vx = ::cos( rad );
589                 const double vy = ::sin( rad );
590                 const LONG length = max( s_clientSize.cx, s_clientSize.cy );
591
592                 COLORREF rgb = (::fabs(s_ship.vector().angleTo(GVOVector(vx,vy))) <= FLT_EPSILON) ? RGB( 0, 255, 0 ) : RGB( 255, 255, 0 );
593
594                 LONG x2 = s_clientSize.cx / 2 + LONG( vx * length );
595                 LONG y2 = s_clientSize.cy / 2 + LONG( vy * length );
596                 HPEN pen = ::CreatePen( PS_SOLID, 3, rgb );
597                 HGDIOBJ old = ::SelectObject( hdcBackbuffer, pen );
598                 ::MoveToEx( hdcBackbuffer, s_clientSize.cx / 2, s_clientSize.cy / 2, NULL );
599                 ::LineTo( hdcBackbuffer, x2, y2 );
600                 ::SelectObject( hdcBackbuffer, old );
601                 ::DeleteObject( pen );
602         }
603 #endif  // #ifndef NDEBUG
604
605         ::BitBlt( hdc, 0, 0, s_clientSize.cx, s_clientSize.cy,
606                 hdcBackbuffer, 0, 0, SRCCOPY );
607
608         ::RestoreDC( hdcBackbuffer, -1 );
609         ::DeleteDC( hdcBackbuffer );
610         EndPaint( hwnd, &ps );
611
612
613 #ifdef GVO_PERF_CHECK
614         ::QueryPerformanceCounter((LARGE_INTEGER*)&perfEnd);
615         int64_t freq = 0;
616         ::QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
617         const double deltaPerSec = (double(perfEnd - perfBegin) / double(freq)) * 1000.0;
618         typedef std::list<double> PerfList;
619         static PerfList perf;
620         perf.push_back(deltaPerSec);
621
622         const double ave = std::accumulate( perf.begin(), perf.end(), 0.0 ) / perf.size();
623         if ( 100 < perf.size() ) {
624                 perf.pop_front();
625         }
626
627         std::wstring s;
628         s = std::wstring( L"perf:" ) + std::to_wstring( ave ) + L"(ms)\n";
629         ::SetWindowText( hwnd, s.c_str() );
630         ::InvalidateRect( hwnd, NULL, FALSE );
631 #endif
632 }
633
634
635 static std::wstring s_makeVersionString()
636 {
637         std::wstring s;
638         s += std::wstring(k_appName) + L" " + k_version + L"\n";
639         s += k_copyright;
640         return s;
641 }
642
643
644 // \83}\83b\83v\89æ\91\9c\82ð\91I\91ð\82³\82¹\82é
645 static std::wstring s_getMapFileName()
646 {
647         wchar_t dir[MAX_PATH] = { 0 };
648         ::GetModuleFileName( g_hinst, dir, _countof( dir ) );
649         ::PathRemoveFileSpec( dir );
650
651         wchar_t filePath[MAX_PATH] = { 0 };
652         OPENFILENAME ofn = { sizeof(ofn) };
653         ofn.lpstrTitle = L"\83}\83b\83v\89æ\91\9c\83t\83@\83C\83\8b\82ð\91I\91ð\82µ\82Ä\82­\82¾\82³\82¢\81B";
654         ofn.lpstrInitialDir = &dir[0];
655         ofn.lpstrFilter = L"\83C\83\81\81[\83W\83t\83@\83C\83\8b\0L" L"*.bmp;*.jpg;*.jpeg;*.png;*.gif;*.tif;*.tiff" L"\0"
656                 L"\91S\82Ä\82Ì\83t\83@\83C\83\8b\0" L"*.*" L"\0\0";
657         ofn.Flags = OFN_READONLY | OFN_FILEMUSTEXIST;
658         ofn.nMaxFile = _countof( filePath );
659         ofn.lpstrFile = &filePath[0];
660         if ( !::GetOpenFileName( &ofn ) ) {
661                 return L"";
662         }
663         return filePath;
664 }
665
666
667 static void s_updateFrame( HWND hwnd, HDC hdc )
668 {
669 #ifndef NDEBUG
670         if ( s_config.m_debugAutoCruiseEnabled ) {
671                 static bool isRandInitialized = false;
672                 if ( !isRandInitialized ) {
673                         srand( ::timeGetTime() );
674                         isRandInitialized = true;
675                 }
676
677                 const double rad = ((s_debugAutoCruiseAngle)* M_PI) / 180;
678                 const double vx = ::cos( rad );
679                 const double vy = ::sin( rad );
680
681                 s_xDebugAutoCruise += vx * s_config.m_debugAutoCruiseVelocity;
682                 s_yDebugAutoCruise += vy * s_config.m_debugAutoCruiseVelocity;
683
684                 static DWORD tick = ::timeGetTime();
685                 static DWORD count = 0;
686                 if ( (tick + s_config.m_debugAutoCruiseTurnInterval) < ::timeGetTime() ) {
687                         if ( 10 < (++count) ) {
688                                 count = 0;
689                                 s_debugAutoCruiseAngle += 90 + (LONG( rand() / double( RAND_MAX ) * 90 ) & ~0x1);
690                         }
691                         else {
692                                 s_debugAutoCruiseAngle += (rand() & 1) ? s_config.m_debugAutoCruiseTurnAngle : -s_config.m_debugAutoCruiseTurnAngle;
693                         }
694                         tick = ::timeGetTime();
695                 }
696                 s_debugAutoCruiseAngle = fmod( ::fabs( s_debugAutoCruiseAngle ), 360 );
697
698                 if ( s_xDebugAutoCruise < 0 ) {
699                         s_xDebugAutoCruise += k_worldWidth;
700                 }
701                 if ( s_yDebugAutoCruise < 0 ) {
702                         s_yDebugAutoCruise += k_worldHeight;
703                 }
704                 s_xDebugAutoCruise = fmod( s_xDebugAutoCruise, (double)k_worldWidth );
705                 s_yDebugAutoCruise = fmod( s_yDebugAutoCruise, (double)k_worldHeight );
706
707                 //// \92n\90}\82ð\8c×\82®\8f\88\97\9d\82Ì\8am\94F\97p\83f\83o\83b\83O\83R\81[\83h
708                 //if ( 100 <= s_xDebugAutoCruise && s_xDebugAutoCruise <= (GVOWorldMap::k_worldWidth - 100) ) {
709                 //      s_xDebugAutoCruise = 0;
710                 //}
711
712
713                 POINT p = {
714                         LONG( s_xDebugAutoCruise ),
715                         LONG( s_yDebugAutoCruise )
716                 };
717                 uint32_t timeStamp = ::timeGetTime();
718                 s_ship.updateWithSurveyCoord( p, timeStamp );
719                 s_speedMeter.updateVelocity( s_ship.velocity(), timeStamp );
720                 s_gvoGameProcess.setSurveyCoord( p );
721                 s_worldMap.setShipPosition( p, s_config.m_traceShipPositionEnabled );
722                 s_worldMap.updateShipRouteMap( hdc );
723                 s_updateWindowTitle( hwnd );
724                 ::InvalidateRect( hwnd, NULL, FALSE );
725                 return;
726         }
727 #endif  // #ifndef NDEBUG
728
729         s_isUpdated = s_gvoGameProcess.updateState();
730
731         if ( s_isUpdated ) {
732                 s_config.m_initialSurveyCoord = s_gvoGameProcess.surveyCoord();
733                 s_ship.updateWithSurveyCoord( s_gvoGameProcess.surveyCoord(), s_gvoGameProcess.timeStamp() );
734                 s_speedMeter.updateVelocity( s_ship.velocity(), s_gvoGameProcess.timeStamp() );
735                 s_worldMap.setShipPosition( s_gvoGameProcess.surveyCoord(), s_config.m_traceShipPositionEnabled );
736                 s_worldMap.updateShipRouteMap( hdc );
737                 s_updateWindowTitle( hwnd );
738                 ::InvalidateRect( hwnd, NULL, FALSE );
739         }
740 }
741
742
743 static void s_updateWindowTitle( HWND hwnd )
744 {
745         const POINT& surveyCoord = s_gvoGameProcess.surveyCoord();
746
747         std::vector<wchar_t> buf( 4096 );
748         ::swprintf( &buf[0], buf.size(), L"%d,%d - (%.1f%%) - %s %s",
749                 surveyCoord.x, surveyCoord.y,
750                 s_worldMap.viewScale() * s_worldMap.viewScaleOrder(),
751                 k_appName, k_version
752                 );
753         ::SetWindowText( hwnd, &buf[0] );
754 }
755
756
757 static void s_toggleKeepForeground( HWND hwnd )
758 {
759         if ( ::GetWindowLong( hwnd, GWL_EXSTYLE ) & WS_EX_TOPMOST ) {
760                 ::SetWindowPos( hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW );
761                 s_config.m_keepForeground = false;
762         }
763         else {
764                 ::SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW );
765                 s_config.m_keepForeground = true;
766         }
767 }
768
769
770 static void s_popupMenu( HWND hwnd, int16_t x, int16_t y )
771 {
772         HMENU hmenu = ::LoadMenu( g_hinst, MAKEINTRESOURCE( IDC_POPUPMENU ) );
773         HMENU popupMenu = ::GetSubMenu( hmenu, 0 );
774
775         ::CheckMenuItem( popupMenu, IDM_TOGGLE_TRACE_SHIP, s_config.m_traceShipPositionEnabled ? MF_CHECKED : MF_UNCHECKED );
776         ::CheckMenuItem( popupMenu, IDM_TOGGLE_KEEP_FOREGROUND, s_config.m_keepForeground ? MF_CHECKED : MF_UNCHECKED );
777         ::CheckMenuItem( popupMenu, IDM_TOGGLE_SPEED_METER, s_config.m_speedMeterEnabled ? MF_CHECKED : MF_UNCHECKED );
778         ::CheckMenuItem( popupMenu, IDM_TOGGLE_VECTOR_LINE, s_config.m_shipVectorLineEnabled ? MF_CHECKED : MF_UNCHECKED );
779
780         POINT p = { x, y };
781         ::ClientToScreen( hwnd, &p );
782         ::TrackPopupMenu( popupMenu, TPM_NONOTIFY | TPM_NOANIMATION | TPM_LEFTALIGN | TPM_TOPALIGN,
783                 p.x, p.y, 0, hwnd, NULL );
784 }
785
786
787 static void s_popupCoord( HWND hwnd, int16_t x, int16_t y )
788 {
789
790 }