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