1 // winmain.cpp
2 //
3 // Copyright (C) 2001-2007, Chris Laurel <claurel@shatters.net>
4 //
5 // Windows front end for Celestia.
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
11 
12 #include <iostream>
13 #include <fstream>
14 #include <sstream>
15 #include <algorithm>
16 #include <set>
17 #include <cstdlib>
18 #include <cctype>
19 #include <cstring>
20 #include <cassert>
21 #include <process.h>
22 #include <time.h>
23 #include <windows.h>
24 #include <commctrl.h>
25 #include <mmsystem.h>
26 
27 #include <celmath/vecmath.h>
28 #include <celmath/quaternion.h>
29 #include <celmath/mathlib.h>
30 #include <celutil/debug.h>
31 #include <celutil/util.h>
32 #include <celutil/winutil.h>
33 #include <celutil/filetype.h>
34 #include <celengine/celestia.h>
35 #include <celengine/astro.h>
36 #include <celengine/cmdparser.h>
37 #include <celengine/axisarrow.h>
38 #include <celengine/planetgrid.h>
39 
40 #include "../celengine/gl.h"
41 #include "../celengine/glext.h"
42 #include "celestiacore.h"
43 #include "imagecapture.h"
44 #include "avicapture.h"
45 #include "url.h"
46 #include "winstarbrowser.h"
47 #include "winssbrowser.h"
48 #include "wintourguide.h"
49 #include "wingotodlg.h"
50 #include "winviewoptsdlg.h"
51 #include "winlocations.h"
52 #include "winbookmarks.h"
53 #include "wineclipses.h"
54 #include "winhyperlinks.h"
55 #include "wintime.h"
56 #include "winsplash.h"
57 #include "odmenu.h"
58 #include "scriptmenu.h"
59 
60 #include "res/resource.h"
61 #include "wglext.h"
62 
63 #include <locale.h>
64 
65 using namespace std;
66 
67 typedef pair<int,string> IntStrPair;
68 typedef vector<IntStrPair> IntStrPairVec;
69 
70 char AppName[] = "Celestia";
71 
72 static CelestiaCore* appCore = NULL;
73 
74 // Display modes for full screen operation
75 static vector<DEVMODE>* displayModes = NULL;
76 
77 // Display mode indices
78 static int currentScreenMode = 0;
79 static int newScreenMode = 0;
80 
81 // The last fullscreen mode set; saved and restored from the registry
82 static int lastFullScreenMode = 0;
83 // A fullscreen mode guaranteed to work
84 static int fallbackFullScreenMode = 0;
85 static RECT windowRect;
86 
87 static HGLRC glContext;
88 static HDC deviceContext;
89 
90 static bool bReady = false;
91 
92 static LPTSTR CelestiaRegKey = "Software\\Shatters.net\\Celestia";
93 
94 HINSTANCE appInstance;
95 HMODULE hRes;
96 HWND mainWindow = 0;
97 
98 static SolarSystemBrowser* solarSystemBrowser = NULL;
99 static StarBrowser* starBrowser = NULL;
100 static TourGuide* tourGuide = NULL;
101 static GotoObjectDialog* gotoObjectDlg = NULL;
102 static ViewOptionsDialog* viewOptionsDlg = NULL;
103 static EclipseFinderDialog* eclipseFinder = NULL;
104 static LocationsDialog* locationsDlg = NULL;
105 static SplashWindow* s_splash = NULL;
106 
107 static HMENU menuBar = 0;
108 ODMenu odAppMenu;
109 static HACCEL acceleratorTable = 0;
110 static bool hideMenuBar = false;
111 
112 // Joystick info
113 static bool useJoystick = false;
114 static bool joystickAvailable = false;
115 static JOYCAPS joystickCaps;
116 
117 static HCURSOR hDefaultCursor = 0;
118 bool cursorVisible = true;
119 static POINT saveCursorPos;
120 static POINT lastMouseMove;
121 class WinCursorHandler;
122 WinCursorHandler* cursorHandler = NULL;
123 
124 static int MovieSizes[8][2] = {
125                                 { 160, 120 },
126                                 { 320, 240 },
127                                 { 640, 480 },
128                                 { 720, 480 },
129                                 { 720, 576 },
130                                 { 1024, 768 },
131                                 { 1280, 720 },
132                                 { 1920, 1080 }
133                               };
134 
135 static float MovieFramerates[5] = { 15.0f, 24.0f, 25.0f, 29.97f, 30.0f };
136 
137 static int movieSize = 1;
138 static int movieFramerate = 1;
139 
140 
141 astro::Date newTime(0.0);
142 
143 #define REFMARKS 1
144 
145 #define INFINITE_MOUSE
146 static int lastX = 0;
147 static int lastY = 0;
148 static bool ignoreNextMoveEvent = false;
149 
150 static const WPARAM ID_GOTO_URL = 62000;
151 
152 HWND hBookmarkTree;
153 char bookmarkName[33];
154 
155 static const string ScriptsDirectory = "scripts";
156 static vector<ScriptMenuItem>* ScriptMenuItems = NULL;
157 
158 
159 static LRESULT CALLBACK MainWindowProc(HWND hWnd,
160                                        UINT uMsg,
161                                        WPARAM wParam, LPARAM lParam);
162 
163 
164 #define MENU_CHOOSE_PLANET   32000
165 #define MENU_CHOOSE_SURFACE  31000
166 
167 
168 struct AppPreferences
169 {
170     int winWidth;
171     int winHeight;
172     int winX;
173     int winY;
174     int renderFlags;
175     int labelMode;
176     int locationFilter;
177     int orbitMask;
178     float visualMagnitude;
179     float ambientLight;
180     float galaxyLightGain;
181     int showLocalTime;
182     int dateFormat;
183     int hudDetail;
184     int fullScreenMode;
185     uint32 lastVersion;
186     string altSurfaceName;
187     uint32 textureResolution;
188     Renderer::StarStyle starStyle;
189     GLContext::GLRenderPath renderPath;
190     bool renderPathSet;
191 };
192 
ChangeDisplayMode()193 void ChangeDisplayMode()
194 {
195     DEVMODE device_mode;
196 
197     memset(&device_mode, 0, sizeof(DEVMODE));
198 
199     device_mode.dmSize = sizeof(DEVMODE);
200 
201     device_mode.dmPelsWidth  = 800;
202     device_mode.dmPelsHeight = 600;
203     device_mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
204 
205     ChangeDisplaySettings(&device_mode, CDS_FULLSCREEN);
206 }
207 
RestoreDisplayMode()208 void RestoreDisplayMode()
209 {
210     ChangeDisplaySettings(0, 0);
211 }
212 
213 //
214 // A very minimal IDropTarget interface implementation
215 //
216 
217 class CelestiaDropTarget : public IDropTarget
218 {
219 public:
220     CelestiaDropTarget();
221     ~CelestiaDropTarget();
222 
223     STDMETHOD  (QueryInterface)(REFIID idd, void** ppvObject);
224     STDMETHOD_ (ULONG, AddRef) (void);
225     STDMETHOD_ (ULONG, Release) (void);
226 
227     // IDropTarget methods
228     STDMETHOD (DragEnter)(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
229     STDMETHOD (DragOver) (DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
230     STDMETHOD (DragLeave)(void);
231     STDMETHOD (Drop)     (LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
232 
233 private:
234     ULONG refCount;
235 };
236 
237 static CelestiaDropTarget* dropTarget = NULL;
238 
CelestiaDropTarget()239 CelestiaDropTarget::CelestiaDropTarget() :
240     refCount(0)
241 {
242 }
243 
~CelestiaDropTarget()244 CelestiaDropTarget::~CelestiaDropTarget()
245 {
246 }
247 
QueryInterface(REFIID iid,void ** ppvObject)248 HRESULT CelestiaDropTarget::QueryInterface(REFIID iid, void** ppvObject)
249 {
250     if (iid == IID_IUnknown || iid == IID_IDropTarget)
251     {
252         *ppvObject = this;
253         AddRef();
254         return ResultFromScode(S_OK);
255     }
256     else
257     {
258         *ppvObject = NULL;
259         return ResultFromScode(E_NOINTERFACE);
260     }
261 }
262 
AddRef(void)263 ULONG CelestiaDropTarget::AddRef(void)
264 {
265     return ++refCount;
266 }
267 
Release(void)268 ULONG CelestiaDropTarget::Release(void)
269 {
270     if (--refCount == 0)
271     {
272         delete this;
273         return 0;
274     }
275 
276     return refCount;
277 }
278 
279 STDMETHODIMP
DragEnter(IDataObject * pDataObject,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect)280 CelestiaDropTarget::DragEnter(IDataObject* pDataObject,
281                               DWORD grfKeyState,
282                               POINTL pt,
283                               DWORD* pdwEffect)
284 {
285     return S_OK;
286 }
287 
288 STDMETHODIMP
DragOver(DWORD grfKeyState,POINTL pt,DWORD * pdwEffect)289 CelestiaDropTarget::DragOver(DWORD grfKeyState,
290                              POINTL pt,
291                              DWORD* pdwEffect)
292 {
293     return S_OK;
294 }
295 
296 STDMETHODIMP
DragLeave(void)297 CelestiaDropTarget::DragLeave(void)
298 {
299     return S_OK;
300 }
301 
302 
303 STDMETHODIMP
Drop(IDataObject * pDataObject,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect)304 CelestiaDropTarget::Drop(IDataObject* pDataObject,
305                          DWORD grfKeyState,
306                          POINTL pt,
307                          DWORD* pdwEffect)
308 {
309 
310     IEnumFORMATETC* enumFormat = NULL;
311     HRESULT hr = pDataObject->EnumFormatEtc(DATADIR_GET, &enumFormat);
312     if (FAILED(hr) || enumFormat == NULL)
313         return E_FAIL;
314 
315     FORMATETC format;
316     ULONG nFetched;
317     while (enumFormat->Next(1, &format, &nFetched) == S_OK)
318     {
319         char buf[512];
320         if (GetClipboardFormatName(format.cfFormat, buf, 511) != 0 &&
321             !strcmp(buf, "UniformResourceLocator"))
322         {
323             STGMEDIUM medium;
324             if (pDataObject->GetData(&format, &medium) == S_OK)
325             {
326                 if (medium.tymed == TYMED_HGLOBAL && medium.hGlobal != 0)
327                 {
328                     char* s = (char*) GlobalLock(medium.hGlobal);
329 					appCore->goToUrl(s);
330                     GlobalUnlock(medium.hGlobal);
331                     break;
332                 }
333             }
334         }
335     }
336 
337     enumFormat->Release();
338 
339     return E_FAIL;
340 }
341 
342 
343 // Cursor handler callback class for Windows.  We pass an instance to
344 // the app core which the calls the setCursorShape method to change
345 // the cursor icon.
346 class WinCursorHandler : public CelestiaCore::CursorHandler
347 {
348 public:
349     WinCursorHandler(HCURSOR _defaultCursor);
350     virtual ~WinCursorHandler();
351     virtual void setCursorShape(CelestiaCore::CursorShape);
352     virtual CelestiaCore::CursorShape getCursorShape() const;
353 
354 private:
355     CelestiaCore::CursorShape shape;
356     HCURSOR defaultCursor;
357     HCURSOR sizeVertical;
358     HCURSOR sizeHorizontal;
359 };
360 
361 
WinCursorHandler(HCURSOR _defaultCursor)362 WinCursorHandler::WinCursorHandler(HCURSOR _defaultCursor) :
363     shape(CelestiaCore::ArrowCursor),
364     defaultCursor(_defaultCursor)
365 {
366     sizeVertical   = LoadCursor(NULL, IDC_SIZENS);
367     sizeHorizontal = LoadCursor(NULL, IDC_SIZEWE);
368 }
369 
370 
~WinCursorHandler()371 WinCursorHandler::~WinCursorHandler()
372 {
373 }
374 
375 
setCursorShape(CelestiaCore::CursorShape _shape)376 void WinCursorHandler::setCursorShape(CelestiaCore::CursorShape _shape)
377 {
378     shape = _shape;
379     switch (shape)
380     {
381     case CelestiaCore::SizeVerCursor:
382         SetCursor(sizeVertical);
383         break;
384     case CelestiaCore::SizeHorCursor:
385         SetCursor(sizeHorizontal);
386         break;
387     default:
388         SetCursor(defaultCursor);
389         break;
390     }
391 }
392 
393 
getCursorShape() const394 CelestiaCore::CursorShape WinCursorHandler::getCursorShape() const
395 {
396     return shape;
397 }
398 
399 // end WinCursorHandler methods
400 
ShowUniversalTime(CelestiaCore * appCore)401 static void ShowUniversalTime(CelestiaCore* appCore)
402 {
403     appCore->setTimeZoneBias(0);
404     appCore->setTimeZoneName("UTC");
405 }
406 
407 
ShowLocalTime(CelestiaCore * appCore)408 static void ShowLocalTime(CelestiaCore* appCore)
409 {
410     TIME_ZONE_INFORMATION tzi;
411     DWORD dst = GetTimeZoneInformation(&tzi);
412     if (dst != TIME_ZONE_ID_INVALID)
413     {
414         LONG dstBias = 0;
415         WCHAR* tzName = NULL;
416 
417         if (dst == TIME_ZONE_ID_STANDARD)
418         {
419             dstBias = tzi.StandardBias;
420             tzName = tzi.StandardName;
421         }
422         else if (dst == TIME_ZONE_ID_DAYLIGHT)
423         {
424             dstBias = tzi.DaylightBias;
425             tzName = tzi.DaylightName;
426         }
427 
428         appCore->setTimeZoneName("   ");
429         appCore->setTimeZoneBias((tzi.Bias + dstBias) * -60);
430     }
431 }
432 
433 
BeginMovieCapture(const std::string & filename,int width,int height,float framerate)434 static bool BeginMovieCapture(const std::string& filename,
435                               int width, int height,
436                               float framerate)
437 {
438     MovieCapture* movieCapture = new AVICapture();
439 
440     bool success = movieCapture->start(filename, width, height, framerate);
441     if (success)
442         appCore->initMovieCapture(movieCapture);
443     else
444         delete movieCapture;
445 
446     return success;
447 }
448 
449 
CopyStateURLToClipboard()450 static bool CopyStateURLToClipboard()
451 {
452     BOOL b;
453     b = OpenClipboard(mainWindow);
454     if (!b)
455         return false;
456 
457     CelestiaState appState;
458     appState.captureState(appCore);
459 
460     Url url(appState, Url::CurrentVersion);
461     string urlString = url.getAsString();
462 
463     char* s = const_cast<char*>(urlString.c_str());
464     HGLOBAL clipboardDataHandle = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
465                                               strlen(s) + 1);
466     char* clipboardData = (char*) GlobalLock(clipboardDataHandle);
467     if (clipboardData != NULL)
468     {
469         strcpy(clipboardData, s);
470         GlobalUnlock(clipboardDataHandle);
471         EmptyClipboard();
472         HANDLE h = SetClipboardData(CF_TEXT, clipboardDataHandle);
473         CloseClipboard();
474         return h != NULL;
475     }
476     else
477     {
478         CloseClipboard();
479         return false;
480     }
481 }
482 
483 
ToggleMenuItem(HMENU menu,int id)484 static bool ToggleMenuItem(HMENU menu, int id)
485 {
486     MENUITEMINFO menuInfo;
487     menuInfo.cbSize = sizeof(MENUITEMINFO);
488     menuInfo.fMask = MIIM_STATE;
489     if (GetMenuItemInfo(menu, id, FALSE, &menuInfo))
490     {
491         bool isChecked = ((menuInfo.fState & MFS_CHECKED) != 0);
492         CheckMenuItem(menu, id, isChecked ? MF_UNCHECKED : MF_CHECKED);
493         return !isChecked;
494     }
495     return false;
496 }
497 
LoadItemTextFromFile(HWND hWnd,int item,char * filename)498 bool LoadItemTextFromFile(HWND hWnd,
499                           int item,
500                           char* filename)
501 {
502     // ifstream textFile(filename, ios::in | ios::binary);
503     ifstream textFile(filename, ios::in);
504     string s;
505 
506     if (!textFile.good())
507     {
508         SetDlgItemText(hWnd, item, "License file missing!\r\r\nSee http://www.gnu.org/copyleft/gpl.html");
509         return true;
510     }
511 
512     char c;
513     while (textFile.get(c))
514     {
515         if (c == '\n')
516             s += "\r\r\n";
517         else
518             s += c;
519     }
520 
521     SetDlgItemText(hWnd, item, UTF8ToCurrentCP(s).c_str());
522 
523     return true;
524 }
525 
526 
AboutProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)527 BOOL APIENTRY AboutProc(HWND hDlg,
528                         UINT message,
529                         UINT wParam,
530                         LONG lParam)
531 {
532     switch (message)
533     {
534     case WM_INITDIALOG:
535         MakeHyperlinkFromStaticCtrl(hDlg, IDC_CELESTIALINK);
536         return(TRUE);
537 
538     case WM_COMMAND:
539         if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
540         {
541             EndDialog(hDlg, 0);
542             return TRUE;
543         }
544         else if (LOWORD(wParam) == IDC_CELESTIALINK)
545         {
546             char urlBuf[256];
547             HWND hCtrl = GetDlgItem(hDlg, IDC_CELESTIALINK);
548             if (hCtrl)
549             {
550                 if (GetWindowText(hCtrl, urlBuf, sizeof(urlBuf)) > 0)
551                 {
552                     ShellExecute(hDlg, "open", urlBuf, NULL, NULL, SW_SHOWNORMAL);
553                     return TRUE;
554                 }
555             }
556         }
557         break;
558     }
559 
560     return FALSE;
561 }
562 
563 
ControlsHelpProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)564 BOOL APIENTRY ControlsHelpProc(HWND hDlg,
565                               UINT message,
566                               UINT wParam,
567                               LONG lParam)
568 {
569     switch (message)
570     {
571     case WM_INITDIALOG:
572         LoadItemTextFromFile(hDlg, IDC_TEXT_CONTROLSHELP, const_cast<char*>(LocaleFilename("controls.txt").c_str()));
573         return(TRUE);
574 
575     case WM_COMMAND:
576         if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
577         {
578             EndDialog(hDlg, 0);
579             return TRUE;
580         }
581         break;
582     }
583 
584     return FALSE;
585 }
586 
587 
LicenseProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)588 BOOL APIENTRY LicenseProc(HWND hDlg,
589                           UINT message,
590                           UINT wParam,
591                           LONG lParam)
592 {
593     switch (message)
594     {
595     case WM_INITDIALOG:
596         LoadItemTextFromFile(hDlg, IDC_LICENSE_TEXT, const_cast<char*>(LocaleFilename("COPYING").c_str()));
597         return(TRUE);
598 
599     case WM_COMMAND:
600         if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
601         {
602             EndDialog(hDlg, 0);
603             return TRUE;
604         }
605         break;
606     }
607 
608     return FALSE;
609 }
610 
611 
GLInfoProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)612 BOOL APIENTRY GLInfoProc(HWND hDlg,
613                          UINT message,
614                          UINT wParam,
615                          LONG lParam)
616 {
617     switch (message)
618     {
619     case WM_INITDIALOG:
620         {
621             const char* vendor = (char*) glGetString(GL_VENDOR);
622             const char* render = (char*) glGetString(GL_RENDERER);
623             const char* version = (char*) glGetString(GL_VERSION);
624             const char* ext = (char*) glGetString(GL_EXTENSIONS);
625             string s;
626             s += UTF8ToCurrentCP(_("Vendor: "));
627             if (vendor != NULL)
628                 s += vendor;
629             s += "\r\r\n";
630 
631             s += UTF8ToCurrentCP(_("Renderer: "));
632             if (render != NULL)
633                 s += render;
634             s += "\r\r\n";
635 
636             s += UTF8ToCurrentCP(_("Version: "));
637             if (version != NULL)
638                 s += version;
639             s += "\r\r\n";
640 
641             if (ExtensionSupported("GL_ARB_shading_language_100"))
642             {
643                 const char* versionString = (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION_ARB);
644                 if (versionString != NULL)
645                 {
646                     s += UTF8ToCurrentCP(_("GLSL version: "));
647                     s += versionString;
648                     s += "\r\r\n";
649                 }
650             }
651 
652             char buf[1024];
653             GLint simTextures = 1;
654             if (ExtensionSupported("GL_ARB_multitexture"))
655                 glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &simTextures);
656             sprintf(buf, "%s%d\r\r\n",
657                     UTF8ToCurrentCP(_("Max simultaneous textures: ")).c_str(),
658                     simTextures);
659             s += buf;
660 
661             GLint maxTextureSize = 0;
662             glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
663             sprintf(buf, "%s%d\r\r\n",
664                     UTF8ToCurrentCP(_("Max texture size: ")).c_str(),
665                     maxTextureSize);
666             s += buf;
667 
668             if (ExtensionSupported("GL_EXT_texture_cube_map"))
669             {
670                 GLint maxCubeMapSize = 0;
671                 glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &maxCubeMapSize);
672                 sprintf(buf, "%s%d\r\r\n",
673                         UTF8ToCurrentCP(_("Max cube map size: ")).c_str(),
674                         maxTextureSize);
675                 s += buf;
676             }
677 
678             GLfloat pointSizeRange[2];
679             glGetFloatv(GL_POINT_SIZE_RANGE, pointSizeRange);
680             sprintf(buf, "%s%f - %f\r\r\n",
681                     UTF8ToCurrentCP(_("Point size range: ")).c_str(),
682                     pointSizeRange[0], pointSizeRange[1]);
683             s += buf;
684 
685             s += "\r\r\n";
686             s += UTF8ToCurrentCP(_("Supported Extensions:")).c_str();
687             s += "\r\r\n";
688 
689             if (ext != NULL)
690             {
691                 string extString(ext);
692                 int pos = extString.find(' ', 0);
693                 while (pos != string::npos)
694                 {
695                     extString.replace(pos, 1, "\r\r\n");
696                     pos = extString.find(' ', pos);
697                 }
698                 s += extString;
699             }
700 
701             SetDlgItemText(hDlg, IDC_GLINFO_TEXT, s.c_str());
702         }
703         return(TRUE);
704 
705     case WM_COMMAND:
706         if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
707         {
708             EndDialog(hDlg, 0);
709             return TRUE;
710         }
711         break;
712     }
713 
714     return FALSE;
715 }
716 
717 
ChooseMovieParamsProc(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)718 UINT CALLBACK ChooseMovieParamsProc(HWND hDlg, UINT message,
719                                     WPARAM wParam, LPARAM lParam)
720 {
721     switch (message)
722     {
723     case WM_INITDIALOG:
724         {
725             char buf[100];
726             HWND hwnd = GetDlgItem(hDlg, IDC_COMBO_MOVIE_SIZE);
727             int nSizes = sizeof MovieSizes / sizeof MovieSizes[0];
728 
729             int i;
730             for (i = 0; i < nSizes; i++)
731             {
732                 sprintf(buf, "%d x %d", MovieSizes[i][0], MovieSizes[i][1]);
733                 SendMessage(hwnd, CB_INSERTSTRING, -1,
734                             reinterpret_cast<LPARAM>(buf));
735 
736             }
737             SendMessage(hwnd, CB_SETCURSEL, movieSize, 0);
738 
739             hwnd = GetDlgItem(hDlg, IDC_COMBO_MOVIE_FRAMERATE);
740             int nFramerates = sizeof MovieFramerates / sizeof MovieFramerates[0];
741             for (i = 0; i < nFramerates; i++)
742             {
743                 sprintf(buf, "%.2f", MovieFramerates[i]);
744                 SendMessage(hwnd, CB_INSERTSTRING, -1,
745                             reinterpret_cast<LPARAM>(buf));
746             }
747             SendMessage(hwnd, CB_SETCURSEL, movieFramerate, 0);
748         }
749         return TRUE;
750 
751     case WM_COMMAND:
752         if (LOWORD(wParam) == IDC_COMBO_MOVIE_SIZE)
753         {
754             if (HIWORD(wParam) == CBN_SELCHANGE)
755             {
756                 HWND hwnd = reinterpret_cast<HWND>(lParam);
757                 int item = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
758                 if (item != CB_ERR)
759                     movieSize = item;
760             }
761             return TRUE;
762         }
763         else if (LOWORD(wParam) == IDC_COMBO_MOVIE_FRAMERATE)
764         {
765             if (HIWORD(wParam) == CBN_SELCHANGE)
766             {
767                 HWND hwnd = reinterpret_cast<HWND>(lParam);
768                 int item = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
769                 if (item != CB_ERR)
770                     movieFramerate = item;
771             }
772             return TRUE;
773         }
774     }
775 
776     return FALSE;
777 }
778 
779 
FindObjectProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)780 BOOL APIENTRY FindObjectProc(HWND hDlg,
781                              UINT message,
782                              UINT wParam,
783                              LONG lParam)
784 {
785     switch (message)
786     {
787     case WM_INITDIALOG:
788         return(TRUE);
789 
790     case WM_COMMAND:
791         if (LOWORD(wParam) == IDOK)
792         {
793             char buf[1024], out[1024];
794             wchar_t wbuff[1024];
795             int len = GetDlgItemText(hDlg, IDC_FINDOBJECT_EDIT, buf, sizeof(buf));
796             if (len > 0)
797             {
798                 int wlen = MultiByteToWideChar(CP_ACP, 0, buf, -1, wbuff, sizeof(wbuff));
799                 WideCharToMultiByte(CP_UTF8, 0, wbuff, wlen, out, sizeof(out), NULL, NULL);
800                 Selection sel = appCore->getSimulation()->findObject(string(out), true);
801                 if (!sel.empty())
802                     appCore->getSimulation()->setSelection(sel);
803             }
804             EndDialog(hDlg, 0);
805             return TRUE;
806         }
807         else if (LOWORD(wParam) == IDCANCEL)
808         {
809             EndDialog(hDlg, 0);
810             return FALSE;
811         }
812         break;
813     }
814 
815     return FALSE;
816 }
817 
818 
AddBookmarkFolderProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)819 BOOL APIENTRY AddBookmarkFolderProc(HWND hDlg,
820                                     UINT message,
821                                     UINT wParam,
822                                     LONG lParam)
823 {
824     switch (message)
825     {
826     case WM_INITDIALOG:
827         {
828             // Center dialog directly over parent
829             HWND hParent = GetParent(hDlg);
830             CenterWindow(hParent, hDlg);
831 
832             // Limit text of folder name to 32 chars
833             HWND hEdit = GetDlgItem(hDlg, IDC_BOOKMARKFOLDER);
834             SendMessage(hEdit, EM_LIMITTEXT, 32, 0);
835 
836             // Set initial button states
837             HWND hOK = GetDlgItem(hDlg, IDOK);
838             HWND hCancel = GetDlgItem(hDlg, IDCANCEL);
839             EnableWindow(hOK, FALSE);
840             RemoveButtonDefaultStyle(hOK);
841             AddButtonDefaultStyle(hCancel);
842         }
843         return TRUE;
844 
845     case WM_COMMAND:
846         {
847             if (HIWORD(wParam) == EN_CHANGE)
848             {
849                 HWND hOK = GetDlgItem(hDlg, IDOK);
850                 HWND hCancel = GetDlgItem(hDlg, IDCANCEL);
851 
852                 if (hOK && hCancel)
853                 {
854                     // If edit control contains text, enable OK button
855                     char name[33];
856                     GetWindowText((HWND)lParam, name, sizeof(name));
857                     if (name[0])
858                     {
859                         // Remove Cancel button default style
860                         RemoveButtonDefaultStyle(hCancel);
861 
862                         // Enable OK button
863                         EnableWindow(hOK, TRUE);
864 
865                         // Make OK button default
866                         AddButtonDefaultStyle(hOK);
867                     }
868                     else
869                     {
870                         // Disable OK button
871                         EnableWindow(hOK, FALSE);
872 
873                         // Remove OK button default style
874                         RemoveButtonDefaultStyle(hOK);
875 
876                         // Make Cancel button default
877                         AddButtonDefaultStyle(hCancel);
878                     }
879                 }
880             }
881         }
882 
883         if (LOWORD(wParam) == IDOK)
884         {
885             // Get text entered in Folder Name Edit box
886             char name[33];
887             HWND hEdit = GetDlgItem(hDlg, IDC_BOOKMARKFOLDER);
888             if (hEdit)
889             {
890                 if (GetWindowText(hEdit, name, sizeof(name)))
891                 {
892                     // Create new folder in parent dialog tree control.
893                     AddNewBookmarkFolderInTree(hBookmarkTree, appCore, name);
894                 }
895             }
896 
897             EndDialog(hDlg, 0);
898             return TRUE;
899         }
900         else if (LOWORD(wParam) == IDCANCEL)
901         {
902             EndDialog(hDlg, 0);
903             return FALSE;
904         }
905     }
906 
907     return FALSE;
908 }
909 
AddBookmarkProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)910 BOOL APIENTRY AddBookmarkProc(HWND hDlg,
911                               UINT message,
912                               UINT wParam,
913                               LONG lParam)
914 {
915     switch (message)
916     {
917     case WM_INITDIALOG:
918         {
919         RECT dlgRect, treeRect;
920         HWND hCtrl;
921         if (GetWindowRect(hDlg, &dlgRect))
922         {
923             if (hCtrl = GetDlgItem(hDlg, IDC_BOOKMARK_FOLDERTREE))
924             {
925                 if (GetWindowRect(hCtrl, &treeRect))
926                 {
927                     int width = dlgRect.right - dlgRect.left;
928                     int height = treeRect.top - dlgRect.top;
929                     SetWindowPos(hDlg, HWND_TOP, 0, 0, width, height,
930                                  SWP_NOMOVE | SWP_NOZORDER);
931                 }
932 
933                 HTREEITEM hParent;
934                 if (hParent = PopulateBookmarkFolders(hCtrl, appCore, appInstance))
935                 {
936                     //Expand bookmarks item
937                     TreeView_Expand(hCtrl, hParent, TVE_EXPAND);
938                 }
939             }
940         }
941 
942         //Set initial button states
943         HWND hOK = GetDlgItem(hDlg, IDOK);
944         HWND hCancel = GetDlgItem(hDlg, IDCANCEL);
945         EnableWindow(hOK, FALSE);
946         RemoveButtonDefaultStyle(hOK);
947         AddButtonDefaultStyle(hCancel);
948 
949         // Set bookmark text to selection text
950         if (hCtrl = GetDlgItem(hDlg, IDC_BOOKMARK_EDIT))
951         {
952             //If this is a body, set the text.
953             Selection sel = appCore->getSimulation()->getSelection();
954             switch (sel.getType())
955             {
956             case Selection::Type_Body:
957                 {
958                     string name = UTF8ToCurrentCP(sel.body()->getName(true));
959                     SetWindowText(hCtrl, (char*)name.c_str());
960                 }
961                 break;
962 
963             default:
964                 break;
965             }
966         }
967 
968         return(TRUE);
969         }
970 
971     case WM_COMMAND:
972         {
973         if (HIWORD(wParam) == EN_CHANGE)
974         {
975             HWND hOK = GetDlgItem(hDlg, IDOK);
976             HWND hCancel = GetDlgItem(hDlg, IDCANCEL);
977 
978             if (hOK && hCancel)
979             {
980                 //If edit control contains text, enable OK button
981                 char name[33];
982                 GetWindowText((HWND)lParam, name, sizeof(name));
983                 if (name[0])
984                 {
985                     //Remove Cancel button default style
986                     RemoveButtonDefaultStyle(hCancel);
987 
988                     //Enable OK button
989                     EnableWindow(hOK, TRUE);
990 
991                     //Make OK button default
992                     AddButtonDefaultStyle(hOK);
993                 }
994                 else
995                 {
996                     //Disable OK button
997                     EnableWindow(hOK, FALSE);
998 
999                     //Remove OK button default style
1000                     RemoveButtonDefaultStyle(hOK);
1001 
1002                     //Make Cancel button default
1003                     AddButtonDefaultStyle(hCancel);
1004                 }
1005             }
1006         }
1007         if (LOWORD(wParam) == IDOK)
1008         {
1009             char name[33];
1010             int len = GetDlgItemText(hDlg, IDC_BOOKMARK_EDIT, name, sizeof(name));
1011             if (len > 0)
1012             {
1013                 HWND hTree;
1014                 if(hTree = GetDlgItem(hDlg, IDC_BOOKMARK_FOLDERTREE))
1015                 {
1016                     InsertBookmarkInFavorites(hTree, name, appCore);
1017 
1018                     appCore->writeFavoritesFile();
1019 
1020                     // Rebuild bookmarks menu.
1021                     BuildFavoritesMenu(menuBar, appCore, appInstance, &odAppMenu);
1022                 }
1023             }
1024             EndDialog(hDlg, 0);
1025             return TRUE;
1026         }
1027         else if (LOWORD(wParam) == IDCANCEL)
1028         {
1029             EndDialog(hDlg, 0);
1030             return FALSE;
1031         }
1032         else if (LOWORD(wParam) == IDC_BOOKMARK_CREATEIN)
1033         {
1034             HWND button;
1035             RECT dlgRect, treeRect;
1036             HWND hTree;
1037             char text[16];
1038             if (GetWindowRect(hDlg, &dlgRect))
1039             {
1040                 if (hTree = GetDlgItem(hDlg, IDC_BOOKMARK_FOLDERTREE))
1041                 {
1042                     if (GetWindowRect(hTree, &treeRect))
1043                     {
1044                         if (button = GetDlgItem(hDlg, IDC_BOOKMARK_CREATEIN))
1045                         {
1046                             if (GetWindowText(button, text, sizeof(text)))
1047                             {
1048                                 int width = dlgRect.right - dlgRect.left;
1049                                 if (strstr(text, ">>"))
1050                                 {
1051                                     //Increase size of dialog
1052                                     int height = treeRect.bottom - dlgRect.top + 12;
1053                                     SetWindowPos(hDlg, HWND_TOP, 0, 0, width, height,
1054                                                  SWP_NOMOVE | SWP_NOZORDER);
1055                                     //Change text in button
1056                                     strcpy(text + strlen(text) - 2, "<<");
1057                                     SetWindowText(button, text);
1058                                 }
1059                                 else
1060                                 {
1061                                     //Decrease size of dialog
1062                                     int height = treeRect.top - dlgRect.top;
1063                                     SetWindowPos(hDlg, HWND_TOP, 0, 0, width, height,
1064                                                  SWP_NOMOVE | SWP_NOZORDER);
1065                                     //Change text in button
1066                                     strcpy(text + strlen(text) - 2, ">>");
1067                                     SetWindowText(button, text);
1068                                 }
1069                             }
1070                         }
1071                     }
1072                 }
1073             }
1074         }
1075         else if (LOWORD(wParam) == IDC_BOOKMARK_NEWFOLDER)
1076         {
1077             if(hBookmarkTree = GetDlgItem(hDlg, IDC_BOOKMARK_FOLDERTREE))
1078             {
1079                 DialogBox(hRes, MAKEINTRESOURCE(IDD_ADDBOOKMARK_FOLDER),
1080                           hDlg, AddBookmarkFolderProc);
1081             }
1082         }
1083         break;
1084         }
1085     }
1086 
1087     return FALSE;
1088 }
1089 
RenameBookmarkProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)1090 BOOL APIENTRY RenameBookmarkProc(HWND hDlg,
1091                                  UINT message,
1092                                  UINT wParam,
1093                                  LONG lParam)
1094 {
1095     switch (message)
1096     {
1097     case WM_INITDIALOG:
1098         {
1099         //Center dialog directly over parent
1100         HWND hParent = GetParent(hDlg);
1101         CenterWindow(hParent, hDlg);
1102 
1103         //Limit text of folder name to 32 chars
1104         HWND hEdit = GetDlgItem(hDlg, IDC_NEWBOOKMARK);
1105         SendMessage(hEdit, EM_LIMITTEXT, 32, 0);
1106 
1107         //Set text in edit control to current bookmark name
1108         SetWindowText(hEdit, bookmarkName);
1109 
1110         return(TRUE);
1111         }
1112 
1113     case WM_COMMAND:
1114         {
1115         if (HIWORD(wParam) == EN_CHANGE)
1116         {
1117             HWND hOK = GetDlgItem(hDlg, IDOK);
1118             HWND hCancel = GetDlgItem(hDlg, IDCANCEL);
1119 
1120             if (hOK && hCancel)
1121             {
1122                 //If edit control contains text, enable OK button
1123                 char name[33];
1124                 GetWindowText((HWND)lParam, name, sizeof(name));
1125                 if (name[0])
1126                 {
1127                     //Remove Cancel button default style
1128                     RemoveButtonDefaultStyle(hCancel);
1129 
1130                     //Enable OK button
1131                     EnableWindow(hOK, TRUE);
1132 
1133                     //Make OK button default
1134                     AddButtonDefaultStyle(hOK);
1135                 }
1136                 else
1137                 {
1138                     //Disable OK button
1139                     EnableWindow(hOK, FALSE);
1140 
1141                     //Remove OK button default style
1142                     RemoveButtonDefaultStyle(hOK);
1143 
1144                     //Make Cancel button default
1145                     AddButtonDefaultStyle(hCancel);
1146                 }
1147             }
1148         }
1149         if (LOWORD(wParam) == IDOK)
1150         {
1151             //Get text entered in Folder Name Edit box
1152             char name[33];
1153             HWND hEdit = GetDlgItem(hDlg, IDC_NEWBOOKMARK);
1154             if (hEdit)
1155             {
1156                 if (GetWindowText(hEdit, name, sizeof(name)))
1157                     RenameBookmarkInFavorites(hBookmarkTree, name, appCore);
1158             }
1159 
1160             EndDialog(hDlg, 0);
1161             return TRUE;
1162         }
1163         else if (LOWORD(wParam) == IDCANCEL)
1164         {
1165             EndDialog(hDlg, 0);
1166             return FALSE;
1167         }
1168         }
1169     }
1170 
1171     return FALSE;
1172 }
1173 
OrganizeBookmarksProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)1174 BOOL APIENTRY OrganizeBookmarksProc(HWND hDlg,
1175                                     UINT message,
1176                                     UINT wParam,
1177                                     LONG lParam)
1178 {
1179     static UINT_PTR dragDropTimer;
1180     switch (message)
1181     {
1182     case WM_INITDIALOG:
1183         {
1184         HWND hCtrl;
1185         if (hCtrl = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1186         {
1187             HTREEITEM hParent;
1188             if (hParent = PopulateBookmarksTree(hCtrl, appCore, hRes))
1189             {
1190                 // Expand bookmarks item
1191                 TreeView_Expand(hCtrl, hParent, TVE_EXPAND);
1192             }
1193         }
1194         if (hCtrl = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARKS_DELETE))
1195             EnableWindow(hCtrl, FALSE);
1196         if (hCtrl = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARKS_RENAME))
1197             EnableWindow(hCtrl, FALSE);
1198 
1199         return(TRUE);
1200         }
1201 
1202     case WM_COMMAND:
1203         {
1204         if (LOWORD(wParam) == IDOK)
1205         {
1206 #if 0
1207             HWND hTree;
1208             if (hTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1209                 SyncTreeFoldersWithFavoriteFolders(hTree, appCore);
1210 #endif
1211 
1212             // Write any change to bookmarks
1213             appCore->writeFavoritesFile();
1214 
1215             // Rebuild bookmarks menu
1216             BuildFavoritesMenu(menuBar, appCore, hRes, &odAppMenu);
1217 
1218             EndDialog(hDlg, 0);
1219             return TRUE;
1220         }
1221         else if (LOWORD(wParam) == IDCANCEL)
1222         {
1223             //Refresh from file
1224             appCore->readFavoritesFile();
1225 
1226             EndDialog(hDlg, 0);
1227             return FALSE;
1228         }
1229         else if (LOWORD(wParam) == IDC_ORGANIZE_BOOKMARKS_NEWFOLDER)
1230         {
1231             if (hBookmarkTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1232             {
1233                 DialogBox(hRes, MAKEINTRESOURCE(IDD_ADDBOOKMARK_FOLDER), hDlg, AddBookmarkFolderProc);
1234             }
1235         }
1236         else if (LOWORD(wParam) == IDC_ORGANIZE_BOOKMARKS_RENAME)
1237         {
1238             if (hBookmarkTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1239             {
1240                 HTREEITEM hItem;
1241                 TVITEM tvItem;
1242                 if (hItem = TreeView_GetSelection(hBookmarkTree))
1243                 {
1244                     tvItem.hItem = hItem;
1245                     tvItem.mask = TVIF_TEXT | TVIF_HANDLE;
1246                     tvItem.pszText = bookmarkName;
1247                     tvItem.cchTextMax = sizeof(bookmarkName);
1248                     if (TreeView_GetItem(hBookmarkTree, &tvItem))
1249                     {
1250                         DialogBox(hRes,
1251                                   MAKEINTRESOURCE(IDD_RENAME_BOOKMARK),
1252                                   hDlg, RenameBookmarkProc);
1253                     }
1254                 }
1255             }
1256         }
1257         else if (LOWORD(wParam) == IDC_ORGANIZE_BOOKMARKS_DELETE)
1258         {
1259             HWND hTree;
1260             if (hTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1261                 DeleteBookmarkFromFavorites(hTree, appCore);
1262         }
1263         break;
1264         }
1265     case WM_NOTIFY:
1266         {
1267             if (((LPNMHDR)lParam)->code == TVN_SELCHANGED)
1268             {
1269                 HWND hTree;
1270                 if (hTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1271                 {
1272                     //Enable buttons as necessary
1273                     HTREEITEM hItem;
1274                     if (hItem = TreeView_GetSelection(hTree))
1275                     {
1276                         HWND hDelete, hRename;
1277                         hDelete = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARKS_DELETE);
1278                         hRename = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARKS_RENAME);
1279                         if (hDelete && hRename)
1280                         {
1281                             if (TreeView_GetParent(hTree, hItem))
1282                             {
1283                                 EnableWindow(hDelete, TRUE);
1284                                 EnableWindow(hRename, TRUE);
1285                             }
1286                             else
1287                             {
1288                                 EnableWindow(hDelete, FALSE);
1289                                 EnableWindow(hRename, FALSE);
1290                             }
1291                         }
1292                     }
1293                 }
1294             }
1295             else if(((LPNMHDR)lParam)->code == TVN_BEGINDRAG)
1296             {
1297                 //Do not allow folders to be dragged
1298                 HWND hTree;
1299                 TVITEM tvItem;
1300                 LPNMTREEVIEW nm = (LPNMTREEVIEW)lParam;
1301                 HTREEITEM hItem = nm->itemNew.hItem;
1302 
1303                 if (hTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1304                 {
1305                     tvItem.hItem = hItem;
1306                     tvItem.mask = TVIF_PARAM | TVIF_HANDLE;
1307                     if (TreeView_GetItem(hTree, &tvItem))
1308                     {
1309                         if(tvItem.lParam != 1)
1310                         {
1311                             //Start a timer to handle auto-scrolling
1312                             dragDropTimer = SetTimer(hDlg, 1, 100, NULL);
1313                             OrganizeBookmarksOnBeginDrag(hTree, (LPNMTREEVIEW)lParam);
1314                         }
1315                     }
1316                 }
1317             }
1318         }
1319         break;
1320     case WM_MOUSEMOVE:
1321         {
1322             if(isOrganizeBookmarksDragDropActive())
1323             {
1324                 HWND hTree;
1325                 if (hTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1326                 {
1327                     OrganizeBookmarksOnMouseMove(hTree, GET_X_LPARAM(lParam),
1328                         GET_Y_LPARAM(lParam));
1329                 }
1330             }
1331         }
1332         break;
1333     case WM_LBUTTONUP:
1334         {
1335             if(isOrganizeBookmarksDragDropActive())
1336             {
1337                 HWND hTree;
1338                 if (hTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1339                 {
1340                     //Kill the auto-scroll timer
1341                     KillTimer(hDlg, dragDropTimer);
1342 
1343                     OrganizeBookmarksOnLButtonUp(hTree);
1344                     MoveBookmarkInFavorites(hTree, appCore);
1345                 }
1346             }
1347         }
1348         break;
1349     case WM_TIMER:
1350         {
1351             if(isOrganizeBookmarksDragDropActive())
1352             {
1353                 if(wParam == 1)
1354                 {
1355                     //Handle
1356                     HWND hTree;
1357                     if (hTree = GetDlgItem(hDlg, IDC_ORGANIZE_BOOKMARK_TREE))
1358                     {
1359                         DragDropAutoScroll(hTree);
1360                     }
1361                 }
1362             }
1363         }
1364         break;
1365     }
1366 
1367     return FALSE;
1368 }
1369 
1370 
1371 int selectedScreenMode = 0;
SelectDisplayModeProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)1372 BOOL APIENTRY SelectDisplayModeProc(HWND hDlg,
1373                                     UINT message,
1374                                     UINT wParam,
1375                                     LONG lParam)
1376 {
1377     switch (message)
1378     {
1379     case WM_INITDIALOG:
1380         {
1381             char buf[100];
1382             HWND hwnd = GetDlgItem(hDlg, IDC_COMBO_RESOLUTION);
1383 
1384             // Add windowed mode as the first item on the menu
1385             bind_textdomain_codeset("celestia", CurrentCP());
1386             SendMessage(hwnd, CB_INSERTSTRING, -1,
1387                         reinterpret_cast<LPARAM>(_("Windowed Mode")));
1388             bind_textdomain_codeset("celestia", "UTF8");
1389 
1390             for (vector<DEVMODE>::const_iterator iter= displayModes->begin();
1391                  iter != displayModes->end(); iter++)
1392             {
1393                 sprintf(buf, "%d x %d x %d",
1394                         iter->dmPelsWidth, iter->dmPelsHeight,
1395                         iter->dmBitsPerPel);
1396                 SendMessage(hwnd, CB_INSERTSTRING, -1,
1397                             reinterpret_cast<LPARAM>(buf));
1398             }
1399             SendMessage(hwnd, CB_SETCURSEL, currentScreenMode, 0);
1400         }
1401         return TRUE;
1402 
1403     case WM_COMMAND:
1404         if (LOWORD(wParam) == IDOK)
1405         {
1406             newScreenMode = selectedScreenMode;
1407             EndDialog(hDlg, 0);
1408             return TRUE;
1409         }
1410         else if (LOWORD(wParam) == IDCANCEL)
1411         {
1412             EndDialog(hDlg, 0);
1413             return TRUE;
1414         }
1415         else if (LOWORD(wParam) == IDC_COMBO_RESOLUTION)
1416         {
1417             if (HIWORD(wParam) == CBN_SELCHANGE)
1418             {
1419                 HWND hwnd = reinterpret_cast<HWND>(lParam);
1420                 int item = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
1421                 if (item != CB_ERR)
1422                     selectedScreenMode = item;
1423             }
1424             return TRUE;
1425         }
1426     }
1427 
1428     return FALSE;
1429 }
1430 
1431 
CreateMenuBar()1432 HMENU CreateMenuBar()
1433 {
1434     return LoadMenu(hRes, MAKEINTRESOURCE(IDR_MAIN_MENU));
1435 }
1436 
setMenuItemCheck(int menuItem,bool checked)1437 static void setMenuItemCheck(int menuItem, bool checked)
1438 {
1439     CheckMenuItem(menuBar, menuItem, checked ? MF_CHECKED : MF_UNCHECKED);
1440 }
1441 
1442 struct IntStrPairComparePredicate
1443 {
IntStrPairComparePredicateIntStrPairComparePredicate1444     IntStrPairComparePredicate() : dummy(0)
1445     {
1446     }
1447 
operator ()IntStrPairComparePredicate1448     bool operator()(const IntStrPair pair1, const IntStrPair pair2) const
1449     {
1450         return (pair1.second.compare(pair2.second) < 0);
1451     }
1452 
1453     int dummy;
1454 };
1455 
CreatePlanetarySystemMenu(string parentName,const PlanetarySystem * psys)1456 static HMENU CreatePlanetarySystemMenu(string parentName, const PlanetarySystem* psys)
1457 {
1458     // Use some vectors to categorize and sort the bodies within this PlanetarySystem.
1459     // In order to generate sorted menus, we must carry the name and menu index as a
1460     // single unit in the sort. One obvous way is to create a vector<pair<int,string>>
1461     // and then use a comparison predicate to sort.the vector based on the string in
1462     // each pair.
1463 
1464     // Declare vector<pair<int,string>> objects for each classification of body
1465     vector<IntStrPair> asteroids;
1466     vector<IntStrPair> comets;
1467     vector<IntStrPair> invisibles;
1468     vector<IntStrPair> moons;
1469     vector<IntStrPair> planets;
1470     vector<IntStrPair> spacecraft;
1471 
1472     // We will use these objects to iterate over all the above vectors
1473     vector<IntStrPairVec> objects;
1474     vector<string> menuNames;
1475 
1476     // Place each body in the correct vector based on classification
1477     HMENU menu = CreatePopupMenu();
1478     for (int i = 0; i < psys->getSystemSize(); i++)
1479     {
1480         Body* body = psys->getBody(i);
1481         if (!body->getName().empty())
1482         {
1483             switch(body->getClassification())
1484             {
1485             case Body::Asteroid:
1486                 asteroids.push_back(make_pair(i, UTF8ToCurrentCP(body->getName(true))));
1487                 break;
1488             case Body::Comet:
1489                 comets.push_back(make_pair(i, UTF8ToCurrentCP(body->getName(true))));
1490                 break;
1491             case Body::Invisible:
1492                 invisibles.push_back(make_pair(i, UTF8ToCurrentCP(body->getName(true))));
1493                 break;
1494             case Body::Moon:
1495                 moons.push_back(make_pair(i, UTF8ToCurrentCP(body->getName(true))));
1496                 break;
1497             case Body::Planet:
1498                 planets.push_back(make_pair(i, UTF8ToCurrentCP(body->getName(true))));
1499                 break;
1500             case Body::Spacecraft:
1501                 spacecraft.push_back(make_pair(i, UTF8ToCurrentCP(body->getName(true))));
1502                 break;
1503             }
1504         }
1505     }
1506 
1507     // Add each vector of PlanetarySystem bodies to a vector to iterate over
1508     objects.push_back(asteroids);
1509     menuNames.push_back(UTF8ToCurrentCP(_("Asteroids")));
1510     objects.push_back(comets);
1511     menuNames.push_back(UTF8ToCurrentCP(_("Comets")));
1512     objects.push_back(invisibles);
1513     menuNames.push_back(UTF8ToCurrentCP(_("Invisibles")));
1514     objects.push_back(moons);
1515     menuNames.push_back(UTF8ToCurrentCP(_("Moons")));
1516     objects.push_back(planets);
1517     menuNames.push_back(UTF8ToCurrentCP(_("Planets")));
1518     objects.push_back(spacecraft);
1519     menuNames.push_back(UTF8ToCurrentCP(_("Spacecraft")));
1520 
1521     // Now sort each vector and generate submenus
1522     IntStrPairComparePredicate pred;
1523     vector<IntStrPairVec>::iterator obj;
1524     vector<IntStrPair>::iterator it;
1525     vector<string>::iterator menuName;
1526     HMENU hSubMenu;
1527     int numSubMenus;
1528 
1529     // Count how many submenus we need to create
1530     numSubMenus = 0;
1531     for (obj=objects.begin(); obj != objects.end(); obj++)
1532     {
1533         if (obj->size() > 0)
1534             numSubMenus++;
1535     }
1536 
1537     menuName = menuNames.begin();
1538     for (obj=objects.begin(); obj != objects.end(); obj++)
1539     {
1540         // Only generate a submenu if the vector is not empty
1541         if (obj->size() > 0)
1542         {
1543             // Don't create a submenu for a single item
1544             if (obj->size() == 1)
1545             {
1546                 it=obj->begin();
1547                 AppendMenu(menu, MF_STRING, MENU_CHOOSE_PLANET + it->first, it->second.c_str());
1548             }
1549             else
1550             {
1551                 // Skip sorting if we are dealing with the planets in our own Solar System.
1552                 if (parentName != "Sol" || *menuName != UTF8ToCurrentCP(_("Planets")))
1553                     sort(obj->begin(), obj->end(), pred);
1554 
1555                 if (numSubMenus > 1)
1556                 {
1557                     // Add items to submenu
1558                     hSubMenu = CreatePopupMenu();
1559                     for(it=obj->begin(); it != obj->end(); it++)
1560                         AppendMenu(hSubMenu, MF_STRING, MENU_CHOOSE_PLANET + it->first, it->second.c_str());
1561 
1562                     AppendMenu(menu, MF_POPUP | MF_STRING, (DWORD)hSubMenu, menuName->c_str());
1563                 }
1564                 else
1565                 {
1566                     // Just add items to the popup
1567                     for(it=obj->begin(); it != obj->end(); it++)
1568                         AppendMenu(menu, MF_STRING, MENU_CHOOSE_PLANET + it->first, it->second.c_str());
1569                 }
1570             }
1571         }
1572         menuName++;
1573     }
1574 
1575     return menu;
1576 }
1577 
1578 
CreateAlternateSurfaceMenu(const vector<string> & surfaces)1579 static HMENU CreateAlternateSurfaceMenu(const vector<string>& surfaces)
1580 {
1581     HMENU menu = CreatePopupMenu();
1582 
1583     AppendMenu(menu, MF_STRING, MENU_CHOOSE_SURFACE, "Normal");
1584     for (unsigned int i = 0; i < surfaces.size(); i++)
1585     {
1586         AppendMenu(menu, MF_STRING, MENU_CHOOSE_SURFACE + i + 1,
1587                    surfaces[i].c_str());
1588     }
1589 
1590     return menu;
1591 }
1592 
1593 
handlePopupMenu(HWND hwnd,float x,float y,const Selection & sel)1594 VOID APIENTRY handlePopupMenu(HWND hwnd,
1595                               float x, float y,
1596                               const Selection& sel)
1597 {
1598     HMENU hMenu;
1599     string name;
1600 
1601     hMenu = CreatePopupMenu();
1602     switch (sel.getType())
1603     {
1604     case Selection::Type_Body:
1605         {
1606             name = sel.body()->getName(true);
1607             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_CENTER, UTF8ToCurrentCP(name).c_str());
1608             AppendMenu(hMenu, MF_SEPARATOR, 0, 0);
1609             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_GOTO, UTF8ToCurrentCP(_("&Goto")).c_str());
1610             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_FOLLOW, UTF8ToCurrentCP(_("&Follow")).c_str());
1611             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_SYNCORBIT, UTF8ToCurrentCP(_("S&ync Orbit")).c_str());
1612             AppendMenu(hMenu, MF_STRING, ID_INFO, UTF8ToCurrentCP(_("&Info")).c_str());
1613             HMENU refVectorMenu = CreatePopupMenu();
1614             AppendMenu(hMenu, MF_POPUP | MF_STRING, (DWORD) refVectorMenu, UTF8ToCurrentCP(_("&Reference Marks")).c_str());
1615             AppendMenu(refVectorMenu, MF_STRING, ID_RENDER_BODY_AXES, UTF8ToCurrentCP(_("Show Body Axes")).c_str());
1616             AppendMenu(refVectorMenu, MF_STRING, ID_RENDER_FRAME_AXES, UTF8ToCurrentCP(_("Show Frame Axes")).c_str());
1617             AppendMenu(refVectorMenu, MF_STRING, ID_RENDER_SUN_DIRECTION, UTF8ToCurrentCP(_("Show Sun Direction")).c_str());
1618             AppendMenu(refVectorMenu, MF_STRING, ID_RENDER_VELOCITY_VECTOR, UTF8ToCurrentCP(_("Show Velocity Vector")).c_str());
1619             AppendMenu(refVectorMenu, MF_STRING, ID_RENDER_PLANETOGRAPHIC_GRID, UTF8ToCurrentCP(_("Show Planetographic Grid")).c_str());
1620             AppendMenu(refVectorMenu, MF_STRING, ID_RENDER_TERMINATOR, UTF8ToCurrentCP(_("Show Terminator")).c_str());
1621 
1622             CheckMenuItem(refVectorMenu, ID_RENDER_BODY_AXES,   sel.body()->findReferenceMark("body axes") ? MF_CHECKED : MF_UNCHECKED);
1623             CheckMenuItem(refVectorMenu, ID_RENDER_FRAME_AXES,  sel.body()->findReferenceMark("frame axes") ? MF_CHECKED : MF_UNCHECKED);
1624             CheckMenuItem(refVectorMenu, ID_RENDER_SUN_DIRECTION,  sel.body()->findReferenceMark("sun direction") ? MF_CHECKED : MF_UNCHECKED);
1625             CheckMenuItem(refVectorMenu, ID_RENDER_VELOCITY_VECTOR,  sel.body()->findReferenceMark("velocity vector") ? MF_CHECKED : MF_UNCHECKED);
1626             CheckMenuItem(refVectorMenu, ID_RENDER_PLANETOGRAPHIC_GRID, sel.body()->findReferenceMark("planetographic grid") ? MF_CHECKED : MF_UNCHECKED);
1627             CheckMenuItem(refVectorMenu, ID_RENDER_TERMINATOR, sel.body()->findReferenceMark("terminator") ? MF_CHECKED : MF_UNCHECKED);
1628 
1629             const PlanetarySystem* satellites = sel.body()->getSatellites();
1630             if (satellites != NULL && satellites->getSystemSize() != 0)
1631             {
1632                 HMENU satMenu = CreatePlanetarySystemMenu(name, satellites);
1633                 AppendMenu(hMenu, MF_POPUP | MF_STRING, (DWORD) satMenu,
1634                            UTF8ToCurrentCP(_("&Satellites")).c_str());
1635             }
1636 
1637             vector<string>* altSurfaces = sel.body()->getAlternateSurfaceNames();
1638             if (altSurfaces != NULL)
1639             {
1640                 if (altSurfaces->size() != NULL)
1641                 {
1642                     HMENU surfMenu = CreateAlternateSurfaceMenu(*altSurfaces);
1643                     AppendMenu(hMenu, MF_POPUP | MF_STRING, (DWORD) surfMenu,
1644                                UTF8ToCurrentCP(_("&Alternate Surfaces")).c_str());
1645                 }
1646                 delete altSurfaces;
1647             }
1648         }
1649         break;
1650 
1651     case Selection::Type_Star:
1652         {
1653             Simulation* sim = appCore->getSimulation();
1654             name = sim->getUniverse()->getStarCatalog()->getStarName(*(sel.star()));
1655             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_CENTER, UTF8ToCurrentCP(name).c_str());
1656             AppendMenu(hMenu, MF_SEPARATOR, 0, 0);
1657             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_GOTO, UTF8ToCurrentCP(_("&Goto")).c_str());
1658             AppendMenu(hMenu, MF_STRING, ID_INFO, UTF8ToCurrentCP(_("&Info")).c_str());
1659 
1660             SolarSystemCatalog* solarSystemCatalog = sim->getUniverse()->getSolarSystemCatalog();
1661             SolarSystemCatalog::iterator iter = solarSystemCatalog->find(sel.star()->getCatalogNumber());
1662             if (iter != solarSystemCatalog->end())
1663             {
1664                 SolarSystem* solarSys = iter->second;
1665                 HMENU planetsMenu = CreatePlanetarySystemMenu(name, solarSys->getPlanets());
1666                 if (name == "Sol")
1667                     AppendMenu(hMenu, MF_POPUP | MF_STRING, (DWORD) planetsMenu, UTF8ToCurrentCP(_("Orbiting Bodies")).c_str());
1668                 else
1669                     AppendMenu(hMenu, MF_POPUP | MF_STRING, (DWORD) planetsMenu, UTF8ToCurrentCP(_("Planets")).c_str());
1670             }
1671         }
1672         break;
1673 
1674     case Selection::Type_DeepSky:
1675         {
1676             Simulation* sim = appCore->getSimulation();
1677             name = sim->getUniverse()->getDSOCatalog()->getDSOName(sel.deepsky());
1678             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_CENTER, UTF8ToCurrentCP(name).c_str());
1679             AppendMenu(hMenu, MF_SEPARATOR, 0, 0);
1680             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_GOTO, UTF8ToCurrentCP(_("&Goto")).c_str());
1681             AppendMenu(hMenu, MF_STRING, ID_NAVIGATION_FOLLOW, UTF8ToCurrentCP(_("&Follow")).c_str());
1682             AppendMenu(hMenu, MF_STRING, ID_INFO, UTF8ToCurrentCP(_("&Info")).c_str());
1683         }
1684         break;
1685 
1686     case Selection::Type_Location:
1687         break;
1688 
1689     default:
1690         break;
1691     }
1692 
1693     if (appCore->getSimulation()->getUniverse()->isMarked(sel, 1))
1694         AppendMenu(hMenu, MF_STRING, ID_TOOLS_UNMARK, UTF8ToCurrentCP(_("&Unmark")).c_str());
1695     else
1696         AppendMenu(hMenu, MF_STRING, ID_TOOLS_MARK, UTF8ToCurrentCP(_("&Mark")).c_str());
1697 
1698     POINT point;
1699     point.x = (int) x;
1700     point.y = (int) y;
1701 
1702 
1703     if (currentScreenMode == 0)
1704         ClientToScreen(hwnd, (LPPOINT) &point);
1705 
1706     appCore->getSimulation()->setSelection(sel);
1707     TrackPopupMenu(hMenu, 0, point.x, point.y, 0, hwnd, NULL);
1708 
1709     // TODO: Do we need to explicitly destroy submenus or does DestroyMenu
1710     // work recursively?
1711     // According to the MSDN documentation, DestroyMenu() IS recursive. Clint 11/01.
1712     DestroyMenu(hMenu);
1713 
1714 #ifdef INFINITE_MOUSE
1715     ignoreNextMoveEvent = true;
1716 #endif // INFINITE_MOUSE
1717 }
1718 
1719 
1720 // TODO: get rid of fixed urls
ShowWWWInfo(const Selection & sel)1721 void ShowWWWInfo(const Selection& sel)
1722 {
1723     string url;
1724     switch (sel.getType())
1725     {
1726     case Selection::Type_Body:
1727         {
1728             url = sel.body()->getInfoURL();
1729             if (url.empty())
1730             {
1731                 string name = sel.body()->getName();
1732                 for (unsigned int i = 0; i < name.size(); i++)
1733                     name[i] = tolower(name[i]);
1734 
1735                 url = string("http://www.nineplanets.org/") + name + ".html";
1736             }
1737         }
1738         break;
1739 
1740     case Selection::Type_Star:
1741         {
1742             url = sel.star()->getInfoURL();
1743             if (url.empty())
1744             {
1745                 char name[32];
1746                 sprintf(name, "HIP%d", sel.star()->getCatalogNumber() & ~0xf0000000);
1747                 url = string("http://simbad.u-strasbg.fr/sim-id.pl?protocol=html&Ident=") + name;
1748             }
1749         }
1750         break;
1751 
1752     case Selection::Type_DeepSky:
1753         url = sel.deepsky()->getInfoURL();
1754         break;
1755 
1756     case Selection::Type_Location:
1757         break;
1758 
1759     default:
1760         break;
1761     }
1762 
1763     ShellExecute(mainWindow,
1764                  "open",
1765                  url.c_str(),
1766                  NULL,
1767                  NULL,
1768                  0);
1769 }
1770 
1771 
ContextMenu(float x,float y,Selection sel)1772 void ContextMenu(float x, float y, Selection sel)
1773 {
1774     handlePopupMenu(mainWindow, x, y, sel);
1775 }
1776 
1777 
EnableFullScreen(const DEVMODE & dm)1778 bool EnableFullScreen(const DEVMODE& dm)
1779 {
1780     DEVMODE devMode;
1781 
1782     ZeroMemory(&devMode, sizeof devMode);
1783     devMode.dmSize = sizeof devMode;
1784     devMode.dmPelsWidth  = dm.dmPelsWidth;
1785     devMode.dmPelsHeight = dm.dmPelsHeight;
1786     devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
1787 
1788     if (ChangeDisplaySettings(&devMode, CDS_FULLSCREEN) !=
1789         DISP_CHANGE_SUCCESSFUL)
1790     {
1791 	MessageBox(NULL,
1792                    "Unable to switch to full screen mode; running in window mode",
1793                    "Error",
1794                    MB_OK | MB_ICONERROR);
1795         return false;
1796     }
1797 
1798     return true;
1799 }
1800 
1801 
DisableFullScreen()1802 void DisableFullScreen()
1803 {
1804     ChangeDisplaySettings(0, 0);
1805 }
1806 
1807 
1808 unsigned int
ChooseBestMSAAPixelFormat(HDC hdc,int * formats,unsigned int numFormats,int samplesRequested)1809 ChooseBestMSAAPixelFormat(HDC hdc, int *formats, unsigned int numFormats,
1810                           int samplesRequested)
1811 {
1812     int idealFormat = 0;
1813     int	bestFormat  = 0;
1814     int	bestSamples = 0;
1815 
1816     for (unsigned int i = 0; i < numFormats; i++)
1817     {
1818         int query = WGL_SAMPLES_ARB;
1819     	int result = 0;
1820         bool isFloatFormat = false;
1821 
1822         query = WGL_SAMPLES_ARB;
1823         wglGetPixelFormatAttribivARB(hdc, formats[i], 0, 1, &query, &result);
1824 
1825         if (result <= samplesRequested && result >= bestSamples)
1826         {
1827             bestSamples = result;
1828             bestFormat = formats[i];
1829         }
1830 
1831         if (result == samplesRequested)
1832             idealFormat = formats[i];
1833     }
1834 
1835     if (idealFormat != 0)
1836         return idealFormat;
1837 
1838     return bestFormat;
1839 }
1840 
1841 
1842 // Select the pixel format for a given device context
SetDCPixelFormat(HDC hDC)1843 bool SetDCPixelFormat(HDC hDC)
1844 {
1845     bool msaa = false;
1846     if (appCore->getConfig()->aaSamples > 1 &&
1847         WGLExtensionSupported("WGL_ARB_pixel_format") &&
1848         WGLExtensionSupported("WGL_ARB_multisample"))
1849     {
1850         msaa = true;
1851     }
1852 
1853     if (!msaa)
1854     {
1855         static PIXELFORMATDESCRIPTOR pfd = {
1856             sizeof(PIXELFORMATDESCRIPTOR),	// Size of this structure
1857             1,				// Version of this structure
1858             PFD_DRAW_TO_WINDOW |	// Draw to Window (not to bitmap)
1859             PFD_SUPPORT_OPENGL |	// Support OpenGL calls in window
1860             PFD_DOUBLEBUFFER,		// Double buffered mode
1861             PFD_TYPE_RGBA,		// RGBA Color mode
1862             GetDeviceCaps(hDC, BITSPIXEL),// Want the display bit depth
1863             0,0,0,0,0,0,		  // Not used to select mode
1864             0,0,			// Not used to select mode
1865             0,0,0,0,0,			// Not used to select mode
1866             24,				// Size of depth buffer
1867             0,				// Not used to select mode
1868             0,				// Not used to select mode
1869             PFD_MAIN_PLANE,             // Draw in main plane
1870             0,                          // Not used to select mode
1871             0,0,0                       // Not used to select mode
1872         };
1873 
1874         // Choose a pixel format that best matches that described in pfd
1875         int nPixelFormat = ChoosePixelFormat(hDC, &pfd);
1876         if (nPixelFormat == 0)
1877         {
1878             // Uh oh . . . looks like we can't handle OpenGL on this device.
1879             return false;
1880         }
1881         else
1882         {
1883             // Set the pixel format for the device context
1884             SetPixelFormat(hDC, nPixelFormat, &pfd);
1885             return true;
1886         }
1887     }
1888     else
1889     {
1890         PIXELFORMATDESCRIPTOR pfd;
1891 
1892         int ifmtList[] = {
1893             WGL_DRAW_TO_WINDOW_ARB,        TRUE,
1894             WGL_SUPPORT_OPENGL_ARB,        TRUE,
1895             WGL_DOUBLE_BUFFER_ARB,         TRUE,
1896             WGL_PIXEL_TYPE_ARB,            WGL_TYPE_RGBA_ARB,
1897             WGL_DEPTH_BITS_ARB,            24,
1898             WGL_COLOR_BITS_ARB,            24,
1899             WGL_RED_BITS_ARB,               8,
1900             WGL_GREEN_BITS_ARB,             8,
1901             WGL_BLUE_BITS_ARB,              8,
1902             WGL_ALPHA_BITS_ARB,             0,
1903             WGL_ACCUM_BITS_ARB,             0,
1904             WGL_STENCIL_BITS_ARB,           0,
1905             WGL_SAMPLE_BUFFERS_ARB,         appCore->getConfig()->aaSamples > 1,
1906             0
1907         };
1908 
1909         int             pixelFormatIndex;
1910         int             pixFormats[256];
1911         unsigned int    numFormats;
1912 
1913         wglChoosePixelFormatARB(hDC, ifmtList, NULL, 256, pixFormats, &numFormats);
1914 
1915         pixelFormatIndex = ChooseBestMSAAPixelFormat(hDC, pixFormats,
1916                                                      numFormats,
1917                                                      appCore->getConfig()->aaSamples);
1918 
1919         DescribePixelFormat(hDC, pixelFormatIndex,
1920                             sizeof(PIXELFORMATDESCRIPTOR), &pfd);
1921         if (!SetPixelFormat(hDC, pixelFormatIndex, &pfd))
1922             return false;
1923 
1924         return true;
1925     }
1926 }
1927 
1928 
CreateOpenGLWindow(int x,int y,int width,int height,int mode,int & newMode)1929 HWND CreateOpenGLWindow(int x, int y, int width, int height,
1930                         int mode, int& newMode)
1931 {
1932     assert(mode >= 0 && mode <= displayModes->size());
1933     if (mode != 0)
1934     {
1935         x = 0;
1936         y = 0;
1937         width = displayModes->at(mode - 1).dmPelsWidth;
1938         height = displayModes->at(mode - 1).dmPelsHeight;
1939     }
1940 
1941     // Set up and register the window class
1942     WNDCLASS wc;
1943     wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
1944     wc.lpfnWndProc = (WNDPROC) MainWindowProc;
1945     wc.cbClsExtra = 0;
1946     wc.cbWndExtra = 0;
1947     wc.hInstance = appInstance;
1948     wc.hIcon = LoadIcon(hRes, MAKEINTRESOURCE(IDI_CELESTIA_ICON));
1949     wc.hCursor = hDefaultCursor;
1950     wc.hbrBackground = NULL;
1951     wc.lpszMenuName = NULL;
1952     wc.lpszClassName = AppName;
1953     if (RegisterClass(&wc) == 0)
1954     {
1955         MessageBox(NULL,
1956                    "Failed to register the window class.", "Fatal Error",
1957                    MB_OK | MB_ICONERROR);
1958     return NULL;
1959     }
1960 
1961     newMode = currentScreenMode;
1962     if (mode != 0)
1963     {
1964         if (EnableFullScreen(displayModes->at(mode - 1)))
1965             newMode = mode;
1966     }
1967     else
1968     {
1969         DisableFullScreen();
1970         newMode = 0;
1971     }
1972 
1973     // Determine the proper window style to use
1974     DWORD dwStyle;
1975     if (newMode != 0)
1976     {
1977         dwStyle = WS_POPUPWINDOW | WS_MAXIMIZE;
1978     }
1979     else
1980     {
1981         dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
1982     }
1983 
1984     // Create the window
1985     HWND hwnd = CreateWindow(AppName,
1986                              AppName,
1987                              dwStyle,
1988                              x, y,
1989                              width, height,
1990                              NULL,
1991                              NULL,
1992                              appInstance,
1993                              NULL);
1994 
1995     if (hwnd == NULL)
1996         return NULL;
1997 
1998     ShowWindow(hwnd, SW_SHOW);
1999     SetForegroundWindow(hwnd);
2000     SetFocus(hwnd);
2001 
2002     deviceContext = GetDC(hwnd);
2003     if (!SetDCPixelFormat(deviceContext))
2004     {
2005         MessageBox(NULL,
2006                    "Could not get appropriate pixel format for OpenGL rendering.", "Fatal Error",
2007                    MB_OK | MB_ICONERROR);
2008         return NULL;
2009     }
2010 
2011     if (newMode == 0)
2012         SetMenu(hwnd, menuBar);
2013     else
2014         hideMenuBar = true;
2015 
2016     if (glContext == NULL)
2017 	{
2018         glContext = wglCreateContext(deviceContext);
2019 	}
2020     wglMakeCurrent(deviceContext, glContext);
2021 
2022     return hwnd;
2023 }
2024 
DestroyOpenGLWindow()2025 void DestroyOpenGLWindow()
2026 {
2027 #if 0
2028     if (glContext != NULL)
2029     {
2030         wglMakeCurrent(NULL, NULL);
2031         if (!wglDeleteContext(glContext))
2032         {
2033             MessageBox(NULL,
2034                        "Releasing GL context failed.", "Error",
2035                        MB_OK | MB_ICONERROR);
2036         }
2037         glContext = NULL;
2038     }
2039 #endif
2040 
2041     if (deviceContext != NULL)
2042     {
2043         if (!ReleaseDC(mainWindow, deviceContext))
2044         {
2045             MessageBox(NULL,
2046                        "Releasing device context failed.", "Error",
2047                        MB_OK | MB_ICONERROR);
2048         }
2049         deviceContext = NULL;
2050     }
2051 
2052     if (mainWindow != NULL)
2053     {
2054         SetMenu(mainWindow, NULL);
2055         DestroyWindow(mainWindow);
2056         mainWindow = NULL;
2057     }
2058 
2059     UnregisterClass(AppName, appInstance);
2060 }
2061 
2062 
handleKey(WPARAM key,bool down)2063 void handleKey(WPARAM key, bool down)
2064 {
2065     int k = -1;
2066     int modifiers = 0;
2067 
2068     if (GetKeyState(VK_SHIFT) & 0x8000)
2069         modifiers |= CelestiaCore::ShiftKey;
2070     if (GetKeyState(VK_CONTROL) & 0x8000)
2071         modifiers |= CelestiaCore::ControlKey;
2072 
2073     switch (key)
2074     {
2075     case VK_UP:
2076         k = CelestiaCore::Key_Up;
2077         break;
2078     case VK_DOWN:
2079         k = CelestiaCore::Key_Down;
2080         break;
2081     case VK_LEFT:
2082         k = CelestiaCore::Key_Left;
2083         break;
2084     case VK_RIGHT:
2085         k = CelestiaCore::Key_Right;
2086         break;
2087     case VK_HOME:
2088         k = CelestiaCore::Key_Home;
2089         break;
2090     case VK_END:
2091         k = CelestiaCore::Key_End;
2092         break;
2093     case VK_PRIOR:
2094         k = CelestiaCore::Key_PageUp;
2095         break;
2096     case VK_NEXT:
2097         k = CelestiaCore::Key_PageDown;
2098         break;
2099     case VK_F1:
2100         k = CelestiaCore::Key_F1;
2101         break;
2102     case VK_F2:
2103         k = CelestiaCore::Key_F2;
2104         break;
2105     case VK_F3:
2106         k = CelestiaCore::Key_F3;
2107         break;
2108     case VK_F4:
2109         k = CelestiaCore::Key_F4;
2110         break;
2111     case VK_F5:
2112         k = CelestiaCore::Key_F5;
2113         break;
2114     case VK_F6:
2115         k = CelestiaCore::Key_F6;
2116         break;
2117     case VK_F7:
2118         k = CelestiaCore::Key_F7;
2119         break;
2120     case VK_F8:
2121         if (joystickAvailable && down)
2122         {
2123             appCore->joystickAxis(CelestiaCore::Joy_XAxis, 0);
2124             appCore->joystickAxis(CelestiaCore::Joy_YAxis, 0);
2125             appCore->joystickAxis(CelestiaCore::Joy_ZAxis, 0);
2126             useJoystick = !useJoystick;
2127         }
2128         break;
2129     case VK_F11:
2130         k = CelestiaCore::Key_F11;
2131         break;
2132     case VK_F12:
2133         k = CelestiaCore::Key_F12;
2134         break;
2135 
2136     case VK_NUMPAD2:
2137         k = CelestiaCore::Key_NumPad2;
2138         break;
2139     case VK_NUMPAD4:
2140         k = CelestiaCore::Key_NumPad4;
2141         break;
2142     case VK_NUMPAD5:
2143         k = CelestiaCore::Key_NumPad5;
2144         break;
2145     case VK_NUMPAD6:
2146         k = CelestiaCore::Key_NumPad6;
2147         break;
2148     case VK_NUMPAD7:
2149         k = CelestiaCore::Key_NumPad7;
2150         break;
2151     case VK_NUMPAD8:
2152         k = CelestiaCore::Key_NumPad8;
2153         break;
2154     case VK_NUMPAD9:
2155         k = CelestiaCore::Key_NumPad9;
2156         break;
2157     case VK_DELETE:
2158         if (!down)
2159             appCore->charEntered('\177');
2160         break;
2161 
2162     case '0':
2163     case '1':
2164     case '2':
2165     case '3':
2166     case '4':
2167     case '5':
2168     case '6':
2169     case '7':
2170     case '8':
2171     case '9':
2172         // Special handling required to send Ctrl+number keys to
2173         // Celestia keyboard handler.
2174         if (!down && (modifiers & CelestiaCore::ControlKey))
2175             appCore->charEntered((char) key, modifiers);
2176         break;
2177 
2178     case 'A':
2179     case 'Z':
2180         if ((GetKeyState(VK_CONTROL) & 0x8000) == 0)
2181             k = key;
2182         break;
2183     }
2184 
2185     if (k >= 0)
2186     {
2187         if (down)
2188             appCore->keyDown(k, modifiers);
2189         else
2190             appCore->keyUp(k, modifiers);
2191     }
2192 }
2193 
2194 
BuildScriptsMenu(HMENU menuBar,const string & scriptsDir)2195 static void BuildScriptsMenu(HMENU menuBar, const string& scriptsDir)
2196 {
2197     HMENU fileMenu = GetSubMenu(menuBar, 0);
2198 
2199     if (ScriptMenuItems != NULL)
2200         delete ScriptMenuItems;
2201 
2202     ScriptMenuItems = ScanScriptsDirectory(scriptsDir, false);
2203     if (ScriptMenuItems == NULL || ScriptMenuItems->size() == 0)
2204     {
2205         EnableMenuItem(fileMenu, ID_FILE_SCRIPTS, MF_GRAYED);
2206         return;
2207     }
2208 
2209     MENUITEMINFO info;
2210     memset(&info, sizeof(info), 0);
2211     info.cbSize = sizeof(info);
2212     info.fMask = MIIM_SUBMENU;
2213 
2214     BOOL result = GetMenuItemInfo(fileMenu, 1, TRUE, &info);
2215 
2216     if (result)
2217     {
2218         HMENU scriptMenu = info.hSubMenu;
2219 
2220         // Remove the old menu items
2221         int count = GetMenuItemCount(scriptMenu);
2222         while (count-- > 0)
2223             DeleteMenu(scriptMenu, 0, MF_BYPOSITION);
2224 
2225         for (unsigned int i = 0; i < ScriptMenuItems->size(); i++)
2226         {
2227             AppendMenu(scriptMenu, MF_STRING, ID_FIRST_SCRIPT + i, UTF8ToCurrentCP((*ScriptMenuItems)[i].title).c_str());
2228         }
2229     }
2230 }
2231 
2232 
syncMenusWithRendererState()2233 static void syncMenusWithRendererState()
2234 {
2235     int renderFlags = appCore->getRenderer()->getRenderFlags();
2236     int labelMode = appCore->getRenderer()->getLabelMode();
2237     float ambientLight = appCore->getRenderer()->getAmbientLightLevel();
2238     unsigned int textureRes = appCore->getRenderer()->getResolution();
2239 
2240     setMenuItemCheck(ID_VIEW_SHOW_FRAMES,
2241                      appCore->getFramesVisible());
2242     setMenuItemCheck(ID_VIEW_SYNC_TIME,
2243                      appCore->getSimulation()->getSyncTime());
2244 
2245     if(abs(0.0 - (double)ambientLight) < 1.0e-3)
2246     {
2247         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_NONE,   MF_CHECKED);
2248         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_LOW,    MF_UNCHECKED);
2249         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_MEDIUM, MF_UNCHECKED);
2250     }
2251     else if(abs(0.1 - (double)ambientLight) < 1.0e-3)
2252     {
2253         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_NONE,   MF_UNCHECKED);
2254         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_LOW,    MF_CHECKED);
2255         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_MEDIUM, MF_UNCHECKED);
2256     }
2257     else if(abs(0.25 - (double)ambientLight) < 1.0e-3)
2258     {
2259         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_NONE,   MF_UNCHECKED);
2260         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_LOW,    MF_UNCHECKED);
2261         CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_MEDIUM, MF_CHECKED);
2262     }
2263 
2264     Renderer::StarStyle style = appCore->getRenderer()->getStarStyle();
2265     CheckMenuItem(menuBar, ID_RENDER_STARSTYLE_FUZZY,
2266                   style == Renderer::FuzzyPointStars ? MF_CHECKED : MF_UNCHECKED);
2267     CheckMenuItem(menuBar, ID_RENDER_STARSTYLE_POINTS,
2268                   style == Renderer::PointStars ? MF_CHECKED : MF_UNCHECKED);
2269     CheckMenuItem(menuBar, ID_RENDER_STARSTYLE_DISCS,
2270                   style == Renderer::ScaledDiscStars ? MF_CHECKED : MF_UNCHECKED);
2271 
2272     CheckMenuItem(menuBar, ID_RENDER_TEXTURERES_LOW,
2273                   textureRes == 0 ? MF_CHECKED : MF_UNCHECKED);
2274     CheckMenuItem(menuBar, ID_RENDER_TEXTURERES_MEDIUM,
2275                   textureRes == 1 ? MF_CHECKED : MF_UNCHECKED);
2276     CheckMenuItem(menuBar, ID_RENDER_TEXTURERES_HIGH,
2277                   textureRes == 2 ? MF_CHECKED : MF_UNCHECKED);
2278 
2279     MENUITEMINFO menuInfo;
2280     menuInfo.cbSize = sizeof(MENUITEMINFO);
2281     menuInfo.fMask = MIIM_STATE;
2282     if (GetMenuItemInfo(menuBar, ID_TIME_SHOWLOCAL, FALSE, &menuInfo))
2283     {
2284         if (appCore->getTimeZoneBias() == 0)
2285             CheckMenuItem(menuBar, ID_TIME_SHOWLOCAL, MF_UNCHECKED);
2286         else
2287             CheckMenuItem(menuBar, ID_TIME_SHOWLOCAL, MF_CHECKED);
2288     }
2289 
2290     CheckMenuItem(menuBar, ID_RENDER_ANTIALIASING,
2291         (renderFlags & Renderer::ShowSmoothLines)?MF_CHECKED:MF_UNCHECKED);
2292     CheckMenuItem(menuBar, ID_RENDER_AUTOMAG,
2293         (renderFlags & Renderer::ShowAutoMag)?MF_CHECKED:MF_UNCHECKED);
2294 }
2295 
2296 
2297 class WinAlerter : public CelestiaCore::Alerter
2298 {
2299 private:
2300     HWND hwnd;
2301 
2302 public:
WinAlerter()2303     WinAlerter() : hwnd(NULL) {};
~WinAlerter()2304     ~WinAlerter() {};
2305 
fatalError(const std::string & msg)2306     void fatalError(const std::string& msg)
2307     {
2308 		if (s_splash != NULL)
2309 			s_splash->close();
2310 
2311         MessageBox(NULL,
2312                    msg.c_str(),
2313                    "Fatal Error",
2314                    MB_OK | MB_ICONERROR | MB_SETFOREGROUND);
2315     }
2316 };
2317 
2318 
InitJoystick(JOYCAPS & caps)2319 static bool InitJoystick(JOYCAPS& caps)
2320 {
2321     int nJoysticks = joyGetNumDevs();
2322     if (nJoysticks <= 0)
2323         return false;
2324 
2325     if (joyGetDevCaps(JOYSTICKID1, &caps, sizeof caps) != JOYERR_NOERROR)
2326     {
2327         cout << "Error getting joystick caps.\n";
2328         return false;
2329     }
2330 
2331     cout << "Using joystick: " << caps.szPname << '\n';
2332 
2333     return true;
2334 }
2335 
2336 
HandleJoystick()2337 static void HandleJoystick()
2338 {
2339     JOYINFOEX info;
2340 
2341     info.dwSize = sizeof info;
2342     info.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNBUTTONS;
2343     MMRESULT err = joyGetPosEx(JOYSTICKID1, &info);
2344 
2345     if (err == JOYERR_NOERROR)
2346     {
2347         float x = (float) info.dwXpos / 32768.0f - 1.0f;
2348         float y = (float) info.dwYpos / 32768.0f - 1.0f;
2349 
2350         appCore->joystickAxis(CelestiaCore::Joy_XAxis, x);
2351         appCore->joystickAxis(CelestiaCore::Joy_YAxis, y);
2352         appCore->joystickButton(CelestiaCore::JoyButton1,
2353                                 (info.dwButtons & 0x1) != 0);
2354         appCore->joystickButton(CelestiaCore::JoyButton2,
2355                                 (info.dwButtons & 0x2) != 0);
2356         appCore->joystickButton(CelestiaCore::JoyButton7,
2357                                 (info.dwButtons & 0x40) != 0);
2358         appCore->joystickButton(CelestiaCore::JoyButton8,
2359                                 (info.dwButtons & 0x80) != 0);
2360     }
2361 }
2362 
GetRegistryValue(HKEY hKey,LPSTR cpValueName,LPVOID lpBuf,DWORD iBufSize)2363 static bool GetRegistryValue(HKEY hKey, LPSTR cpValueName, LPVOID lpBuf, DWORD iBufSize)
2364 {
2365 /*
2366     Function retrieves a value from the registry.
2367     Key specified by open handle.
2368 
2369     hKey        - Handle of open key for which a key value is requested.
2370     cpValueName    - Name of Key Value to obtain value for.
2371     lpBuf      - Buffer to receive value of Key Value.
2372     iBufSize   - Size of buffer pointed to by lpBuf.
2373 
2374     RETURN     - true if value was successfully retrieved, false otherwise.
2375 
2376     Remarks: If requesting a string value, pass the character buffer to lpBuf.
2377              If requesting a DWORD value, pass the DWORD variable's address to lpBuf.
2378              If requesting binary data, pass the address of the binary buffer.
2379 
2380              This function assumes you have an open registry key. Be sure to call
2381              CloseKey() when finished.
2382 */
2383     DWORD dwValueType, dwDataSize=0;
2384     bool bRC=false;
2385 
2386     dwDataSize = iBufSize;
2387     if(RegQueryValueEx(hKey, cpValueName, NULL, &dwValueType,
2388         (LPBYTE)lpBuf, &dwDataSize) == ERROR_SUCCESS)
2389         bRC = true;
2390 
2391     return bRC;
2392 }
2393 
SetRegistryInt(HKEY key,LPCTSTR value,int intVal)2394 static bool SetRegistryInt(HKEY key, LPCTSTR value, int intVal)
2395 {
2396     LONG err = RegSetValueEx(key,
2397                              value,
2398                              0,
2399                              REG_DWORD,
2400                              reinterpret_cast<CONST BYTE*>(&intVal),
2401                              sizeof(DWORD));
2402     return err == ERROR_SUCCESS;
2403 }
2404 
SetRegistry(HKEY key,LPCTSTR value,const string & strVal)2405 static bool SetRegistry(HKEY key, LPCTSTR value, const string& strVal)
2406 {
2407     LONG err = RegSetValueEx(key,
2408                              value,
2409                              0,
2410                              REG_SZ,
2411                              reinterpret_cast<CONST BYTE*> (strVal.c_str()),
2412                              strVal.length() + 1);
2413 
2414     return err == ERROR_SUCCESS;
2415 }
2416 
SetRegistryBin(HKEY hKey,LPSTR cpValueName,LPVOID lpData,int iDataSize)2417 static bool SetRegistryBin(HKEY hKey, LPSTR cpValueName, LPVOID lpData, int iDataSize)
2418 {
2419 /*
2420     Function sets BINARY data in the registry.
2421     Key specified by open handle.
2422 
2423     hKey        - Handle of Key for which a value is to be set.
2424 
2425     cpValueName - Name of Key Value to obtain value for.
2426 
2427     lpData      - Address of binary data to store.
2428 
2429     iDataSize   - Size of binary data contained in lpData.
2430 
2431     RETURN      - Boolean true if value is successfully stored, false otherwise.
2432 
2433     Remarks:        This function assumes you have an open registry key. Be sure to call
2434                     CloseKey() when finished.
2435 */
2436 
2437     bool bRC = false;
2438 
2439     if (RegSetValueEx(hKey, cpValueName, 0, REG_BINARY,
2440                       (LPBYTE) lpData, (DWORD) iDataSize) == ERROR_SUCCESS)
2441     {
2442         bRC = true;
2443     }
2444 
2445     return bRC;
2446 }
2447 
2448 
LoadPreferencesFromRegistry(LPTSTR regkey,AppPreferences & prefs)2449 static bool LoadPreferencesFromRegistry(LPTSTR regkey, AppPreferences& prefs)
2450 {
2451     LONG err;
2452     HKEY key;
2453 
2454     DWORD disposition;
2455     err = RegCreateKeyEx(HKEY_CURRENT_USER,
2456                          regkey,
2457                          0,
2458                          NULL,
2459                          REG_OPTION_NON_VOLATILE,
2460                          KEY_ALL_ACCESS,
2461                          NULL,
2462                          &key,
2463                          &disposition);
2464     if (err  != ERROR_SUCCESS)
2465     {
2466         cout << "Error opening registry key: " << err << '\n';
2467         return false;
2468     }
2469 
2470     GetRegistryValue(key, "Width", &prefs.winWidth, sizeof(prefs.winWidth));
2471     GetRegistryValue(key, "Height", &prefs.winHeight, sizeof(prefs.winHeight));
2472     GetRegistryValue(key, "XPos", &prefs.winX, sizeof(prefs.winX));
2473     GetRegistryValue(key, "YPos", &prefs.winY, sizeof(prefs.winY));
2474     GetRegistryValue(key, "RenderFlags", &prefs.renderFlags, sizeof(prefs.renderFlags));
2475     GetRegistryValue(key, "LabelMode", &prefs.labelMode, sizeof(prefs.labelMode));
2476     GetRegistryValue(key, "LocationFilter", &prefs.locationFilter, sizeof(prefs.locationFilter));
2477     GetRegistryValue(key, "OrbitMask", &prefs.orbitMask, sizeof(prefs.orbitMask));
2478     GetRegistryValue(key, "VisualMagnitude", &prefs.visualMagnitude, sizeof(prefs.visualMagnitude));
2479     GetRegistryValue(key, "AmbientLight", &prefs.ambientLight, sizeof(prefs.ambientLight));
2480     GetRegistryValue(key, "GalaxyLightGain", &prefs.galaxyLightGain, sizeof(prefs.galaxyLightGain));
2481     GetRegistryValue(key, "ShowLocalTime", &prefs.showLocalTime, sizeof(prefs.showLocalTime));
2482     GetRegistryValue(key, "DateFormat", &prefs.dateFormat, sizeof(prefs.dateFormat));
2483     GetRegistryValue(key, "HudDetail", &prefs.hudDetail, sizeof(prefs.hudDetail));
2484     GetRegistryValue(key, "FullScreenMode", &prefs.fullScreenMode, sizeof(prefs.fullScreenMode));
2485 
2486     prefs.starStyle = Renderer::FuzzyPointStars;
2487     GetRegistryValue(key, "StarStyle", &prefs.starStyle, sizeof(prefs.starStyle));
2488 	prefs.renderPath = GLContext::GLPath_Basic;
2489 	prefs.renderPathSet = GetRegistryValue(key, "RenderPath", &prefs.renderPath, sizeof(prefs.renderPath));
2490 
2491     GetRegistryValue(key, "LastVersion", &prefs.lastVersion, sizeof(prefs.lastVersion));
2492     GetRegistryValue(key, "TextureResolution", &prefs.textureResolution, sizeof(prefs.textureResolution));
2493 
2494     char surfaceName[512];
2495     surfaceName[0] = '\0';
2496     if (GetRegistryValue(key, "AltSurface", surfaceName, sizeof(surfaceName)))
2497         prefs.altSurfaceName = string(surfaceName);
2498 
2499     if (prefs.lastVersion < 0x01020500)
2500     {
2501         prefs.renderFlags |= Renderer::ShowCometTails;
2502         prefs.renderFlags |= Renderer::ShowRingShadows;
2503     }
2504 
2505     RegCloseKey(key);
2506 
2507     return true;
2508 }
2509 
2510 
SavePreferencesToRegistry(LPTSTR regkey,AppPreferences & prefs)2511 static bool SavePreferencesToRegistry(LPTSTR regkey, AppPreferences& prefs)
2512 {
2513     LONG err;
2514     HKEY key;
2515 
2516     cout << "Saving preferences . . .\n";
2517     err = RegOpenKeyEx(HKEY_CURRENT_USER,
2518                        regkey,
2519                        0,
2520                        KEY_ALL_ACCESS,
2521                        &key);
2522     if (err  != ERROR_SUCCESS)
2523     {
2524         cout << "Error opening registry key: " << err << '\n';
2525         return false;
2526     }
2527     cout << "Opened registry key\n";
2528 
2529     SetRegistryInt(key, "Width", prefs.winWidth);
2530     SetRegistryInt(key, "Height", prefs.winHeight);
2531     SetRegistryInt(key, "XPos", prefs.winX);
2532     SetRegistryInt(key, "YPos", prefs.winY);
2533     SetRegistryInt(key, "RenderFlags", prefs.renderFlags);
2534     SetRegistryInt(key, "LabelMode", prefs.labelMode);
2535     SetRegistryInt(key, "LocationFilter", prefs.locationFilter);
2536     SetRegistryInt(key, "OrbitMask", prefs.orbitMask);
2537     SetRegistryBin(key, "VisualMagnitude", &prefs.visualMagnitude, sizeof(prefs.visualMagnitude));
2538     SetRegistryBin(key, "AmbientLight", &prefs.ambientLight, sizeof(prefs.ambientLight));
2539     SetRegistryBin(key, "GalaxyLightGain", &prefs.galaxyLightGain, sizeof(prefs.galaxyLightGain));
2540     SetRegistryInt(key, "ShowLocalTime", prefs.showLocalTime);
2541     SetRegistryInt(key, "DateFormat", prefs.dateFormat);
2542     SetRegistryInt(key, "HudDetail", prefs.hudDetail);
2543     SetRegistryInt(key, "FullScreenMode", prefs.fullScreenMode);
2544     SetRegistryInt(key, "LastVersion", prefs.lastVersion);
2545     SetRegistryInt(key, "StarStyle", prefs.starStyle);
2546 	  SetRegistryInt(key, "RenderPath", prefs.renderPath);
2547     SetRegistry(key, "AltSurface", prefs.altSurfaceName);
2548     SetRegistryInt(key, "TextureResolution", prefs.textureResolution);
2549 
2550     RegCloseKey(key);
2551 
2552     return true;
2553 }
2554 
2555 
GetCurrentPreferences(AppPreferences & prefs)2556 static bool GetCurrentPreferences(AppPreferences& prefs)
2557 {
2558     WINDOWPLACEMENT placement;
2559 
2560     placement.length = sizeof(placement);
2561     if (!GetWindowPlacement(mainWindow, &placement))
2562         return false;
2563 
2564     RECT rect = placement.rcNormalPosition;
2565     prefs.winX = rect.left;
2566     prefs.winY = rect.top;
2567     prefs.winWidth = rect.right - rect.left;
2568     prefs.winHeight = rect.bottom - rect.top;
2569     prefs.renderFlags = appCore->getRenderer()->getRenderFlags();
2570     prefs.labelMode = appCore->getRenderer()->getLabelMode();
2571     prefs.locationFilter = appCore->getSimulation()->getActiveObserver()->getLocationFilter();
2572     prefs.orbitMask = appCore->getRenderer()->getOrbitMask();
2573     prefs.visualMagnitude = appCore->getSimulation()->getFaintestVisible();
2574     prefs.ambientLight = appCore->getRenderer()->getAmbientLightLevel();
2575     prefs.galaxyLightGain = Galaxy::getLightGain();
2576     prefs.showLocalTime = (appCore->getTimeZoneBias() != 0);
2577     prefs.dateFormat = appCore->getDateFormat();
2578     prefs.hudDetail = appCore->getHudDetail();
2579     prefs.fullScreenMode = lastFullScreenMode;
2580     prefs.lastVersion = 0x01040100;
2581     prefs.altSurfaceName = appCore->getSimulation()->getActiveObserver()->getDisplayedSurface();
2582     prefs.starStyle = appCore->getRenderer()->getStarStyle();
2583 	prefs.renderPath = appCore->getRenderer()->getGLContext()->getRenderPath();
2584     prefs.textureResolution = appCore->getRenderer()->getResolution();
2585 
2586     return true;
2587 }
2588 
2589 
HandleCaptureImage(HWND hWnd)2590 static void HandleCaptureImage(HWND hWnd)
2591 {
2592     // Display File SaveAs dialog to allow user to specify name and location of
2593     // of captured screen image.
2594     OPENFILENAME Ofn;
2595     char szFile[_MAX_PATH+1], szFileTitle[_MAX_PATH+1];
2596 
2597     szFile[0] = '\0';
2598     szFileTitle[0] = '\0';
2599 
2600     // Initialize OPENFILENAME
2601     ZeroMemory(&Ofn, sizeof(OPENFILENAME));
2602     Ofn.lStructSize = sizeof(OPENFILENAME);
2603     Ofn.hwndOwner = hWnd;
2604     Ofn.lpstrFilter = "JPEG - JFIF Compliant\0*.jpg;*.jif;*.jpeg\0Portable Network Graphics\0*.png\0";
2605     Ofn.lpstrFile= szFile;
2606     Ofn.nMaxFile = sizeof(szFile);
2607     Ofn.lpstrFileTitle = szFileTitle;
2608     Ofn.nMaxFileTitle = sizeof(szFileTitle);
2609     Ofn.lpstrInitialDir = (LPSTR)NULL;
2610 
2611     // Comment this out if you just want the standard "Save As" caption.
2612     //Ofn.lpstrTitle = "Save As - Specify File to Capture Image";
2613 
2614     // OFN_HIDEREADONLY - Do not display read-only JPEG or PNG files
2615     // OFN_OVERWRITEPROMPT - If user selected a file, prompt for overwrite confirmation.
2616     Ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR;
2617 
2618     // Display the Save dialog box.
2619     if (GetSaveFileName(&Ofn))
2620     {
2621         // If you got here, a path and file has been specified.
2622         // Ofn.lpstrFile contains full path to specified file
2623         // Ofn.lpstrFileTitle contains just the filename with extension
2624 
2625         // Get the dimensions of the current viewport
2626         int viewport[4];
2627         glGetIntegerv(GL_VIEWPORT, viewport);
2628 
2629         bool success = false;
2630 
2631         DWORD nFileType=0;
2632         char defaultExtensions[][4] = {"jpg", "png"};
2633         if (Ofn.nFileExtension == 0)
2634         {
2635             // If no extension was specified, use the selection of filter to
2636             // determine which type of file should be created, instead of
2637             // just defaulting to JPEG.
2638             nFileType = Ofn.nFilterIndex;
2639             strcat(Ofn.lpstrFile, ".");
2640             strcat(Ofn.lpstrFile, defaultExtensions[nFileType-1]);
2641         }
2642         else if (*(Ofn.lpstrFile + Ofn.nFileExtension) == '\0')
2643         {
2644             // If just a period was specified for the extension, use the
2645             // selection of filter to determine which type of file should be
2646             // created instead of just defaulting to JPEG.
2647             nFileType = Ofn.nFilterIndex;
2648             strcat(Ofn.lpstrFile, defaultExtensions[nFileType-1]);
2649         }
2650         else
2651         {
2652             switch (DetermineFileType(Ofn.lpstrFile))
2653             {
2654             case Content_JPEG:
2655                 nFileType = 1;
2656                 break;
2657             case Content_PNG:
2658                 nFileType = 2;
2659                 break;
2660             default:
2661                 nFileType = 0;
2662                 break;
2663             }
2664         }
2665 
2666         // Redraw to make sure that the back buffer is up to date
2667         appCore->draw();
2668 
2669         if (nFileType == 1)
2670         {
2671             success = CaptureGLBufferToJPEG(string(Ofn.lpstrFile),
2672                                             viewport[0], viewport[1],
2673                                             viewport[2], viewport[3]);
2674         }
2675         else if (nFileType == 2)
2676         {
2677             success = CaptureGLBufferToPNG(string(Ofn.lpstrFile),
2678                                            viewport[0], viewport[1],
2679                                            viewport[2], viewport[3]);
2680         }
2681         else
2682         {
2683             // Invalid file extension specified.
2684             DPRINTF(0, "WTF? Unknown file extension specified for screen capture.\n");
2685         }
2686 
2687         if (!success)
2688         {
2689             char errorMsg[64];
2690 
2691             if(nFileType == 0)
2692                 sprintf(errorMsg, "Specified file extension is not recognized.");
2693             else
2694                 sprintf(errorMsg, "Could not save image file.");
2695 
2696             MessageBox(hWnd, errorMsg, "Error", MB_OK | MB_ICONERROR);
2697         }
2698     }
2699 }
2700 
2701 
HandleCaptureMovie(HWND hWnd)2702 static void HandleCaptureMovie(HWND hWnd)
2703 {
2704     // TODO: The menu item should be disable so that the user doesn't even
2705     // have the opportunity to record two movies simultaneously; the only
2706     // thing missing to make this happen is notification when recording
2707     // is complete.
2708     if (appCore->isCaptureActive())
2709     {
2710         MessageBox(hWnd, "Stop current movie capture before starting another one.", "Error", MB_OK | MB_ICONERROR);
2711         return;
2712     }
2713 
2714     // Display File SaveAs dialog to allow user to specify name and location of
2715     // of captured movie
2716     OPENFILENAME Ofn;
2717     char szFile[_MAX_PATH+1], szFileTitle[_MAX_PATH+1];
2718 
2719     szFile[0] = '\0';
2720     szFileTitle[0] = '\0';
2721 
2722     // Initialize OPENFILENAME
2723     ZeroMemory(&Ofn, sizeof(OPENFILENAME));
2724     Ofn.lStructSize = sizeof(OPENFILENAME);
2725     Ofn.hwndOwner = hWnd;
2726     Ofn.lpstrFilter = "Microsoft AVI\0*.avi\0";
2727     Ofn.lpstrFile= szFile;
2728     Ofn.nMaxFile = sizeof(szFile);
2729     Ofn.lpstrFileTitle = szFileTitle;
2730     Ofn.nMaxFileTitle = sizeof(szFileTitle);
2731     Ofn.lpstrInitialDir = (LPSTR)NULL;
2732 
2733     // Comment this out if you just want the standard "Save As" caption.
2734     //Ofn.lpstrTitle = "Save As - Specify Output File for Capture Movie";
2735 
2736     // OFN_HIDEREADONLY - Do not display read-only video files
2737     // OFN_OVERWRITEPROMPT - If user selected a file, prompt for overwrite confirmation.
2738     Ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT  | OFN_EXPLORER | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_NOCHANGEDIR;
2739 
2740     Ofn.hInstance = appInstance;
2741     Ofn.lpTemplateName = MAKEINTRESOURCE(IDD_MOVIE_PARAMS_CHOOSER);
2742     Ofn.lpfnHook = ChooseMovieParamsProc;
2743 
2744     // Display the Save dialog box.
2745     if (GetSaveFileName(&Ofn))
2746     {
2747         // If you got here, a path and file has been specified.
2748         // Ofn.lpstrFile contains full path to specified file
2749         // Ofn.lpstrFileTitle contains just the filename with extension
2750 
2751         bool success = false;
2752 
2753         DWORD nFileType=0;
2754         char defaultExtensions[][4] = { "avi" };
2755         if (Ofn.nFileExtension == 0)
2756         {
2757             // If no extension was specified, use the selection of filter to
2758             // determine which type of file should be created, instead of
2759             // just defaulting to AVI.
2760             nFileType = Ofn.nFilterIndex;
2761             strcat(Ofn.lpstrFile, ".");
2762             strcat(Ofn.lpstrFile, defaultExtensions[nFileType-1]);
2763         }
2764         else if (*(Ofn.lpstrFile + Ofn.nFileExtension) == '\0')
2765         {
2766             // If just a period was specified for the extension, use
2767             // the selection of filter to determine which type of file
2768             // should be created, instead of just defaulting to AVI.
2769             nFileType = Ofn.nFilterIndex;
2770             strcat(Ofn.lpstrFile, defaultExtensions[nFileType-1]);
2771         }
2772         else
2773         {
2774             switch (DetermineFileType(Ofn.lpstrFile))
2775             {
2776             case Content_AVI:
2777                 nFileType = 1;
2778                 break;
2779             default:
2780                 nFileType = 0;
2781                 break;
2782             }
2783         }
2784 
2785         if (nFileType != 1)
2786         {
2787             // Invalid file extension specified.
2788             DPRINTF(0, "Unknown file extension specified for movie capture.\n");
2789         }
2790         else
2791         {
2792             success = BeginMovieCapture(string(Ofn.lpstrFile),
2793                                         MovieSizes[movieSize][0],
2794                                         MovieSizes[movieSize][1],
2795                                         MovieFramerates[movieFramerate]);
2796         }
2797 
2798         if (!success)
2799         {
2800             char errorMsg[64];
2801 
2802             if (nFileType == 0)
2803                 sprintf(errorMsg, "Specified file extension is not recognized.");
2804             else
2805                 sprintf(errorMsg, "Could not capture movie.");
2806 
2807             MessageBox(hWnd, errorMsg, "Error", MB_OK | MB_ICONERROR);
2808         }
2809     }
2810 }
2811 
2812 
HandleOpenScript(HWND hWnd,CelestiaCore * appCore)2813 static void HandleOpenScript(HWND hWnd, CelestiaCore* appCore)
2814 {
2815     // Display File Open dialog to allow user to specify name and location of
2816     // of captured screen image.
2817     OPENFILENAME Ofn;
2818     char szFile[_MAX_PATH + 1];
2819     char szFileTitle[_MAX_PATH + 1];
2820 
2821     // Save the current directory
2822     char currentDir[_MAX_PATH + 1];
2823     currentDir[0] = '\0';
2824     GetCurrentDirectory(sizeof(currentDir), currentDir);
2825 
2826     szFile[0] = '\0';
2827     szFileTitle[0] = '\0';
2828 
2829     // Initialize OPENFILENAME
2830     ZeroMemory(&Ofn, sizeof(OPENFILENAME));
2831     Ofn.lStructSize = sizeof(OPENFILENAME);
2832     Ofn.hwndOwner = hWnd;
2833     Ofn.lpstrFilter = "Celestia Script\0*.celx;*.clx;*.cel\0";
2834     Ofn.lpstrFile= szFile;
2835     Ofn.nMaxFile = sizeof(szFile);
2836     Ofn.lpstrFileTitle = szFileTitle;
2837     Ofn.nMaxFileTitle = sizeof(szFileTitle);
2838     Ofn.lpstrInitialDir = (LPSTR)NULL;
2839 
2840     // Comment this out if you just want the standard "Save As" caption.
2841     // Ofn.lpstrTitle = "Save As - Specify File to Capture Image";
2842 
2843     // Display the Open dialog box.
2844     if (GetOpenFileName(&Ofn))
2845     {
2846         // If you got here, a path and file has been specified.
2847         // Ofn.lpstrFile contains full path to specified file
2848         // Ofn.lpstrFileTitle contains just the filename with extension
2849         ContentType type = DetermineFileType(Ofn.lpstrFile);
2850 
2851         if (type == Content_CelestiaScript)
2852         {
2853             appCore->runScript(Ofn.lpstrFile);
2854         }
2855         else if (type == Content_CelestiaLegacyScript)
2856         {
2857             ifstream scriptfile(Ofn.lpstrFile);
2858             if (!scriptfile.good())
2859             {
2860                 MessageBox(hWnd, "Error opening script file.", "Error",
2861                            MB_OK | MB_ICONERROR);
2862             }
2863             else
2864             {
2865                 CommandParser parser(scriptfile);
2866                 CommandSequence* script = parser.parse();
2867                 if (script == NULL)
2868                 {
2869                     const vector<string>* errors = parser.getErrors();
2870                     const char* errorMsg = "";
2871                     if (errors->size() > 0)
2872                         errorMsg = (*errors)[0].c_str();
2873                     MessageBox(hWnd, errorMsg, "Error in script file.",
2874                                MB_OK | MB_ICONERROR);
2875                 }
2876                 else
2877                 {
2878                     appCore->cancelScript(); // cancel any running script
2879                     appCore->runScript(script);
2880                 }
2881             }
2882         }
2883     }
2884 
2885     if (strlen(currentDir) != 0)
2886         SetCurrentDirectory(currentDir);
2887 }
2888 
2889 
operator <(const DEVMODE & a,const DEVMODE & b)2890 bool operator<(const DEVMODE& a, const DEVMODE& b)
2891 {
2892     if (a.dmBitsPerPel != b.dmBitsPerPel)
2893         return a.dmBitsPerPel < b.dmBitsPerPel;
2894     if (a.dmPelsWidth != b.dmPelsWidth)
2895         return a.dmPelsWidth < b.dmPelsWidth;
2896     if (a.dmPelsHeight != b.dmPelsHeight)
2897         return a.dmPelsHeight < b.dmPelsHeight;
2898     return a.dmDisplayFrequency < b.dmDisplayFrequency;
2899 }
2900 
EnumerateDisplayModes(unsigned int minBPP)2901 vector<DEVMODE>* EnumerateDisplayModes(unsigned int minBPP)
2902 {
2903     vector<DEVMODE>* modes = new vector<DEVMODE>();
2904     if (modes == NULL)
2905         return NULL;
2906 
2907     DEVMODE dm;
2908     int i = 0;
2909     while (EnumDisplaySettings(NULL, i, &dm))
2910     {
2911         if (dm.dmBitsPerPel >= minBPP)
2912             modes->insert(modes->end(), dm);
2913         i++;
2914     }
2915     sort(modes->begin(), modes->end());
2916 
2917     // Bail out early if EnumDisplaySettings fails for some messed up reason
2918     if (i == 0)
2919         return modes;
2920 
2921     // Go through the sorted list and eliminate modes that differ only
2922     // by refresh rates.
2923     vector<DEVMODE>::iterator keepIter = modes->begin();
2924     vector<DEVMODE>::const_iterator iter = modes->begin();
2925     iter++;
2926     for (; iter != modes->end(); iter++)
2927     {
2928         if (iter->dmPelsWidth != keepIter->dmPelsWidth ||
2929             iter->dmPelsHeight != keepIter->dmPelsHeight ||
2930             iter->dmBitsPerPel != keepIter->dmBitsPerPel)
2931         {
2932             *++keepIter = *iter;
2933         }
2934     }
2935 
2936     modes->resize(keepIter - modes->begin() + 1);
2937 
2938     // Select the fallback display mode--choose 640x480 at the current
2939     // pixel depth.  If for some bizarre reason that's not available,
2940     // fall back to the first mode in the list.
2941     fallbackFullScreenMode = 0;
2942     for (iter = modes->begin(); iter != modes->end(); iter++)
2943     {
2944         if (iter->dmPelsWidth == 640 && iter->dmPelsHeight == 480)
2945         {
2946             // Add one to the mode index, since mode 0 means windowed mode
2947             fallbackFullScreenMode = (iter - modes->begin()) + 1;
2948             break;
2949         }
2950     }
2951     if (fallbackFullScreenMode == 0 && modes->size() > 0)
2952         fallbackFullScreenMode = 1;
2953     lastFullScreenMode = fallbackFullScreenMode;
2954 
2955     return modes;
2956 }
2957 
2958 
skipSpace(char * s)2959 static char* skipSpace(char* s)
2960 {
2961     while (*s == ' ')
2962         s++;
2963     return s;
2964 }
2965 
skipUntilQuote(char * s)2966 static char* skipUntilQuote(char* s)
2967 {
2968     while (*s != '"' && s != '\0')
2969         s++;
2970     return s;
2971 }
2972 
nextWord(char * s)2973 static char* nextWord(char* s)
2974 {
2975     while (*s != ' ' && *s != '\0')
2976         s++;
2977     return s;
2978 }
2979 
splitCommandLine(LPSTR cmdLine,int & argc)2980 static char** splitCommandLine(LPSTR cmdLine,
2981                                int& argc)
2982 {
2983     int nArgs = 0;
2984     int maxArgs = 50;
2985     char** argv = new char*[maxArgs];
2986 
2987     cmdLine = skipSpace(cmdLine);
2988     while (*cmdLine != '\0')
2989     {
2990         char* startOfWord = cmdLine;
2991         char* endOfWord = cmdLine;
2992         int wordLength = 0;
2993 
2994         if (*cmdLine == '"')
2995         {
2996             // Handle quoted strings
2997             startOfWord = cmdLine + 1;
2998             endOfWord = skipUntilQuote(startOfWord);
2999             wordLength = endOfWord - startOfWord;
3000             if (*endOfWord != '\0')
3001                 endOfWord++;
3002         }
3003         else
3004         {
3005             endOfWord = nextWord(cmdLine);
3006             wordLength = endOfWord - startOfWord;
3007             assert(wordLength != 0);
3008         }
3009 
3010         char* newWord = new char[wordLength + 1];
3011         strncpy(newWord, startOfWord, wordLength);
3012         newWord[wordLength] = '\0';
3013 
3014         if (nArgs == maxArgs)
3015         {
3016             char** newArgv = new char*[maxArgs * 2];
3017             copy(argv, argv + nArgs, newArgv);
3018             delete argv;
3019             argv = newArgv;
3020             maxArgs *= 2;
3021         }
3022 
3023         argv[nArgs] = newWord;
3024         nArgs++;
3025 
3026         cmdLine = endOfWord;
3027         cmdLine = skipSpace(cmdLine);
3028     }
3029 
3030     argc = nArgs;
3031 
3032     return argv;
3033 }
3034 
3035 
3036 static bool startFullscreen = false;
3037 static bool runOnce = false;
3038 static string startURL;
3039 static string startDirectory;
3040 static string startScript;
3041 static vector<string> extrasDirectories;
3042 static string configFileName;
3043 static bool useAlternateConfigFile = false;
3044 static bool skipSplashScreen = false;
3045 
parseCommandLine(int argc,char * argv[])3046 static bool parseCommandLine(int argc, char* argv[])
3047 {
3048     int i = 0;
3049 
3050     while (i < argc)
3051     {
3052         bool isLastArg = (i == argc - 1);
3053         if (strcmp(argv[i], "--verbose") == 0)
3054         {
3055             SetDebugVerbosity(1);
3056         }
3057         else if (strcmp(argv[i], "--fullscreen") == 0)
3058         {
3059             startFullscreen = true;
3060         }
3061         else if (strcmp(argv[i], "--once") == 0)
3062         {
3063             runOnce = true;
3064         }
3065         else if (strcmp(argv[i], "--dir") == 0)
3066         {
3067             if (isLastArg)
3068             {
3069                 MessageBox(NULL,
3070                            "Directory expected after --dir", "Celestia Command Line Error",
3071                            MB_OK | MB_ICONERROR);
3072                 return false;
3073             }
3074             i++;
3075             startDirectory = string(argv[i]);
3076         }
3077         else if (strcmp(argv[i], "--conf") == 0)
3078         {
3079             if (isLastArg)
3080             {
3081                 MessageBox(NULL,
3082                            "Configuration file name expected after --conf",
3083                            "Celestia Command Line Error",
3084                            MB_OK | MB_ICONERROR);
3085                 return false;
3086             }
3087             i++;
3088             configFileName = string(argv[i]);
3089             useAlternateConfigFile = true;
3090         }
3091         else if (strcmp(argv[i], "--extrasdir") == 0)
3092         {
3093             if (isLastArg)
3094             {
3095                 MessageBox(NULL,
3096                            "Directory expected after --extrasdir", "Celestia Command Line Error",
3097                            MB_OK | MB_ICONERROR);
3098                 return false;
3099             }
3100             i++;
3101             extrasDirectories.push_back(string(argv[i]));
3102         }
3103         else if (strcmp(argv[i], "-u") == 0 || strcmp(argv[i], "--url") == 0)
3104         {
3105             if (isLastArg)
3106             {
3107                 MessageBox(NULL,
3108                            "URL expected after --url", "Celestia Command Line Error",
3109                            MB_OK | MB_ICONERROR);
3110                 return false;
3111             }
3112             i++;
3113             startURL = string(argv[i]);
3114         }
3115         else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--nosplash") == 0)
3116         {
3117             skipSplashScreen = true;
3118         }
3119         else
3120         {
3121             char* buf = new char[strlen(argv[i]) + 256];
3122             sprintf(buf, "Invalid command line option '%s'", argv[i]);
3123             MessageBox(NULL,
3124                        buf, "Celestia Command Line Error",
3125                        MB_OK | MB_ICONERROR);
3126             delete[] buf;
3127             return false;
3128         }
3129 
3130         i++;
3131     }
3132 
3133     return true;
3134 }
3135 
3136 
3137 class WinSplashProgressNotifier : public ProgressNotifier
3138 {
3139 public:
WinSplashProgressNotifier(SplashWindow * _splash)3140     WinSplashProgressNotifier(SplashWindow* _splash) : splash(_splash) {};
~WinSplashProgressNotifier()3141     virtual ~WinSplashProgressNotifier() {};
3142 
update(const string & filename)3143     virtual void update(const string& filename)
3144     {
3145         splash->setMessage(UTF8ToCurrentCP(_("Loading: ")) + filename);
3146     }
3147 
3148 private:
3149     SplashWindow* splash;
3150 };
3151 
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)3152 int APIENTRY WinMain(HINSTANCE hInstance,
3153                      HINSTANCE hPrevInstance,
3154                      LPSTR     lpCmdLine,
3155                      int       nCmdShow)
3156 {
3157 
3158     // Say we're not ready to render yet.
3159     bReady = false;
3160 
3161     appInstance = hInstance;
3162 
3163     int argc;
3164     char** argv;
3165     argv = splitCommandLine(lpCmdLine, argc);
3166     bool cmdLineOK = parseCommandLine(argc, argv);
3167     if (!cmdLineOK)
3168         return 1;
3169 
3170     // If Celestia was invoked with the --once command line parameter,
3171     // check to see if there's already an instance of Celestia running.
3172     if (runOnce)
3173     {
3174         // The FindWindow method of checking for another running instance
3175         // is broken, and we should be using CreateMutex instead.  But we'll
3176         // sort that out later . . .
3177         HWND existingWnd = FindWindow(AppName, AppName);
3178         if (existingWnd)
3179         {
3180             // If there's an existing instance and we've been given a
3181             // URL on the command line, send the URL to the running instance
3182             // of Celestia before terminating.
3183             if (startURL != "")
3184             {
3185                 COPYDATASTRUCT cd;
3186                 cd.dwData = 0;
3187                 cd.cbData = startURL.length();
3188                 cd.lpData = reinterpret_cast<void*>(const_cast<char*>(startURL.c_str()));
3189                 SendMessage(existingWnd, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&cd));
3190             }
3191             SetForegroundWindow(existingWnd);
3192             Sleep(1000);
3193             exit(0);
3194         }
3195     }
3196 
3197     // If a start directory was given on the command line, switch to it
3198     // now.
3199     if (startDirectory != "")
3200         SetCurrentDirectory(startDirectory.c_str());
3201 
3202 	s_splash = new SplashWindow("splash.png");
3203     s_splash->setMessage("Loading data files...");
3204     if (!skipSplashScreen)
3205         s_splash->showSplash();
3206 
3207     OleInitialize(NULL);
3208     dropTarget = new CelestiaDropTarget();
3209     if (dropTarget)
3210     {
3211         if (CoLockObjectExternal(dropTarget, TRUE, TRUE) != S_OK)
3212         {
3213             cout << "Error locking drop target\n";
3214             delete dropTarget;
3215             dropTarget = NULL;
3216         }
3217     }
3218 
3219     // Specify some default values in case registry keys are not found. Ideally, these
3220     // defaults should never be used--they should be overridden by settings in
3221     // celestia.cfg.
3222     AppPreferences prefs;
3223     prefs.winWidth = 800;
3224     prefs.winHeight = 600;
3225     prefs.winX = CW_USEDEFAULT;
3226     prefs.winY = CW_USEDEFAULT;
3227     prefs.ambientLight = 0.1f;  // Low
3228     prefs.galaxyLightGain = 0.0f;
3229     prefs.labelMode = 0;
3230     prefs.locationFilter = 0;
3231     prefs.orbitMask = Body::Planet | Body::Moon;
3232     prefs.renderFlags = Renderer::DefaultRenderFlags;
3233 
3234     prefs.visualMagnitude = 6.0f;   //Default specified in Simulation::Simulation()
3235     prefs.showLocalTime = 0;
3236     prefs.dateFormat = 0;
3237     prefs.hudDetail = 1;
3238     prefs.fullScreenMode = -1;
3239     prefs.lastVersion = 0x00000000;
3240     prefs.textureResolution = 1;
3241     LoadPreferencesFromRegistry(CelestiaRegKey, prefs);
3242 
3243     // Adjust window dimensions for screen dimensions
3244     int screenWidth = GetSystemMetrics(SM_CXSCREEN);
3245     int screenHeight = GetSystemMetrics(SM_CYSCREEN);
3246     if (prefs.winWidth > screenWidth)
3247         prefs.winWidth = screenWidth;
3248     if (prefs.winHeight > screenHeight)
3249         prefs.winHeight = screenHeight;
3250     if (prefs.winX != CW_USEDEFAULT && prefs.winY != CW_USEDEFAULT)
3251     {
3252         if (prefs.winX + prefs.winWidth > screenWidth)
3253             prefs.winX = screenWidth - prefs.winWidth;
3254         if (prefs.winY + prefs.winHeight > screenHeight)
3255             prefs.winY = screenHeight - prefs.winHeight;
3256     }
3257 
3258     // Make sure windowRect contains default window size in case Celestia is
3259     // launched in fullscreen mode. Without this code, CreateOpenGLWindow()
3260     // will be called with windowRect = {0, 0, 0, 0} when the user switches to
3261     // windowed mode.
3262     windowRect.left = prefs.winX;
3263     windowRect.top = prefs.winY;
3264     windowRect.right = windowRect.left + prefs.winWidth;
3265     windowRect.bottom = windowRect.top + prefs.winHeight;
3266 
3267     joystickAvailable = InitJoystick(joystickCaps);
3268 
3269     displayModes = EnumerateDisplayModes(16);
3270 
3271     // If full screen mode was found in registry, override default with it.
3272     if (prefs.fullScreenMode != -1)
3273         lastFullScreenMode = prefs.fullScreenMode;
3274 
3275     // If the user changes the computer's graphics card or driver, the
3276     // number of display modes may change. Since we're storing a display
3277     // index this can cause troubles. Avoid out of range errors by
3278     // checking against the size of the mode list, falling back to a
3279     // safe mode if the last used mode index is now out of range.
3280     // TODO: A MUCH better fix for the problem of changing GPU/driver
3281     // is to store the mode parameters instead of just the mode index.
3282     if (lastFullScreenMode > displayModes->size())
3283     {
3284         lastFullScreenMode = fallbackFullScreenMode;
3285     }
3286 
3287     appCore = new CelestiaCore();
3288     if (appCore == NULL)
3289     {
3290         if (s_splash != NULL)
3291         {
3292             s_splash->close();
3293             delete s_splash;
3294             s_splash = NULL;
3295         }
3296 
3297         MessageBox(NULL,
3298                    "Out of memory.", "Fatal Error",
3299                    MB_OK | MB_ICONERROR | MB_TOPMOST);
3300         return false;
3301     }
3302 
3303     // Get Application Path
3304     char appPath[_MAX_PATH];
3305     GetCurrentDirectory(_MAX_PATH - 1, appPath);
3306 
3307     // Gettext integration
3308     setlocale(LC_ALL, "");
3309     setlocale(LC_NUMERIC, "C");
3310     string localeDir = (string)appPath + "/locale";
3311     bindtextdomain("celestia", localeDir.c_str());
3312     bind_textdomain_codeset("celestia", "UTF-8");
3313     bindtextdomain("celestia_constellations", localeDir.c_str());
3314     bind_textdomain_codeset("celestia_constellations", "UTF-8");
3315     textdomain("celestia");
3316 
3317     // Loading localized resources
3318     char res[255];
3319     sprintf(res, "locale\\res_%s.dll", _("LANGUAGE"));
3320     int langID = 0;
3321     if (sscanf(_("WinLangID"), "%x", &langID) == 1)
3322         SetThreadLocale(langID);
3323     if ((hRes = LoadLibrary(res)) == NULL) {
3324         cout << "Couldn't load localized resources: "<< res<< "\n";
3325         hRes = hInstance;
3326     }
3327 
3328     appCore->setAlerter(new WinAlerter());
3329 
3330     WinSplashProgressNotifier* progressNotifier = NULL;
3331     if (!skipSplashScreen)
3332         progressNotifier = new WinSplashProgressNotifier(s_splash);
3333 
3334     string* altConfig = useAlternateConfigFile ? &configFileName : NULL;
3335     bool initSucceeded = appCore->initSimulation(altConfig, &extrasDirectories, progressNotifier);
3336 
3337     delete progressNotifier;
3338 
3339     // Close the splash screen after all data has been loaded
3340 	if (s_splash != NULL)
3341 	{
3342 		s_splash->close();
3343 		delete s_splash;
3344         s_splash = NULL;
3345 	}
3346 
3347 	// Give up now if we failed initialization
3348 	if (!initSucceeded)
3349 		return 1;
3350 
3351     if (startURL != "")
3352         appCore->setStartURL(startURL);
3353 
3354     menuBar = CreateMenuBar();
3355     acceleratorTable = LoadAccelerators(hRes,
3356                                         MAKEINTRESOURCE(IDR_ACCELERATORS));
3357 
3358     if (appCore->getConfig() != NULL)
3359     {
3360         if (!compareIgnoringCase(appCore->getConfig()->cursor, "arrow"))
3361             hDefaultCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
3362         else if (!compareIgnoringCase(appCore->getConfig()->cursor, "inverting crosshair"))
3363             hDefaultCursor = LoadCursor(hRes, MAKEINTRESOURCE(IDC_CROSSHAIR));
3364         else
3365             hDefaultCursor = LoadCursor(hRes, MAKEINTRESOURCE(IDC_CROSSHAIR_OPAQUE));
3366     }
3367 
3368     cursorHandler = new WinCursorHandler(hDefaultCursor);
3369     appCore->setCursorHandler(cursorHandler);
3370 
3371     InitWGLExtensions(appInstance);
3372 
3373     HWND hWnd;
3374     if (startFullscreen)
3375     {
3376         hWnd = CreateOpenGLWindow(0, 0, 800, 600,
3377                                   lastFullScreenMode, currentScreenMode);
3378 
3379         // Prevent unnecessary destruction and recreation of OpenGLWindow in
3380         // while() loop below.
3381         newScreenMode = currentScreenMode;
3382     }
3383     else
3384     {
3385         hWnd = CreateOpenGLWindow(prefs.winX, prefs.winY,
3386                                   prefs.winWidth, prefs.winHeight,
3387                                   0, currentScreenMode);
3388     }
3389 
3390     if (hWnd == NULL)
3391     {
3392         MessageBox(NULL,
3393                    "Failed to create the application window.",
3394                    "Fatal Error",
3395 	           MB_OK | MB_ICONERROR);
3396         return FALSE;
3397     }
3398 
3399     if (dropTarget != NULL)
3400     {
3401         HRESULT hr = RegisterDragDrop(hWnd, dropTarget);
3402         if (hr != S_OK)
3403         {
3404             cout << "Failed to register drop target as OLE object.\n";
3405         }
3406     }
3407 
3408     mainWindow = hWnd;
3409 
3410     UpdateWindow(mainWindow);
3411 
3412     // Initialize common controls
3413     INITCOMMONCONTROLSEX icex;
3414     icex.dwSize = sizeof(icex);
3415     icex.dwICC = ICC_DATE_CLASSES;
3416     InitCommonControlsEx(&icex);
3417 
3418     extern void RegisterDatePicker();
3419     RegisterDatePicker();
3420 
3421     if (!appCore->initRenderer())
3422     {
3423         return 1;
3424     }
3425 
3426     // Set values saved in registry: renderFlags, visualMagnitude, labelMode and timezone bias.
3427     if (prefs.lastVersion != 0)
3428     {
3429         appCore->getSimulation()->setFaintestVisible(prefs.visualMagnitude);
3430         appCore->getRenderer()->setRenderFlags(prefs.renderFlags);
3431         appCore->getRenderer()->setLabelMode(prefs.labelMode);
3432         appCore->getSimulation()->getActiveObserver()->setLocationFilter(prefs.locationFilter);
3433         appCore->getRenderer()->setOrbitMask(prefs.orbitMask);
3434         appCore->getRenderer()->setAmbientLightLevel(prefs.ambientLight);
3435         Galaxy::setLightGain(prefs.galaxyLightGain);
3436         appCore->getRenderer()->setStarStyle(prefs.starStyle);
3437         appCore->setHudDetail(prefs.hudDetail);
3438         if (prefs.showLocalTime == 1)
3439             ShowLocalTime(appCore);
3440         else
3441             ShowUniversalTime(appCore);
3442         appCore->setDateFormat((astro::Date::Format) prefs.dateFormat);
3443         appCore->getSimulation()->getActiveObserver()->setDisplayedSurface(prefs.altSurfaceName);
3444         appCore->getRenderer()->setResolution(prefs.textureResolution);
3445         if (prefs.renderPathSet)
3446         {
3447             GLContext* glContext = appCore->getRenderer()->getGLContext();
3448             if (glContext->renderPathSupported(prefs.renderPath))
3449                 glContext->setRenderPath(prefs.renderPath);
3450         }
3451     }
3452     else
3453     {
3454         // Set default render flags for a new installation
3455         appCore->getRenderer()->setRenderFlags(Renderer::DefaultRenderFlags);
3456     }
3457 
3458     BuildFavoritesMenu(menuBar, appCore, appInstance, &odAppMenu);
3459     BuildScriptsMenu(menuBar, ScriptsDirectory);
3460     syncMenusWithRendererState();
3461 
3462     appCore->setContextMenuCallback(ContextMenu);
3463 
3464     bReady = true;
3465 
3466     // Get the current time
3467     time_t systime = time(NULL);
3468     struct tm *gmt = gmtime(&systime);
3469     double timeTDB = astro::J2000;
3470     if (gmt != NULL)
3471     {
3472         astro::Date d;
3473         d.year = gmt->tm_year + 1900;
3474         d.month = gmt->tm_mon + 1;
3475         d.day = gmt->tm_mday;
3476         d.hour = gmt->tm_hour;
3477         d.minute = gmt->tm_min;
3478         d.seconds = (int) gmt->tm_sec;
3479         timeTDB = astro::UTCtoTDB(d);
3480     }
3481     appCore->start(timeTDB);
3482 
3483     if (startURL != "")
3484     {
3485         COPYDATASTRUCT cd;
3486         cd.dwData = 0;
3487         cd.cbData = startURL.length();
3488         cd.lpData = reinterpret_cast<void*>(const_cast<char*>(startURL.c_str()));
3489         SendMessage(mainWindow, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&cd));
3490     }
3491 
3492     // Initial tick required before first frame is rendered; this gives
3493     // start up scripts a chance to run.
3494     appCore->tick();
3495 
3496     MSG msg;
3497     PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
3498     while (msg.message != WM_QUIT)
3499     {
3500         bool isVisible = !IsIconic(mainWindow);
3501 
3502         // If Celestia is in an inactive state, use GetMessage to avoid
3503         // sucking CPU cycles.  If time is paused and the camera isn't
3504         // moving in any view, we can probably also avoid constantly
3505         // rendering.
3506         BOOL haveMessage;
3507         if (isVisible)
3508             haveMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
3509         else
3510             haveMessage = GetMessage(&msg, NULL, 0U, 0U);
3511 
3512         if (!haveMessage)
3513         {
3514             // Tick the simulation
3515             appCore->tick();
3516         }
3517 
3518         if (haveMessage)
3519         {
3520             bool dialogMessage = false;
3521 
3522             if (starBrowser != NULL &&
3523                 IsDialogMessage(starBrowser->hwnd, &msg))
3524                 dialogMessage = true;
3525             if (solarSystemBrowser != NULL &&
3526                 IsDialogMessage(solarSystemBrowser->hwnd, &msg))
3527                 dialogMessage = true;
3528             if (tourGuide != NULL &&
3529                 IsDialogMessage(tourGuide->hwnd, &msg))
3530                 dialogMessage = true;
3531             if (gotoObjectDlg != NULL &&
3532                 IsDialogMessage(gotoObjectDlg->hwnd, &msg))
3533                 dialogMessage = true;
3534             if (viewOptionsDlg != NULL &&
3535                 IsDialogMessage(viewOptionsDlg->hwnd, &msg))
3536                 dialogMessage = true;
3537             if (eclipseFinder != NULL &&
3538                 IsDialogMessage(eclipseFinder->hwnd, &msg))
3539                 dialogMessage = true;
3540             if (locationsDlg != NULL &&
3541                 IsDialogMessage(locationsDlg->hwnd, &msg))
3542                 dialogMessage = true;
3543 
3544             // Translate and dispatch the message
3545             if (!dialogMessage)
3546             {
3547                 if (!TranslateAccelerator(mainWindow, acceleratorTable, &msg))
3548                     TranslateMessage(&msg);
3549                 DispatchMessage(&msg);
3550             }
3551         }
3552         else
3553         {
3554             // And force a redraw
3555             InvalidateRect(mainWindow, NULL, FALSE);
3556         }
3557 
3558         if (useJoystick)
3559             HandleJoystick();
3560 
3561         if (currentScreenMode != newScreenMode)
3562         {
3563             if (currentScreenMode == 0)
3564                 GetWindowRect(mainWindow, &windowRect);
3565             else
3566                 lastFullScreenMode = currentScreenMode;
3567             DestroyOpenGLWindow();
3568             mainWindow = CreateOpenGLWindow(windowRect.left,
3569                                             windowRect.top,
3570                                             windowRect.right-windowRect.left,
3571                                             windowRect.bottom-windowRect.top,
3572                                             newScreenMode,
3573                                             currentScreenMode);
3574             UpdateWindow(mainWindow);
3575         }
3576     }
3577 
3578     // Save application preferences
3579     {
3580         AppPreferences prefs;
3581         if (GetCurrentPreferences(prefs))
3582             SavePreferencesToRegistry(CelestiaRegKey, prefs);
3583     }
3584 
3585     // Not ready to render anymore.
3586     bReady = false;
3587 
3588     // Clean up the window
3589     if (currentScreenMode != 0)
3590         RestoreDisplayMode();
3591     DestroyOpenGLWindow();
3592 
3593     if (appCore != NULL)
3594         delete appCore;
3595 
3596     OleUninitialize();
3597 
3598     return msg.wParam;
3599 }
3600 
3601 
modifiers(WPARAM wParam,WPARAM mods)3602 bool modifiers(WPARAM wParam, WPARAM mods)
3603 {
3604     return (wParam & mods) == mods;
3605 }
3606 
3607 
RestoreCursor()3608 static void RestoreCursor()
3609 {
3610     ShowCursor(TRUE);
3611     cursorVisible = true;
3612     SetCursorPos(saveCursorPos.x, saveCursorPos.y);
3613 }
3614 
3615 
MainWindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)3616 LRESULT CALLBACK MainWindowProc(HWND hWnd,
3617                                 UINT uMsg,
3618                                 WPARAM wParam, LPARAM lParam)
3619 {
3620     switch(uMsg)
3621     {
3622     case WM_CREATE:
3623         //Instruct menu class to enumerate menu structure
3624         odAppMenu.Init(hWnd, menuBar);
3625 
3626         //Associate some menu items with bitmap resources
3627         odAppMenu.SetItemImage(appInstance, ID_FILE_OPENSCRIPT, IDB_SCRIPT);
3628         odAppMenu.SetItemImage(appInstance, ID_FILE_CAPTUREIMAGE, IDB_CAMERA);
3629         odAppMenu.SetItemImage(appInstance, ID_FILE_CAPTUREMOVIE, IDB_CAMCORDER);
3630         odAppMenu.SetItemImage(appInstance, ID_FILE_EXIT, IDB_EXIT);
3631         odAppMenu.SetItemImage(appInstance, ID_TIME_SETTIME, IDB_CLOCK);
3632         odAppMenu.SetItemImage(appInstance, ID_TIME_FREEZE, IDB_STOP);
3633         odAppMenu.SetItemImage(appInstance, ID_RENDER_VIEWOPTIONS, IDB_SUNGLASSES);
3634         odAppMenu.SetItemImage(appInstance, ID_RENDER_LOCATIONS, IDB_GLOBE);
3635         odAppMenu.SetItemImage(appInstance, ID_HELP_RUNDEMO, IDB_SCRIPT);
3636         odAppMenu.SetItemImage(appInstance, ID_HELP_CONTROLS, IDB_CONFIG);
3637         odAppMenu.SetItemImage(appInstance, ID_HELP_ABOUT, IDB_ABOUT);
3638 
3639         DragAcceptFiles(hWnd, TRUE);
3640         break;
3641 
3642     case WM_DROPFILES:
3643         break;
3644 
3645     case WM_MEASUREITEM:
3646         odAppMenu.MeasureItem(hWnd, lParam);
3647         return TRUE;
3648 
3649     case WM_DRAWITEM:
3650         odAppMenu.DrawItem(hWnd, lParam);
3651         return TRUE;
3652 
3653     case WM_MOUSEMOVE:
3654         {
3655 	    int x, y;
3656 	    x = LOWORD(lParam);
3657 	    y = HIWORD(lParam);
3658 
3659             bool reallyMoved = x != lastMouseMove.x || y != lastMouseMove.y;
3660             lastMouseMove.x = x;
3661             lastMouseMove.y = y;
3662 
3663             if (reallyMoved)
3664             {
3665                 appCore->mouseMove((float) x, (float) y);
3666 
3667                 if ((wParam & (MK_LBUTTON | MK_RBUTTON)) != 0)
3668                 {
3669 #ifdef INFINITE_MOUSE
3670                     // A bit of mouse tweaking here . . .  we want to allow the
3671                     // user to rotate and zoom continuously, without having to
3672                     // pick up the mouse every time it leaves the window.  So,
3673                     // once we start dragging, we'll hide the mouse and reset
3674                     // its position every time it's moved.
3675                     POINT pt;
3676                     pt.x = lastX;
3677                     pt.y = lastY;
3678                     ClientToScreen(hWnd, &pt);
3679 
3680                     // If the cursor is still visible, this is the first mouse
3681                     // move message of this drag.  Hide the cursor and set the
3682                     // cursor position to the center of the window.  Once the
3683                     // drag is complete, we'll restore the cursor position and
3684                     // make it visible again.
3685                     if (ignoreNextMoveEvent)
3686                     {
3687                         // This hack is required because there's a move event
3688                         // right after canceling a context menu by clicking
3689                         // outside of it.  Because it was canceled by clicking,
3690                         // the mouse button down bits are set, and the infinite
3691                         // mouse code gets confused.
3692                         ignoreNextMoveEvent = false;
3693                     }
3694                     else if (cursorVisible)
3695                     {
3696                         // Hide the cursor
3697                         ShowCursor(FALSE);
3698                         cursorVisible = false;
3699 
3700                         // Save the cursor position
3701                         saveCursorPos = pt;
3702 
3703                         // Compute the center point of the client area
3704                         RECT rect;
3705                         GetClientRect(hWnd, &rect);
3706                         POINT center;
3707                         center.x = (rect.right - rect.left) / 2;
3708                         center.y = (rect.bottom - rect.top) / 2;
3709 
3710                         // Set the cursor position to the center of the window
3711                         x = center.x + (x - lastX);
3712                         y = center.y + (y - lastY);
3713                         lastX = center.x;
3714                         lastY = center.y;
3715 
3716                         ClientToScreen(hWnd, &center);
3717                         SetCursorPos(center.x, center.y);
3718                     }
3719                     else
3720                     {
3721                         if (x - lastX != 0 || y - lastY != 0)
3722                             SetCursorPos(pt.x, pt.y);
3723                     }
3724 #else
3725                     lastX = x;
3726                     lastY = y;
3727 #endif // INFINITE_MOUSE
3728                 }
3729 
3730                 int buttons = 0;
3731                 if ((wParam & MK_LBUTTON) != 0)
3732                     buttons |= CelestiaCore::LeftButton;
3733                 if ((wParam & MK_RBUTTON) != 0)
3734                     buttons |= CelestiaCore::RightButton;
3735                 if ((wParam & MK_MBUTTON) != 0)
3736                     buttons |= CelestiaCore::MiddleButton;
3737                 if ((wParam & MK_SHIFT) != 0)
3738                     buttons |= CelestiaCore::ShiftKey;
3739                 if ((wParam & MK_CONTROL) != 0)
3740                     buttons |= CelestiaCore::ControlKey;
3741                 appCore->mouseMove((float) (x - lastX),
3742                                    (float) (y - lastY),
3743                                    buttons);
3744 
3745                 if (currentScreenMode != 0)
3746                 {
3747                     if (hideMenuBar && y < 10)
3748                     {
3749                         SetMenu(mainWindow, menuBar);
3750                         hideMenuBar = false;
3751                     }
3752                     else if (!hideMenuBar && y >= 10)
3753                     {
3754                         SetMenu(mainWindow, NULL);
3755                         hideMenuBar = true;
3756                     }
3757                 }
3758             }
3759         }
3760 
3761         break;
3762 
3763     case WM_LBUTTONDOWN:
3764         lastX = LOWORD(lParam);
3765         lastY = HIWORD(lParam);
3766         appCore->mouseButtonDown(LOWORD(lParam), HIWORD(lParam),
3767                                  CelestiaCore::LeftButton);
3768         break;
3769     case WM_RBUTTONDOWN:
3770         lastX = LOWORD(lParam);
3771         lastY = HIWORD(lParam);
3772         appCore->mouseButtonDown(LOWORD(lParam), HIWORD(lParam),
3773                                  CelestiaCore::RightButton);
3774         break;
3775     case WM_MBUTTONDOWN:
3776         lastX = LOWORD(lParam);
3777         lastY = HIWORD(lParam);
3778         appCore->mouseButtonDown(LOWORD(lParam), HIWORD(lParam),
3779                                  CelestiaCore::MiddleButton);
3780         break;
3781 
3782     case WM_LBUTTONUP:
3783         if (!cursorVisible)
3784             RestoreCursor();
3785         appCore->mouseButtonUp(LOWORD(lParam), HIWORD(lParam),
3786                                CelestiaCore::LeftButton);
3787         break;
3788 
3789     case WM_RBUTTONUP:
3790         if (!cursorVisible)
3791             RestoreCursor();
3792         appCore->mouseButtonUp(LOWORD(lParam), HIWORD(lParam),
3793                                CelestiaCore::RightButton);
3794         break;
3795 
3796     case WM_MBUTTONUP:
3797         lastX = LOWORD(lParam);
3798         lastY = HIWORD(lParam);
3799         appCore->mouseButtonUp(LOWORD(lParam), HIWORD(lParam),
3800                                CelestiaCore::MiddleButton);
3801         break;
3802 
3803     case WM_MOUSEWHEEL:
3804         {
3805             int modifiers = 0;
3806             if ((wParam & MK_SHIFT) != 0)
3807                 modifiers |= CelestiaCore::ShiftKey;
3808             appCore->mouseWheel((short) HIWORD(wParam) > 0 ? -1.0f : 1.0f,
3809                                 modifiers);
3810         }
3811         break;
3812 
3813     case WM_KEYDOWN:
3814         switch (wParam)
3815         {
3816         case VK_ESCAPE:
3817             appCore->charEntered('\033');
3818             break;
3819         case VK_INSERT:
3820         case 'C':
3821             if ((GetKeyState(VK_LCONTROL) | GetKeyState(VK_RCONTROL)) & 0x8000)
3822             {
3823                 CopyStateURLToClipboard();
3824                 appCore->flash(_("Copied URL"));
3825             }
3826             break;
3827         default:
3828             handleKey(wParam, true);
3829             break;
3830         }
3831         break;
3832 
3833     case WM_KEYUP:
3834         handleKey(wParam, false);
3835         break;
3836 
3837     case WM_CHAR:
3838         {
3839             // Bits 16-23 of lParam specify the scan code of the key pressed.
3840 
3841             // Ignore all keypad input, this will be handled by WM_KEYDOWN
3842             // messages.
3843             char cScanCode = (char) (HIWORD(lParam) & 0xFF);
3844             if((cScanCode >= 71 && cScanCode <= 73) ||
3845                (cScanCode >= 75 && cScanCode <= 77) ||
3846                (cScanCode >= 79 && cScanCode <= 83))
3847             {
3848                 break;
3849             }
3850 
3851             int charCode = (char) wParam;
3852             int modifiers = 0;
3853             if (GetKeyState(VK_SHIFT) & 0x8000)
3854                 modifiers |= CelestiaCore::ShiftKey;
3855 
3856             // Catch backtab (shift+Tab)
3857             if (charCode == '\011' && (modifiers & CelestiaCore::ShiftKey))
3858                 charCode = CelestiaCore::Key_BackTab;
3859 
3860             Renderer* r = appCore->getRenderer();
3861             int oldRenderFlags = r->getRenderFlags();
3862             int oldLabelMode = r->getLabelMode();
3863 
3864             //  Convert charCode from current locale to UTF-8
3865             char utf8CharCode[7];
3866             memset(utf8CharCode, 0, sizeof(utf8CharCode));
3867             WCHAR wCharCode;
3868             MultiByteToWideChar(CP_ACP, 0, (char*)&charCode, 1, &wCharCode, 1);
3869             WideCharToMultiByte(CP_UTF8, 0, &wCharCode, 1, utf8CharCode, 7, 0, 0);
3870 
3871             /*cerr << "Char input: (ANSI) " << (int)(unsigned char)charCode << " - UTF8 -> ";
3872             for(int i=0; utf8CharCode[i] != '\0'; i++) cerr << (int)(unsigned char)(utf8CharCode[i]) << " ";
3873             cerr << "[" << utf8CharCode << "]" << endl;*/
3874 
3875             Renderer::StarStyle oldStarStyle = r->getStarStyle();
3876             appCore->charEntered(utf8CharCode, modifiers);
3877             if (r->getRenderFlags() != oldRenderFlags ||
3878                 r->getLabelMode() != oldLabelMode ||
3879                 r->getStarStyle() != oldStarStyle)
3880             {
3881                 syncMenusWithRendererState();
3882             }
3883         }
3884         break;
3885 
3886     case WM_IME_CHAR:
3887         {
3888             char ch[2];
3889             char utf8CharCode[7];
3890             memset(utf8CharCode, 0, sizeof(utf8CharCode));
3891             WCHAR wCharCode;
3892             ch[0] = (wParam >> 8);
3893             ch[1] = (wParam & 0xff);
3894             if (ch[0]) MultiByteToWideChar(CP_ACP, 0, ch, 2, &wCharCode, 1);
3895             else MultiByteToWideChar(CP_ACP, 0, &ch[1], 1, &wCharCode, 1);
3896             WideCharToMultiByte(CP_UTF8, 0, &wCharCode, 1, utf8CharCode, 7, 0, 0);
3897             appCore->charEntered(utf8CharCode);
3898             /*cerr << "IME input: (ANSI) " << (int)(unsigned char)ch[0] << " " << (int)(unsigned char)ch[1] << " - UTF8 -> ";
3899             for(int i=0; utf8CharCode[i] != '\0'; i++) cerr << (int)(unsigned char)(utf8CharCode[i]) << " ";
3900             cerr << "[" << utf8CharCode << "]" << endl;*/
3901         }
3902         break;
3903 
3904     case WM_COPYDATA:
3905         // The copy data message is used to send URL strings between
3906         // processes.
3907         {
3908             COPYDATASTRUCT* cd = reinterpret_cast<COPYDATASTRUCT*>(lParam);
3909             if (cd != NULL && cd->lpData != NULL)
3910             {
3911                 char* urlChars = reinterpret_cast<char*>(cd->lpData);
3912                 if (cd->cbData > 3) // minimum of "cel:" or ".cel"
3913                 {
3914                     string urlString(urlChars, cd->cbData);
3915 
3916                     if (!urlString.substr(0,4).compare("cel:"))
3917                     {
3918                         appCore->flash(_("Loading URL"));
3919                         appCore->goToUrl(urlString);
3920                     }
3921                     else if (DetermineFileType(urlString) == Content_CelestiaScript)
3922                     {
3923                         appCore->runScript(urlString);
3924                     }
3925                     else
3926                     {
3927                         ifstream scriptfile(urlString.c_str());
3928                         if (!scriptfile.good())
3929                         {
3930                             appCore->flash(_("Error opening script"));
3931                         }
3932                         else
3933                         {
3934                             // TODO: Need to fix memory leak with scripts;
3935                             // a refcount is probably required.
3936                             CommandParser parser(scriptfile);
3937                             CommandSequence* script = parser.parse();
3938                             if (script == NULL)
3939                             {
3940                                 const vector<string>* errors = parser.getErrors();
3941                                 const char* errorMsg = "";
3942                                 if (errors->size() > 0)
3943                                 {
3944                                     errorMsg = (*errors)[0].c_str();
3945                                     appCore->flash(errorMsg);
3946                                 }
3947                                 else
3948                                 {
3949                                     appCore->flash(_("Error loading script"));
3950                                 }
3951                             }
3952                             else
3953                             {
3954                                 appCore->flash(_("Running script"));
3955                                 appCore->runScript(script);
3956                             }
3957                         }
3958                     }
3959                 }
3960             }
3961         }
3962         break;
3963 
3964     case WM_COMMAND:
3965         switch (LOWORD(wParam))
3966         {
3967         case ID_NAVIGATION_CENTER:
3968             appCore->charEntered('c');
3969             break;
3970         case ID_NAVIGATION_GOTO:
3971             appCore->charEntered('G');
3972             break;
3973         case ID_NAVIGATION_FOLLOW:
3974             appCore->charEntered('F');
3975             break;
3976         case ID_NAVIGATION_SYNCORBIT:
3977             appCore->charEntered('Y');
3978             break;
3979         case ID_NAVIGATION_TRACK:
3980             appCore->charEntered('T');
3981             break;
3982         case ID_NAVIGATION_HOME:
3983             appCore->charEntered('H');
3984             break;
3985         case ID_NAVIGATION_SELECT:
3986             DialogBox(hRes, MAKEINTRESOURCE(IDD_FINDOBJECT), hWnd, FindObjectProc);
3987             break;
3988         case ID_NAVIGATION_GOTO_OBJECT:
3989             if (gotoObjectDlg == NULL)
3990                 gotoObjectDlg = new GotoObjectDialog(hRes, hWnd, appCore);
3991             break;
3992         case IDCLOSE:
3993             if (reinterpret_cast<LPARAM>(gotoObjectDlg) == lParam &&
3994                 gotoObjectDlg != NULL)
3995             {
3996                 delete gotoObjectDlg;
3997                 gotoObjectDlg = NULL;
3998             }
3999             else if (reinterpret_cast<LPARAM>(tourGuide) == lParam &&
4000                      tourGuide != NULL)
4001             {
4002                 delete tourGuide;
4003                 tourGuide = NULL;
4004             }
4005             else if (reinterpret_cast<LPARAM>(starBrowser) == lParam &&
4006                      starBrowser != NULL)
4007             {
4008                 delete starBrowser;
4009                 starBrowser = NULL;
4010             }
4011             else if (reinterpret_cast<LPARAM>(solarSystemBrowser) == lParam &&
4012                      solarSystemBrowser != NULL)
4013             {
4014                 delete solarSystemBrowser;
4015                 solarSystemBrowser = NULL;
4016             }
4017             else if (reinterpret_cast<LPARAM>(viewOptionsDlg) == lParam &&
4018                 viewOptionsDlg != NULL)
4019             {
4020                 delete viewOptionsDlg;
4021                 viewOptionsDlg = NULL;
4022             }
4023             else if (reinterpret_cast<LPARAM>(eclipseFinder) == lParam &&
4024                      eclipseFinder != NULL)
4025             {
4026                 delete eclipseFinder;
4027                 eclipseFinder = NULL;
4028             }
4029             else if (reinterpret_cast<LPARAM>(locationsDlg) == lParam &&
4030                      locationsDlg != NULL)
4031             {
4032                 delete locationsDlg;
4033                 locationsDlg = NULL;
4034             }
4035             break;
4036 
4037         case ID_NAVIGATION_TOURGUIDE:
4038             if (tourGuide == NULL)
4039                 tourGuide = new TourGuide(hRes, hWnd, appCore);
4040             break;
4041 
4042         case ID_NAVIGATION_SSBROWSER:
4043             if (solarSystemBrowser == NULL)
4044                 solarSystemBrowser = new SolarSystemBrowser(hRes, hWnd, appCore);
4045             break;
4046 
4047         case ID_NAVIGATION_STARBROWSER:
4048             if (starBrowser == NULL)
4049                 starBrowser = new StarBrowser(hRes, hWnd, appCore);
4050             break;
4051 
4052         case ID_NAVIGATION_ECLIPSEFINDER:
4053             if (eclipseFinder == NULL)
4054                 eclipseFinder = new EclipseFinderDialog(hRes, hWnd, appCore);
4055             break;
4056 
4057         case ID_RENDER_DISPLAYMODE:
4058             newScreenMode = currentScreenMode;
4059             CreateDialogParam(hRes,
4060                               MAKEINTRESOURCE(IDD_DISPLAYMODE),
4061                               hWnd,
4062                               SelectDisplayModeProc,
4063                               NULL);
4064             break;
4065 
4066         case ID_RENDER_FULLSCREEN:
4067             if (currentScreenMode == 0)
4068                 newScreenMode = lastFullScreenMode;
4069             else
4070                 newScreenMode = 0;
4071             break;
4072 
4073         case ID_RENDER_VIEWOPTIONS:
4074             if (viewOptionsDlg == NULL)
4075                 viewOptionsDlg = new ViewOptionsDialog(hRes, hWnd, appCore);
4076             break;
4077 
4078         case ID_RENDER_LOCATIONS:
4079             if (locationsDlg == NULL)
4080                 locationsDlg = new LocationsDialog(hRes, hWnd, appCore);
4081             break;
4082 
4083         case ID_RENDER_MORESTARS:
4084             appCore->charEntered(']');
4085             break;
4086 
4087         case ID_RENDER_FEWERSTARS:
4088             appCore->charEntered('[');
4089             break;
4090 
4091         case ID_RENDER_AUTOMAG:
4092             appCore->charEntered('\031');
4093             syncMenusWithRendererState();
4094             break;
4095 
4096         case ID_RENDER_AMBIENTLIGHT_NONE:
4097             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_NONE,   MF_CHECKED);
4098             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_LOW,    MF_UNCHECKED);
4099             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_MEDIUM, MF_UNCHECKED);
4100             appCore->getRenderer()->setAmbientLightLevel(0.0f);
4101             break;
4102         case ID_RENDER_AMBIENTLIGHT_LOW:
4103             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_NONE,   MF_UNCHECKED);
4104             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_LOW,    MF_CHECKED);
4105             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_MEDIUM, MF_UNCHECKED);
4106             appCore->getRenderer()->setAmbientLightLevel(0.1f);
4107             break;
4108         case ID_RENDER_AMBIENTLIGHT_MEDIUM:
4109             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_NONE,   MF_UNCHECKED);
4110             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_LOW,    MF_UNCHECKED);
4111             CheckMenuItem(menuBar, ID_RENDER_AMBIENTLIGHT_MEDIUM, MF_CHECKED);
4112             appCore->getRenderer()->setAmbientLightLevel(0.25f);
4113             break;
4114 
4115         case ID_RENDER_STARSTYLE_FUZZY:
4116             appCore->getRenderer()->setStarStyle(Renderer::FuzzyPointStars);
4117             syncMenusWithRendererState();
4118             break;
4119 
4120         case ID_RENDER_STARSTYLE_POINTS:
4121             appCore->getRenderer()->setStarStyle(Renderer::PointStars);
4122             syncMenusWithRendererState();
4123             break;
4124 
4125         case ID_RENDER_STARSTYLE_DISCS:
4126             appCore->getRenderer()->setStarStyle(Renderer::ScaledDiscStars);
4127             syncMenusWithRendererState();
4128             break;
4129 
4130         case ID_RENDER_TEXTURERES_LOW:
4131             appCore->getRenderer()->setResolution(0);
4132             syncMenusWithRendererState();
4133             break;
4134         case ID_RENDER_TEXTURERES_MEDIUM:
4135             appCore->getRenderer()->setResolution(1);
4136             syncMenusWithRendererState();
4137             break;
4138         case ID_RENDER_TEXTURERES_HIGH:
4139             appCore->getRenderer()->setResolution(2);
4140             syncMenusWithRendererState();
4141             break;
4142 
4143         case ID_RENDER_ANTIALIASING:
4144             appCore->charEntered('\030');
4145             syncMenusWithRendererState();
4146             break;
4147 
4148         case ID_RENDER_BODY_AXES:
4149             appCore->toggleReferenceMark("body axes");
4150             break;
4151 
4152         case ID_RENDER_FRAME_AXES:
4153             appCore->toggleReferenceMark("frame axes");
4154             break;
4155 
4156         case ID_RENDER_SUN_DIRECTION:
4157             appCore->toggleReferenceMark("sun direction");
4158             break;
4159 
4160         case ID_RENDER_VELOCITY_VECTOR:
4161             appCore->toggleReferenceMark("velocity vector");
4162             break;
4163 
4164         case ID_RENDER_PLANETOGRAPHIC_GRID:
4165             appCore->toggleReferenceMark("planetographic grid");
4166             break;
4167 
4168         case ID_RENDER_TERMINATOR:
4169             appCore->toggleReferenceMark("terminator");
4170             break;
4171 
4172         case ID_TIME_FASTER:
4173             appCore->charEntered('l');
4174             break;
4175         case ID_TIME_SLOWER:
4176             appCore->charEntered('k');
4177             break;
4178         case ID_TIME_REALTIME:
4179             appCore->charEntered('\\');
4180             break;
4181 
4182         case ID_TIME_FREEZE:
4183             appCore->charEntered(' ');
4184             break;
4185         case ID_TIME_REVERSE:
4186             appCore->charEntered('J');
4187             break;
4188         case ID_TIME_SETTIME:
4189             ShowSetTimeDialog(hRes, hWnd, appCore);
4190 
4191             // Update the local time menu item--since the set time dialog handles setting the time zone,
4192             // should we just get rid of the menu item?
4193             if (appCore->getTimeZoneBias() == 0)
4194                 CheckMenuItem(menuBar, ID_TIME_SHOWLOCAL, MF_UNCHECKED);
4195             else
4196                 CheckMenuItem(menuBar, ID_TIME_SHOWLOCAL, MF_CHECKED);
4197             break;
4198         case ID_TIME_SHOWLOCAL:
4199             if (ToggleMenuItem(menuBar, ID_TIME_SHOWLOCAL))
4200                 ShowLocalTime(appCore);
4201             else
4202                 ShowUniversalTime(appCore);
4203             break;
4204 
4205         case ID_VIEW_HSPLIT:
4206             appCore->splitView(View::HorizontalSplit);
4207             break;
4208 
4209         case ID_VIEW_VSPLIT:
4210             appCore->splitView(View::VerticalSplit);
4211             break;
4212 
4213         case ID_VIEW_SINGLE:
4214             appCore->singleView();
4215             break;
4216 
4217         case ID_VIEW_DELETE_ACTIVE:
4218             appCore->deleteView();
4219             break;
4220 
4221         case ID_VIEW_SHOW_FRAMES:
4222             appCore->setFramesVisible(!appCore->getFramesVisible());
4223             syncMenusWithRendererState();
4224             break;
4225 
4226         case ID_VIEW_SYNC_TIME:
4227             {
4228                 Simulation* sim = appCore->getSimulation();
4229                 sim->setSyncTime(!sim->getSyncTime());
4230                 if (sim->getSyncTime())
4231                     sim->synchronizeTime();
4232                 syncMenusWithRendererState();
4233             }
4234             break;
4235 
4236         case ID_BOOKMARKS_ADDBOOKMARK:
4237             DialogBox(hRes, MAKEINTRESOURCE(IDD_ADDBOOKMARK), hWnd, AddBookmarkProc);
4238             break;
4239 
4240         case ID_BOOKMARKS_ORGANIZE:
4241             DialogBox(hRes, MAKEINTRESOURCE(IDD_ORGANIZE_BOOKMARKS), hWnd, OrganizeBookmarksProc);
4242             break;
4243 
4244         case ID_HELP_RUNDEMO:
4245             appCore->charEntered('D');
4246             break;
4247 
4248         case ID_HELP_GUIDE:
4249             ShellExecute(hWnd, "open", "help\\CelestiaGuide.html", NULL, NULL, SW_NORMAL);
4250 
4251             break;
4252 
4253         case ID_HELP_CONTROLS:
4254             CreateDialogParam(hRes,
4255                               MAKEINTRESOURCE(IDD_CONTROLSHELP),
4256                               hWnd,
4257                               ControlsHelpProc,
4258                               NULL);
4259             break;
4260 
4261         case ID_HELP_ABOUT:
4262             DialogBox(hRes, MAKEINTRESOURCE(IDD_ABOUT), hWnd, AboutProc);
4263             break;
4264 
4265         case ID_HELP_GLINFO:
4266             DialogBox(hRes, MAKEINTRESOURCE(IDD_GLINFO), hWnd, GLInfoProc);
4267             break;
4268 
4269         case ID_HELP_LICENSE:
4270             DialogBox(hRes, MAKEINTRESOURCE(IDD_LICENSE), hWnd, LicenseProc);
4271             break;
4272 
4273         case ID_INFO:
4274             ShowWWWInfo(appCore->getSimulation()->getSelection());
4275             break;
4276 
4277         case ID_FILE_OPENSCRIPT:
4278             HandleOpenScript(hWnd, appCore);
4279             break;
4280 
4281         case ID_FILE_CAPTUREIMAGE:
4282             HandleCaptureImage(hWnd);
4283             break;
4284 
4285         case ID_FILE_CAPTUREMOVIE:
4286             HandleCaptureMovie(hWnd);
4287             break;
4288 
4289         case ID_FILE_EXIT:
4290             SendMessage(hWnd, WM_CLOSE, 0, 0);
4291             break;
4292 
4293         case ID_GOTO_URL:
4294             {
4295                 // Relies on a pointer in lparam, do this does not
4296                 // work cross-process.
4297                 char* urlString = reinterpret_cast<char*>(lParam);
4298                 if (urlString != NULL)
4299                 {
4300                     appCore->flash(string("URL: ") + string(urlString));
4301 					appCore->goToUrl(urlString);
4302                 }
4303             }
4304             break;
4305 
4306         case ID_TOOLS_MARK:
4307             {
4308                 Simulation* sim = appCore->getSimulation();
4309                 if (sim->getUniverse() != NULL)
4310                 {
4311                     MarkerRepresentation markerRep(MarkerRepresentation::Diamond,
4312                                                    10.0f,
4313                                                    Color(0.0f, 1.0f, 0.0f, 0.9f));
4314 
4315                     sim->getUniverse()->markObject(sim->getSelection(),
4316                                                    markerRep,
4317                                                    1);
4318 
4319                     appCore->getRenderer()->setRenderFlags(appCore->getRenderer()->getRenderFlags() | Renderer::ShowMarkers);
4320                 }
4321             }
4322             break;
4323 
4324         case ID_TOOLS_UNMARK:
4325             {
4326                 Simulation* sim = appCore->getSimulation();
4327                 if (sim->getUniverse() != NULL)
4328                     sim->getUniverse()->unmarkObject(sim->getSelection(), 1);
4329             }
4330             break;
4331 
4332         default:
4333             {
4334                 const FavoritesList* favorites = appCore->getFavorites();
4335                 if (favorites != NULL &&
4336                     LOWORD(wParam) >= ID_BOOKMARKS_FIRSTBOOKMARK &&
4337                     LOWORD(wParam) - ID_BOOKMARKS_FIRSTBOOKMARK < (int) favorites->size())
4338                 {
4339                     int whichFavorite = LOWORD(wParam) - ID_BOOKMARKS_FIRSTBOOKMARK;
4340                     appCore->activateFavorite(*(*favorites)[whichFavorite]);
4341                 }
4342                 else if (LOWORD(wParam) >= MENU_CHOOSE_PLANET &&
4343                          LOWORD(wParam) < MENU_CHOOSE_PLANET + 1000)
4344                 {
4345                     // Handle the satellite/child object submenu
4346                     Selection sel = appCore->getSimulation()->getSelection();
4347                     switch (sel.getType())
4348                     {
4349                     case Selection::Type_Star:
4350                         appCore->getSimulation()->selectPlanet(LOWORD(wParam) - MENU_CHOOSE_PLANET);
4351                         break;
4352 
4353                     case Selection::Type_Body:
4354                         {
4355                             PlanetarySystem* satellites = (PlanetarySystem*) sel.body()->getSatellites();
4356                             appCore->getSimulation()->setSelection(Selection(satellites->getBody(LOWORD(wParam) - MENU_CHOOSE_PLANET)));
4357                             break;
4358                         }
4359 
4360                     case Selection::Type_DeepSky:
4361                         // Current deep sky object/galaxy implementation does
4362                         // not have children to select.
4363                         break;
4364 
4365                     case Selection::Type_Location:
4366                         break;
4367 
4368                     default:
4369                         break;
4370                     }
4371                 }
4372                 else if (LOWORD(wParam) >= MENU_CHOOSE_SURFACE &&
4373                          LOWORD(wParam) < MENU_CHOOSE_SURFACE + 1000)
4374                 {
4375                     // Handle the alternate surface submenu
4376                     Selection sel = appCore->getSimulation()->getSelection();
4377                     if (sel.body() != NULL)
4378                     {
4379                         int index = (int) LOWORD(wParam) - MENU_CHOOSE_SURFACE - 1;
4380                         vector<string>* surfNames = sel.body()->getAlternateSurfaceNames();
4381                         if (surfNames != NULL)
4382                         {
4383                             string surfName;
4384                             if (index >= 0 && index < (int) surfNames->size())
4385                                 surfName = surfNames->at(index);
4386                             appCore->getSimulation()->getActiveObserver()->setDisplayedSurface(surfName);
4387                             delete surfNames;
4388                         }
4389                     }
4390                 }
4391                 else if (LOWORD(wParam) >= ID_FIRST_SCRIPT &&
4392                          LOWORD(wParam) <  ID_FIRST_SCRIPT + ScriptMenuItems->size())
4393                 {
4394                     // Handle the script menu
4395                     unsigned int scriptIndex = LOWORD(wParam) - ID_FIRST_SCRIPT;
4396                     appCore->runScript((*ScriptMenuItems)[scriptIndex].filename);
4397                 }
4398             }
4399             break;
4400         }
4401         break;
4402 
4403     case WM_CLOSE:
4404         PostQuitMessage(0);
4405         break;
4406 
4407     case WM_SIZE:
4408         appCore->resize(LOWORD(lParam), HIWORD(lParam));
4409         break;
4410 
4411     case WM_PAINT:
4412         if (bReady)
4413         {
4414             appCore->draw();
4415             SwapBuffers(deviceContext);
4416             ValidateRect(hWnd, NULL);
4417         }
4418         break;
4419 
4420     default:
4421         return DefWindowProc( hWnd, uMsg, wParam, lParam );
4422     }
4423 
4424     return 0;
4425 }
4426