celestia/src/celestia/gtk/main.cpp

561 lines
18 KiB
C++
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* Celestia GTK+ Front-End
* Copyright (C) 2005 Pat Suwalski <pat@suwalski.net>
*
* 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 <config.h>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <time.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif /* WIN32 */
#include <gtk/gtk.h>
#include <gtk/gtkgl.h>
#include <celengine/astro.h>
#include <celengine/galaxy.h>
#include <celengine/glsupport.h>
#include <celengine/simulation.h>
#include <celestia/celestiacore.h>
#include <celutil/debug.h>
/* Includes for the GNOME front-end */
#ifdef GNOME
#include <gnome.h>
#include <libgnomeui/libgnomeui.h>
#include <gconf/gconf-client.h>
#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 gchar* configFile = NULL;
static gchar* installDir = NULL;
static gchar** extrasDir = NULL;
static gboolean fullScreen = FALSE;
static gboolean noSplash = FALSE;
/* Command-Line Options specification */
static GOptionEntry optionEntries[] =
{
{ "conf", 'c', 0, G_OPTION_ARG_FILENAME, &configFile, "Alternate configuration file", "file" },
{ "dir", 'd', 0, G_OPTION_ARG_FILENAME, &installDir, "Alternate installation directory", "directory" },
{ "extrasdir", 'e', 0, G_OPTION_ARG_FILENAME_ARRAY, &extrasDir, "Additional \"extras\" directory", "directory" },
{ "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &fullScreen, "Start full-screen", NULL },
{ "nosplash", 's', 0, G_OPTION_ARG_NONE, &noSplash, "Disable splash screen", NULL },
{ NULL, '\0', 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
};
/* 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)
{
if (!gl::init() || !gl::checkVersion(gl::GL_2_1))
{
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 2.1.");
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 framerate limiting is off, set it so. */
if (!app->renderer->getVideoSync())
gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_action_group_get_action(app->agMain, "VideoSync")), FALSE);
/* 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(widget->window, 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;
/* Command line option parsing */
GError *error = NULL;
GOptionContext* context = g_option_context_new("");
g_option_context_add_main_entries(context, optionEntries, NULL);
g_option_context_add_group(context, gtk_get_option_group(TRUE));
g_option_context_parse(context, &argc, &argv, &error);
if (error != NULL)
{
g_print("Error in command line options. Use --help for full list.\n");
exit(1);
}
/* At this point, the argument count should be 1 or 2, with the lastX
* potentially being a cel:// URL. */
/* If there's an argument left, assume it's a URL. This happens here
* because it's after the saved prefs are applied. The appCore gets
* initialized elsewhere. */
if (argc > 1)
app->startURL = argv[argc - 1];
if (installDir == NULL)
installDir = (gchar*)CONFIG_DATA_DIR;
if (chdir(installDir) == -1)
cerr << "Cannot chdir to '" << installDir << "', probably due to improper installation.\n";
#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();
if (app->core == NULL)
{
cerr << "Failed to initialize Celestia core.\n";
return 1;
}
app->renderer = app->core->getRenderer();
g_assert(app->renderer);
/* Parse simulation arguments */
string altConfig;
if (configFile != NULL)
altConfig = string(configFile);
vector<fs::path> configDirs;
if (extrasDir != NULL)
{
/* Add each extrasDir to the vector */
int i = 0;
while (extrasDir[i] != NULL)
{
configDirs.push_back(extrasDir[i]);
i++;
}
}
/* Initialize the simulation */
if (!app->core->initSimulation(altConfig, configDirs, ss->notifier))
return 1;
app->simulation = app->core->getSimulation();
g_assert(app->simulation);
app->renderer->setSolarSystemMaxDistance(app->core->getConfig()->SolarSystemMaxDistance);
#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(GTK_OBJECT(app->mainWindow), "destroy",
G_CALLBACK(actionQuit), app);
/* 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<GdkGLConfigMode>
(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<GdkGLConfigMode>
(GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH));
if (glconfig == NULL)
{
g_print ("*** No appropriate OpenGL-capable visual found.\n");
exit(1);
}
}
/* 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. */
gtk_widget_set_gl_capability(app->glArea,
glconfig,
NULL,
TRUE,
GDK_GL_RGBA_TYPE);
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_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(GTK_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_FLAGS(app->glArea, GTK_CAN_FOCUS);
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 */