1 /*
2 * Celestia GTK+ Front-End
3 * Copyright (C) 2005 Pat Suwalski <pat@suwalski.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * $Id: main.cpp,v 1.9 2008-01-21 04:55:19 suwalski Exp $
11 */
12
13 #ifdef HAVE_CONFIG_H
14 #include <config.h>
15 #endif /* HAVE_CONFIG_H */
16
17 #include <iostream>
18 #include <fstream>
19 #include <cstdlib>
20 #include <cctype>
21 #include <cstring>
22 #include <time.h>
23
24 #ifdef WIN32
25 #include <direct.h>
26 #else
27 #include <unistd.h>
28 #endif /* WIN32 */
29
30 #include <gtk/gtk.h>
31 #include <gtk/gtkgl.h>
32
33 #include <celengine/astro.h>
34 #include <celengine/celestia.h>
35 #include <celengine/gl.h>
36 #include <celengine/glext.h>
37 #include <celengine/galaxy.h>
38 #include <celengine/simulation.h>
39 #include <celestia/celestiacore.h>
40 #include <celutil/debug.h>
41
42 /* Includes for the GNOME front-end */
43 #ifdef GNOME
44 #include <gnome.h>
45 #include <libgnomeui/libgnomeui.h>
46 #include <gconf/gconf-client.h>
47 #endif /* GNOME */
48
49 /* Includes for the GTK front-end */
50 #include "common.h"
51 #include "glwidget.h"
52 #include "menu-context.h"
53 #include "splash.h"
54 #include "ui.h"
55
56 /* Includes for the settings interface */
57 #ifdef GNOME
58 #include "settings-gconf.h"
59 #else
60 #include "settings-file.h"
61 #endif /* GNOME */
62
63 #ifndef DEBUG
64 #define G_DISABLE_ASSERT
65 #endif /* DEBUG */
66
67
68 using namespace std;
69
70
71 /* Function Definitions */
72 static void createMainMenu(GtkWidget* window, AppData* app);
73 static void initRealize(GtkWidget* widget, AppData* app);
74
75
76 /* Command-Line Options */
77 static gchar* configFile = NULL;
78 static gchar* installDir = NULL;
79 static gchar** extrasDir = NULL;
80 static gboolean fullScreen = FALSE;
81 static gboolean noSplash = FALSE;
82
83 /* Command-Line Options specification */
84 static GOptionEntry optionEntries[] =
85 {
86 { "conf", 'c', 0, G_OPTION_ARG_FILENAME, &configFile, "Alternate configuration file", "file" },
87 { "dir", 'd', 0, G_OPTION_ARG_FILENAME, &installDir, "Alternate installation directory", "directory" },
88 { "extrasdir", 'e', 0, G_OPTION_ARG_FILENAME_ARRAY, &extrasDir, "Additional \"extras\" directory", "directory" },
89 { "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &fullScreen, "Start full-screen", NULL },
90 { "nosplash", 's', 0, G_OPTION_ARG_NONE, &noSplash, "Disable splash screen", NULL },
91 { NULL },
92 };
93
94
95 /* Initializes GtkActions and creates main menu */
createMainMenu(GtkWidget * window,AppData * app)96 static void createMainMenu(GtkWidget* window, AppData* app)
97 {
98 GtkUIManager *ui_manager;
99 GtkAccelGroup *accel_group;
100 GError *error;
101
102 app->agMain = gtk_action_group_new ("MenuActions");
103 app->agRender = gtk_action_group_new("RenderActions");
104 app->agLabel = gtk_action_group_new("LabelActions");
105 app->agOrbit = gtk_action_group_new("OrbitActions");
106 app->agVerbosity = gtk_action_group_new("VerbosityActions");
107 app->agStarStyle = gtk_action_group_new("StarStyleActions");
108 app->agAmbient = gtk_action_group_new("AmbientActions");
109
110 /* All actions have the AppData structure passed */
111 gtk_action_group_add_actions(app->agMain, actionsPlain, G_N_ELEMENTS(actionsPlain), app);
112 gtk_action_group_add_toggle_actions(app->agMain, actionsToggle, G_N_ELEMENTS(actionsToggle), app);
113 gtk_action_group_add_radio_actions(app->agVerbosity, actionsVerbosity, G_N_ELEMENTS(actionsVerbosity), 0, G_CALLBACK(actionVerbosity), app);
114 gtk_action_group_add_radio_actions(app->agStarStyle, actionsStarStyle, G_N_ELEMENTS(actionsStarStyle), 0, G_CALLBACK(actionStarStyle), app);
115 gtk_action_group_add_radio_actions(app->agAmbient, actionsAmbientLight, G_N_ELEMENTS(actionsAmbientLight), 0, G_CALLBACK(actionAmbientLight), app);
116 gtk_action_group_add_toggle_actions(app->agRender, actionsRenderFlags, G_N_ELEMENTS(actionsRenderFlags), app);
117 gtk_action_group_add_toggle_actions(app->agLabel, actionsLabelFlags, G_N_ELEMENTS(actionsLabelFlags), app);
118 gtk_action_group_add_toggle_actions(app->agOrbit, actionsOrbitFlags, G_N_ELEMENTS(actionsOrbitFlags), app);
119
120 ui_manager = gtk_ui_manager_new();
121 gtk_ui_manager_insert_action_group(ui_manager, app->agMain, 0);
122 gtk_ui_manager_insert_action_group(ui_manager, app->agRender, 0);
123 gtk_ui_manager_insert_action_group(ui_manager, app->agLabel, 0);
124 gtk_ui_manager_insert_action_group(ui_manager, app->agOrbit, 0);
125 gtk_ui_manager_insert_action_group(ui_manager, app->agStarStyle, 0);
126 gtk_ui_manager_insert_action_group(ui_manager, app->agAmbient, 0);
127 gtk_ui_manager_insert_action_group(ui_manager, app->agVerbosity, 0);
128
129 accel_group = gtk_ui_manager_get_accel_group(ui_manager);
130 gtk_window_add_accel_group(GTK_WINDOW (window), accel_group);
131
132 error = NULL;
133 if (!gtk_ui_manager_add_ui_from_file(ui_manager, "celestiaui.xml", &error))
134 {
135 g_message("Building menus failed: %s", error->message);
136 g_error_free(error);
137 exit(EXIT_FAILURE);
138 }
139
140 app->mainMenu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
141 }
142
143
144 /* Our own watcher. Celestiacore will call notifyChange() to tell us
145 * we need to recheck the check menu items and option buttons. */
146
147 class GtkWatcher : public CelestiaWatcher
148 {
149 public:
150 GtkWatcher(CelestiaCore*, AppData*);
151 virtual void notifyChange(CelestiaCore*, int);
152 private:
153 AppData* app;
154 };
155
GtkWatcher(CelestiaCore * _appCore,AppData * _app)156 GtkWatcher::GtkWatcher(CelestiaCore* _appCore, AppData* _app) :
157 CelestiaWatcher(*_appCore), app(_app)
158 {
159 }
160
notifyChange(CelestiaCore *,int property)161 void GtkWatcher::notifyChange(CelestiaCore*, int property)
162 {
163 if (property & CelestiaCore::LabelFlagsChanged)
164 resyncLabelActions(app);
165
166 else if (property & CelestiaCore::RenderFlagsChanged)
167 {
168 resyncRenderActions(app);
169 resyncOrbitActions(app);
170 resyncStarStyleActions(app);
171 resyncTextureResolutionActions(app);
172 }
173
174 else if (property & CelestiaCore::VerbosityLevelChanged)
175 resyncVerbosityActions(app);
176
177 else if (property & CelestiaCore::TimeZoneChanged)
178 resyncTimeZoneAction(app);
179
180 else if (property & CelestiaCore::AmbientLightChanged)
181 resyncAmbientActions(app);
182
183 /*
184 else if (property & CelestiaCore::FaintestChanged) DEPRECATED?
185 else if (property & CelestiaCore::HistoryChanged)
186 */
187
188 else if (property == CelestiaCore::TextEnterModeChanged)
189 {
190 if (app->core->getTextEnterMode() != 0)
191 {
192 /* Grey-out the menu */
193 gtk_widget_set_sensitive(app->mainMenu, FALSE);
194
195 /* Disable any actions that will interfere in typing and
196 autocomplete */
197 gtk_action_group_set_sensitive(app->agMain, FALSE);
198 gtk_action_group_set_sensitive(app->agRender, FALSE);
199 gtk_action_group_set_sensitive(app->agLabel, FALSE);
200 }
201 else
202 {
203 /* Set the menu normal */
204 gtk_widget_set_sensitive(app->mainMenu, TRUE);
205
206 /* Re-enable action groups */
207 gtk_action_group_set_sensitive(app->agMain, TRUE);
208 gtk_action_group_set_sensitive(app->agRender, TRUE);
209 gtk_action_group_set_sensitive(app->agLabel, TRUE);
210 }
211 }
212
213 else if (property & CelestiaCore::GalaxyLightGainChanged)
214 resyncGalaxyGainActions(app);
215 }
216
217 /* END Watcher */
218
219
220 /* CALLBACK: Event "realize" on the main GL area. Things that go here are those
221 * that require the glArea to be set up. */
initRealize(GtkWidget * widget,AppData * app)222 static void initRealize(GtkWidget* widget, AppData* app)
223 {
224 if (!app->core->initRenderer())
225 {
226 cerr << "Failed to initialize renderer.\n";
227 }
228
229 /* Read/Apply Settings */
230 #ifdef GNOME
231 applySettingsGConfMain(app, app->client);
232 #else
233 applySettingsFileMain(app, app->settingsFile);
234 #endif /* GNOME */
235
236 /* Synchronize all actions with core settings */
237 resyncLabelActions(app);
238 resyncRenderActions(app);
239 resyncOrbitActions(app);
240 resyncVerbosityActions(app);
241 resyncAmbientActions(app);
242 resyncStarStyleActions(app);
243
244 /* If full-screen at startup, make it so. */
245 if (app->fullScreen)
246 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_action_group_get_action(app->agMain, "FullScreen")), TRUE);
247
248 /* If framerate limiting is off, set it so. */
249 if (!app->renderer->getVideoSync())
250 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_action_group_get_action(app->agMain, "VideoSync")), FALSE);
251
252 /* If URL at startup, make it so. */
253 if (app->startURL != NULL)
254 app->core->setStartURL(app->startURL);
255
256 /* Set simulation time */
257 app->core->start((double)time(NULL) / 86400.0 + (double)astro::Date(1970, 1, 1));
258 updateTimeZone(app, app->showLocalTime);
259
260 /* Setting time zone name not very useful, but makes space for "LT" status in
261 * the top-right corner. Set to some default. */
262 app->core->setTimeZoneName("UTC");
263
264 /* Set the cursor to a crosshair */
265 gdk_window_set_cursor(widget->window, gdk_cursor_new(GDK_CROSSHAIR));
266 }
267
268
269 /* MAIN */
main(int argc,char * argv[])270 int main(int argc, char* argv[])
271 {
272 /* Initialize the structure that holds the application's vitals. */
273 AppData* app = g_new0(AppData, 1);
274
275 /* Not ready to render yet. */
276 app->bReady = FALSE;
277
278 /* Initialize variables in the AppData structure. */
279 app->lastX = 0;
280 app->lastY = 0;
281 app->showLocalTime = FALSE;
282 app->fullScreen = FALSE;
283 app->startURL = NULL;
284
285 /* Watcher enables sending signals from inside of core */
286 GtkWatcher* gtkWatcher;
287
288 /* Command line option parsing */
289 GError *error = NULL;
290 GOptionContext* context = g_option_context_new("");
291 g_option_context_add_main_entries(context, optionEntries, NULL);
292 g_option_context_add_group(context, gtk_get_option_group(TRUE));
293 g_option_context_parse(context, &argc, &argv, &error);
294
295 if (error != NULL)
296 {
297 g_print("Error in command line options. Use --help for full list.\n");
298 exit(1);
299 }
300
301 /* At this point, the argument count should be 1 or 2, with the lastX
302 * potentially being a cel:// URL. */
303
304 /* If there's an argument left, assume it's a URL. This happens here
305 * because it's after the saved prefs are applied. The appCore gets
306 * initialized elsewhere. */
307 if (argc > 1)
308 app->startURL = argv[argc - 1];
309
310 if (installDir == NULL)
311 installDir = (gchar*)CONFIG_DATA_DIR;
312
313 if (chdir(installDir) == -1)
314 cerr << "Cannot chdir to '" << installDir << "', probably due to improper installation.\n";
315
316 #ifdef GNOME
317 /* GNOME Initialization */
318 GnomeProgram *program;
319 program = gnome_program_init("Celestia", VERSION, LIBGNOMEUI_MODULE,
320 argc, argv, GNOME_PARAM_NONE);
321 #else
322 /* GTK-Only Initialization */
323 gtk_init(&argc, &argv);
324 #endif
325
326 /* Turn on the splash screen */
327 SplashData* ss = splashStart(app, !noSplash);
328 splashSetText(ss, "Initializing...");
329
330 SetDebugVerbosity(0);
331
332 /* Force number displays into C locale. */
333 setlocale(LC_NUMERIC, "C");
334 setlocale(LC_ALL, "");
335
336 #ifndef WIN32
337 bindtextdomain(PACKAGE, LOCALEDIR);
338 bind_textdomain_codeset(PACKAGE, "UTF-8");
339 textdomain(PACKAGE);
340 #endif /* WIN32 */
341
342 app->core = new CelestiaCore();
343 if (app->core == NULL)
344 {
345 cerr << "Failed to initialize Celestia core.\n";
346 return 1;
347 }
348
349 app->renderer = app->core->getRenderer();
350 g_assert(app->renderer);
351
352 /* Parse simulation arguments */
353 string cf;
354 if (configFile != NULL)
355 cf = string(configFile);
356
357 string* altConfig = (configFile != NULL) ? &cf : NULL;
358
359 vector<string> configDirs;
360 if (extrasDir != NULL)
361 {
362 /* Add each extrasDir to the vector */
363 int i = 0;
364 while (extrasDir[i] != NULL)
365 {
366 configDirs.push_back(extrasDir[i]);
367 i++;
368 }
369 }
370
371 /* Initialize the simulation */
372 if (!app->core->initSimulation(altConfig, &configDirs, ss->notifier))
373 return 1;
374
375 app->simulation = app->core->getSimulation();
376 g_assert(app->simulation);
377
378 #ifdef GNOME
379 /* Create the main window (GNOME) */
380 app->mainWindow = gnome_app_new("Celestia", "Celestia");
381 #else
382 /* Create the main window (GTK) */
383 app->mainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
384 gtk_window_set_title(GTK_WINDOW(app->mainWindow), "Celestia");
385 #endif /* GNOME */
386
387 /* Set pointer to AppData structure. This is for when a function is in a
388 * *real* bind to get at this structure. */
389 g_object_set_data(G_OBJECT(app->mainWindow), "CelestiaData", app);
390
391 GtkWidget* mainBox = GTK_WIDGET(gtk_vbox_new(FALSE, 0));
392 gtk_container_set_border_width(GTK_CONTAINER(mainBox), 0);
393
394 g_signal_connect(GTK_OBJECT(app->mainWindow), "destroy",
395 G_CALLBACK(actionQuit), app);
396
397 /* Initialize the OpenGL widget */
398 gtk_gl_init (&argc, &argv);
399
400 /* Configure OpenGL. Try double-buffered visual. */
401 GdkGLConfig* glconfig = gdk_gl_config_new_by_mode(static_cast<GdkGLConfigMode>
402 (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE));
403
404 if (glconfig == NULL)
405 {
406 g_print("*** Cannot find the double-buffered visual.\n");
407 g_print("*** Trying single-buffered visual.\n");
408
409 /* Try single-buffered visual */
410 glconfig = gdk_gl_config_new_by_mode(static_cast<GdkGLConfigMode>
411 (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH));
412 if (glconfig == NULL)
413 {
414 g_print ("*** No appropriate OpenGL-capable visual found.\n");
415 exit(1);
416 }
417 }
418
419 /* Initialize settings system */
420 #ifdef GNOME
421 initSettingsGConf(app);
422 #else
423 initSettingsFile(app);
424 #endif /* GNOME */
425
426 /* Create area to be used for OpenGL display */
427 app->glArea = gtk_drawing_area_new();
428
429 /* Set OpenGL-capability to the widget. */
430 gtk_widget_set_gl_capability(app->glArea,
431 glconfig,
432 NULL,
433 TRUE,
434 GDK_GL_RGBA_TYPE);
435
436 gtk_widget_set_events(GTK_WIDGET(app->glArea),
437 GDK_EXPOSURE_MASK |
438 GDK_KEY_PRESS_MASK |
439 GDK_KEY_RELEASE_MASK |
440 GDK_BUTTON_PRESS_MASK |
441 GDK_BUTTON_RELEASE_MASK |
442 GDK_POINTER_MOTION_MASK);
443
444 /* Load settings the can be applied before the simulation is initialized */
445 #ifdef GNOME
446 applySettingsGConfPre(app, app->client);
447 #else
448 applySettingsFilePre(app, app->settingsFile);
449 #endif /* GNOME */
450
451 /* Full-Screen option from the command line (overrides above). */
452 if (fullScreen)
453 app->fullScreen = TRUE;
454
455 /* Initialize handlers to all events in the glArea */
456 initGLCallbacks(app);
457
458 /* Handler than completes initialization when the glArea is realized */
459 g_signal_connect(GTK_OBJECT(app->glArea), "realize",
460 G_CALLBACK(initRealize), app);
461
462 /* Create the main menu bar */
463 createMainMenu(app->mainWindow, app);
464
465 /* Initialize the context menu */
466 initContext(app);
467
468 /* Set context menu callback for the core */
469 app->core->setContextMenuCallback(menuContext);
470
471 #ifdef GNOME
472 /* Set window contents (GNOME) */
473 gnome_app_set_contents((GnomeApp *)app->mainWindow, GTK_WIDGET(mainBox));
474 #else
475 /* Set window contents (GTK) */
476 gtk_container_add(GTK_CONTAINER(app->mainWindow), GTK_WIDGET(mainBox));
477 #endif /* GNOME */
478
479 gtk_box_pack_start(GTK_BOX(mainBox), app->mainMenu, FALSE, TRUE, 0);
480 gtk_box_pack_start(GTK_BOX(mainBox), app->glArea, TRUE, TRUE, 0);
481
482 gtk_window_set_default_icon_from_file("celestia-logo.png", NULL);
483
484 /* Set focus to glArea widget */
485 GTK_WIDGET_SET_FLAGS(app->glArea, GTK_CAN_FOCUS);
486 gtk_widget_grab_focus(GTK_WIDGET(app->glArea));
487
488 /* Initialize the Watcher */
489 gtkWatcher = new GtkWatcher(app->core, app);
490
491 /* Unload the splash screen */
492 splashEnd(ss);
493
494 gtk_widget_show_all(app->mainWindow);
495
496 /* HACK: Now that window is drawn, set minimum window size */
497 gtk_widget_set_size_request(app->glArea, 320, 240);
498
499 #ifdef GNOME
500 initSettingsGConfNotifiers(app);
501 #endif /* GNOME */
502
503 /* Set the ready flag */
504 app->bReady = TRUE;
505
506 /* Call Main GTK Loop */
507 gtk_main();
508
509 g_free(app);
510
511 return 0;
512 }
513
514
515 #ifdef WIN32
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)516 int APIENTRY WinMain(HINSTANCE hInstance,
517 HINSTANCE hPrevInstance,
518 LPSTR lpCmdLine,
519 int nCmdShow)
520 {
521 return main(__argc, __argv);
522 }
523 #endif /* WIN32 */
524