OSDN Git Service

航路保存機能試作。
[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
20 #include "GVONavish.h"
21 #include "GVOConfig.h"
22 #include "GVOGameProcess.h"
23 #include "GVOWorldMap.h"
24 #include "GVOShip.h"
25 #include "GVOShipRouteList.h"
26 #include "GVORenderer.h"
27 #include "GVOTexture.h"
28 #include "GVOShipRouteManageView.h"
29
30
31 // \83f\83o\83b\83O\8e\9e\82Ì\95`\89æ\83p\83t\83H\81[\83}\83\93\83X\91ª\92è\97p\81B
32 //#define GVO_PERF_CHECK
33
34
35
36 // \83O\83\8d\81[\83o\83\8b\95Ï\90\94:
37 HINSTANCE g_hinst;                                                                              // \8c»\8dÝ\82Ì\83C\83\93\83^\81[\83t\83F\83C\83X
38 HWND g_hwndMain;
39 HDC g_hdcMain;
40
41
42 // \8aÖ\90\94\83v\83\8d\83g\83^\83C\83v\90é\8c¾
43 static ATOM MyRegisterClass( HINSTANCE hInstance );
44 static BOOL InitInstance( HINSTANCE, int );
45 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
46 BOOL CALLBACK aboutDlgProc( HWND, UINT, WPARAM, LPARAM );
47 static LRESULT s_mainLoop();
48
49
50 // \83\81\83b\83Z\81[\83W\83n\83\93\83h\83\89
51 static bool s_onCreate( HWND, LPCREATESTRUCT );
52 static void s_onMove( HWND, WORD, WORD );
53 static void s_onSize( HWND, UINT, WORD, WORD );
54 static void s_onMouseWheel( HWND, int16_t, UINT, int16_t, int16_t );
55 static void s_onMouseMove( HWND, UINT, int16_t, int16_t );
56 static void s_onMouseLeftButtonDown( HWND, UINT, int16_t, int16_t );
57 static void s_onMouseLeftButtonUp( HWND, UINT, int16_t, int16_t );
58 static void s_onMouseLeftButtonDoubleClick( HWND, UINT, int16_t, int16_t );
59 static void s_onMouseRightButtonUp( HWND, UINT, int16_t, int16_t );
60 static void s_onPaint( HWND );
61
62
63 // \83A\83v\83\8a\8f\88\97\9d
64 static std::wstring s_getMapFileName();
65 static void s_updateFrame(HWND);
66 static void s_updateWindowTitle( HWND, POINT, double );
67 static void s_toggleKeepForeground( HWND );
68 static void s_popupMenu( HWND, int16_t, int16_t );
69 static void s_popupCoord( HWND, int16_t, int16_t );
70 static void s_closeShipRoute();
71
72
73 // \83\8d\81[\83J\83\8b\95Ï\90\94
74 static LPCWSTR const k_appName = L"GVONavish";          // \83A\83v\83\8a\83P\81[\83V\83\87\83\93\96¼
75 static LPCWSTR const k_version = L"ver 1.3";            // \83o\81[\83W\83\87\83\93\94Ô\8d\86
76 static LPCWSTR const k_copyright = L"copyright(c) 2014 @MandhelingFreak";       // \92\98\8dì\8c \95\\8e¦\81i\82¢\82¿\82¨\81[\81j
77
78 static LPCWSTR const k_windowClassName = L"GVONavish";          // \83\81\83C\83\93 \83E\83B\83\93\83h\83\83N\83\89\83X\96¼
79 static const LPCWSTR k_configFileName = L"GVONavish.ini";       // \90Ý\92è\83t\83@\83C\83\8b\96¼
80 static LPCWSTR const k_appMutexName = L"Global\\{7554E265-3247-4FCA-BC60-5AA814658351}";
81 static HANDLE s_appMutex;
82
83 static Gdiplus::GdiplusStartupInput s_gdisi;
84 static ULONG_PTR s_gdiToken;
85
86 static GVOConfig s_config( k_configFileName );
87 static GVOGameProcess s_gvoGameProcess;
88 static GVORenderer s_renderer;
89 static GVOWorldMap s_worldMap;
90 const std::wstring && k_routeListFilePath = g_makeFullPath(L"RouteList.dat");
91 static std::unique_ptr<GVOShipRouteList> s_shipRouteList;
92 static POINT s_latestSurveyCoord;
93 static GVOVector s_latestShipVector;
94 static double s_latestShipVelocity;
95 static DWORD s_latestTimeStamp;
96 static const DWORD k_surveyCoordLostThreshold = 5000;
97 static std::unique_ptr<GVOShipRouteManageView> s_shipRouteManageView;
98
99 //\88ê\8e\9e\92u\82«
100 static std::unique_ptr<GVOTexture> s_shipTexture;
101
102 static UINT s_pollingInterval = 1000;   // \8fó\91Ô\8aÄ\8e\8b\8aÔ\8au\81i1\95b\81j
103 static bool s_isDragging = false;               // \83h\83\89\83b\83O\8fó\91Ô\83t\83\89\83O
104 static SIZE s_clientSize;                               // \83N\83\89\83C\83A\83\93\83g\97Ì\88æ\82Ì\91å\82«\82³
105 static POINT s_dragOrg;                                 // \83h\83\89\83b\83O\8c´\93_\81i\88Ú\93®\97Ê\8eZ\8fo\97p\81j
106
107 #ifdef GVO_PERF_CHECK
108 typedef std::deque<double> PerfCountList;
109 static PerfCountList s_perfCountList;
110 #endif
111
112
113
114
115
116 int APIENTRY _tWinMain( _In_ HINSTANCE hInstance,
117         _In_opt_ HINSTANCE hPrevInstance,
118         _In_ LPTSTR    lpCmdLine,
119         _In_ int       nCmdShow )
120 {
121         UNREFERENCED_PARAMETER( hPrevInstance );
122         UNREFERENCED_PARAMETER( lpCmdLine );
123
124         ::SetLastError( NOERROR );
125         s_appMutex = ::CreateMutex( NULL, TRUE, k_appMutexName );
126         if ( ::GetLastError() == ERROR_ALREADY_EXISTS ) {
127                 HWND hwnd = ::FindWindow( k_windowClassName, NULL );
128                 if ( hwnd ) {
129                         ::SetForegroundWindow( hwnd );
130                 }
131                 return 0;
132         }
133
134         ::CoInitialize( NULL );
135         TIMECAPS tc;
136         ::timeGetDevCaps( &tc, sizeof(tc) );
137         ::timeBeginPeriod( tc.wPeriodMin );
138         INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES };
139         ::InitCommonControlsEx( &icc );
140         GdiplusStartup( &s_gdiToken, &s_gdisi, NULL );
141         s_config.load();
142
143         MyRegisterClass( hInstance );
144
145         // \83A\83v\83\8a\83P\81[\83V\83\87\83\93\82Ì\8f\89\8aú\89»\82ð\8eÀ\8ds\82µ\82Ü\82·:
146         if ( !InitInstance( hInstance, nCmdShow ) ) {
147                 return 0;
148         }
149
150         // \83\81\83C\83\93 \83\81\83b\83Z\81[\83\83\8b\81[\83v:
151         const LRESULT retVal = s_mainLoop();
152
153         s_shipRouteList->saveToFile( k_routeListFilePath );
154         s_gvoGameProcess.teardown();
155
156         s_config.save();
157         Gdiplus::GdiplusShutdown( s_gdiToken );
158         ::timeEndPeriod( tc.wPeriodMin );
159         ::CoUninitialize();
160         return retVal;
161 }
162
163 static ATOM MyRegisterClass( HINSTANCE hInstance )
164 {
165         WNDCLASSEX wcex = { sizeof(wcex) };
166
167         wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC;
168         wcex.lpfnWndProc = WndProc;
169         wcex.hInstance = hInstance;
170         wcex.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDR_MAINFRAME ) );
171         wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
172         wcex.hbrBackground = (HBRUSH)::GetStockObject( BLACK_BRUSH );
173         wcex.lpszMenuName       = MAKEINTRESOURCE(IDR_MAINFRAME);
174         wcex.lpszClassName = k_windowClassName;
175         wcex.hIconSm = LoadIcon( wcex.hInstance, MAKEINTRESOURCE( IDI_SMALL ) );
176
177         return RegisterClassEx( &wcex );
178 }
179
180 static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
181 {
182         if ( !s_worldMap.loadFromFile( s_config.m_mapFileName ) ) {
183                 std::wstring fileName = s_getMapFileName();
184                 if ( !s_worldMap.loadFromFile( fileName ) ) {
185                         ::MessageBox( NULL,
186                                 L"\83}\83b\83v\89æ\91\9c\82ð\8aJ\82¯\82Ü\82¹\82ñ\82Å\82µ\82½\81B",
187                                 k_appName,
188                                 MB_ICONERROR | MB_SETFOREGROUND | MB_OK );
189                         return FALSE;
190                 }
191                 s_config.m_mapFileName = fileName;
192         }
193
194         HWND hwnd;
195
196         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
197
198         DWORD exStyle = 0;
199         if ( s_config.m_keepForeground ) {
200                 exStyle |= WS_EX_TOPMOST;
201         }
202         DWORD style = 0;
203         style |= WS_OVERLAPPEDWINDOW;
204         style |= WS_CLIPCHILDREN;
205         style |= WS_CLIPSIBLINGS;
206         hwnd = CreateWindowEx( exStyle, k_windowClassName, k_appName, style,
207                 s_config.m_windowPos.x, s_config.m_windowPos.y,
208                 s_config.m_windowSize.cx, s_config.m_windowSize.cy,
209                 NULL, NULL, hInstance, NULL );
210
211         if ( !hwnd ) {
212                 return FALSE;
213         }
214
215         g_hwndMain = hwnd;
216         g_hdcMain = ::GetDC( g_hwndMain );
217
218         s_renderer.setup( &s_config, g_hdcMain, &s_worldMap );
219
220         s_shipRouteList.reset( new GVOShipRouteList() );
221         s_shipRouteList->loadFromFile( k_routeListFilePath );
222         s_pollingInterval = s_config.m_pollingInterval;
223         s_gvoGameProcess.setup( s_config );
224
225         s_updateWindowTitle( hwnd, s_config.m_initialSurveyCoord, s_renderer.viewScale() );
226
227         ShowWindow( hwnd, nCmdShow );
228         UpdateWindow( hwnd );
229
230         return TRUE;
231 }
232
233 static LRESULT s_mainLoop()
234 {
235         MSG msg;
236         HACCEL hAccelTable;
237
238         hAccelTable = LoadAccelerators( g_hinst, MAKEINTRESOURCE( IDR_MAINFRAME ) );
239
240         for ( ;; ) {
241                 if ( ::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
242                         if ( msg.message == WM_QUIT ) {
243                                 break;
244                         }
245                         if ( !TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) ) {
246                                 TranslateMessage( &msg );
247                                 DispatchMessage( &msg );
248                         }
249                         continue;
250                 }
251                 std::vector<HANDLE> handles;
252
253                 // \8aÄ\8e\8b\82·\82é\83n\83\93\83h\83\8b\82ð\92Ç\89Á\82·\82é\81B
254                 if ( s_gvoGameProcess.processHandle() ) {
255                         handles.push_back( s_gvoGameProcess.processHandle() );
256                 }
257                 //handles.push_back( s_pollingTimerEvent );
258                 handles.push_back( s_gvoGameProcess.dataReadyEvent() );
259
260                 if ( handles.empty() ) {
261                         ::WaitMessage();
262                         continue;
263                 }
264
265                 DWORD const waitResult = ::MsgWaitForMultipleObjects( handles.size(), &handles[0], FALSE, INFINITE, QS_ALLINPUT );
266                 if ( handles.size() <= waitResult ) {
267                         continue;
268                 }
269
270                 // \8aÄ\8e\8b\82·\82é\83n\83\93\83h\83\8b\82É\91Î\89\9e\82·\82é\81B
271                 HANDLE const activeHandle = handles[waitResult];
272
273                 if ( activeHandle == s_gvoGameProcess.processHandle() ) {
274                         // \83Q\81[\83\80\83v\83\8d\83Z\83X\82ª\8fI\97¹\82µ\82½\81B
275                         s_gvoGameProcess.clear();
276                         continue;
277                 }
278
279                 if ( activeHandle == s_gvoGameProcess.dataReadyEvent() ) {
280                         s_updateFrame( g_hwndMain );
281                         continue;
282                 }
283         }
284         return (int)msg.wParam;
285 }
286
287
288 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
289 {
290         int wmId, wmEvent;
291
292         switch ( message ) {
293         case WM_ERASEBKGND:
294                 return TRUE;
295         case WM_PAINT:
296                 s_onPaint( hwnd );
297                 break;
298
299         case WM_TIMER:
300                 s_updateFrame( hwnd );
301                 break;
302
303         case WM_MOVE:
304                 s_onMove( hwnd, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
305                 break;
306         case WM_SIZE:
307                 s_onSize( hwnd, wp, LOWORD( lp ), HIWORD( lp ) );
308                 break;
309
310         case WM_COMMAND:
311                 wmId = LOWORD( wp );
312                 wmEvent = HIWORD( wp );
313                 // \91I\91ð\82³\82ê\82½\83\81\83j\83\85\81[\82Ì\89ð\90Í:
314                 switch ( wmId ) {
315                 case IDM_ABOUT:
316                         ::DialogBox( g_hinst, MAKEINTRESOURCE( IDD_ABOUTBOX ), hwnd, aboutDlgProc );
317                         break;
318                 case IDM_EXIT:
319                         DestroyWindow( hwnd );
320                         break;
321                 case IDM_TOGGLE_TRACE_SHIP:
322                         s_config.m_traceShipPositionEnabled = !s_config.m_traceShipPositionEnabled;
323                         s_renderer.enableTraceShip( s_config.m_traceShipPositionEnabled );
324                         break;
325                 case IDM_ERASE_SHIP_ROUTE:
326                         s_shipRouteList->clearAllItems();
327                         break;
328                 case IDM_TOGGLE_KEEP_FOREGROUND:
329                         s_toggleKeepForeground( hwnd );
330                         break;
331                 case IDM_TOGGLE_SPEED_METER:
332                         s_config.m_speedMeterEnabled = !s_config.m_speedMeterEnabled;
333                         s_renderer.enableSpeedMeter( s_config.m_speedMeterEnabled );
334                         ::InvalidateRect( hwnd, NULL, FALSE );
335                         break;
336                 case IDM_TOGGLE_VECTOR_LINE:
337                         s_config.m_shipVectorLineEnabled = !s_config.m_shipVectorLineEnabled;
338                         s_renderer.setVisibleShipRoute( s_config.m_shipVectorLineEnabled );
339                         break;
340                 case IDM_SAME_SCALE:
341                         if ( s_renderer.viewScale() != 1.0 ) {
342                                 s_renderer.resetViewScale();
343                         }
344                         break;
345                 case IDM_ZOOM_IN:
346                         if ( s_renderer.zoomIn() ) {
347 #ifdef GVO_PERF_CHECK
348                                 s_perfCountList.clear();
349 #endif
350                                 s_updateWindowTitle( hwnd, s_latestSurveyCoord, s_renderer.viewScale() );
351                                 ::InvalidateRect( hwnd, NULL, FALSE );
352                         }
353                         break;
354                 case IDM_ZOOM_OUT:
355                         if ( s_renderer.zoomOut() ) {
356 #ifdef GVO_PERF_CHECK
357                                 s_perfCountList.clear();
358 #endif
359                                 s_updateWindowTitle( hwnd, s_latestSurveyCoord, s_renderer.viewScale() );
360                                 ::InvalidateRect( hwnd, NULL, FALSE );
361                         }
362                         break;
363                 case IDM_SHOW_SHIPROUTEMANAGEVIEW:
364                         if ( !s_shipRouteManageView.get() ) {
365                                 s_shipRouteManageView.reset( new GVOShipRouteManageView() );
366                                 if ( !s_shipRouteManageView->setup( *s_shipRouteList.get() ) ) {
367                                         ::MessageBox( hwnd, L"\82È\82ñ\82©\82¦\82ç\81[\82Å\82·", L"\83G\83\89\81[", MB_OK | MB_ICONERROR );
368                                 }
369                         }
370                         else {
371                                 s_shipRouteManageView->activate();
372                         }
373                         break;
374 #ifndef NDEBUG
375                 case IDM_TOGGLE_DEBUG_AUTO_CRUISE:
376                         s_config.m_debugAutoCruiseEnabled = !s_config.m_debugAutoCruiseEnabled;
377                         s_gvoGameProcess.enableDebugAutoCruise( s_config.m_debugAutoCruiseEnabled );
378                         break;
379                 case IDM_DEBUG_CLOSE_ROUTE:
380                         s_closeShipRoute();
381                         break;
382                 case IDM_DEBUG_INTERVAL_NORMAL:
383                         s_pollingInterval = 1000;
384                         s_gvoGameProcess.setPollingInterval( s_pollingInterval );
385                         break;
386                 case IDM_DEBUG_INTERVAL_HIGH:
387                         s_pollingInterval = 1;
388                         s_gvoGameProcess.setPollingInterval( s_pollingInterval );
389                         break;
390 #endif
391                 default:
392                         return DefWindowProc( hwnd, message, wp, lp );
393                 }
394                 break;
395
396         case WM_MOUSEWHEEL:
397                 s_onMouseWheel( hwnd, HIWORD( wp ), LOWORD( wp ), int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
398                 break;
399         case WM_MOUSEMOVE:
400                 s_onMouseMove( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
401                 break;
402         case WM_LBUTTONDOWN:
403                 s_onMouseLeftButtonDown( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
404                 break;
405         case WM_LBUTTONUP:
406                 s_onMouseLeftButtonUp( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
407                 break;
408         case WM_RBUTTONUP:
409                 s_onMouseRightButtonUp( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
410                 break;
411         case WM_LBUTTONDBLCLK:
412                 s_onMouseLeftButtonDoubleClick( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
413                 break;
414
415         case WM_CREATE:
416                 if ( !s_onCreate( hwnd, reinterpret_cast<LPCREATESTRUCT>(lp) ) ) {
417                         return -1;
418                 }
419                 break;
420         case WM_DESTROY:
421                 if ( s_shipRouteManageView.get() ) {
422                         s_shipRouteManageView.reset();
423                 }
424                 s_renderer.teardown();
425                 PostQuitMessage( 0 );
426                 break;
427         default:
428                 return DefWindowProc( hwnd, message, wp, lp );
429         }
430         return 0;
431 }
432
433
434 static bool s_onCreate( HWND hwnd, LPCREATESTRUCT /*cs*/ )
435 {
436         return true;
437 }
438
439
440 static void s_onMove( HWND hwnd, WORD /*cx*/, WORD /*cy*/ )
441 {
442         const DWORD style = ::GetWindowLong( hwnd, GWL_STYLE );
443         if ( style & WS_MAXIMIZE ) {
444                 return;
445         }
446         RECT rc = { 0 };
447         ::GetWindowRect( hwnd, &rc );
448         s_config.m_windowPos.x = rc.left;
449         s_config.m_windowPos.y = rc.top;
450 }
451
452
453 static void s_onSize( HWND hwnd, UINT state, WORD cx, WORD cy )
454 {
455         RECT rc = { 0 };
456
457         switch ( state ) {
458         case SIZE_RESTORED:
459                 ::GetWindowRect( hwnd, &rc );
460                 s_config.m_windowSize.cx = rc.right - rc.left;
461                 s_config.m_windowSize.cy = rc.bottom - rc.top;
462                 break;
463         case SIZE_MAXIMIZED:
464                 break;
465         default:
466                 return;
467         }
468
469         if ( s_clientSize.cx != cx || s_clientSize.cy != cy ) {
470                 s_clientSize.cx = cx;
471                 s_clientSize.cy = cy;
472                 s_renderer.setViewSize( s_clientSize );
473         }
474 }
475
476
477 static void s_onMouseWheel( HWND hwnd, int16_t delta, UINT vkey, int16_t x, int16_t y )
478 {
479         bool isChanged = false;
480
481         if ( 0 < delta ) {
482                 isChanged = s_renderer.zoomIn();
483         }
484         else {
485                 isChanged = s_renderer.zoomOut();
486         }
487
488         if ( isChanged ) {
489 #ifdef GVO_PERF_CHECK
490                 s_perfCountList.clear();
491 #endif
492                 s_updateWindowTitle( hwnd, s_latestSurveyCoord, s_renderer.viewScale() );
493                 ::InvalidateRect( hwnd, NULL, FALSE );
494         }
495 }
496
497
498 static void s_onMouseMove( HWND hwnd, UINT vkey, int16_t x, int16_t y )
499 {
500         if ( s_isDragging ) {
501                 const int dx = x - s_dragOrg.x;
502                 const int dy = y - s_dragOrg.y;
503                 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ô
504                 if ( s_config.m_traceShipPositionEnabled ) {
505                         if ( ::abs( dx ) <= threshold && ::abs( dy ) < threshold ) {
506                                 return;
507                         }
508                 }
509                 const POINT offset = { -dx, -dy };
510
511                 s_renderer.offsetFocusInViewCoord( offset );
512                 ::InvalidateRect( hwnd, NULL, FALSE );
513
514                 s_dragOrg.x = x;
515                 s_dragOrg.y = y;
516                 s_config.m_traceShipPositionEnabled = false;
517                 s_renderer.enableTraceShip( s_config.m_traceShipPositionEnabled );
518         }
519         else {
520
521         }
522 }
523
524
525 static void s_onMouseLeftButtonDown( HWND hwnd, UINT vkey, int16_t x, int16_t y )
526 {
527         if ( s_isDragging ) {
528
529         }
530         else {
531                 ::SetCapture( hwnd );
532                 s_isDragging = true;
533                 s_dragOrg.x = x;
534                 s_dragOrg.y = y;
535         }
536 }
537
538
539 static void s_onMouseLeftButtonUp( HWND hwnd, UINT vkey, int16_t x, int16_t y )
540 {
541         if ( s_isDragging ) {
542                 ::ReleaseCapture();
543                 s_isDragging = false;
544                 s_dragOrg.x = 0;
545                 s_dragOrg.y = 0;
546         }
547         else {
548
549         }
550 }
551
552
553 static void s_onMouseLeftButtonDoubleClick( HWND hwnd, UINT vkey, int16_t x, int16_t y )
554 {
555         if ( s_isDragging ) {
556
557         }
558         else {
559                 s_popupCoord( hwnd, x, y );
560         }
561 }
562
563
564 static void s_onMouseRightButtonUp( HWND hwnd, UINT vkey, int16_t x, int16_t y )
565 {
566         if ( s_isDragging ) {
567
568         }
569         else {
570                 s_popupMenu( hwnd, x, y );
571         }
572 }
573
574
575 static void s_onPaint( HWND hwnd )
576 {
577 #ifdef GVO_PERF_CHECK
578         const int64_t perfBegin = g_queryPerformanceCounter();
579 #endif
580         s_renderer.render( s_latestShipVector, s_latestShipVelocity, s_shipTexture.get(), s_shipRouteList.get() );
581         ::ValidateRect( hwnd, NULL );
582
583 #ifdef GVO_PERF_CHECK
584         const int64_t perfEnd = g_queryPerformanceCounter();
585         const int64_t freq = g_queryPerformanceFrequency();
586         const double deltaPerSec = (double(perfEnd - perfBegin) / double(freq)) * 1000.0;
587         s_perfCountList.push_back(deltaPerSec);
588
589         const double ave = std::accumulate( s_perfCountList.begin(), s_perfCountList.end(), 0.0 ) / s_perfCountList.size();
590         if ( 100 < s_perfCountList.size() ) {
591                 s_perfCountList.pop_front();
592         }
593
594         std::wstring s;
595         s = std::wstring( L"\95`\89æ\91¬\93x:" ) + std::to_wstring( ave ) + L"(ms)\n";
596         ::SetWindowText( hwnd, s.c_str() );
597 #endif
598 }
599
600
601 // \83}\83b\83v\89æ\91\9c\82ð\91I\91ð\82³\82¹\82é
602 static std::wstring s_getMapFileName()
603 {
604         wchar_t dir[MAX_PATH] = { 0 };
605         ::GetModuleFileName( g_hinst, dir, _countof( dir ) );
606         ::PathRemoveFileSpec( dir );
607
608         wchar_t filePath[MAX_PATH] = { 0 };
609         OPENFILENAME ofn = { sizeof(ofn) };
610         ofn.lpstrTitle = L"\83}\83b\83v\89æ\91\9c\83t\83@\83C\83\8b\82ð\91I\91ð\82µ\82Ä\82­\82¾\82³\82¢\81B";
611         ofn.lpstrInitialDir = &dir[0];
612         ofn.lpstrFilter = L"\83C\83\81\81[\83W\83t\83@\83C\83\8b\0L" L"*.bmp;*.jpg;*.jpeg;*.png;*.gif;*.tif;*.tiff" L"\0"
613                 L"\91S\82Ä\82Ì\83t\83@\83C\83\8b\0" L"*.*" L"\0\0";
614         ofn.Flags = OFN_READONLY | OFN_FILEMUSTEXIST;
615         ofn.nMaxFile = _countof( filePath );
616         ofn.lpstrFile = &filePath[0];
617         if ( !::GetOpenFileName( &ofn ) ) {
618                 return L"";
619         }
620         return filePath;
621 }
622
623
624 static void s_updateFrame( HWND hwnd )
625 {
626         std::vector<GVOGameStatus> gameStats;
627         gameStats = s_gvoGameProcess.getState();
628         if ( gameStats.empty() ) {
629                 return;
630         }
631
632         // \83Q\81[\83\80\83v\83\8d\83Z\83X\82Ì\83A\83C\83R\83\93\83e\83N\83X\83`\83\83\81[\82ª\96¢\8dì\90¬\82È\82ç\82Î\8dì\90¬\82ð\8e\8e\82Ý\82é
633         if ( !s_shipTexture.get() ) {
634                 const GVOImage * image = s_gvoGameProcess.shipIconImage();
635                 if ( image ) {
636                         s_shipTexture.reset( s_renderer.createTextureFromImage( *image ) );
637                 }
638         }
639
640         for ( std::vector<GVOGameStatus>::const_iterator it = gameStats.begin(); it != gameStats.end(); ++it ) {
641                 const GVOGameStatus& status = *it;
642                 s_latestSurveyCoord = status.m_surveyCoord;
643                 s_latestShipVector = status.m_shipVector;
644                 s_latestShipVelocity = status.m_shipVelocity;
645                 s_config.m_initialSurveyCoord = s_latestSurveyCoord;
646                 s_renderer.setShipPositionInWorld( s_latestSurveyCoord );
647                 if ( (s_latestTimeStamp + k_surveyCoordLostThreshold) < status.m_timeStamp ) {
648                         s_closeShipRoute();
649                 }
650                 s_latestTimeStamp = status.m_timeStamp;
651                 s_shipRouteList->addRoutePoint( s_worldMap.normalizedPoint( s_latestSurveyCoord ) );
652         }
653 #ifndef GVO_PERF_CHECK
654         s_updateWindowTitle( hwnd, s_latestSurveyCoord, s_renderer.viewScale() );
655 #endif
656         ::InvalidateRect( hwnd, NULL, FALSE );
657 }
658
659
660 static void s_updateWindowTitle( HWND hwnd, POINT surveyCoord, double viewScale )
661 {
662         std::vector<wchar_t> buf( 4096 );
663         ::swprintf( &buf[0], buf.size(), L"%d,%d - (%.1f%%) - %s %s",
664                 surveyCoord.x, surveyCoord.y,
665                 viewScale * 100.0,
666                 k_appName, k_version
667                 );
668         ::SetWindowText( hwnd, &buf[0] );
669 }
670
671
672 static void s_toggleKeepForeground( HWND hwnd )
673 {
674         if ( ::GetWindowLong( hwnd, GWL_EXSTYLE ) & WS_EX_TOPMOST ) {
675                 ::SetWindowPos( hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW );
676                 s_config.m_keepForeground = false;
677         }
678         else {
679                 ::SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW );
680                 s_config.m_keepForeground = true;
681         }
682 }
683
684
685 static void s_popupMenu( HWND hwnd, int16_t x, int16_t y )
686 {
687         HMENU hmenu = ::LoadMenu( g_hinst, MAKEINTRESOURCE( IDR_POPUPMENU ) );
688         HMENU popupMenu = ::GetSubMenu( hmenu, 0 );
689
690         ::CheckMenuItem( popupMenu, IDM_TOGGLE_TRACE_SHIP, s_config.m_traceShipPositionEnabled ? MF_CHECKED : MF_UNCHECKED );
691         ::CheckMenuItem( popupMenu, IDM_TOGGLE_KEEP_FOREGROUND, s_config.m_keepForeground ? MF_CHECKED : MF_UNCHECKED );
692         ::CheckMenuItem( popupMenu, IDM_TOGGLE_SPEED_METER, s_config.m_speedMeterEnabled ? MF_CHECKED : MF_UNCHECKED );
693         ::CheckMenuItem( popupMenu, IDM_TOGGLE_VECTOR_LINE, s_config.m_shipVectorLineEnabled ? MF_CHECKED : MF_UNCHECKED );
694
695 #ifndef NDEBUG
696         MENUITEMINFO mii = { sizeof(mii) };
697         mii.fMask = MIIM_TYPE | MIIM_ID;
698         mii.fType = MFT_STRING;
699
700         // \83f\83o\83b\83O\97p\8e©\93®\8dq\8ds\82Ì\90Ø\82è\91Ö\82¦
701         mii.wID = IDM_TOGGLE_DEBUG_AUTO_CRUISE;
702         mii.dwTypeData = L"[DEBUG]\8e©\93®\8dq\8ds\82ð\97L\8cø";
703         ::InsertMenuItem( popupMenu, ::GetMenuItemCount( popupMenu ), TRUE, &mii );
704         ::CheckMenuItem( popupMenu, IDM_TOGGLE_DEBUG_AUTO_CRUISE, s_config.m_debugAutoCruiseEnabled ? MF_CHECKED : MF_UNCHECKED );
705
706         mii.wID = IDM_DEBUG_CLOSE_ROUTE;
707         mii.dwTypeData = L"[DEBUG]\8dq\98H\82ð\95Â\82\82é";
708         ::InsertMenuItem( popupMenu, ::GetMenuItemCount( popupMenu ), TRUE, &mii );
709
710         mii.wID = IDM_DEBUG_INTERVAL_NORMAL;
711         mii.dwTypeData = L"[DEBUG]\8dX\90V\8aÔ\8au - \95W\8f\80";
712         ::InsertMenuItem( popupMenu, ::GetMenuItemCount( popupMenu ), TRUE, &mii );
713
714         mii.wID = IDM_DEBUG_INTERVAL_HIGH;
715         mii.dwTypeData = L"[DEBUG]\8dX\90V\8aÔ\8au - \8d\82\95p\93x";
716         ::InsertMenuItem( popupMenu, ::GetMenuItemCount( popupMenu ), TRUE, &mii );
717 #endif
718
719         // \83\81\83j\83\85\81[\95\\8e¦\92\86\82Í\83\81\83b\83Z\81[\83W\83|\83\93\83v\82©\82ç\94²\82¯\82ç\82ê\82È\82¢\82Ì\82Å\83^\83C\83}\81[\82Å\8dX\90V\82ð\8aÄ\8e\8b\82µ\82Ä\82¨\82­\81B
720         const UINT_PTR timerID = ::SetTimer( hwnd, 0, s_pollingInterval, NULL );
721         s_updateFrame( hwnd );
722
723         POINT p = { x, y };
724         ::ClientToScreen( hwnd, &p );
725         ::TrackPopupMenu( popupMenu, TPM_NONOTIFY | TPM_NOANIMATION | TPM_LEFTALIGN | TPM_TOPALIGN,
726                 p.x, p.y, 0, hwnd, NULL );
727         ::DestroyMenu( popupMenu );
728
729         ::KillTimer( hwnd, timerID );
730 }
731
732
733 static void s_popupCoord( HWND hwnd, int16_t x, int16_t y )
734 {
735 }
736
737
738 static void s_closeShipRoute()
739 {
740         s_shipRouteList->closeRoute();
741 }
742
743
744 BOOL CALLBACK aboutDlgProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
745 {
746         switch ( msg ) {
747         case WM_INITDIALOG:
748         {
749                 std::wstring versionString = std::wstring(k_appName) + L" " + k_version;
750                 std::wstring copyRightString = std::wstring( k_copyright );
751
752                 ::SetDlgItemText( hwnd, IDC_VERSION_LABEL, versionString.c_str() );
753                 ::SetDlgItemText( hwnd, IDC_COPYRIGHT_LABEL, copyRightString.c_str() );
754         }
755                 break;
756         case WM_COMMAND:
757                 switch ( LOWORD( wp ) ) {
758                 case IDOK:
759                 case IDCANCEL:
760                         ::EndDialog( hwnd, 0 );
761                         break;
762                 default:
763                         return FALSE;
764                 }
765                 break;
766         default:
767                 return FALSE;
768         }
769         return TRUE;
770 }