CelestiaContent/src/celestia/gtk/menu-context.cpp

449 lines
15 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: menu-context.cpp,v 1.4 2008-01-03 00:20:33 vincent_gian Exp $
*/
#include <algorithm>
#include <gtk/gtk.h>
#include <celengine/simulation.h>
#include <celestia/celestiacore.h>
#include <celestia/helper.h>
#include <celutil/utf8.h>
#include "menu-context.h"
#include "actions.h"
#include "common.h"
/* Definitions: Callbacks */
static void wrapAction(GtkAction* action);
static void menuMark();
static void menuUnMark();
static void handleContextPrimary();
static void handleContextPlanet(gpointer data);
static void handleContextSurface(gpointer data);
/* Definitions: Helpers */
static GtkMenuItem* AppendMenu(GtkWidget* parent, GtkSignalFunc callback, const gchar* name, gpointer extra);
static GtkMenu* CreatePlanetarySystemMenu(string parentName, const PlanetarySystem* psys);
static GtkMenu* CreateAlternateSurfaceMenu(const vector<string>& surfaces);
/* There is no way to pass the AppData struct to the menu at this time. This
* keeps the global variable local to this file, at least conceptually. */
static AppData* app;
/* Initializer. Sets the appData */
GTKContextMenuHandler::GTKContextMenuHandler(AppData* _app) :
CelestiaCore::ContextMenuHandler()
{
// FIXME: a workaround to have it referenced by any menu callback
app = _app;
}
/* ENTRY: Context menu (event handled by appCore)
* Normally, float x and y, but unused, so removed. */
void GTKContextMenuHandler::requestContextMenu(float, float, Selection sel)
{
GtkWidget* popup = gtk_menu_new();
string name;
switch (sel.getType())
{
case Selection::Type_Body:
{
name = sel.body()->getName();
AppendMenu(popup, NULL, name.c_str(), gtk_action_group_get_action(app->agMain, "CenterSelection"));
AppendMenu(popup, NULL, NULL, 0);
AppendMenu(popup, NULL, "_Goto", gtk_action_group_get_action(app->agMain, "GotoSelection"));
AppendMenu(popup, NULL, "_Follow", gtk_action_group_get_action(app->agMain, "FollowSelection"));
AppendMenu(popup, NULL, "S_ync Orbit", gtk_action_group_get_action(app->agMain, "SyncSelection"));
/* Add info eventually:
* AppendMenu(popup, NULL, "_Info", 0); */
if (Helper::hasPrimary(sel.body()))
{
AppendMenu(popup, GTK_SIGNAL_FUNC(handleContextPrimary), "Select Primary Body", NULL);
}
const PlanetarySystem* satellites = sel.body()->getSatellites();
if (satellites != NULL && satellites->getSystemSize() != 0)
{
GtkMenu* satMenu = CreatePlanetarySystemMenu(name, satellites);
gtk_menu_item_set_submenu(AppendMenu(popup, NULL, "_Satellites", 0), GTK_WIDGET(satMenu));
}
vector<string>* altSurfaces = sel.body()->getAlternateSurfaceNames();
if (altSurfaces != NULL)
{
if (altSurfaces->size() > 0)
{
GtkMenu* surfMenu = CreateAlternateSurfaceMenu(*altSurfaces);
gtk_menu_item_set_submenu(AppendMenu(popup, NULL, "_Alternate Surfaces", 0), GTK_WIDGET(surfMenu));
delete altSurfaces;
}
}
}
break;
case Selection::Type_Star:
{
Simulation* sim = app->simulation;
name = ReplaceGreekLetterAbbr(sim->getUniverse()->getStarCatalog()->getStarName(*(sel.star())));
AppendMenu(popup, NULL, name.c_str(), gtk_action_group_get_action(app->agMain, "CenterSelection"));
AppendMenu(popup, NULL, NULL, 0);
AppendMenu(popup, NULL, "_Goto", gtk_action_group_get_action(app->agMain, "GotoSelection"));
/* Add info eventually:
* AppendMenu(popup, NULL, "_Info", 0); */
SolarSystemCatalog* solarSystemCatalog = sim->getUniverse()->getSolarSystemCatalog();
SolarSystemCatalog::iterator iter = solarSystemCatalog->find(sel.star()->getCatalogNumber());
if (iter != solarSystemCatalog->end())
{
SolarSystem* solarSys = iter->second;
GtkMenu* planetsMenu = CreatePlanetarySystemMenu(name, solarSys->getPlanets());
if (name == "Sol")
gtk_menu_item_set_submenu(AppendMenu(popup, NULL, "Orbiting Bodies", 0), GTK_WIDGET(planetsMenu));
else
gtk_menu_item_set_submenu(AppendMenu(popup, NULL, "Planets", 0), GTK_WIDGET(planetsMenu));
}
}
break;
case Selection::Type_DeepSky:
{
AppendMenu(popup, NULL, app->simulation->getUniverse()->getDSOCatalog()->getDSOName(sel.deepsky()).c_str(), gtk_action_group_get_action(app->agMain, "CenterSelection"));
AppendMenu(popup, NULL, NULL, 0);
AppendMenu(popup, NULL, "_Goto", gtk_action_group_get_action(app->agMain, "GotoSelection"));
AppendMenu(popup, NULL, "_Follow", gtk_action_group_get_action(app->agMain, "FollowSelection"));
/* Add info eventually:
* AppendMenu(popup, NULL, "_Info", 0); */
}
break;
case Selection::Type_Location:
break;
default:
break;
}
if (app->simulation->getUniverse()->isMarked(sel, 1))
AppendMenu(popup, menuUnMark, "_Unmark", 0);
else
AppendMenu(popup, menuMark, "_Mark", 0);
app->simulation->setSelection(sel);
gtk_widget_show_all(popup);
gtk_menu_popup(GTK_MENU(popup), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
}
/* CALLBACK: Wrap a GtkAction. The whole context menu should be replaced at
* some point */
static void wrapAction(GtkAction* action)
{
gtk_action_activate(action);
}
/* CALLBACK: Mark the selected object. Might some day be a GtkAction. */
static void menuMark()
{
Simulation* sim = app->simulation;
if (sim->getUniverse() != NULL)
{
MarkerRepresentation markerRep(MarkerRepresentation::Diamond, 10.0f, Color(0.0f, 1.0f, 0.0f, 0.9f));
sim->getUniverse()->markObject(sim->getSelection(), markerRep, 1);
}
}
/* CALLBACK: Unmark the selected object. Might some day be a GtkAction. */
static void menuUnMark()
{
Simulation* sim = app->simulation;
if (sim->getUniverse() != NULL)
sim->getUniverse()->unmarkObject(sim->getSelection(), 1);
}
/* CALLBACK: Handle a planetary selection from the context menu. */
static void handleContextPlanet(gpointer data)
{
int value = GPOINTER_TO_INT(data);
/* Handle the satellite/child object submenu */
Selection sel = app->simulation->getSelection();
switch (sel.getType())
{
case Selection::Type_Star:
app->simulation->selectPlanet(value);
break;
case Selection::Type_Body:
{
PlanetarySystem* satellites = (PlanetarySystem*) sel.body()->getSatellites();
app->simulation->setSelection(Selection(satellites->getBody(value)));
break;
}
case Selection::Type_DeepSky:
/* Current deep sky object/galaxy implementation does
* not have children to select. */
break;
case Selection::Type_Location:
break;
default:
break;
}
}
/* CALLBACK: Handle an object's primary selection */
static void handleContextPrimary()
{
Selection sel = app->simulation->getSelection();
if (sel.body() != NULL)
{
app->simulation->setSelection(Helper::getPrimary(sel.body()));
}
}
/* CALLBACK: Handle an alternate surface from the context menu. */
static void handleContextSurface(gpointer data)
{
int value = GPOINTER_TO_INT(data);
/* Handle the alternate surface submenu */
Selection sel = app->simulation->getSelection();
if (sel.body() != NULL)
{
guint index = value - 1;
vector<string>* surfNames = sel.body()->getAlternateSurfaceNames();
if (surfNames != NULL)
{
string surfName;
if (index < surfNames->size())
surfName = surfNames->at(index);
app->simulation->getActiveObserver()->setDisplayedSurface(surfName);
delete surfNames;
}
}
}
/* HELPER: Append a menu item and return pointer. Used for context menu. */
static GtkMenuItem* AppendMenu(GtkWidget* parent, GtkSignalFunc callback, const gchar* name, gpointer extra)
{
GtkWidget* menuitem;
gpointer data;
/* Check for separator */
if (name == NULL)
menuitem = gtk_separator_menu_item_new();
else
menuitem = gtk_menu_item_new_with_mnemonic(name);
/* If no callback was provided, pass GtkAction, else convert to pointer */
if (callback == NULL && extra != 0)
{
callback = G_CALLBACK(wrapAction);
data = extra;
}
else
data = GINT_TO_POINTER(extra);
/* Add handler */
if (callback != NULL)
g_signal_connect_swapped (G_OBJECT(menuitem), "activate",
G_CALLBACK(callback),
data);
gtk_menu_shell_append(GTK_MENU_SHELL(parent), menuitem);
return GTK_MENU_ITEM(menuitem);
}
/* Typedefs and structs for sorting objects by name in context menu. */
typedef pair<int,string> IntStrPair;
typedef vector<IntStrPair> IntStrPairVec;
struct IntStrPairComparePredicate
{
IntStrPairComparePredicate() : dummy(0) {}
bool operator()(const IntStrPair pair1, const IntStrPair pair2) const
{
return (pair1.second.compare(pair2.second) < 0);
}
int dummy;
};
/* HELPER: Create planetary submenu for context menu, return menu pointer. */
static GtkMenu* CreatePlanetarySystemMenu(string parentName, const PlanetarySystem* psys)
{
/*
* Modified from winmain.cpp
*
* Use some vectors to categorize and sort the bodies within this
* PlanetarySystem. In order to generate sorted menus, we must carry the
* name and menu index as a single unit in the sort. One obvious way is to
* create a vector<pair<int,string>> and then use a comparison predicate
* to sort the vector based on the string in each pair.
*/
/* Declare vector<pair<int,string>> objects for each classification of body */
vector<IntStrPair> asteroids;
vector<IntStrPair> comets;
vector<IntStrPair> invisibles;
vector<IntStrPair> moons;
vector<IntStrPair> minorMoons;
vector<IntStrPair> planets;
vector<IntStrPair> dwarfPlanets;
vector<IntStrPair> spacecraft;
/* We will use these objects to iterate over all the above vectors */
vector<IntStrPairVec> objects;
vector<string> menuNames;
/* Place each body in the correct vector based on classification */
GtkWidget* menu = gtk_menu_new();
for (int i = 0; i < psys->getSystemSize(); i++)
{
Body* body = psys->getBody(i);
switch(body->getClassification())
{
case Body::Asteroid:
asteroids.push_back(make_pair(i, body->getName()));
break;
case Body::Comet:
comets.push_back(make_pair(i, body->getName()));
break;
case Body::Invisible:
invisibles.push_back(make_pair(i, body->getName()));
break;
case Body::Moon:
moons.push_back(make_pair(i, body->getName()));
break;
case Body::MinorMoon:
minorMoons.push_back(make_pair(i, body->getName()));
break;
case Body::Planet:
planets.push_back(make_pair(i, body->getName()));
break;
case Body::DwarfPlanet:
dwarfPlanets.push_back(make_pair(i, body->getName()));
break;
case Body::Spacecraft:
spacecraft.push_back(make_pair(i, body->getName()));
break;
}
}
/* Add each vector of PlanetarySystem bodies to a vector to iterate over */
objects.push_back(asteroids);
menuNames.push_back("Asteroids");
objects.push_back(comets);
menuNames.push_back("Comets");
objects.push_back(invisibles);
menuNames.push_back("Invisibles");
objects.push_back(moons);
menuNames.push_back("Moons");
objects.push_back(minorMoons);
menuNames.push_back("Minor moons");
objects.push_back(planets);
menuNames.push_back("Planets");
objects.push_back(dwarfPlanets);
menuNames.push_back("Dwarf planets");
objects.push_back(spacecraft);
menuNames.push_back("Spacecraft");
/* Now sort each vector and generate submenus */
IntStrPairComparePredicate pred;
vector<IntStrPairVec>::iterator obj;
vector<IntStrPair>::iterator it;
vector<string>::iterator menuName;
GtkWidget* subMenu;
int numSubMenus;
/* Count how many submenus we need to create */
numSubMenus = 0;
for (obj=objects.begin(); obj != objects.end(); obj++)
{
if (obj->size() > 0)
numSubMenus++;
}
menuName = menuNames.begin();
for (obj=objects.begin(); obj != objects.end(); obj++)
{
/* Only generate a submenu if the vector is not empty */
if (obj->size() > 0)
{
/* Don't create a submenu for a single item */
if (obj->size() == 1)
{
it=obj->begin();
AppendMenu(menu, GTK_SIGNAL_FUNC(handleContextPlanet), it->second.c_str(), GINT_TO_POINTER(it->first));
}
else
{
/* Skip sorting if we are dealing with the planets in our own
* Solar System. */
if (parentName != "Sol" || *menuName != "Planets")
sort(obj->begin(), obj->end(), pred);
if (numSubMenus > 1)
{
/* Add items to submenu */
subMenu = gtk_menu_new();
for(it=obj->begin(); it != obj->end(); it++)
AppendMenu(subMenu, GTK_SIGNAL_FUNC(handleContextPlanet), it->second.c_str(), GINT_TO_POINTER(it->first));
gtk_menu_item_set_submenu(AppendMenu(menu, NULL, menuName->c_str(), 0), GTK_WIDGET(subMenu));
}
else
{
/* Just add items to the popup */
for(it=obj->begin(); it != obj->end(); it++)
AppendMenu(menu, GTK_SIGNAL_FUNC(handleContextPlanet), it->second.c_str(), GINT_TO_POINTER(it->first));
}
}
}
menuName++;
}
return GTK_MENU(menu);
}
/* HELPER: Create surface submenu for context menu, return menu pointer. */
static GtkMenu* CreateAlternateSurfaceMenu(const vector<string>& surfaces)
{
GtkWidget* menu = gtk_menu_new();
AppendMenu(menu, GTK_SIGNAL_FUNC(handleContextSurface), "Normal", 0);
for (guint i = 0; i < surfaces.size(); i++)
{
AppendMenu(menu, GTK_SIGNAL_FUNC(handleContextSurface), surfaces[i].c_str(), GINT_TO_POINTER(i+1));
}
return GTK_MENU(menu);
}