OSDN Git Service

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