/* * Celestia GTK+ Front-End * Copyright (C) 2005 Pat Suwalski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * $Id: main.cpp,v 1.9 2008-01-21 04:55:19 suwalski Exp $ */ #include #include #include #include #include #include #include #ifdef WIN32 #include #else #include #endif /* WIN32 */ #include #ifdef GTKGLEXT #include #else #include "gtkegl.h" #endif #include #include #include #include #include #include #include /* Includes for the GNOME front-end */ #ifdef GNOME #include #include #include #endif /* GNOME */ /* Includes for the GTK front-end */ #include "common.h" #include "glwidget.h" #include "menu-context.h" #include "splash.h" #include "ui.h" /* Includes for the settings interface */ #ifdef GNOME #include "settings-gconf.h" #else #include "settings-file.h" #endif /* GNOME */ #ifndef DEBUG #define G_DISABLE_ASSERT #endif /* DEBUG */ using namespace celestia; using namespace std; /* Function Definitions */ static void createMainMenu(GtkWidget* window, AppData* app); static void initRealize(GtkWidget* widget, AppData* app); /* Command-Line Options */ static gboolean fullScreen = FALSE; static gboolean noSplash = FALSE; /* Initializes GtkActions and creates main menu */ static void createMainMenu(GtkWidget* window, AppData* app) { GtkUIManager *ui_manager; GtkAccelGroup *accel_group; GError *error; app->agMain = gtk_action_group_new ("MenuActions"); app->agRender = gtk_action_group_new("RenderActions"); app->agLabel = gtk_action_group_new("LabelActions"); app->agOrbit = gtk_action_group_new("OrbitActions"); app->agVerbosity = gtk_action_group_new("VerbosityActions"); app->agStarStyle = gtk_action_group_new("StarStyleActions"); app->agAmbient = gtk_action_group_new("AmbientActions"); /* All actions have the AppData structure passed */ gtk_action_group_add_actions(app->agMain, actionsPlain, G_N_ELEMENTS(actionsPlain), app); gtk_action_group_add_toggle_actions(app->agMain, actionsToggle, G_N_ELEMENTS(actionsToggle), app); gtk_action_group_add_radio_actions(app->agVerbosity, actionsVerbosity, G_N_ELEMENTS(actionsVerbosity), 0, G_CALLBACK(actionVerbosity), app); gtk_action_group_add_radio_actions(app->agStarStyle, actionsStarStyle, G_N_ELEMENTS(actionsStarStyle), 0, G_CALLBACK(actionStarStyle), app); gtk_action_group_add_radio_actions(app->agAmbient, actionsAmbientLight, G_N_ELEMENTS(actionsAmbientLight), 0, G_CALLBACK(actionAmbientLight), app); gtk_action_group_add_toggle_actions(app->agRender, actionsRenderFlags, G_N_ELEMENTS(actionsRenderFlags), app); gtk_action_group_add_toggle_actions(app->agLabel, actionsLabelFlags, G_N_ELEMENTS(actionsLabelFlags), app); gtk_action_group_add_toggle_actions(app->agOrbit, actionsOrbitFlags, G_N_ELEMENTS(actionsOrbitFlags), app); ui_manager = gtk_ui_manager_new(); gtk_ui_manager_insert_action_group(ui_manager, app->agMain, 0); gtk_ui_manager_insert_action_group(ui_manager, app->agRender, 0); gtk_ui_manager_insert_action_group(ui_manager, app->agLabel, 0); gtk_ui_manager_insert_action_group(ui_manager, app->agOrbit, 0); gtk_ui_manager_insert_action_group(ui_manager, app->agStarStyle, 0); gtk_ui_manager_insert_action_group(ui_manager, app->agAmbient, 0); gtk_ui_manager_insert_action_group(ui_manager, app->agVerbosity, 0); accel_group = gtk_ui_manager_get_accel_group(ui_manager); gtk_window_add_accel_group(GTK_WINDOW (window), accel_group); error = NULL; if (!gtk_ui_manager_add_ui_from_file(ui_manager, "celestiaui.xml", &error)) { g_message("Building menus failed: %s", error->message); g_error_free(error); exit(EXIT_FAILURE); } app->mainMenu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu"); } /* Our own watcher. Celestiacore will call notifyChange() to tell us * we need to recheck the check menu items and option buttons. */ class GtkWatcher : public CelestiaWatcher { public: GtkWatcher(CelestiaCore*, AppData*); virtual void notifyChange(CelestiaCore*, int); private: AppData* app; }; GtkWatcher::GtkWatcher(CelestiaCore* _appCore, AppData* _app) : CelestiaWatcher(*_appCore), app(_app) { } void GtkWatcher::notifyChange(CelestiaCore*, int property) { if (property & CelestiaCore::LabelFlagsChanged) resyncLabelActions(app); else if (property & CelestiaCore::RenderFlagsChanged) { resyncRenderActions(app); resyncOrbitActions(app); resyncStarStyleActions(app); resyncTextureResolutionActions(app); } else if (property & CelestiaCore::VerbosityLevelChanged) resyncVerbosityActions(app); else if (property & CelestiaCore::TimeZoneChanged) resyncTimeZoneAction(app); else if (property & CelestiaCore::AmbientLightChanged) resyncAmbientActions(app); /* else if (property & CelestiaCore::FaintestChanged) DEPRECATED? else if (property & CelestiaCore::HistoryChanged) */ else if (property == CelestiaCore::TextEnterModeChanged) { if (app->core->getTextEnterMode() != 0) { /* Grey-out the menu */ gtk_widget_set_sensitive(app->mainMenu, FALSE); /* Disable any actions that will interfere in typing and autocomplete */ gtk_action_group_set_sensitive(app->agMain, FALSE); gtk_action_group_set_sensitive(app->agRender, FALSE); gtk_action_group_set_sensitive(app->agLabel, FALSE); } else { /* Set the menu normal */ gtk_widget_set_sensitive(app->mainMenu, TRUE); /* Re-enable action groups */ gtk_action_group_set_sensitive(app->agMain, TRUE); gtk_action_group_set_sensitive(app->agRender, TRUE); gtk_action_group_set_sensitive(app->agLabel, TRUE); } } else if (property & CelestiaCore::GalaxyLightGainChanged) resyncGalaxyGainActions(app); } /* END Watcher */ class GtkAlerter : public CelestiaCore::Alerter { private: AppData* app; public: GtkAlerter() = delete; GtkAlerter(AppData* _app) : app(_app) {}; ~GtkAlerter() = default; GtkAlerter(const GtkAlerter&) = delete; GtkAlerter(GtkAlerter&&) = delete; GtkAlerter& operator=(const GtkAlerter&) = delete; GtkAlerter& operator=(GtkAlerter&&) = delete; void fatalError(const std::string& errorMsg) { GtkWidget* errBox = gtk_message_dialog_new(GTK_WINDOW(app->mainWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", errorMsg.c_str()); gtk_dialog_run(GTK_DIALOG(errBox)); gtk_widget_destroy(errBox); } }; /* CALLBACK: Event "realize" on the main GL area. Things that go here are those * that require the glArea to be set up. */ static void initRealize(GtkWidget* widget, AppData* app) { #ifdef GL_ES constexpr int reqVersion = gl::GLES_2; #else constexpr int reqVersion = gl::GL_2_1; #endif bool ok = gl::init(app->core->getConfig()->ignoreGLExtensions) && gl::checkVersion(reqVersion); if (!ok) { GtkWidget *message; message = gtk_message_dialog_new(GTK_WINDOW(app->mainWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Celestia was unable to initialize OpenGL"); gtk_dialog_run(GTK_DIALOG(message)); gtk_widget_destroy(message); exit(1); } app->core->setAlerter(new GtkAlerter(app)); if (!app->core->initRenderer()) { cerr << "Failed to initialize renderer.\n"; } /* Read/Apply Settings */ #ifdef GNOME applySettingsGConfMain(app, app->client); #else applySettingsFileMain(app, app->settingsFile); #endif /* GNOME */ /* Synchronize all actions with core settings */ resyncLabelActions(app); resyncRenderActions(app); resyncOrbitActions(app); resyncVerbosityActions(app); resyncAmbientActions(app); resyncStarStyleActions(app); /* If full-screen at startup, make it so. */ if (app->fullScreen) gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_action_group_get_action(app->agMain, "FullScreen")), TRUE); /* If URL at startup, make it so. */ if (app->startURL != NULL) app->core->setStartURL(app->startURL); /* Set simulation time */ app->core->start(); updateTimeZone(app, app->showLocalTime); /* Setting time zone name not very useful, but makes space for "LT" status in * the top-right corner. Set to some default. */ app->core->setTimeZoneName("UTC"); /* Set the cursor to a crosshair */ gdk_window_set_cursor(gtk_widget_get_window(widget), gdk_cursor_new(GDK_CROSSHAIR)); } /* MAIN */ int main(int argc, char* argv[]) { /* Force number displays into C locale. */ setlocale(LC_NUMERIC, "C"); setlocale(LC_ALL, ""); #ifndef WIN32 bindtextdomain(PACKAGE, LOCALEDIR); bind_textdomain_codeset(PACKAGE, "UTF-8"); textdomain(PACKAGE); #endif /* WIN32 */ /* Initialize the structure that holds the application's vitals. */ AppData* app = g_new0(AppData, 1); /* Not ready to render yet. */ app->bReady = FALSE; /* Initialize variables in the AppData structure. */ app->lastX = 0; app->lastY = 0; app->showLocalTime = FALSE; app->fullScreen = FALSE; app->startURL = NULL; /* Watcher enables sending signals from inside of core */ GtkWatcher* gtkWatcher; #ifdef GNOME /* GNOME Initialization */ GnomeProgram *program; program = gnome_program_init("Celestia", VERSION, LIBGNOMEUI_MODULE, argc, argv, GNOME_PARAM_NONE); #else /* GTK-Only Initialization */ gtk_init(&argc, &argv); #endif /* Turn on the splash screen */ SplashData* ss = splashStart(app, !noSplash); splashSetText(ss, "Initializing..."); SetDebugVerbosity(0); app->core = new CelestiaCore(); auto parser = app->core->getCommandLineParser(); parser.on("fullscreen", 'f', false, _("Start full-screen"), [](bool) { fullScreen = TRUE; }); parser.on("nosplash", 's', false, _("Disable splash screen"), [](bool) { noSplash = TRUE; }); parser.parse(argc, argv); app->renderer = app->core->getRenderer(); g_assert(app->renderer); /* Initialize the simulation */ if (!app->core->initSimulation(ss->notifier)) return 1; app->simulation = app->core->getSimulation(); g_assert(app->simulation); app->renderer->setSolarSystemMaxDistance(app->core->getConfig()->SolarSystemMaxDistance); app->renderer->setShadowMapSize(app->core->getConfig()->ShadowMapSize); #ifdef GNOME /* Create the main window (GNOME) */ app->mainWindow = gnome_app_new("Celestia", "Celestia"); #else /* Create the main window (GTK) */ app->mainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(app->mainWindow), "Celestia"); #endif /* GNOME */ /* Set pointer to AppData structure. This is for when a function is in a * *real* bind to get at this structure. */ g_object_set_data(G_OBJECT(app->mainWindow), "CelestiaData", app); GtkWidget* mainBox = GTK_WIDGET(gtk_vbox_new(FALSE, 0)); gtk_container_set_border_width(GTK_CONTAINER(mainBox), 0); g_signal_connect(G_OBJECT(app->mainWindow), "destroy", G_CALLBACK(actionQuit), app); #ifdef GTKGLEXT /* Initialize the OpenGL widget */ gtk_gl_init (&argc, &argv); /* Configure OpenGL. Try double-buffered visual. */ GdkGLConfig* glconfig = gdk_gl_config_new_by_mode(static_cast (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE)); if (glconfig == NULL) { g_print("*** Cannot find the double-buffered visual.\n"); g_print("*** Trying single-buffered visual.\n"); /* Try single-buffered visual */ glconfig = gdk_gl_config_new_by_mode(static_cast (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH)); if (glconfig == NULL) { g_print ("*** No appropriate OpenGL-capable visual found.\n"); exit(1); } } #endif /* Initialize settings system */ #ifdef GNOME initSettingsGConf(app); #else initSettingsFile(app); #endif /* GNOME */ /* Create area to be used for OpenGL display */ app->glArea = gtk_drawing_area_new(); /* Set OpenGL-capability to the widget. */ #ifdef GTKGLEXT gtk_widget_set_gl_capability(app->glArea, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE); #else gtk_widget_set_egl_capability(app->glArea); #ifdef GL_ES gtk_egl_drawable_set_require_es(app->glArea, TRUE); gtk_egl_drawable_set_require_version(app->glArea, 2, 0); #endif //gtk_egl_drawable_set_require_stencil_size(app->glArea, 1); gtk_egl_drawable_set_require_depth_size(app->glArea, 24); gtk_egl_drawable_set_require_msaa_samples(app->glArea, 8); gtk_egl_drawable_set_require_rgba_sizes(app->glArea, 8, 8, 8, 8); #endif gtk_widget_set_events(GTK_WIDGET(app->glArea), GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK | GDK_POINTER_MOTION_MASK); /* Load settings the can be applied before the simulation is initialized */ #ifdef GNOME applySettingsGConfPre(app, app->client); #else applySettingsFilePre(app, app->settingsFile); #endif /* GNOME */ /* Full-Screen option from the command line (overrides above). */ if (fullScreen) app->fullScreen = TRUE; /* Initialize handlers to all events in the glArea */ initGLCallbacks(app); /* Handler than completes initialization when the glArea is realized */ g_signal_connect(G_OBJECT(app->glArea), "realize", G_CALLBACK(initRealize), app); /* Create the main menu bar */ createMainMenu(app->mainWindow, app); /* Initialize the context menu handler */ GTKContextMenuHandler *handler = new GTKContextMenuHandler(app); /* Set context menu handler for the core */ app->core->setContextMenuHandler(handler); #ifdef GNOME /* Set window contents (GNOME) */ gnome_app_set_contents((GnomeApp *)app->mainWindow, GTK_WIDGET(mainBox)); #else /* Set window contents (GTK) */ gtk_container_add(GTK_CONTAINER(app->mainWindow), GTK_WIDGET(mainBox)); #endif /* GNOME */ gtk_box_pack_start(GTK_BOX(mainBox), app->mainMenu, FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(mainBox), app->glArea, TRUE, TRUE, 0); gtk_window_set_default_icon_from_file("celestia-logo.png", NULL); /* Set focus to glArea widget */ gtk_widget_set_can_focus(GTK_WIDGET(app->glArea), true); gtk_widget_grab_focus(GTK_WIDGET(app->glArea)); /* Initialize the Watcher */ gtkWatcher = new GtkWatcher(app->core, app); /* Unload the splash screen */ splashEnd(ss); gtk_widget_show_all(app->mainWindow); /* HACK: Now that window is drawn, set minimum window size */ gtk_widget_set_size_request(app->glArea, 320, 240); #ifdef GNOME initSettingsGConfNotifiers(app); #endif /* GNOME */ /* Set the ready flag */ app->bReady = TRUE; /* Call Main GTK Loop */ gtk_main(); g_free(app); return 0; } #ifdef WIN32 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { return main(__argc, __argv); } #endif /* WIN32 */