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