561 lines
18 KiB
C++
561 lines
18 KiB
C++
/*
|
||
* 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 */
|