celestiacore.cpp
上传用户:center1979
上传日期:2022-07-26
资源大小:50633k
文件大小:147k
源码类别:

OpenGL

开发平台:

Visual C++

  1. // celestiacore.cpp
  2. //
  3. // Platform-independent UI handling and initialization for Celestia.
  4. // winmain, gtkmain, and glutmain are thin, platform-specific modules
  5. // that sit directly on top of CelestiaCore and feed it mouse and
  6. // keyboard events.  CelestiaCore then turns those events into calls
  7. // to Renderer and Simulation.
  8. //
  9. // Copyright (C) 2001-2009, the Celestia Development Team
  10. //
  11. // This program is free software; you can redistribute it and/or
  12. // modify it under the terms of the GNU General Public License
  13. // as published by the Free Software Foundation; either version 2
  14. // of the License, or (at your option) any later version.
  15. #include <cstdio>
  16. #include <iostream>
  17. #include <fstream>
  18. #include <iomanip>
  19. #include <algorithm>
  20. #include <cstdlib>
  21. #include <cctype>
  22. #include <cstring>
  23. #include <cassert>
  24. #include <ctime>
  25. #include <celengine/gl.h>
  26. #include <celmath/vecmath.h>
  27. #include <celmath/quaternion.h>
  28. #include <celmath/mathlib.h>
  29. #include <celutil/util.h>
  30. #include <celutil/filetype.h>
  31. #include <celutil/directory.h>
  32. #include <celutil/formatnum.h>
  33. #include <celengine/astro.h>
  34. #include <celengine/overlay.h>
  35. #include <celengine/console.h>
  36. #include <celengine/execution.h>
  37. #include <celengine/cmdparser.h>
  38. #include <celengine/multitexture.h>
  39. #include <celengine/spiceinterface.h>
  40. #include <celengine/axisarrow.h>
  41. #include <celengine/planetgrid.h>
  42. #include <celengine/visibleregion.h>
  43. #include "favorites.h"
  44. #include "celestiacore.h"
  45. #include <celutil/debug.h>
  46. #include <celutil/utf8.h>
  47. #include "url.h"
  48. #ifdef CELX
  49. #include <celengine/scriptobject.h>
  50. #endif
  51. #ifdef _WIN32
  52. #define TIMERATE_PRINTF_FORMAT "%.12g"
  53. #else
  54. #define TIMERATE_PRINTF_FORMAT "%'.12g"
  55. #endif
  56. using namespace std;
  57. static const int DragThreshold = 3;
  58. // Perhaps you'll want to put this stuff in configuration file.
  59. static const double CoarseTimeScaleFactor = 10.0;
  60. static const double FineTimeScaleFactor = 2.0;
  61. static const double fMinSlewRate = 3.0;
  62. static const double fMaxKeyAccel = 20.0;
  63. static const float fAltitudeThreshold = 4.0f;
  64. static const float RotationBraking = 10.0f;
  65. static const float RotationDecay = 2.0f;
  66. static const double MaximumTimeRate = 1.0e15;
  67. static const double MinimumTimeRate = 1.0e-15;
  68. static const float stdFOV = degToRad(45.0f);
  69. static const float MaximumFOV = degToRad(120.0f);
  70. static const float MinimumFOV = degToRad(0.001f);
  71. static float KeyRotationAccel = degToRad(120.0f);
  72. static float MouseRotationSensitivity = degToRad(1.0f);
  73. static const int ConsolePageRows = 10;
  74. static Console console(200, 120);
  75. static void warning(string s)
  76. {
  77.     cout << s;
  78. }
  79. struct OverlayImage
  80. {
  81.     Texture* texture;
  82.     int xSize;
  83.     int ySize;
  84.     int left;
  85.     int bottom;
  86. };
  87. vector<OverlayImage> overlayImages;
  88. // Extremely basic implementation of an ExecutionEnvironment for
  89. // running scripts.
  90. class CoreExecutionEnvironment : public ExecutionEnvironment
  91. {
  92. private:
  93.     CelestiaCore& core;
  94. public:
  95.     CoreExecutionEnvironment(CelestiaCore& _core) : core(_core)
  96.     {
  97.     }
  98.     Simulation* getSimulation() const
  99.     {
  100.         return core.getSimulation();
  101.     }
  102.     Renderer* getRenderer() const
  103.     {
  104.         return core.getRenderer();
  105.     }
  106.     CelestiaCore* getCelestiaCore() const
  107.     {
  108.         return &core;
  109.     }
  110.     void showText(string s, int horig, int vorig, int hoff, int voff,
  111.                   double duration)
  112.     {
  113.         core.showText(s, horig, vorig, hoff, voff, duration);
  114.     }
  115. };
  116. // If right dragging to rotate, adjust the rotation rate based on the
  117. // distance from the reference object.  This makes right drag rotation
  118. // useful even when the camera is very near the surface of an object.
  119. // Disable adjustments if the reference is a deep sky object, since they
  120. // have no true surface (and the observer is likely to be inside one.)
  121. float ComputeRotationCoarseness(Simulation& sim)
  122. {
  123.     float coarseness = 1.5f;
  124.     Selection selection = sim.getActiveObserver()->getFrame()->getRefObject();
  125.     if (selection.getType() == Selection::Type_Star ||
  126.         selection.getType() == Selection::Type_Body)
  127.     {
  128.         double radius = selection.radius();
  129.         double t = sim.getTime();
  130.         UniversalCoord observerPosition = sim.getActiveObserver()->getPosition();
  131.         UniversalCoord selectionPosition = selection.getPosition(t);
  132.         double distance = astro::microLightYearsToKilometers(observerPosition.distanceTo(selectionPosition));
  133.         double altitude = distance - radius;
  134.         if (altitude > 0.0 && altitude < radius)
  135.         {
  136.             coarseness *= (float) max(0.01, altitude / radius);
  137.         }
  138.     }
  139.     return coarseness;
  140. }
  141. View::View(View::Type _type,
  142.            Observer* _observer,
  143.            float _x, float _y,
  144.            float _width, float _height) :
  145.     type(_type),
  146.     observer(_observer),
  147.     parent(0),
  148.     child1(0),
  149.     child2(0),
  150.     x(_x),
  151.     y(_y),
  152.     width(_width),
  153.     height(_height),
  154.     renderFlags(0),
  155.     labelMode(0),
  156.     zoom(1),
  157.     alternateZoom(1)
  158. {
  159. }
  160. void View::mapWindowToView(float wx, float wy, float& vx, float& vy) const
  161. {
  162.     vx = (wx - x) / width;
  163.     vy = (wy + (y + height - 1)) / height;
  164.     vx = (vx - 0.5f) * (width / height);
  165.     vy = 0.5f - vy;
  166. }
  167. void View::walkTreeResize(View* sibling, int sign) {
  168.    float ratio;
  169.    switch (parent->type)
  170.     {
  171.     case View::HorizontalSplit:
  172.         ratio = parent->height / (parent->height -  height);
  173.         sibling->height *= ratio;
  174.         if (sign == 1)
  175.         {
  176.             sibling->y = parent->y + (sibling->y - parent->y) * ratio;
  177.         }
  178.         else
  179.         {
  180.             sibling->y = parent->y + (sibling->y - (y + height)) * ratio;
  181.         }
  182.         break;
  183.     case View::VerticalSplit:
  184.         ratio = parent->width / (parent->width - width);
  185.         sibling->width *= ratio;
  186.         if (sign == 1)
  187.         {
  188.             sibling->x = parent->x + (sibling->x - parent->x) * ratio;
  189.         }
  190.         else
  191.         {
  192.             sibling->x = parent->x + (sibling->x - (x + width) ) * ratio;
  193.         }
  194.         break;
  195.     case View::ViewWindow:
  196.         break;
  197.     }
  198.     if (sibling->child1) walkTreeResize(sibling->child1, sign);
  199.     if (sibling->child2) walkTreeResize(sibling->child2, sign);
  200. }
  201. bool View::walkTreeResizeDelta(View* v, float delta, bool check)
  202. {
  203.    View *p=v;
  204.    int sign = -1;
  205.    float ratio;
  206.    double newSize;
  207.    if (v->child1)
  208.    {
  209.        if (!walkTreeResizeDelta(v->child1, delta, check))
  210.            return false;
  211.    }
  212.    if (v->child2)
  213.    {
  214.        if (!walkTreeResizeDelta(v->child2, delta, check))
  215.            return false;
  216.    }
  217.    while ( p != child1 && p != child2 && (p = p->parent) ) ;
  218.    if (p == child1) sign = 1;
  219.    switch (type)
  220.     {
  221.     case View::HorizontalSplit:
  222.         delta = -delta;
  223.         ratio = (p->height  + sign * delta) / p->height;
  224.         newSize = v->height * ratio;
  225.         if (newSize <= .1) return false;
  226.         if (check) return true;
  227.         v->height = (float) newSize;
  228.         if (sign == 1)
  229.         {
  230.             v->y = p->y + (v->y - p->y) * ratio;
  231.         }
  232.         else
  233.         {
  234.             v->y = p->y + delta + (v->y - p->y) * ratio;
  235.         }
  236.         break;
  237.     case View::VerticalSplit:
  238.         ratio = (p->width + sign * delta) / p->width;
  239.         newSize = v->width * ratio;
  240.         if (newSize <= .1) return false;
  241.         if (check) return true;
  242.         v->width = (float) newSize;
  243.         if (sign == 1)
  244.         {
  245.             v->x = p->x + (v->x - p->x) * ratio;
  246.         }
  247.         else
  248.         {
  249.             v->x = p->x + delta + (v->x - p->x) * ratio;
  250.         }
  251.         break;
  252.     case View::ViewWindow:
  253.         break;
  254.     }
  255.     return true;
  256. }
  257. CelestiaCore::CelestiaCore() :
  258.     config(NULL),
  259.     universe(NULL),
  260.     favorites(NULL),
  261.     destinations(NULL),
  262.     sim(NULL),
  263.     renderer(NULL),
  264.     overlay(NULL),
  265.     width(1),
  266.     height(1),
  267.     font(NULL),
  268.     titleFont(NULL),
  269.     messageText(""),
  270.     messageHOrigin(0),
  271.     messageVOrigin(0),
  272.     messageHOffset(0),
  273.     messageVOffset(0),
  274.     messageStart(0.0),
  275.     messageDuration(0.0),
  276.     typedText(""),
  277.     typedTextCompletionIdx(-1),
  278.     textEnterMode(KbNormal),
  279.     hudDetail(1),
  280.     dateFormat(astro::Date::Locale),
  281.     dateStrWidth(0),
  282.     overlayElements(ShowTime | ShowVelocity | ShowSelection | ShowFrame),
  283.     wireframe(false),
  284.     editMode(false),
  285.     altAzimuthMode(false),
  286.     showConsole(false),
  287.     lightTravelFlag(false),
  288.     flashFrameStart(0.0),
  289.     timer(NULL),
  290.     runningScript(NULL),
  291.     execEnv(NULL),
  292. #ifdef CELX
  293.     celxScript(NULL),
  294.     luaHook(NULL),
  295.     luaSandbox(NULL),
  296. #endif // CELX
  297.     scriptState(ScriptCompleted),
  298.     timeZoneBias(0),
  299.     showFPSCounter(false),
  300.     nFrames(0),
  301.     fps(0.0),
  302.     fpsCounterStartTime(0.0),
  303.     oldFOV(stdFOV),
  304.     mouseMotion(0.0f),
  305.     dollyMotion(0.0),
  306.     dollyTime(0.0),
  307.     zoomMotion(0.0),
  308.     zoomTime(0.0),
  309.     sysTime(0.0),
  310.     currentTime(0.0),
  311.     viewChanged(true),
  312.     joystickRotation(0.0f, 0.0f, 0.0f),
  313.     KeyAccel(1.0),
  314.     movieCapture(NULL),
  315.     recording(false),
  316.     contextMenuCallback(NULL),
  317.     logoTexture(NULL),
  318.     alerter(NULL),
  319.     cursorHandler(NULL),
  320.     defaultCursorShape(CelestiaCore::CrossCursor),
  321.     historyCurrent(0),
  322.     activeView(views.begin()),
  323.     showActiveViewFrame(false),
  324.     showViewFrames(true),
  325.     resizeSplit(0),
  326.     screenDpi(96),
  327.     distanceToScreen(400)
  328. {
  329.     /* Get a renderer here so it may be queried for capabilities of the
  330.        underlying engine even before rendering is enabled. It's initRenderer()
  331.        routine will be called much later. */
  332.     renderer = new Renderer();
  333.     timer = CreateTimer();
  334.     execEnv = new CoreExecutionEnvironment(*this);
  335.     int i;
  336.     for (i = 0; i < KeyCount; i++)
  337.     {
  338.         keysPressed[i] = false;
  339.         shiftKeysPressed[i] = false;
  340.     }
  341.     for (i = 0; i < JoyButtonCount; i++)
  342.         joyButtonsPressed[i] = false;
  343.     clog.rdbuf(console.rdbuf());
  344.     cerr.rdbuf(console.rdbuf());
  345.     console.setWindowHeight(ConsolePageRows);
  346. }
  347. CelestiaCore::~CelestiaCore()
  348. {
  349.     if (movieCapture != NULL)
  350.         recordEnd();
  351. #ifdef CELX
  352.     // Clean up all scripts
  353.     if (celxScript != NULL)
  354.         delete celxScript;
  355.     if (luaHook != NULL)
  356.         delete luaHook;
  357.     if (luaSandbox != NULL)
  358.         delete luaSandbox;
  359. #endif
  360.     delete execEnv;
  361. }
  362. void CelestiaCore::readFavoritesFile()
  363. {
  364.     // Set up favorites list
  365.     if (config->favoritesFile != "")
  366.     {
  367.         ifstream in(config->favoritesFile.c_str(), ios::in);
  368.         if (in.good())
  369.         {
  370.             favorites = ReadFavoritesList(in);
  371.             if (favorites == NULL)
  372.             {
  373.                 warning(_("Error reading favorites file."));
  374.             }
  375.         }
  376.     }
  377. }
  378. void CelestiaCore::writeFavoritesFile()
  379. {
  380.     if (config->favoritesFile != "")
  381.     {
  382.         ofstream out(config->favoritesFile.c_str(), ios::out);
  383.         if (out.good())
  384.         WriteFavoritesList(*favorites, out);
  385.     }
  386. }
  387. void CelestiaCore::activateFavorite(FavoritesEntry& fav)
  388. {
  389.     sim->cancelMotion();
  390.     sim->setTime(fav.jd);
  391.     sim->setObserverPosition(fav.position);
  392.     sim->setObserverOrientation(fav.orientation);
  393.     sim->setSelection(sim->findObjectFromPath(fav.selectionName));
  394.     sim->setFrame(fav.coordSys, sim->getSelection());
  395. }
  396. void CelestiaCore::addFavorite(string name, string parentFolder, FavoritesList::iterator* iter)
  397. {
  398.     FavoritesList::iterator pos;
  399.     if(!iter)
  400.         pos = favorites->end();
  401.     else
  402.         pos = *iter;
  403.     FavoritesEntry* fav = new FavoritesEntry();
  404.     fav->jd = sim->getTime();
  405.     fav->position = sim->getObserver().getPosition();
  406.     fav->orientation = sim->getObserver().getOrientationf();
  407.     fav->name = name;
  408.     fav->isFolder = false;
  409.     fav->parentFolder = parentFolder;
  410.     Selection sel = sim->getSelection();
  411.     if (sel.deepsky() != NULL)
  412.         fav->selectionName = sim->getUniverse()->getDSOCatalog()->getDSOName(sel.deepsky());
  413.     else
  414.         fav->selectionName = sel.getName();
  415.     fav->coordSys = sim->getFrame()->getCoordinateSystem();
  416.     favorites->insert(pos, fav);
  417. }
  418. void CelestiaCore::addFavoriteFolder(string name, FavoritesList::iterator* iter)
  419. {
  420.     FavoritesList::iterator pos;
  421.     if(!iter)
  422.         pos = favorites->end();
  423.     else
  424.         pos = *iter;
  425.     FavoritesEntry* fav = new FavoritesEntry();
  426.     fav->name = name;
  427.     fav->isFolder = true;
  428.     favorites->insert(pos, fav);
  429. }
  430. FavoritesList* CelestiaCore::getFavorites()
  431. {
  432.     return favorites;
  433. }
  434. const DestinationList* CelestiaCore::getDestinations()
  435. {
  436.     return destinations;
  437. }
  438. // Used in the super-secret edit mode
  439. void showSelectionInfo(const Selection& sel)
  440. {
  441.     Vec3f axis(0.0f, 1.0, 0.0f);
  442.     float angle = 0.0f;
  443.     if (sel.deepsky() != NULL)
  444.         sel.deepsky()->getOrientation().getAxisAngle(axis, angle);
  445.     else if (sel.body() != NULL)
  446.         sel.body()->getOrientation().getAxisAngle(axis, angle);
  447.     cout << sel.getName() << 'n';
  448.     cout << _("Orientation: ") << '[' << axis.x << ',' << axis.y << ',' << axis.z << "], " << radToDeg(angle) << 'n';
  449. }
  450. void CelestiaCore::cancelScript()
  451. {
  452.     if (runningScript != NULL)
  453.     {
  454.         delete runningScript;
  455.         scriptState = ScriptCompleted;
  456.         runningScript = NULL;
  457.     }
  458. #ifdef CELX
  459.     if (celxScript != NULL)
  460.     {
  461.         celxScript->cleanup();
  462.         if (textEnterMode & KbPassToScript)
  463.             setTextEnterMode(textEnterMode & ~KbPassToScript);
  464.         scriptState = ScriptCompleted;
  465.     }
  466. #endif
  467. }
  468. void CelestiaCore::runScript(CommandSequence* script)
  469. {
  470.     cancelScript();
  471.     if (runningScript == NULL && script != NULL && scriptState == ScriptCompleted)
  472.     {
  473.         scriptState = ScriptRunning;
  474.         runningScript = new Execution(*script, *execEnv);
  475.     }
  476. }
  477. void CelestiaCore::runScript(const string& filename)
  478. {
  479.     cancelScript();
  480.     string localeFilename = LocaleFilename(filename);
  481.     ContentType type = DetermineFileType(localeFilename);
  482.     if (type == Content_CelestiaLegacyScript)
  483.     {
  484.         ifstream scriptfile(localeFilename.c_str());
  485.         if (!scriptfile.good())
  486.         {
  487.             if (alerter != NULL)
  488.                 alerter->fatalError(_("Error opening script file."));
  489.             else
  490.                 flash(_("Error opening script file."));
  491.         }
  492.         else
  493.         {
  494.             CommandParser parser(scriptfile);
  495.             CommandSequence* script = parser.parse();
  496.             if (script == NULL)
  497.             {
  498.                 const vector<string>* errors = parser.getErrors();
  499.                 const char* errorMsg = "";
  500.                 if (errors->size() > 0)
  501.                     errorMsg = (*errors)[0].c_str();
  502.                 if (alerter != NULL)
  503.                     alerter->fatalError(errorMsg);
  504.                 else
  505.                     flash(errorMsg);
  506.             }
  507.             else
  508.             {
  509.                 runningScript = new Execution(*script, *execEnv);
  510.                 scriptState = sim->getPauseState()?ScriptPaused:ScriptRunning;
  511.             }
  512.         }
  513.     }
  514. #ifdef CELX
  515.     else if (type == Content_CelestiaScript)
  516.     {
  517.         ifstream scriptfile(localeFilename.c_str());
  518.         if (!scriptfile.good())
  519.         {
  520.             char errMsg[1024];
  521.             sprintf(errMsg, _("Error opening script '%s'"), localeFilename.c_str());
  522.             if (alerter != NULL)
  523.                 alerter->fatalError(errMsg);
  524.             else
  525.                 flash(errMsg);
  526.         }
  527.         if (celxScript == NULL)
  528.         {
  529.             celxScript = new LuaState();
  530.             celxScript->init(this);
  531.         }
  532.         int status = celxScript->loadScript(scriptfile, localeFilename);
  533.         if (status != 0)
  534.         {
  535.             string errMsg = celxScript->getErrorMessage();
  536.             if (errMsg.empty())
  537.                 errMsg = _("Unknown error opening script");
  538.             if (alerter != NULL)
  539.                 alerter->fatalError(errMsg);
  540.             else
  541.                 flash(errMsg);
  542.         }
  543.         else
  544.         {
  545.             // Coroutine execution; control may be transferred between the
  546.             // script and Celestia's event loop
  547.             if (!celxScript->createThread())
  548.             {
  549.                 const char* errMsg = _("Script coroutine initialization failed");
  550.                 if (alerter != NULL)
  551.                     alerter->fatalError(errMsg);
  552.                 else
  553.                     flash(errMsg);
  554.             }
  555.             else
  556.             {
  557.                 scriptState = sim->getPauseState()?ScriptPaused:ScriptRunning;
  558.             }
  559.         }
  560.     }
  561. #endif
  562.     else
  563.     {
  564.         if (alerter != NULL)
  565.             alerter->fatalError(_("Invalid filetype"));
  566.         else
  567.             flash(_("Invalid filetype"));
  568.     }
  569. }
  570. bool checkMask(int modifiers, int mask)
  571. {
  572.     return (modifiers & mask) == mask;
  573. }
  574. void CelestiaCore::mouseButtonDown(float x, float y, int button)
  575. {
  576.     setViewChanged();
  577.     mouseMotion = 0.0f;
  578. #ifdef CELX
  579.     if (celxScript != NULL)
  580.     {
  581.         if (celxScript->handleMouseButtonEvent(x, y, button, true))
  582.             return;
  583.     }
  584.     if (luaHook && luaHook->callLuaHook(this, "mousebuttondown", x, y, button))
  585.         return;
  586. #endif
  587.     if (views.size() > 1)
  588.     {
  589.         // To select the clicked into view before a drag.
  590.         pickView(x, y);
  591.     }
  592.     if (views.size() > 1 && button == LeftButton) // look if click is near a view border
  593.     {
  594.         View *v1 = 0, *v2 = 0;
  595.         for (list<View*>::iterator i = views.begin(); i != views.end(); i++)
  596.         {
  597.             View* v = *i;
  598.             if (v->type == View::ViewWindow)
  599.             {
  600.                 float vx, vy, vxp, vyp;
  601.                 vx = ( x / width - v->x ) / v->width;
  602.                 vy = ( (1 - y / height ) - v->y ) / v->height;
  603.                 vxp = vx * v->width * width;
  604.                 vyp = vy * v->height * height;
  605.                 if ( (vx >=0 && vx <= 1 && ( abs(vyp) <= 2 || abs(vyp - v->height * height) <= 2))
  606.                   || (vy >=0 && vy <= 1 && ( abs(vxp) <= 2 || abs(vxp - v->width * width) <= 2)) )
  607.                 {
  608.                     if (v1 == 0)
  609.                     {
  610.                         v1 = v;
  611.                     }
  612.                     else
  613.                     {
  614.                         v2 = v;
  615.                         break;
  616.                     }
  617.                 }
  618.             }
  619.         }
  620.         if (v2 != 0) {
  621.              // Look for common ancestor to v1 & v2 = split being draged.
  622.              View *p1 = v1, *p2 = v2;
  623.              while ( (p1 = p1->parent) )
  624.              {
  625.                  p2 = v2;
  626.                  while ( (p2 = p2->parent) && p1 != p2) ;
  627.                  if (p2 != 0) break;
  628.              }
  629.              if (p2 != 0)
  630.              {
  631.                  resizeSplit = p1;
  632.              }
  633.         }
  634.     }
  635. }
  636. void CelestiaCore::mouseButtonUp(float x, float y, int button)
  637. {
  638.     setViewChanged();
  639.     // Four pixel tolerance for picking
  640.     float pickTolerance = sim->getActiveObserver()->getFOV() / height * 4.0f;
  641.     if (resizeSplit)
  642.     {
  643.         resizeSplit = 0;
  644.         return;
  645.     }
  646. #ifdef CELX
  647.     if (celxScript != NULL)
  648.     {
  649.         if (celxScript->handleMouseButtonEvent(x, y, button, false))
  650.             return;
  651.     }
  652.     if (luaHook && luaHook->callLuaHook(this,"mousebuttonup", x, y, button))
  653.         return;
  654. #endif
  655.     // If the mouse hasn't moved much since it was pressed, treat this
  656.     // as a selection or context menu event.  Otherwise, assume that the
  657.     // mouse was dragged and ignore the event.
  658.     if (mouseMotion < DragThreshold)
  659.     {
  660.         if (button == LeftButton)
  661.         {
  662.             pickView(x, y);
  663.             float pickX, pickY;
  664.             float aspectRatio = ((float) width / (float) height);
  665.             (*activeView)->mapWindowToView((float) x / (float) width,
  666.                                         (float) y / (float) height,
  667.                                         pickX, pickY);
  668.             Vec3f pickRay =
  669.                 sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY);
  670.             Selection oldSel = sim->getSelection();
  671.             Selection newSel = sim->pickObject(pickRay, renderer->getRenderFlags(), pickTolerance);
  672.             addToHistory();
  673.             sim->setSelection(newSel);
  674.             if (!oldSel.empty() && oldSel == newSel)
  675.                 sim->centerSelection();
  676.         }
  677.         else if (button == RightButton)
  678.         {
  679.             float pickX, pickY;
  680.             float aspectRatio = ((float) width / (float) height);
  681.             (*activeView)->mapWindowToView((float) x / (float) width,
  682.                                         (float) y / (float) height,
  683.                                         pickX, pickY);
  684.             Vec3f pickRay =
  685.                 sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY);
  686.             Selection sel = sim->pickObject(pickRay, renderer->getRenderFlags(), pickTolerance);
  687.             if (!sel.empty())
  688.             {
  689.                 if (contextMenuCallback != NULL)
  690.                     contextMenuCallback(x, y, sel);
  691.             }
  692.         }
  693.         else if (button == MiddleButton)
  694.     {
  695.             if ((*activeView)->zoom != 1)
  696.         {
  697.                 (*activeView)->alternateZoom = (*activeView)->zoom;
  698.                 (*activeView)->zoom = 1;
  699.             }
  700.             else
  701.             {
  702.                 (*activeView)->zoom = (*activeView)->alternateZoom;
  703.             }
  704.             setFOVFromZoom();
  705.             // If AutoMag, adapt the faintestMag to the new fov
  706.             if((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
  707.         setFaintestAutoMag();
  708.     }
  709.     }
  710. }
  711. void CelestiaCore::mouseWheel(float motion, int modifiers)
  712. {
  713.     setViewChanged();
  714.     if (config->reverseMouseWheel) motion = -motion;
  715.     if (motion != 0.0)
  716.     {
  717.         if ((modifiers & ShiftKey) != 0)
  718.         {
  719.             zoomTime = currentTime;
  720.             zoomMotion = 0.25f * motion;
  721.         }
  722.         else
  723.         {
  724.             dollyTime = currentTime;
  725.             dollyMotion = 0.25f * motion;
  726.         }
  727.     }
  728. }
  729. /// Handles cursor shape changes on view borders if the cursorHandler is defined.
  730. /// This must be called on mouse move events on the OpenGL Widget.
  731. /// x and y are the pixel coordinates relative to the widget.
  732. void CelestiaCore::mouseMove(float x, float y)
  733. {
  734.     if (views.size() > 1 && cursorHandler != NULL)
  735.     {
  736.         /*View* v1 = 0;     Unused*/
  737.         /*View* v2 = 0;     Unused*/
  738.         for (list<View*>::iterator i = views.begin(); i != views.end(); i++)
  739.         {
  740.             View* v = *i;
  741.             if (v->type == View::ViewWindow)
  742.             {
  743.                 float vx, vy, vxp, vyp;
  744.                 vx = (x / width - v->x) / v->width;
  745.                 vy = ((1 - y / height) - v->y ) / v->height;
  746.                 vxp = vx * v->width * width;
  747.                 vyp = vy * v->height * height;
  748.                 if (vx >=0 && vx <= 1 && (abs(vyp) <= 2 || abs(vyp - v->height * height) <= 2))
  749.                 {
  750.                     cursorHandler->setCursorShape(CelestiaCore::SizeVerCursor);
  751.                     return;
  752.                 }
  753.                 else if (vy >=0 && vy <= 1 && (abs(vxp) <= 2 || abs(vxp - v->width * width) <= 2))
  754.                 {
  755.                     cursorHandler->setCursorShape(CelestiaCore::SizeHorCursor);
  756.                     return;
  757.                 }
  758.             }
  759.         }
  760.         cursorHandler->setCursorShape(defaultCursorShape);
  761.     }
  762.     return;
  763. }
  764. void CelestiaCore::mouseMove(float dx, float dy, int modifiers)
  765. {
  766.     if (modifiers != 0)
  767.         setViewChanged();
  768.     if (resizeSplit != 0)
  769.     {
  770.         switch(resizeSplit->type) {
  771.         case View::HorizontalSplit:
  772.             if (   resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dy / height, true)
  773.                 && resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dy / height, true))
  774.             {
  775.                 resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dy / height, false);
  776.                 resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dy / height, false);
  777.             }
  778.             break;
  779.         case View::VerticalSplit:
  780.             if (   resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dx / width, true)
  781.                 && resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dx / width, true)
  782.             )
  783.             {
  784.                 resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dx / width, false);
  785.                 resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dx / width, false);
  786.             }
  787.             break;
  788.         case View::ViewWindow:
  789.             break;
  790.         }
  791.         setFOVFromZoom();
  792.         return;
  793.     }
  794. #ifdef CELX
  795. if (luaHook &&
  796.             luaHook->callLuaHook(this,"mousebuttonmove", dx, dy, modifiers))
  797.         {
  798.             return;
  799.         }
  800. #endif
  801.     if ((modifiers & (LeftButton | RightButton)) != 0)
  802.     {
  803.         if (editMode && checkMask(modifiers, LeftButton | ShiftKey | ControlKey))
  804.         {
  805.             // Rotate the selected object
  806.             Selection sel = sim->getSelection();
  807.             Quatf q(1);
  808.             if (sel.getType() == Selection::Type_DeepSky)
  809.                 q = sel.deepsky()->getOrientation();
  810.             else if (sel.getType() == Selection::Type_Body)
  811.                 q = sel.body()->getOrientation();
  812.             q.yrotate(dx / width);
  813.             q.xrotate(dy / height);
  814.             if (sel.getType() == Selection::Type_DeepSky)
  815.                 sel.deepsky()->setOrientation(q);
  816.             else if (sel.getType() == Selection::Type_Body)
  817.                 sel.body()->setOrientation(q);
  818.         }
  819.         else if (editMode && checkMask(modifiers, RightButton | ShiftKey | ControlKey))
  820.         {
  821.             // Rotate the selected object about an axis from its center to the
  822.             // viewer.
  823.             Selection sel = sim->getSelection();
  824.             if (sel.deepsky() != NULL)
  825.             {
  826.                 double t = sim->getTime();
  827.                 Vec3d v = sel.getPosition(t) - sim->getObserver().getPosition();
  828.                 Vec3f axis((float) v.x, (float) v.y, (float) v.z);
  829.                 axis.normalize();
  830.                 Quatf r;
  831.                 r.setAxisAngle(axis, dx / width);
  832.                 Quatf q = sel.deepsky()->getOrientation();
  833.                 sel.deepsky()->setOrientation(r * q);
  834.             }
  835.         }
  836.         else if (checkMask(modifiers, LeftButton | RightButton) ||
  837.                  checkMask(modifiers, LeftButton | ControlKey))
  838.         {
  839.             // Y-axis controls distance (exponentially), and x-axis motion
  840.             // rotates the camera about the view normal.
  841.             float amount = dy / height;
  842.             sim->changeOrbitDistance(amount * 5);
  843.             if (dx * dx > dy * dy)
  844.             {
  845.                 Observer& observer = sim->getObserver();
  846.                 Vec3d v = Vec3d(0, 0, dx * -MouseRotationSensitivity);
  847.                 Quatd obsOrientation = observer.getOrientation();
  848.                 Quatd dr = 0.5 * (v * obsOrientation);
  849.                 obsOrientation += dr;
  850.                 obsOrientation.normalize();
  851.                 observer.setOrientation(obsOrientation);
  852.             }
  853.         }
  854.         else if (checkMask(modifiers, LeftButton | ShiftKey))
  855.         {
  856.             // Mouse zoom control
  857.             float amount = dy / height;
  858.             float minFOV = MinimumFOV;
  859.             float maxFOV = MaximumFOV;
  860.             float fov = sim->getActiveObserver()->getFOV();
  861.             // In order for the zoom to have the right feel, it should be
  862.             // exponential.
  863.             float newFOV = minFOV + (float) exp(log(fov - minFOV) + amount * 4);
  864.             if (newFOV > maxFOV)
  865.                 newFOV = maxFOV;
  866.             if (newFOV > minFOV)
  867.             {
  868.                 sim->getActiveObserver()->setFOV(newFOV);
  869.                 setZoomFromFOV();
  870.             }
  871.          if ((renderer->getRenderFlags() & Renderer::ShowAutoMag))
  872.         {
  873.             setFaintestAutoMag();
  874.          char buf[128];
  875.          sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
  876.          flash(buf);
  877.         }
  878.         }
  879.         else
  880.         {
  881.             Quatf q(1);
  882.             // For a small field of view, rotate the camera more finely
  883.             float coarseness = 1.5f;
  884.             if ((modifiers & RightButton) == 0)
  885.             {
  886.                 coarseness = radToDeg(sim->getActiveObserver()->getFOV()) / 30.0f;
  887.             }
  888.             else
  889.             {
  890.                 // If right dragging to rotate, adjust the rotation rate
  891.                 // based on the distance from the reference object.
  892.                 coarseness = ComputeRotationCoarseness(*sim);
  893.             }
  894.             q.yrotate(dx / width * coarseness);
  895.             q.xrotate(dy / height * coarseness);
  896.             if ((modifiers & RightButton) != 0)
  897.                 sim->orbit(q);
  898.             else
  899.                 sim->rotate(~q);
  900.         }
  901.         mouseMotion += abs(dy) + abs(dx);
  902.     }
  903. }
  904. /// Makes the view under x, y the active view.
  905. void CelestiaCore::pickView(float x, float y)
  906. {
  907.     if (x+2 < (*activeView)->x * width || x-2 > ((*activeView)->x + (*activeView)->width) * width
  908.         || (height - y)+2 < (*activeView)->y * height ||  (height - y)-2 > ((*activeView)->y + (*activeView)->height) * height)
  909.     {
  910.         activeView = views.begin();
  911.         while ( (activeView != views.end())
  912.                 &&
  913.                 ( (x+2 < (*activeView)->x * width || x-2 > ((*activeView)->x + (*activeView)->width) * width || (height - y)+2 < (*activeView)->y * height ||  (height - y)-2 > ((*activeView)->y + (*activeView)->height) * height)
  914.                   ||
  915.                   ((*activeView)->type != View::ViewWindow)
  916.                 )
  917.               )
  918.         {
  919.                 activeView++;
  920.         }
  921.         
  922.         // Make sure that we're left with a valid view
  923.         if (activeView == views.end())
  924.         {
  925.             activeView = views.begin();
  926.         }
  927.         sim->setActiveObserver((*activeView)->observer);
  928.         if (!showActiveViewFrame)
  929.             flashFrameStart = currentTime;
  930.         return;
  931.     }
  932. }
  933. void CelestiaCore::joystickAxis(int axis, float amount)
  934. {
  935.     setViewChanged();
  936.     float deadZone = 0.25f;
  937.     if (abs(amount) < deadZone)
  938.         amount = 0.0f;
  939.     else
  940.         amount = (amount - deadZone) * (1.0f / (1.0f - deadZone));
  941.     amount = sign(amount) * square(amount);
  942.     if (axis == Joy_XAxis)
  943.         joystickRotation.y = amount;
  944.     else if (axis == Joy_YAxis)
  945.         joystickRotation.x = -amount;
  946. }
  947. void CelestiaCore::joystickButton(int button, bool down)
  948. {
  949.     setViewChanged();
  950.     if (button >= 0 && button < JoyButtonCount)
  951.         joyButtonsPressed[button] = down;
  952. }
  953. static void scrollConsole(Console& con, int lines)
  954. {
  955.     int topRow = con.getWindowRow();
  956.     int height = con.getHeight();
  957.     if (lines < 0)
  958.     {
  959.         if (topRow + lines > -height)
  960.             console.setWindowRow(topRow + lines);
  961.         else
  962.             console.setWindowRow(-(height - 1));
  963.     }
  964.     else
  965.     {
  966.         if (topRow + lines <= -ConsolePageRows)
  967.             console.setWindowRow(topRow + lines);
  968.         else
  969.             console.setWindowRow(-ConsolePageRows);
  970.     }
  971. }
  972. void CelestiaCore::keyDown(int key, int modifiers)
  973. {
  974.     setViewChanged();
  975. #ifdef CELX
  976.     // TODO: should pass modifiers as a Lua table
  977.     if (luaHook && luaHook->callLuaHook(this,
  978.                                         "keydown",
  979.                                         (float) key, (float) modifiers))
  980.     {
  981.         return;
  982.     }
  983. #endif
  984.     switch (key)
  985.     {
  986.     case Key_F1:
  987.         sim->setTargetSpeed(0);
  988.         break;
  989.     case Key_F2:
  990.         sim->setTargetSpeed(astro::kilometersToMicroLightYears(1.0f));
  991.         break;
  992.     case Key_F3:
  993.         sim->setTargetSpeed(astro::kilometersToMicroLightYears(1000.0f));
  994.         break;
  995.     case Key_F4:
  996.         sim->setTargetSpeed((float) astro::kilometersToMicroLightYears(astro::speedOfLight));
  997.         break;
  998.     case Key_F5:
  999.         sim->setTargetSpeed((float) astro::kilometersToMicroLightYears(astro::speedOfLight * 10.0));
  1000.         break;
  1001.     case Key_F6:
  1002.         sim->setTargetSpeed(astro::AUtoMicroLightYears(1.0f));
  1003.         break;
  1004.     case Key_F7:
  1005.         sim->setTargetSpeed(1e6);
  1006.         break;
  1007.     case Key_F11:
  1008.         if (movieCapture != NULL)
  1009.         {
  1010.             if (isRecording())
  1011.                 recordPause();
  1012.             else
  1013.                 recordBegin();
  1014.         }
  1015.         break;
  1016.     case Key_F12:
  1017.         if (movieCapture != NULL)
  1018.             recordEnd();
  1019.         break;
  1020.     case Key_NumPad2:
  1021.     case Key_NumPad4:
  1022.     case Key_NumPad6:
  1023.     case Key_NumPad7:
  1024.     case Key_NumPad8:
  1025.     case Key_NumPad9:
  1026.         sim->setTargetSpeed(sim->getTargetSpeed());
  1027.         break;
  1028.     case Key_Down:
  1029.         if (showConsole)
  1030.             scrollConsole(console, 1);
  1031.         break;
  1032.     case Key_Up:
  1033.         if (showConsole)
  1034.             scrollConsole(console, -1);
  1035.         break;
  1036.     case Key_PageDown:
  1037.         if (showConsole)
  1038.             scrollConsole(console, ConsolePageRows);
  1039.         else
  1040.             back();
  1041.         break;
  1042.     case Key_PageUp:
  1043.         if (showConsole)
  1044.             scrollConsole(console, -ConsolePageRows);
  1045.         else
  1046.             forward();
  1047.         break;
  1048.     }
  1049.     if (KeyAccel < fMaxKeyAccel)
  1050.         KeyAccel *= 1.1;
  1051.     // Only process alphanumeric keys if we're not in text enter mode
  1052.     if (islower(key))
  1053.         key = toupper(key);
  1054.     if (!(key >= 'A' && key <= 'Z' && (textEnterMode != KbNormal) ))
  1055.     {
  1056.         if (modifiers & ShiftKey)
  1057.             shiftKeysPressed[key] = true;
  1058.         else
  1059.             keysPressed[key] = true;
  1060.     }
  1061. }
  1062. void CelestiaCore::keyUp(int key, int)
  1063. {
  1064.     setViewChanged();
  1065.     KeyAccel = 1.0;
  1066.     if (islower(key))
  1067.         key = toupper(key);
  1068.     keysPressed[key] = false;
  1069.     shiftKeysPressed[key] = false;
  1070. }
  1071. #ifdef CELX
  1072. static bool getKeyName(const char* c, int modifiers, char* keyName, unsigned int keyNameLength)
  1073. {
  1074.     unsigned int length = strlen(c);
  1075.     // Translate control characters
  1076.     if (length == 1 && c[0] >= '01' && c[0] <= '32')
  1077.     {
  1078.         if (keyNameLength < 4)
  1079.             return false;
  1080.         sprintf(keyName, "C-%c", '140' + c[0]);
  1081.     }
  1082.     else if (modifiers & CelestiaCore::ControlKey)
  1083.     {
  1084.         if (keyNameLength < length + 4)
  1085.             return false;
  1086.         sprintf(keyName, "C-%s", c);
  1087.     }
  1088.     else
  1089.     {
  1090.         if (keyNameLength < length + 1)
  1091.             return false;
  1092.         strcpy(keyName, c);
  1093.     }
  1094.     return true;
  1095. }
  1096. #endif
  1097. void CelestiaCore::charEntered(char c, int modifiers)
  1098. {
  1099.     setViewChanged();
  1100.     char C[2];
  1101.     C[0] = c;
  1102.     C[1] = '';
  1103.     charEntered(C, modifiers);
  1104. }
  1105. void CelestiaCore::charEntered(const char *c_p, int modifiers)
  1106. {
  1107.     setViewChanged();
  1108.     Observer* observer = sim->getActiveObserver();
  1109.     char c = *c_p;
  1110. #ifdef CELX
  1111.     if (celxScript != NULL && (textEnterMode & KbPassToScript))
  1112.     {
  1113.         if (c != '33' && celxScript->charEntered(c_p))
  1114.         {
  1115.             return;
  1116.         }
  1117.     }
  1118. #endif
  1119.     if (textEnterMode & KbAutoComplete)
  1120.     {
  1121.         wchar_t wc = 0; // Null wide character
  1122.         UTF8Decode(c_p, 0, strlen(c_p), wc);
  1123. #ifdef TARGET_OS_MAC
  1124.         if ( wc && (!iscntrl(wc)) )
  1125. #else
  1126.         if ( wc && (!iswcntrl(wc)) )
  1127. #endif
  1128.         {
  1129.             typedText += string(c_p);
  1130.             typedTextCompletion = sim->getObjectCompletion(typedText, (renderer->getLabelMode() & Renderer::LocationLabels) != 0);
  1131.             typedTextCompletionIdx = -1;
  1132. #ifdef AUTO_COMPLETION
  1133.             if (typedTextCompletion.size() == 1)
  1134.             {
  1135.                 string::size_type pos = typedText.rfind('/', typedText.length());
  1136.                 if (pos != string::npos)
  1137.                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[0];
  1138.                 else
  1139.                     typedText = typedTextCompletion[0];
  1140.             }
  1141. #endif
  1142.         }
  1143.         else if (c == 'b')
  1144.         {
  1145.             typedTextCompletionIdx = -1;
  1146.             if (typedText.size() > 0)
  1147.             {
  1148. #ifdef AUTO_COMPLETION
  1149.                 do
  1150.                 {
  1151. #endif
  1152.                     // We remove bytes like b10xxx xxxx at the end of typeText
  1153.                     // these are guarantied to not be the first byte of a UTF-8 char
  1154.                     while (typedText.size() && ((typedText[typedText.size() - 1] & 0xC0) == 0x80)) {
  1155.                         typedText = string(typedText, 0, typedText.size() - 1);
  1156.                     }
  1157.                     // We then remove the first byte of the last UTF-8 char of typedText.
  1158.                     typedText = string(typedText, 0, typedText.size() - 1);
  1159.                     if (typedText.size() > 0)
  1160.                     {
  1161.                         typedTextCompletion = sim->getObjectCompletion(typedText, (renderer->getLabelMode() & Renderer::LocationLabels) != 0);
  1162.                     } else {
  1163.                         typedTextCompletion.clear();
  1164.                     }
  1165. #ifdef AUTO_COMPLETION
  1166.                 } while (typedText.size() > 0 && typedTextCompletion.size() == 1);
  1167. #endif
  1168.             }
  1169.         }
  1170.         else if (c == '11') // TAB
  1171.         {
  1172.             if (typedTextCompletionIdx + 1 < (int) typedTextCompletion.size())
  1173.                 typedTextCompletionIdx++;
  1174.             else if ((int) typedTextCompletion.size() > 0 && typedTextCompletionIdx + 1 == (int) typedTextCompletion.size())
  1175.                 typedTextCompletionIdx = 0;
  1176.             if (typedTextCompletionIdx >= 0) {
  1177.                 string::size_type pos = typedText.rfind('/', typedText.length());
  1178.                 if (pos != string::npos)
  1179.                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[typedTextCompletionIdx];
  1180.                 else
  1181.                     typedText = typedTextCompletion[typedTextCompletionIdx];
  1182.             }
  1183.         }
  1184.         else if (c == Key_BackTab)
  1185.         {
  1186.             if (typedTextCompletionIdx > 0)
  1187.                 typedTextCompletionIdx--;
  1188.             else if (typedTextCompletionIdx == 0)
  1189.                 typedTextCompletionIdx = typedTextCompletion.size() - 1;
  1190.             else if (typedTextCompletion.size() > 0)
  1191.                 typedTextCompletionIdx = typedTextCompletion.size() - 1;
  1192.             if (typedTextCompletionIdx >= 0) {
  1193.                 string::size_type pos = typedText.rfind('/', typedText.length());
  1194.                 if (pos != string::npos)
  1195.                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[typedTextCompletionIdx];
  1196.                 else
  1197.                     typedText = typedTextCompletion[typedTextCompletionIdx];
  1198.             }
  1199.         }
  1200.         else if (c == '33') // ESC
  1201.         {
  1202.             setTextEnterMode(textEnterMode & ~KbAutoComplete);
  1203.         }
  1204.         else if (c == 'n' || c == 'r')
  1205.         {
  1206.             if (typedText != "")
  1207.             {
  1208.                 Selection sel = sim->findObjectFromPath(typedText, true);
  1209.                 if (!sel.empty())
  1210.                 {
  1211.                     addToHistory();
  1212.                     sim->setSelection(sel);
  1213.                 }
  1214.                 typedText = "";
  1215.             }
  1216.             setTextEnterMode(textEnterMode & ~KbAutoComplete);
  1217.         }
  1218.         return;
  1219.     }
  1220. #ifdef CELX
  1221.     if (celxScript != NULL)
  1222.     {
  1223.         if (c != '33')
  1224.         {
  1225.             char keyName[8];
  1226.             getKeyName(c_p, modifiers, keyName, sizeof(keyName));
  1227.             if (celxScript->handleKeyEvent(keyName))
  1228.                 return;
  1229.         }
  1230.     }
  1231.     if (luaHook)
  1232.     {
  1233.         char keyName[8];
  1234.         getKeyName(c_p, modifiers, keyName, sizeof(keyName));
  1235.         if (luaHook->callLuaHook(this, "charentered", keyName))
  1236.         {
  1237.             return;
  1238.         }
  1239.     }
  1240. #endif
  1241.     char C = toupper(c);
  1242.     switch (C)
  1243.     {
  1244.     case '01': // Ctrl+A
  1245.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowAtmospheres);
  1246.         notifyWatchers(RenderFlagsChanged);
  1247.         break;
  1248.     case '02': // Ctrl+B
  1249.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowBoundaries);
  1250.         notifyWatchers(RenderFlagsChanged);
  1251.         break;
  1252.     case 'n':
  1253.     case 'r':
  1254.         setTextEnterMode(textEnterMode | KbAutoComplete);
  1255.         break;
  1256.     case 'b':
  1257.         sim->setSelection(sim->getSelection().parent());
  1258.         break;
  1259.     case '14': // Ctrl+L
  1260.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowNightMaps);
  1261.         notifyWatchers(RenderFlagsChanged);
  1262.         break;
  1263.     case '13': // Ctrl+K
  1264.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowMarkers);
  1265.         if (renderer->getRenderFlags() & Renderer::ShowMarkers)
  1266. {
  1267.             flash(_("Markers enabled"));
  1268. }
  1269.         else
  1270.             flash(_("Markers disabled"));
  1271.         notifyWatchers(RenderFlagsChanged);
  1272.         break;
  1273.     case '05':  // Ctrl+E
  1274.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowEclipseShadows);
  1275.         notifyWatchers(RenderFlagsChanged);
  1276.         break;
  1277.     case '07':  // Ctrl+G
  1278.         flash(_("Goto surface"));
  1279.         addToHistory();
  1280.         //if (sim->getFrame().coordSys == astro::Universal)
  1281.             sim->geosynchronousFollow();
  1282.         sim->gotoSurface(5.0);
  1283.         // sim->gotoSelection(0.0001, Vec3f(0, 1, 0), astro::ObserverLocal);
  1284.         break;
  1285.     case '06': // Ctrl+F
  1286.         addToHistory();
  1287.         altAzimuthMode = !altAzimuthMode;
  1288.         if (altAzimuthMode)
  1289.         {
  1290.             flash(_("Alt-azimuth mode enabled"));
  1291.         }
  1292.         else
  1293.             flash(_("Alt-azimuth mode disabled"));
  1294.         break;
  1295.     case 127: // Delete
  1296.         deleteView();
  1297.         break;
  1298.     case '11': // TAB
  1299.         do
  1300.         {
  1301.             activeView++;
  1302.             if (activeView == views.end())
  1303.                 activeView = views.begin();
  1304.         } while ((*activeView)->type != View::ViewWindow);
  1305.         sim->setActiveObserver((*activeView)->observer);
  1306.         if (!showActiveViewFrame)
  1307.             flashFrameStart = currentTime;
  1308.         break;
  1309.     case '20':  // Ctrl+P
  1310.         if (!sim->getSelection().empty())
  1311.         {
  1312.             Selection sel = sim->getSelection();
  1313.             if (sim->getUniverse()->isMarked(sel, 1))
  1314.             {
  1315.                 sim->getUniverse()->unmarkObject(sel, 1);
  1316.             }
  1317.             else
  1318.             {
  1319.                 MarkerRepresentation markerRep(MarkerRepresentation::Diamond);
  1320.                 markerRep.setSize(10.0f);
  1321.                 markerRep.setColor(Color(0.0f, 1.0f, 0.0f, 0.9f));
  1322.     
  1323.                 sim->getUniverse()->markObject(sel, markerRep, 1);
  1324.             }
  1325.         }
  1326.         break;
  1327.     case '25': // Ctrl+U
  1328.         splitView(View::VerticalSplit);
  1329.         break;
  1330.     case '22': // Ctrl+R
  1331.         splitView(View::HorizontalSplit);
  1332.         break;
  1333.     case '04': // Ctrl+D
  1334.         singleView();
  1335.         break;
  1336.     case '23':  // Ctrl+S
  1337.         renderer->setStarStyle((Renderer::StarStyle) (((int) renderer->getStarStyle() + 1) %
  1338.                                                       (int) Renderer::StarStyleCount));
  1339.         switch (renderer->getStarStyle())
  1340.         {
  1341.         case Renderer::FuzzyPointStars:
  1342.             flash(_("Star style: fuzzy points"));
  1343.             break;
  1344.         case Renderer::PointStars:
  1345.             flash(_("Star style: points"));
  1346.             break;
  1347.         case Renderer::ScaledDiscStars:
  1348.             flash(_("Star style: scaled discs"));
  1349.             break;
  1350.         default:
  1351.             break;
  1352.         }
  1353.         notifyWatchers(RenderFlagsChanged);
  1354.         break;
  1355.     case '24':  // Ctrl+T
  1356.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCometTails);
  1357.         if (renderer->getRenderFlags() & Renderer::ShowCometTails)
  1358. {
  1359.             flash(_("Comet tails enabled"));
  1360. }
  1361.         else
  1362.             flash(_("Comet tails disabled"));
  1363.         notifyWatchers(RenderFlagsChanged);
  1364.         break;
  1365.     case '26':  // Ctrl+V
  1366.         {
  1367.             GLContext* context = renderer->getGLContext();
  1368.             GLContext::GLRenderPath path = context->getRenderPath();
  1369.             GLContext::GLRenderPath newPath = context->nextRenderPath();
  1370.             if (newPath != path)
  1371.             {
  1372.                 switch (newPath)
  1373.                 {
  1374.                 case GLContext::GLPath_Basic:
  1375.                     flash(_("Render path: Basic"));
  1376.                     break;
  1377.                 case GLContext::GLPath_Multitexture:
  1378.                     flash(_("Render path: Multitexture"));
  1379.                     break;
  1380.                 case GLContext::GLPath_NvCombiner:
  1381.                     flash(_("Render path: NVIDIA combiners"));
  1382.                     break;
  1383.                 case GLContext::GLPath_DOT3_ARBVP:
  1384.                     flash(_("Render path: OpenGL vertex program"));
  1385.                     break;
  1386.                 case GLContext::GLPath_NvCombiner_NvVP:
  1387.                     flash(_("Render path: NVIDIA vertex program and combiners"));
  1388.                     break;
  1389.                 case GLContext::GLPath_NvCombiner_ARBVP:
  1390.                     flash(_("Render path: OpenGL vertex program/NVIDIA combiners"));
  1391.                     break;
  1392.                 case GLContext::GLPath_ARBFP_ARBVP:
  1393.                     flash(_("Render path: OpenGL 1.5 vertex/fragment program"));
  1394.                     break;
  1395.                 case GLContext::GLPath_NV30:
  1396.                     flash(_("Render path: NVIDIA GeForce FX"));
  1397.                     break;
  1398.                 case GLContext::GLPath_GLSL:
  1399.                     flash(_("Render path: OpenGL 2.0"));
  1400.                     break;
  1401.                 }
  1402.                 context->setRenderPath(newPath);
  1403.                 notifyWatchers(RenderFlagsChanged);
  1404.             }
  1405.         }
  1406.         break;
  1407.     case '27':  // Ctrl+W
  1408.         wireframe = !wireframe;
  1409.         renderer->setRenderMode(wireframe ? GL_LINE : GL_FILL);
  1410.         break;
  1411.     case '30':  // Ctrl+X
  1412.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowSmoothLines);
  1413.         notifyWatchers(RenderFlagsChanged);
  1414.         break;
  1415.     case '31':  // Ctrl+Y
  1416.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowAutoMag);
  1417.         if (renderer->getRenderFlags() & Renderer::ShowAutoMag)
  1418.         {
  1419.             flash(_("Auto-magnitude enabled"));
  1420.             setFaintestAutoMag();
  1421.         }
  1422.         else
  1423.         {
  1424.             flash(_("Auto-magnitude disabled"));
  1425.         }
  1426.         notifyWatchers(RenderFlagsChanged);
  1427.         break;
  1428.     case '33': // Escape
  1429.         cancelScript();
  1430.         addToHistory();
  1431.         if (textEnterMode != KbNormal)
  1432.         {
  1433.             setTextEnterMode(KbNormal);
  1434.         }
  1435.         else
  1436.         {
  1437.             if (sim->getObserverMode() == Observer::Travelling)
  1438.                 sim->setObserverMode(Observer::Free);
  1439.             else
  1440.                 sim->setFrame(ObserverFrame::Universal, Selection());
  1441.             if (!sim->getTrackedObject().empty())
  1442.                 sim->setTrackedObject(Selection());
  1443.         }
  1444.         flash(_("Cancel"));
  1445.         break;
  1446.     case ' ':
  1447.         if (sim->getPauseState() == true)
  1448.         {
  1449.             if (scriptState == ScriptPaused)
  1450.                 scriptState = ScriptRunning;
  1451.             sim->setPauseState(false);
  1452.         }
  1453.         else
  1454.         {
  1455.             sim->setPauseState(true);
  1456.             // If there's a script running then pause it.  This has the
  1457.             // potentially confusing side effect of rendering nonfunctional
  1458.             // goto, center, and other movement commands.
  1459. #ifdef CELX
  1460.             if (runningScript != NULL || celxScript != NULL)
  1461. #else
  1462.             if (runningScript != NULL)
  1463. #endif
  1464.             {
  1465.                 if (scriptState == ScriptRunning)
  1466.                     scriptState = ScriptPaused;
  1467.             }
  1468.             else
  1469.             {
  1470.                 if (scriptState == ScriptPaused)
  1471.                     scriptState = ScriptRunning;
  1472.             }
  1473.         }
  1474.         if (sim->getPauseState() == true)
  1475.         {
  1476.             if (scriptState == ScriptPaused)
  1477.                 flash(_("Time and script are paused"));
  1478.             else
  1479.                 flash(_("Time is paused"));
  1480.         }
  1481.         else
  1482.         {
  1483.             flash(_("Resume"));
  1484.         }
  1485.         break;
  1486.     case '!':
  1487.         if (editMode)
  1488.         {
  1489.             showSelectionInfo(sim->getSelection());
  1490.         }
  1491.         else
  1492.         {
  1493.             time_t t = time(NULL);
  1494.             struct tm *gmt = gmtime(&t);
  1495.             if (gmt != NULL)
  1496.             {
  1497.                 astro::Date d;
  1498.                 d.year = gmt->tm_year + 1900;
  1499.                 d.month = gmt->tm_mon + 1;
  1500.                 d.day = gmt->tm_mday;
  1501.                 d.hour = gmt->tm_hour;
  1502.                 d.minute = gmt->tm_min;
  1503.                 d.seconds = (int) gmt->tm_sec;
  1504.                 sim->setTime(astro::UTCtoTDB(d));
  1505.             }
  1506.         }
  1507.         break;
  1508.     case '%':
  1509.         {
  1510.             const ColorTemperatureTable* current =
  1511.                 renderer->getStarColorTable();
  1512.             if (current == GetStarColorTable(ColorTable_Enhanced))
  1513.             {
  1514.                 renderer->setStarColorTable(GetStarColorTable(ColorTable_Blackbody_D65));
  1515.             }
  1516.             else if (current == GetStarColorTable(ColorTable_Blackbody_D65))
  1517.             {
  1518.                 renderer->setStarColorTable(GetStarColorTable(ColorTable_Enhanced));
  1519.             }
  1520.             else
  1521.             {
  1522.                 // Unknown color table
  1523.             }
  1524.         }
  1525.         break;
  1526.     case '^':
  1527.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowNebulae);
  1528.         notifyWatchers(RenderFlagsChanged);
  1529.         break;
  1530.     case '&':
  1531.         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::LocationLabels);
  1532.         notifyWatchers(LabelFlagsChanged);
  1533.         break;
  1534.     case '*':
  1535.         addToHistory();
  1536. sim->reverseObserverOrientation();
  1537.         break;
  1538.     case '?':
  1539.         addToHistory();
  1540.         if (!sim->getSelection().empty())
  1541.         {
  1542.             Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
  1543.             sim->getObserver().getPosition();
  1544.             int hours, mins;
  1545.             float secs;
  1546.             char buf[128];
  1547.     if (astro::microLightYearsToKilometers(v.length()) >=
  1548.                 86400.0 * astro::speedOfLight)
  1549.     {
  1550.         // Light travel time in years, if >= 1day
  1551.         sprintf(buf, _("Light travel time:  %.4f yr "),
  1552.                         v.length() * 1.0e-6);
  1553.                 flash(buf, 2.0);
  1554.     }
  1555.     else
  1556.     {
  1557.         // If Light travel delay < 1 day, display in [ hr : min : sec ]
  1558.                 getLightTravelDelay(v.length(), hours, mins, secs);
  1559.                 if (hours == 0)
  1560.                     sprintf(buf, _("Light travel time:  %d min  %.1f s"),
  1561.                             mins, secs);
  1562.                 else
  1563.     sprintf(buf, _("Light travel time:  %d h  %d min  %.1f s")
  1564.                             ,hours, mins, secs);
  1565.                 flash(buf, 2.0);
  1566.     }
  1567.         }
  1568.         break;
  1569.     case '-':
  1570.         addToHistory();
  1571.         if (sim->getSelection().body() &&
  1572.             (sim->getTargetSpeed() < 0.99 *
  1573.             astro::kilometersToMicroLightYears(astro::speedOfLight)))
  1574.         {
  1575.             Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
  1576.                       sim->getObserver().getPosition();
  1577.             lightTravelFlag = !lightTravelFlag;
  1578.             if (lightTravelFlag)
  1579.             {
  1580.                 flash(_("Light travel delay included"),2.0);
  1581.                 setLightTravelDelay(v.length());
  1582.             }
  1583.             else
  1584.             {
  1585.                 flash(_("Light travel delay switched off"),2.0);
  1586.                 setLightTravelDelay(-v.length());
  1587.             }
  1588.         }
  1589.         else
  1590.         {
  1591.             flash(_("Light travel delay ignored"));
  1592.         }
  1593.         break;
  1594.     case ',':
  1595.         addToHistory();
  1596.         if (observer->getFOV() > MinimumFOV)
  1597. {
  1598.     observer->setFOV(observer->getFOV() / 1.05f);
  1599.             setZoomFromFOV();
  1600.     if((renderer->getRenderFlags() & Renderer::ShowAutoMag))
  1601.     {
  1602.         setFaintestAutoMag();
  1603. char buf[128];
  1604.                 setlocale(LC_NUMERIC, "");
  1605. sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
  1606.                 setlocale(LC_NUMERIC, "C");
  1607. flash(buf);
  1608.     }
  1609. }
  1610.         break;
  1611.     case '.':
  1612.         addToHistory();
  1613.         if (observer->getFOV() < MaximumFOV)
  1614. {
  1615.     observer->setFOV(observer->getFOV() * 1.05f);
  1616.             setZoomFromFOV();
  1617.     if((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
  1618.     {
  1619.         setFaintestAutoMag();
  1620. char buf[128];
  1621.                 setlocale(LC_NUMERIC, "");
  1622. sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
  1623.                 setlocale(LC_NUMERIC, "C");
  1624. flash(buf);
  1625.     }
  1626. }
  1627.         break;
  1628.     case '+':
  1629.         addToHistory();
  1630.         if (observer->getDisplayedSurface() != "")
  1631.         {
  1632.             observer->setDisplayedSurface("");
  1633.             flash(_("Using normal surface textures."));
  1634.         }
  1635.         else
  1636.         {
  1637.             observer->setDisplayedSurface("limit of knowledge");
  1638.             flash(_("Using limit of knowledge surface textures."));
  1639.         }
  1640.         break;
  1641.     case '/':
  1642.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowDiagrams);
  1643.         notifyWatchers(RenderFlagsChanged);
  1644.         break;
  1645.     case '0':
  1646.         addToHistory();
  1647.         sim->selectPlanet(-1);
  1648.         break;
  1649.     case '1':
  1650.     case '2':
  1651.     case '3':
  1652.     case '4':
  1653.     case '5':
  1654.     case '6':
  1655.     case '7':
  1656.     case '8':
  1657.     case '9':
  1658.         addToHistory();
  1659.         if (!(modifiers & ControlKey))
  1660.             sim->selectPlanet(c - '1');
  1661.         break;
  1662.     case ';':
  1663.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCelestialSphere);
  1664.         notifyWatchers(RenderFlagsChanged);
  1665.         break;
  1666.     case '=':
  1667.         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::ConstellationLabels);
  1668.         notifyWatchers(LabelFlagsChanged);
  1669.         break;
  1670.     case 'B':
  1671.         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::StarLabels);
  1672.         notifyWatchers(LabelFlagsChanged);
  1673.         break;
  1674.     case 'C':
  1675.         addToHistory();
  1676.         if (c == 'c')
  1677.             sim->centerSelection();
  1678.         else
  1679.             sim->centerSelectionCO();
  1680.         break;
  1681.     case 'D':
  1682.         addToHistory();
  1683.         if (config->demoScriptFile != "")
  1684.            runScript(config->demoScriptFile);
  1685.         break;
  1686. case 'E':
  1687. if (c == 'e')
  1688. renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::GalaxyLabels);
  1689. else
  1690.          renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::GlobularLabels);
  1691.         notifyWatchers(LabelFlagsChanged);
  1692. break;
  1693.     case 'F':
  1694.         addToHistory();
  1695.         flash(_("Follow"));
  1696.         sim->follow();
  1697.         break;
  1698.     case 'G':
  1699.         addToHistory();
  1700.         if (sim->getFrame()->getCoordinateSystem() == ObserverFrame::Universal)
  1701.             sim->follow();
  1702.         sim->gotoSelection(5.0, Vec3f(0, 1, 0), ObserverFrame::ObserverLocal);
  1703.         break;
  1704.     case 'H':
  1705.         addToHistory();
  1706.         sim->setSelection(sim->getUniverse()->getStarCatalog()->find(0));
  1707.         break;
  1708.     case 'I':
  1709.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCloudMaps);
  1710.         notifyWatchers(RenderFlagsChanged);
  1711.         break;
  1712.     case 'J':
  1713.         addToHistory();
  1714.         sim->setTimeScale(-sim->getTimeScale());
  1715.         if (sim->getTimeScale() >= 0)
  1716.             flash(_("Time: Forward"));
  1717.         else
  1718.             flash(_("Time: Backward"));
  1719.         break;
  1720.     case 'K':
  1721.         addToHistory();
  1722.         if (abs(sim->getTimeScale()) > MinimumTimeRate)
  1723.         {
  1724.             if (c == 'k')
  1725.                 sim->setTimeScale(sim->getTimeScale() / CoarseTimeScaleFactor);
  1726.             else
  1727.                 sim->setTimeScale(sim->getTimeScale() / FineTimeScaleFactor);
  1728.             char buf[128];
  1729.             setlocale(LC_NUMERIC, "");
  1730.             sprintf(buf, "%s: " TIMERATE_PRINTF_FORMAT,  _("Time rate"), sim->getTimeScale());
  1731.             setlocale(LC_NUMERIC, "C");
  1732.             flash(buf);
  1733.         }
  1734.         break;
  1735.     case 'L':
  1736.         addToHistory();
  1737.         if (abs(sim->getTimeScale()) < MaximumTimeRate)
  1738.         {
  1739.             if (c == 'l')
  1740.                 sim->setTimeScale(sim->getTimeScale() * CoarseTimeScaleFactor);
  1741.             else
  1742.                 sim->setTimeScale(sim->getTimeScale() * FineTimeScaleFactor);
  1743.             char buf[128];
  1744.             setlocale(LC_NUMERIC, "");
  1745.             sprintf(buf, "%s: " TIMERATE_PRINTF_FORMAT,  _("Time rate"), sim->getTimeScale());
  1746.             setlocale(LC_NUMERIC, "C");
  1747.             flash(buf);
  1748.         }
  1749.         break;
  1750.     case 'M':
  1751.         if (c == 'm')
  1752.             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::MoonLabels);
  1753.         else
  1754.             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::MinorMoonLabels);
  1755.         notifyWatchers(LabelFlagsChanged);
  1756.         break;
  1757.     case 'N':
  1758.         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::SpacecraftLabels);
  1759.         notifyWatchers(LabelFlagsChanged);
  1760.         break;
  1761.     case 'O':
  1762.         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowOrbits);
  1763.         notifyWatchers(RenderFlagsChanged);
  1764.         break;
  1765.     case 'P':
  1766.         if (c == 'p')
  1767.             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::PlanetLabels);
  1768.         else
  1769.             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::DwarfPlanetLabels);
  1770.         notifyWatchers(LabelFlagsChanged);
  1771.         break;
  1772.     case 'Q':
  1773.         sim->setTargetSpeed(-sim->getTargetSpeed());
  1774.         break;
  1775.     case 'S':
  1776.         sim->setTargetSpeed(0);
  1777.         break;
  1778.     case 'T':
  1779.         addToHistory();
  1780.         if (sim->getTrackedObject().empty())
  1781.             sim->setTrackedObject(sim->getSelection());
  1782.         else
  1783.             sim->setTrackedObject(Selection());
  1784.         break;
  1785. case 'U':
  1786. if (c == 'u')
  1787. renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowGalaxies);
  1788. else
  1789. renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowGlobulars);
  1790.         notifyWatchers(RenderFlagsChanged);
  1791.         break;
  1792.     case 'V':
  1793.         setHudDetail((getHudDetail() + 1) % 3);
  1794.         break;
  1795.     case 'W':
  1796.         if (c == 'w')
  1797.             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::AsteroidLabels);
  1798.         else
  1799.             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::CometLabels);
  1800.         notifyWatchers(LabelFlagsChanged);
  1801.         break;
  1802.     case 'X':
  1803.         sim->setTargetSpeed(sim->getTargetSpeed());
  1804.         break;
  1805.     case 'Y':
  1806.         flash(_("Sync Orbit"));
  1807.         addToHistory();
  1808.         sim->geosynchronousFollow();
  1809.         break;
  1810.     case ':':
  1811.         flash(_("Lock"));
  1812.         addToHistory();
  1813.         sim->phaseLock();
  1814.         break;
  1815.     case '"':
  1816.         flash(_("Chase"));
  1817.         addToHistory();
  1818.         sim->chase();
  1819.         break;
  1820.     case '[':
  1821.         if ((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
  1822.         {
  1823.             if (sim->getFaintestVisible() > 1.0f)
  1824.             {
  1825.                 setFaintest(sim->getFaintestVisible() - 0.2f);
  1826.                 notifyWatchers(FaintestChanged);
  1827.                 char buf[128];
  1828.                 setlocale(LC_NUMERIC, "");
  1829.                 sprintf(buf, _("Magnitude limit: %.2f"),sim->getFaintestVisible());
  1830.                 setlocale(LC_NUMERIC, "C");
  1831.                 flash(buf);
  1832.             }
  1833.         }
  1834.         else if (renderer->getFaintestAM45deg() > 6.0f)
  1835.         {
  1836.             renderer->setFaintestAM45deg(renderer->getFaintestAM45deg() - 0.1f);
  1837.             setFaintestAutoMag();
  1838.             char buf[128];
  1839.             setlocale(LC_NUMERIC, "");
  1840.             sprintf(buf, _("Auto magnitude limit at 45 degrees:  %.2f"),renderer->getFaintestAM45deg());
  1841.             setlocale(LC_NUMERIC, "C");
  1842.             flash(buf);
  1843.         }
  1844.         break;
  1845.     case '\':
  1846.         addToHistory();
  1847.         sim->setTimeScale(1.0f);
  1848.         break;
  1849.     case ']':
  1850.         if((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
  1851.         {
  1852.             if (sim->getFaintestVisible() < 15.0f)
  1853.           {
  1854.               setFaintest(sim->getFaintestVisible() + 0.2f);
  1855.                 notifyWatchers(FaintestChanged);
  1856.                 char buf[128];
  1857.                 setlocale(LC_NUMERIC, "");
  1858.                 sprintf(buf, _("Magnitude limit: %.2f"),sim->getFaintestVisible());
  1859.                 setlocale(LC_NUMERIC, "C");
  1860.                 flash(buf);
  1861.             }
  1862.         }
  1863.         else if (renderer->getFaintestAM45deg() < 12.0f)
  1864.         {
  1865.             renderer->setFaintestAM45deg(renderer->getFaintestAM45deg() + 0.1f);
  1866.             setFaintestAutoMag();
  1867.             char buf[128];
  1868.             setlocale(LC_NUMERIC, "");
  1869.             sprintf(buf, _("Auto magnitude limit at 45 degrees:  %.2f"),renderer->getFaintestAM45deg());
  1870.             setlocale(LC_NUMERIC, "C");
  1871.             flash(buf);
  1872.         }
  1873.         break;
  1874.     case '`':
  1875.         showFPSCounter = !showFPSCounter;
  1876.         break;
  1877.     case '{':
  1878.         {
  1879.             if (renderer->getAmbientLightLevel() > 0.05f)
  1880.                 renderer->setAmbientLightLevel(renderer->getAmbientLightLevel() - 0.05f);
  1881.             else
  1882.                 renderer->setAmbientLightLevel(0.0f);
  1883.             notifyWatchers(AmbientLightChanged);
  1884.             char buf[128];
  1885.             setlocale(LC_NUMERIC, "");
  1886.             sprintf(buf, _("Ambient light level:  %.2f"),renderer->getAmbientLightLevel());
  1887.             setlocale(LC_NUMERIC, "C");
  1888.             flash(buf);
  1889.         }
  1890.         break;
  1891.     case '}':
  1892.         {
  1893.             if (renderer->getAmbientLightLevel() < 0.95f)
  1894.                 renderer->setAmbientLightLevel(renderer->getAmbientLightLevel() + 0.05f);
  1895.             else
  1896.                 renderer->setAmbientLightLevel(1.0f);
  1897.             notifyWatchers(AmbientLightChanged);
  1898.             char buf[128];
  1899.             setlocale(LC_NUMERIC, "");
  1900.             sprintf(buf, _("Ambient light level:  %.2f"),renderer->getAmbientLightLevel());
  1901.             setlocale(LC_NUMERIC, "C");
  1902.             flash(buf);
  1903.         }
  1904.         break;
  1905.     case '(':
  1906.         {
  1907.             char buf[128];
  1908.             Galaxy::decreaseLightGain();
  1909.             setlocale(LC_NUMERIC, "");
  1910.             sprintf(buf, "%s:  %3.0f %%", _("Light gain"), Galaxy::getLightGain() * 100.0f);
  1911.             setlocale(LC_NUMERIC, "C");
  1912.             flash(buf);
  1913.             notifyWatchers(GalaxyLightGainChanged);
  1914.         }
  1915.         break;
  1916.     case ')':
  1917.         {
  1918.             char buf[128];
  1919.             Galaxy::increaseLightGain();
  1920.             setlocale(LC_NUMERIC, "");
  1921.             sprintf(buf, "%s:  %3.0f %%", _("Light gain"), Galaxy::getLightGain() * 100.0f);
  1922.             setlocale(LC_NUMERIC, "C");
  1923.             flash(buf);
  1924.             notifyWatchers(GalaxyLightGainChanged);
  1925.         }
  1926.         break;
  1927.     case '~':
  1928.         showConsole = !showConsole;
  1929.         break;
  1930.     case '@':
  1931.         // TODO: 'Edit mode' should be eliminated; it can be done better
  1932.         // with a Lua script.
  1933.         editMode = !editMode;
  1934.         break;
  1935. #ifdef USE_HDR
  1936.     case '|':
  1937.         renderer->setBloomEnabled(!renderer->getBloomEnabled());
  1938.         if (renderer->getBloomEnabled())
  1939.             flash(_("Bloom enabled"));
  1940.         else
  1941.             flash(_("Bloom disabled"));
  1942.         break;
  1943.     case '<':
  1944.         {
  1945.             char buf[64];
  1946.             renderer->decreaseBrightness();
  1947.             sprintf(buf, "%s:  %+3.2f", _("Exposure"), -renderer->getBrightness());
  1948.             flash(buf);
  1949.         }
  1950.         break;
  1951.     case '>':
  1952.         {
  1953.             char buf[64];
  1954.             renderer->increaseBrightness();
  1955.             sprintf(buf, "%s:  %+3.2f", _("Exposure"), -renderer->getBrightness());
  1956.             flash(buf);
  1957.         }
  1958.         break;
  1959. #endif
  1960.     }
  1961. }
  1962. void CelestiaCore::getLightTravelDelay(double distance, int& hours, int& mins,
  1963.                                     float& secs)
  1964. {
  1965.     // light travel time in hours
  1966.     double lt = astro::microLightYearsToKilometers(distance)/
  1967.         (3600.0 * astro::speedOfLight);
  1968.     hours = (int) lt;
  1969.     double mm    = (lt - hours) * 60;
  1970.     mins = (int) mm;
  1971.     secs = (float) ((mm  - mins) * 60);
  1972. }
  1973. void CelestiaCore::setLightTravelDelay(double distance)
  1974. {
  1975.     // light travel time in days
  1976.     double lt = astro::microLightYearsToKilometers(distance)/
  1977.         (86400.0 * astro::speedOfLight);
  1978.     sim->setTime(sim->getTime() - lt);
  1979. }
  1980. bool CelestiaCore::getAltAzimuthMode() const
  1981. {
  1982.     return altAzimuthMode;
  1983. }
  1984. void CelestiaCore::setAltAzimuthMode(bool enable)
  1985. {
  1986.     altAzimuthMode = enable;
  1987. }
  1988. void CelestiaCore::start(double t)
  1989. {
  1990.     if (config->initScriptFile != "")
  1991.     {
  1992.         // using the KdeAlerter in runScript would create an infinite loop,
  1993.         // break it here by resetting config->initScriptFile:
  1994.         string filename = config->initScriptFile;
  1995.         config->initScriptFile = "";
  1996.         runScript(filename);
  1997.     }
  1998.     // Set the simulation starting time to the current system time
  1999.     sim->setTime(t);
  2000.     sim->update(0.0);
  2001.     sysTime = timer->getTime();
  2002.     if (startURL != "")
  2003.         goToUrl(startURL);
  2004. }
  2005. void CelestiaCore::setStartURL(string url)
  2006. {
  2007.     if (!url.substr(0,4).compare("cel:"))
  2008.     {
  2009.         startURL = url;
  2010.         config->initScriptFile = "";
  2011.     }
  2012.     else
  2013.     {
  2014.         config->initScriptFile = url;
  2015.     }
  2016. }
  2017. void CelestiaCore::tick()
  2018. {
  2019.     double lastTime = sysTime;
  2020.     sysTime = timer->getTime();
  2021.     // The time step is normally driven by the system clock; however, when
  2022.     // recording a movie, we fix the time step the frame rate of the movie.
  2023.     double dt = 0.0;
  2024.     if (movieCapture != NULL && recording)
  2025.     {
  2026.         dt = 1.0 / movieCapture->getFrameRate();
  2027.     }
  2028.     else
  2029.     {
  2030.         dt = sysTime - lastTime;
  2031.     }
  2032.     // Pause script execution
  2033.     if (scriptState == ScriptPaused)
  2034.         dt = 0.0;
  2035.     currentTime += dt;
  2036.     // Mouse wheel zoom
  2037.     if (zoomMotion != 0.0f)
  2038.     {
  2039.         double span = 0.1;
  2040.         double fraction;
  2041.         if (currentTime - zoomTime >= span)
  2042.             fraction = (zoomTime + span) - (currentTime - dt);
  2043.         else
  2044.             fraction = dt / span;
  2045.         // sim->changeOrbitDistance(zoomMotion * (float) fraction);
  2046.         if (currentTime - zoomTime >= span)
  2047.             zoomMotion = 0.0f;
  2048.     }
  2049.     // Mouse wheel dolly
  2050.     if (dollyMotion != 0.0)
  2051.     {
  2052.         double span = 0.1;
  2053.         double fraction;
  2054.         if (currentTime - dollyTime >= span)
  2055.             fraction = (dollyTime + span) - (currentTime - dt);
  2056.         else
  2057.             fraction = dt / span;
  2058.         sim->changeOrbitDistance((float) (dollyMotion * fraction));
  2059.         if (currentTime - dollyTime >= span)
  2060.             dollyMotion = 0.0f;
  2061.     }
  2062.     // Keyboard dolly
  2063.     if (keysPressed[Key_Home])
  2064.         sim->changeOrbitDistance((float) (-dt * 2));
  2065.     if (keysPressed[Key_End])
  2066.         sim->changeOrbitDistance((float) (dt * 2));
  2067.     // Keyboard rotate
  2068.     Vec3d av = sim->getObserver().getAngularVelocity();
  2069.     av = av * exp(-dt * RotationDecay);
  2070.     float fov = sim->getActiveObserver()->getFOV() / stdFOV;
  2071.     Selection refObject = sim->getFrame()->getRefObject();
  2072.     // Handle arrow keys; disable them when the log console is displayed,
  2073.     // because then they're used to scroll up and down.
  2074.     if (!showConsole)
  2075.     {
  2076.         if (!altAzimuthMode)
  2077.         {
  2078.             if (keysPressed[Key_Left])
  2079.                 av += Vec3d(0.0, 0.0, dt * -KeyRotationAccel);
  2080.             if (keysPressed[Key_Right])
  2081.                 av += Vec3d(0.0, 0.0, dt * KeyRotationAccel);
  2082.             if (keysPressed[Key_Down])
  2083.                 av += Vec3d(dt * fov * -KeyRotationAccel, 0.0, 0.0);
  2084.             if (keysPressed[Key_Up])
  2085.                 av += Vec3d(dt * fov * KeyRotationAccel, 0.0, 0.0);
  2086.         }
  2087.         else
  2088.         {
  2089.             if (!refObject.empty())
  2090.             {
  2091.                 Quatd orientation = sim->getObserver().getOrientation();
  2092.                 Vec3d up = sim->getObserver().getPosition() - refObject.getPosition(sim->getTime());
  2093.                 up.normalize();
  2094.                 Vec3d v = up * (KeyRotationAccel * dt);
  2095.                 v = v * (~orientation).toMatrix3();
  2096.                 if (keysPressed[Key_Left])
  2097.                     av -= v;
  2098.                 if (keysPressed[Key_Right])
  2099.                     av += v;
  2100.                 if (keysPressed[Key_Down])
  2101.                     av += Vec3d(dt * fov * -KeyRotationAccel, 0.0, 0.0);
  2102.                 if (keysPressed[Key_Up])
  2103.                     av += Vec3d(dt * fov * KeyRotationAccel, 0.0, 0.0);
  2104.             }
  2105.         }
  2106.     }
  2107.     if (keysPressed[Key_NumPad4])
  2108.         av += Vec3d(0.0, dt * fov * -KeyRotationAccel, 0.0);
  2109.     if (keysPressed[Key_NumPad6])
  2110.         av += Vec3d(0.0, dt * fov * KeyRotationAccel, 0.0);
  2111.     if (keysPressed[Key_NumPad2])
  2112.         av += Vec3d(dt * fov * -KeyRotationAccel, 0.0, 0.0);
  2113.     if (keysPressed[Key_NumPad8])
  2114.         av += Vec3d(dt * fov * KeyRotationAccel, 0.0, 0.0);
  2115.     if (keysPressed[Key_NumPad7] || joyButtonsPressed[JoyButton7])
  2116.         av += Vec3d(0.0, 0.0, dt * -KeyRotationAccel);
  2117.     if (keysPressed[Key_NumPad9] || joyButtonsPressed[JoyButton8])
  2118.         av += Vec3d(0.0, 0.0, dt * KeyRotationAccel);
  2119.     //Use Boolean to indicate if sim->setTargetSpeed() is called
  2120.     bool bSetTargetSpeed = false;
  2121.     if (joystickRotation != Vec3f(0.0f, 0.0f, 0.0f))
  2122.     {
  2123.         bSetTargetSpeed = true;
  2124.         av += (dt * KeyRotationAccel) * Vec3d(joystickRotation.x, joystickRotation.y, joystickRotation.z);
  2125.         sim->setTargetSpeed(sim->getTargetSpeed());
  2126.     }
  2127.     if (keysPressed[Key_NumPad5])
  2128.         av = av * exp(-dt * RotationBraking);
  2129.     sim->getObserver().setAngularVelocity(av);
  2130.     if (keysPressed[(int)'A'] || joyButtonsPressed[JoyButton2])
  2131.     {
  2132.         bSetTargetSpeed = true;
  2133.         if (sim->getTargetSpeed() == 0.0f)
  2134.             sim->setTargetSpeed(astro::kilometersToMicroLightYears(0.1f));
  2135.         else
  2136.             sim->setTargetSpeed(sim->getTargetSpeed() * (float) exp(dt * 3));
  2137.     }
  2138.     if (keysPressed[(int)'Z'] || joyButtonsPressed[JoyButton1])
  2139.     {
  2140.         bSetTargetSpeed = true;
  2141.         sim->setTargetSpeed(sim->getTargetSpeed() / (float) exp(dt * 3));
  2142.     }
  2143.     if (!bSetTargetSpeed && av.length() > 0.0f)
  2144.     {
  2145.         //Force observer velocity vector to align with observer direction if an observer
  2146.         //angular velocity still exists.
  2147.         sim->setTargetSpeed(sim->getTargetSpeed());
  2148.     }
  2149.     if (!refObject.empty())
  2150.     {
  2151.         Quatf q(1.0f);
  2152.         float coarseness = ComputeRotationCoarseness(*sim);
  2153.         if (shiftKeysPressed[Key_Left])
  2154.             q = q * Quatf::yrotation((float) (dt * -KeyRotationAccel * coarseness));
  2155.         if (shiftKeysPressed[Key_Right])
  2156.             q = q * Quatf::yrotation((float) (dt *  KeyRotationAccel * coarseness));
  2157.         if (shiftKeysPressed[Key_Up])
  2158.             q = q * Quatf::xrotation((float) (dt * -KeyRotationAccel * coarseness));
  2159.         if (shiftKeysPressed[Key_Down])
  2160.             q = q * Quatf::xrotation((float) (dt *  KeyRotationAccel * coarseness));
  2161.         sim->orbit(q);
  2162.     }
  2163.     // If there's a script running, tick it
  2164.     if (runningScript != NULL)
  2165.     {
  2166.         bool finished = runningScript->tick(dt);
  2167.         if (finished)
  2168.             cancelScript();
  2169.     }
  2170. #ifdef CELX
  2171.     if (celxScript != NULL)
  2172.     {
  2173.         celxScript->handleTickEvent(dt);
  2174.         if (scriptState == ScriptRunning)
  2175.         {
  2176.             bool finished = celxScript->tick(dt);
  2177.             if (finished)
  2178.                 cancelScript();
  2179.         }
  2180.     }
  2181.     if (luaHook != NULL)
  2182.         luaHook->callLuaHook(this, "tick", dt);
  2183. #endif // CELX
  2184.     sim->update(dt);
  2185. }
  2186. void CelestiaCore::draw()
  2187. {
  2188.     if (!viewUpdateRequired())
  2189.         return;
  2190.     viewChanged = false;
  2191.     if (views.size() == 1)
  2192.     {
  2193.         // I'm not certain that a special case for one view is required; but,
  2194.         // it's possible that there exists some broken hardware out there
  2195.         // that has to fall back to software rendering if the scissor test
  2196.         // is enable.  To keep performance on this hypothetical hardware
  2197.         // reasonable in the typical single view case, we'll use this
  2198.         // scissorless special case.  I'm only paranoid because I've been
  2199.         // burned by crap hardware so many times. cjl
  2200.         glViewport(0, 0, width, height);
  2201.         renderer->resize(width, height);
  2202.         sim->render(*renderer);
  2203.     }
  2204.     else
  2205.     {
  2206.         glEnable(GL_SCISSOR_TEST);
  2207.         for (list<View*>::iterator iter = views.begin();
  2208.              iter != views.end(); iter++)
  2209.         {
  2210.             View* view = *iter;
  2211.             if (view->type == View::ViewWindow)
  2212.             {
  2213.                 glScissor((GLint) (view->x * width),
  2214.                           (GLint) (view->y * height),
  2215.                           (GLsizei) (view->width * width),
  2216.                           (GLsizei) (view->height * height));
  2217.                 glViewport((GLint) (view->x * width),
  2218.                            (GLint) (view->y * height),
  2219.                            (GLsizei) (view->width * width),
  2220.                            (GLsizei) (view->height * height));
  2221.                 renderer->resize((int) (view->width * width),
  2222.                                  (int) (view->height * height));
  2223.                 sim->render(*renderer, *view->observer);
  2224.             }
  2225.         }
  2226.         glDisable(GL_SCISSOR_TEST);
  2227.         glViewport(0, 0, width, height);
  2228.     }
  2229.     renderOverlay();
  2230. if (showConsole)
  2231.     {
  2232.         console.setFont(font);
  2233.         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
  2234.         console.begin();
  2235.         glTranslatef(0.0f, 200.0f, 0.0f);
  2236.         console.render(ConsolePageRows);
  2237.         console.end();
  2238.     }
  2239.     if (movieCapture != NULL && recording)
  2240.         movieCapture->captureFrame();
  2241.     // Frame rate counter
  2242.     nFrames++;
  2243.     if (nFrames == 100 || sysTime - fpsCounterStartTime > 10.0)
  2244.     {
  2245.         fps = (double) nFrames / (sysTime - fpsCounterStartTime);
  2246.         nFrames = 0;
  2247.         fpsCounterStartTime = sysTime;
  2248.     }
  2249. #if 0
  2250.     GLenum err = glGetError();
  2251.     if (err != GL_NO_ERROR)
  2252.     {
  2253.         cout << _("GL error: ") << gluErrorString(err) << 'n';
  2254.     }
  2255. #endif
  2256. }
  2257. void CelestiaCore::resize(GLsizei w, GLsizei h)
  2258. {
  2259.     if (h == 0)
  2260. h = 1;
  2261.     glViewport(0, 0, w, h);
  2262.     if (renderer != NULL)
  2263.         renderer->resize(w, h);
  2264.     if (overlay != NULL)
  2265.         overlay->setWindowSize(w, h);
  2266.     console.setScale(w, h);
  2267.     width = w;
  2268.     height = h;
  2269.     setFOVFromZoom();
  2270. #ifdef CELX
  2271.     if (luaHook && luaHook->callLuaHook(this,"resize", (float) w, (float) h))
  2272.         return;
  2273. #endif
  2274. }