/*
 * See Licensing and Copyright notice in naev.h
 */

/**
 * @file dev_sysedit.c
 *
 * @brief Handles the star system editor.
 */

#include "dev_sysedit.h"
#include "dev_uniedit.h"

#include "naev.h"

#include "SDL.h"

#include "space.h"
#include "toolkit.h"
#include "opengl.h"
#include "map.h"
#include "dev_planet.h"
#include "dev_system.h"
#include "unidiff.h"
#include "dialogue.h"
#include "tk/toolkit_priv.h"
#include "ndata.h"
#include "nfile.h"
#include "nstring.h"
#include "npng.h"
#include "conf.h"


#define BUTTON_WIDTH    90 /**< Map button width. */
#define BUTTON_HEIGHT   30 /**< Map button height. */


#define SYSEDIT_EDIT_WIDTH       500 /**< System editor width. */
#define SYSEDIT_EDIT_HEIGHT      400 /**< System editor height. */


#define SYSEDIT_DRAG_THRESHOLD   300   /**< Drag threshold. */
#define SYSEDIT_MOVE_THRESHOLD   10    /**< Movement threshold. */

#define SYSEDIT_ZOOM_STEP        1.2   /**< Factor to zoom by for each zoom level. */
#define SYSEDIT_ZOOM_MAX         1     /**< Maximum zoom level (close). */
#define SYSEDIT_ZOOM_MIN         -23   /**< Minimum zoom level (far). */


/*
 * Selection types.
 */
#define SELECT_NONE        0 /**< No selection. */
#define SELECT_PLANET      1 /**< Selection is a planet. */
#define SELECT_JUMPPOINT   2 /**< Selection is a jump point. */


/**
 * @brief Selection generic for stuff in a system.
 */
typedef struct Select_s {
   int type; /**< Type of selection. */
   union {
      int planet;
      int jump;
   } u; /**< Data itself. */
} Select_t;
static Select_t *sysedit_select  = NULL; /**< Current system selection. */
static int sysedit_nselect       = 0; /**< Number of selections in current system. */
static int sysedit_mselect       = 0; /**< Memory allocated for selections. */
static Select_t sysedit_tsel;         /**< Temporary selection. */
static int  sysedit_tadd         = 0; /**< Add to selection. */


/*
 * System editor stuff.
 */
static StarSystem *sysedit_sys = NULL; /**< Currently opened system. */
static unsigned int sysedit_wid = 0; /**< Sysedit wid. */
static unsigned int sysedit_widEdit = 0; /**< Planet editor wid. */
static int sysedit_grid       = 1;  /**< Grid is visible. */
static double sysedit_xpos    = 0.; /**< Viewport X position. */
static double sysedit_ypos    = 0.; /**< Viewport Y position. */
static double sysedit_zoom    = 1.; /**< Viewport zoom level. */
static int sysedit_moved      = 0;  /**< Space moved since mouse down. */
static unsigned int sysedit_dragTime = 0; /**< Tick last started to drag. */
static int sysedit_drag       = 0;  /**< Dragging viewport around. */
static int sysedit_dragSel    = 0;  /**< Dragging system around. */
static double sysedit_mx      = 0.; /**< Cursor X position. */
static double sysedit_my      = 0.; /**< Cursor Y position. */

/* Stored checkbox values. */
static int jp_hidden = 0; /**< Jump point hidden checkbox value. */
static int jp_exit   = 0; /**< Jump point exit only checkbox value. */

/*
 * Property editor stuff.
 */
static glTexture **sysedit_tex = NULL; /**< Planet textures. */
static int sysedit_ntex       = 0; /**< Number of planet textures. */


/*
 * System editor Prototypes.
 */
/* Custom system editor widget. */
static void sysedit_buttonZoom( unsigned int wid, char* str );
static void sysedit_render( double bx, double by, double w, double h, void *data );
static void sysedit_renderBG( double bx, double bw, double w, double h, double x, double y);
static void sysedit_renderSprite( glTexture *gfx, double bx, double by, double x, double y,
      int sx, int sy, const glColour *c, int selected, const char *caption );
static void sysedit_renderOverlay( double bx, double by, double bw, double bh, void* data );
static int sysedit_mouse( unsigned int wid, SDL_Event* event, double mx, double my,
      double w, double h, void *data );
/* Button functions. */
static void sysedit_close( unsigned int wid, char *wgt );
static void sysedit_btnNew( unsigned int wid_unused, char *unused );
static void sysedit_btnRename( unsigned int wid_unused, char *unused );
static void sysedit_btnRemove( unsigned int wid_unused, char *unused );
static void sysedit_btnReset( unsigned int wid_unused, char *unused );
static void sysedit_btnScale( unsigned int wid_unused, char *unused );
static void sysedit_btnGrid( unsigned int wid_unused, char *unused );
static void sysedit_btnEdit( unsigned int wid_unused, char *unused );
/* Planet editing. */
static void sysedit_editPnt( void );
static void sysedit_editPntClose( unsigned int wid, char *unused );
static void sysedit_planetDesc( unsigned int wid, char *unused );
static void sysedit_planetDescReturn( unsigned int wid, char *unused );
static void sysedit_planetDescClose( unsigned int wid, char *unused );
static void sysedit_genServicesList( unsigned int wid );
static void sysedit_btnTechEdit( unsigned int wid, char *unused );
static void sysedit_genTechList( unsigned int wid );
static void sysedit_btnAddTech( unsigned int wid, char *unused );
static void sysedit_btnRmTech( unsigned int wid, char *unused );
static void sysedit_btnAddService( unsigned int wid, char *unused );
static void sysedit_btnRmService( unsigned int wid, char *unused );
static void sysedit_planetGFX( unsigned int wid_unused, char *unused );
static void sysedit_btnGFXClose( unsigned int wid, char *wgt );
static void sysedit_btnGFXApply( unsigned int wid, char *wgt );
static void sysedit_btnFaction( unsigned int wid_unused, char *unused );
static void sysedit_btnFactionSet( unsigned int wid, char *unused );
static void sysedit_editJumpClose( unsigned int wid, char *unused );
/* Jump editing */
static void sysedit_editJump( void );
/* Keybindings handling. */
static int sysedit_keys( unsigned int wid, SDLKey key, SDLMod mod );
/* Selection. */
static int sysedit_selectCmp( Select_t *a, Select_t *b );
static void sysedit_checkButtons (void);
static void sysedit_deselect (void);
static void sysedit_selectAdd( Select_t *sel );
static void sysedit_selectRm( Select_t *sel );


/**
 * @brief Opens the system editor interface.
 */
void sysedit_open( StarSystem *sys )
{
   unsigned int wid;
   char buf[PATH_MAX];
   int i;

   /* Reconstructs the jumps - just in case. */
   systems_reconstructJumps();

   /* Reset some variables. */
   sysedit_sys    = sys;
   sysedit_drag   = 0;
   sysedit_zoom   = pow(SYSEDIT_ZOOM_STEP, SYSEDIT_ZOOM_MIN);
   sysedit_xpos   = 0.;
   sysedit_ypos   = 0.;

   /* Load graphics. */
   space_gfxLoad( sysedit_sys );

   /* Create the window. */
   nsnprintf( buf, sizeof(buf), "%s - Star System Editor", sys->name );
   wid = window_create( buf, -1, -1, -1, -1 );
   window_handleKeys( wid, sysedit_keys );
   sysedit_wid = wid;

   window_setAccept( wid, sysedit_close );

   /* Close button. */
   window_addButton( wid, -15, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnClose", "Close", sysedit_close );
   i = 1;

   /* Autosave toggle. */
   window_addCheckbox( wid, -150, 25, 250, 20,
         "chkEditAutoSave", "Automatically save changes", uniedit_autosave, conf.devautosave );

   /* Scale. */
   window_addButton( wid, -15, 20+(BUTTON_HEIGHT+20)*i, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnScale", "Scale", sysedit_btnScale );
   i += 1;

   /* Reset. */
   window_addButtonKey( wid, -15, 20+(BUTTON_HEIGHT+20)*i, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnReset", "Reset Jumps", sysedit_btnReset, SDLK_r );
   i += 1;

   /* Editing. */
   window_addButtonKey( wid, -15, 20+(BUTTON_HEIGHT+20)*i, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnEdit", "Edit", sysedit_btnEdit, SDLK_e );
   i += 1;

   /* Remove. */
   window_addButton( wid, -15, 20+(BUTTON_HEIGHT+20)*i, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnRemove", "Remove", sysedit_btnRemove );
   i += 1;

   /* Rename. */
   window_addButton( wid, -15, 20+(BUTTON_HEIGHT+20)*i, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnRename", "Rename", sysedit_btnRename );
   i += 1;

   /* New system. */
   window_addButtonKey( wid, -15, 20+(BUTTON_HEIGHT+20)*i, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnNew", "New Planet", sysedit_btnNew, SDLK_n );
   i += 2;

   /* Toggle Grid. */
   window_addButtonKey( wid, -15, 20+(BUTTON_HEIGHT+20)*i, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnGrid", "Grid", sysedit_btnGrid, SDLK_g );

   /* Zoom buttons */
   window_addButton( wid, 40, 20, 30, 30, "btnZoomIn", "+", sysedit_buttonZoom );
   window_addButton( wid, 80, 20, 30, 30, "btnZoomOut", "-", sysedit_buttonZoom );

   /* Selected text. */
   nsnprintf( buf, sizeof(buf), "Radius: %.0f", sys->radius );
   window_addText( wid, 140, 10, SCREEN_W - 80 - 30 - 30 - BUTTON_WIDTH - 20, 30, 0,
         "txtSelected", &gl_smallFont, &cBlack, buf );

   /* Actual viewport. */
   window_addCust( wid, 20, -40, SCREEN_W - 150, SCREEN_H - 100,
         "cstSysEdit", 1, sysedit_render, sysedit_mouse, NULL );
   window_custSetOverlay( wid, "cstSysEdit", sysedit_renderOverlay );

   /* Deselect everything. */
   sysedit_deselect();
}


/**
 * @brief Handles keybindings.
 */
static int sysedit_keys( unsigned int wid, SDLKey key, SDLMod mod )
{
   (void) wid;
   (void) mod;

   switch (key) {

      default:
         return 0;
   }
}


/**
 * @brief Closes the system editor widget.
 */
static void sysedit_close( unsigned int wid, char *wgt )
{
   /* Unload graphics. */
   space_gfxLoad( sysedit_sys );

   /* Remove selection. */
   sysedit_deselect();

   /* Set the dominant faction. */
   system_setFaction( sysedit_sys );

   /* Save the system */
   if (conf.devautosave)
      dsys_saveSystem( sysedit_sys );

   /* Reconstruct universe presences. */
   space_reconstructPresences();

   /* Close the window. */
   window_close( wid, wgt );

   /* Update the universe editor's sidebar text. */
   uniedit_selectText();

   /* Propagate autosave checkbox state */
   uniedit_updateAutosave();
}


/**
 * @brief Closes the planet editor, saving the changes made.
 */
static void sysedit_editPntClose( unsigned int wid, char *unused )
{
   (void) unused;
   Planet *p;
   char *inp;

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];

   /* Remove the old presence. */
   system_addPresence(sysedit_sys, p->faction, -p->presenceAmount, p->presenceRange);

   p->population = (uint64_t)strtoull( window_getInput( sysedit_widEdit, "inpPop" ), 0, 10);

   inp = window_getInput( sysedit_widEdit, "inpClass" );
   if (p->class != NULL)
      free( p->class );

   if ((inp == NULL) || (strlen(inp) == 0))
      p->class = NULL;
   else
      p->class = strdup( inp );

   inp = window_getInput( sysedit_widEdit, "inpLand" );
   if (p->land_func != NULL)
      free( p->land_func );

   if ((inp == NULL) || (strlen(inp) == 0))
      p->land_func = NULL;
   else
      p->land_func = strdup( inp );

   p->presenceAmount = atof(window_getInput( sysedit_widEdit, "inpPresence" ));
   p->presenceRange  = atoi(window_getInput( sysedit_widEdit, "inpPresenceRange" ));
   p->hide           = pow2( atof(window_getInput( sysedit_widEdit, "inpHide" )) );

   /* Add the new presence. */
   system_addPresence(sysedit_sys, p->faction, p->presenceAmount, p->presenceRange);

   if (conf.devautosave)
      dpl_savePlanet( p );

   window_close( wid, unused );
}

/**
 * @brief Enters the editor in new planet mode.
 */
static void sysedit_btnNew( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;
   Planet *p, *b;
   char *name;

   /* Get new name. */
   name = dialogue_inputRaw( "New Planet Creation", 1, 32, "What do you want to name the new planet?" );
   if (name == NULL)
      return;

   /* Check for collision. */
   if (planet_exists( name )) {
      dialogue_alert( "Planet by the name of \er'%s'\e0 already exists in the \er'%s'\e0 system",
            name, planet_getSystem( name ) );
      free(name);
      sysedit_btnNew( 0, NULL );
      return;
   }

   /* Create the new planet. */
   p        = planet_new();
   p->real  = ASSET_REAL;
   p->name  = name;

   /* Base planet data off another. */
   b                    = planet_get( space_getRndPlanet(0, 0, NULL) );
   p->class             = strdup( b->class );
   p->gfx_spacePath     = strdup( b->gfx_spacePath );
   p->gfx_spaceName     = strdup( b->gfx_spaceName );
   p->gfx_exterior      = strdup( b->gfx_exterior );
   p->gfx_exteriorPath  = strdup( b->gfx_exteriorPath );
   p->pos.x             = sysedit_xpos / sysedit_zoom;
   p->pos.y             = sysedit_ypos / sysedit_zoom;
   p->hide              = pow2(HIDE_DEFAULT_PLANET);
   p->radius            = b->radius;

   /* Add new planet. */
   system_addPlanet( sysedit_sys, name );

   /* Update economy due to galaxy modification. */
   economy_execQueued();

   if (conf.devautosave)
      dpl_savePlanet( p );

   /* Reload graphics. */
   space_gfxLoad( sysedit_sys );
}


static void sysedit_btnRename( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;
   int i;
   char *name, *oldName, *newName, *filtered;
   Select_t *sel;
   Planet *p;
   for (i=0; i<sysedit_nselect; i++) {
      sel = &sysedit_select[i];
      if (sel->type == SELECT_PLANET) {
         p = sysedit_sys[i].planets[ sel->u.planet ];

         /* Get new name. */
         name = dialogue_input( "New Planet Creation", 1, 32,
               "What do you want to rename the planet \er%s\e0?", p->name );
         if (name == NULL)
            continue;

         /* Check for collision. */
         if (planet_exists( name )) {
            dialogue_alert( "Planet by the name of \er'%s'\e0 already exists in the \er'%s'\e0 system",
                  name, planet_getSystem( name ) );
            free(name);
            continue;
         }

         /* Rename. */
         filtered = uniedit_nameFilter(p->name);
         oldName = malloc(16 + strlen(filtered));
         nsnprintf(oldName, 16 + strlen(filtered), "dat/assets/%s.xml", filtered);
         free(filtered);

         filtered = uniedit_nameFilter(name);
         newName = malloc(16 + strlen(filtered));
         nsnprintf(newName, 16 + strlen(filtered), "dat/assets/%s.xml", filtered);
         free(filtered);

         nfile_rename(oldName, newName);

         free(oldName);
         free(newName);
         free(p->name);

         p->name = name;
         window_modifyText( sysedit_widEdit, "txtName", p->name );
         dpl_savePlanet( p );
      }
   }
}


/**
 * @brief Removes planets.
 */
static void sysedit_btnRemove( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;
   Select_t *sel;
   char *file, *filtered;
   int i;

   if (dialogue_YesNo( "Remove selected planets?", "This can not be undone." )) {
      for (i=0; i<sysedit_nselect; i++) {
         sel = &sysedit_select[i];
         if (sel->type == SELECT_PLANET) {
            filtered = uniedit_nameFilter( sysedit_sys->planets[ sel->u.planet ]->name );
            file = malloc(16 + strlen(filtered));
            nsnprintf(file, 16 + strlen(filtered), "dat/assets/%s.xml", filtered);
            nfile_delete(file);

            free(filtered);
            free(file);

            system_rmPlanet( sysedit_sys, sysedit_sys->planets[ sel->u.planet ]->name );
         }
      }

      /* Update economy due to galaxy modification. */
      economy_execQueued();
   }
}


/**
 * @brief Resets jump points.
 */
static void sysedit_btnReset( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;
   Select_t *sel;
   int i;
   for (i=0; i<sysedit_nselect; i++) {
      sel = &sysedit_select[i];
      if (sel->type == SELECT_JUMPPOINT)
         sysedit_sys[i].jumps[ sel->u.jump ].flags |= JP_AUTOPOS;
   }

   /* Must reconstruct jumps. */
   systems_reconstructJumps();
}


/**
 * @brief Interface for scaling a system from the system view.
 */
static void sysedit_btnScale( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;
   char *str;
   double s;
   int i;
   StarSystem *sys;

   /* Prompt scale amount. */
   str = dialogue_inputRaw( "Scale Star System", 1, 32, "By how much do you want to scale the star system?" );
   if (str == NULL)
      return;

   sys   = sysedit_sys; /* Comfort. */
   s     = atof(str);
   free(str);

   /* In case screwed up. */
   if ((s < 0.1) || (s > 10.)) {
      i = dialogue_YesNo( "Scale Star System", "Are you sure you want to scale the star system by %.2f (from %.2f to %.2f)?",
            s, sys->radius, sys->radius*s );
      if (i==0)
         return;
   }

   sysedit_sysScale(sys, s);
}

/**
 * @brief Scales a system.
 */
void sysedit_sysScale( StarSystem *sys, double factor )
{
   char buf[PATH_MAX];
   Planet *p;
   JumpPoint *jp;
   int i;

   /* Scale radius. */
   sys->radius *= factor;
   nsnprintf( buf, sizeof(buf), "Radius: %.0f", sys->radius );
   window_modifyText( sysedit_wid, "txtSelected", buf );

   /* Scale planets. */
   for (i=0; i<sys->nplanets; i++) {
      p     = sys->planets[i];
      vect_cset( &p->pos, p->pos.x*factor, p->pos.y*factor );
   }

   /* Scale jumps. */
   for (i=0; i<sys->njumps; i++) {
      jp    = &sys->jumps[i];
      vect_cset( &jp->pos, jp->pos.x*factor, jp->pos.y*factor );
   }

   /* Must reconstruct jumps. */
   systems_reconstructJumps();
}

/**
 * @brief Toggles the grid.
 */
static void sysedit_btnGrid( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;

   sysedit_grid = !sysedit_grid;
}


/**
 * @brief System editor custom widget rendering.
 */
static void sysedit_render( double bx, double by, double w, double h, void *data )
{
   (void) data;
   int i, j;
   StarSystem *sys;
   Planet *p;
   JumpPoint *jp;
   AsteroidAnchor *ast;
   double x,y, z;
   const glColour *c;
   int selected;
   Select_t sel;

   /* Comfort++. */
   sys   = sysedit_sys;
   z     = sysedit_zoom;

   /* Coordinate translation. */
   x = bx - sysedit_xpos + w/2;
   y = by - sysedit_ypos + h/2;

   /* First render background with lines. */
   sysedit_renderBG( bx, by, w, h, x, y );

   /* Render planets. */
   for (i=0; i<sys->nplanets; i++) {
      p              = sys->planets[i];

      /* Must be real. */
      if (p->real != ASSET_REAL)
         continue;

      /* Check if selected. */
      sel.type       = SELECT_PLANET;
      sel.u.planet   = i;
      selected       = 0;
      for (j=0; j<sysedit_nselect; j++) {
         if (sysedit_selectCmp( &sel, &sysedit_select[j] )) {
            selected = 1;
            break;
         }
      }

      /* Render. */
      sysedit_renderSprite( p->gfx_space, x, y, p->pos.x, p->pos.y, 0, 0, NULL, selected, p->name );
   }

   /* Render jump points. */
   for (i=0; i<sys->njumps; i++) {
      jp    = &sys->jumps[i];

      /* Choose colour. */
      if (jp->flags & JP_AUTOPOS)
         c = &cGreen;
      else
         c = NULL;

      /* Check if selected. */
      sel.type       = SELECT_JUMPPOINT;
      sel.u.planet   = i;
      selected       = 0;
      for (j=0; j<sysedit_nselect; j++) {
         if (sysedit_selectCmp( &sel, &sysedit_select[j] )) {
            selected = 1;
            break;
         }
      }

      /* Render. */
      sysedit_renderSprite( jumppoint_gfx, x, y, jp->pos.x, jp->pos.y,
            jp->sx, jp->sy, c, selected, jp->target->name );
   }

   /* Render asteroids */
   for (i=0; i<sys->nasteroids; i++) {
      ast = &sys->asteroids[i];
      selected = 0;
      sysedit_renderSprite( asteroid_gfx[0], x, y, ast->pos.x, ast->pos.y,
                            0, 0, NULL, selected, "Asteroid Field" );
   }

   /* Render cursor position. */
   gl_print( &gl_smallFont, bx + 5., by + 5.,
         &cWhite, "%.2f, %.2f",
         (bx + sysedit_mx - x)/z,
         (by + sysedit_my - y)/z );
}


/**
 * @brief Renders the custom widget background.
 */
static void sysedit_renderBG( double bx, double by, double w, double h, double x, double y )
{
   double z, s;
   double sx, sy, sz;

   /* Render blackness. */
   gl_renderRect( bx, by, w, h, &cBlack );

   /* Must have grid activated. */
   if (!sysedit_grid)
      return;

   /* Comfort. */
   z  = sysedit_zoom;
   s  = 1000.;

   /* Draw lines that go through 0,0 */
   gl_renderRect( x-1., by, 3., h, &cLightBlue );
   gl_renderRect( bx, y-1., w, 3., &cLightBlue );

   /* Render lines. */
   sz    = s*z;
   sx    = w/2. - fmod( sysedit_xpos, sz ) - sz*round( w/2. / sz );
   sy    = h/2. - fmod( sysedit_ypos, sz ) - sz*round( h/2. / sz );
   /* Vertical. */
   for (   ; sx<w; sx += sz)
      gl_renderRect( bx+sx, by, 1., h, &cBlue );
   /* Horizontal. */
   for (   ; sy<w; sy += sz)
      gl_renderRect( bx, by+sy, w, 1., &cBlue );

   glEnable(GL_LINE_SMOOTH);
   gl_drawCircleLoop( x, y, sysedit_sys->radius * sysedit_zoom, &cLightBlue );
   glDisable(GL_LINE_SMOOTH);
}


/**
 * @brief Renders a sprite for the custom widget.
 */
static void sysedit_renderSprite( glTexture *gfx, double bx, double by, double x, double y,
      int sx, int sy, const glColour *c, int selected, const char *caption )
{
   double tx, ty, z;
   glColour cc;
   const glColour *col;

   /* Comfort. */
   z  = sysedit_zoom;

   /* Translate coords. */
   tx = bx + (x - gfx->sw/2.)*z;
   ty = by + (y - gfx->sh/2.)*z;

   /* Selection graphic. */
   if (selected) {
      cc.r = cFontBlue.r;
      cc.g = cFontBlue.g;
      cc.b = cFontBlue.b;
      cc.a = 0.5;
      gl_drawCircle( bx + x*z, by + y*z, gfx->sw*z*1.1, &cc, 1 );
   }

   /* Blit the planet. */
   gl_blitScaleSprite( gfx, tx, ty, sx, sy, gfx->sw*z, gfx->sh*z, c );

   /* Display caption. */
   if (caption != NULL) {
      if (selected)
         col = &cRed;
      else
         col = c;
      gl_printMidRaw( &gl_smallFont, gfx->sw*z+100,
            tx - 50, ty - gl_smallFont.h - 5, col, caption );
   }
}


/**
 * @brief Renders the overlay.
 */
static void sysedit_renderOverlay( double bx, double by, double bw, double bh, void* data )
{
   (void) bx;
   (void) by;
   (void) bw;
   (void) bh;
   (void) data;
}

/**
 * @brief System editor custom widget mouse handling.
 */
static int sysedit_mouse( unsigned int wid, SDL_Event* event, double mx, double my,
      double w, double h, void *data )
{
   (void) wid;
   (void) data;
   int i, j;
   double x,y, t;
   SDLMod mod;
   StarSystem *sys;
   Planet *p;
   JumpPoint *jp;
   Select_t sel;

   /* Comfort. */
   sys = sysedit_sys;

   /* Handle modifiers. */
   mod = SDL_GetModState();

   switch (event->type) {

#if SDL_VERSION_ATLEAST(2,0,0)
      case SDL_MOUSEWHEEL:
         /* Must be in bounds. */
         if ((mx < 0.) || (mx > w) || (my < 0.) || (my > h))
            return 0;

         if (event->wheel.y > 0)
            sysedit_buttonZoom( 0, "btnZoomIn" );
         else
            sysedit_buttonZoom( 0, "btnZoomOut" );

         return 1;
#endif /* SDL_VERSION_ATLEAST(2,0,0) */

      case SDL_MOUSEBUTTONDOWN:
         /* Must be in bounds. */
         if ((mx < 0.) || (mx > w) || (my < 0.) || (my > h))
            return 0;

#if !SDL_VERSION_ATLEAST(2,0,0)
         /* Zooming */
         if (event->button.button == SDL_BUTTON_WHEELUP)
            sysedit_buttonZoom( 0, "btnZoomIn" );
         else if (event->button.button == SDL_BUTTON_WHEELDOWN)
            sysedit_buttonZoom( 0, "btnZoomOut" );
#endif /* !SDL_VERSION_ATLEAST(2,0,0) */

         /* selecting star system */
         else {
            mx -= w/2 - sysedit_xpos;
            my -= h/2 - sysedit_ypos;


            /* Check planets. */
            for (i=0; i<sys->nplanets; i++) {
               p = sys->planets[i];

               /* Must be real. */
               if (p->real != ASSET_REAL)
                  continue;

               /* Position. */
               x = p->pos.x * sysedit_zoom;
               y = p->pos.y * sysedit_zoom;

               /* Set selection. */
               sel.type       = SELECT_PLANET;
               sel.u.planet   = i;

               /* Threshold. */
               t  = p->gfx_space->sw * p->gfx_space->sh / 4.; /* Radius^2 */
               t *= pow2(2.*sysedit_zoom);

               /* Can select. */
               if ((pow2(mx-x)+pow2(my-y)) < t) {

                  /* Check if already selected. */
                  for (j=0; j<sysedit_nselect; j++) {
                     if (sysedit_selectCmp( &sel, &sysedit_select[j] )) {
                        sysedit_dragSel   = 1;
                        sysedit_tsel      = sel;

                        /* Check modifier. */
                        if (mod & (KMOD_LCTRL | KMOD_RCTRL))
                           sysedit_tadd      = 0;
                        else {
                           /* Detect double click to open planet editor. */
                           if ((SDL_GetTicks() - sysedit_dragTime < SYSEDIT_DRAG_THRESHOLD*2)
                                 && (sysedit_moved < SYSEDIT_MOVE_THRESHOLD)) {
                              sysedit_editPnt();
                              sysedit_dragSel = 0;
                              return 1;
                           }
                           sysedit_tadd      = -1;
                        }
                        sysedit_dragTime  = SDL_GetTicks();
                        sysedit_moved     = 0;
                        return 1;
                     }
                  }

                  /* Add the system if not selected. */
                  if (mod & (KMOD_LCTRL | KMOD_RCTRL))
                     sysedit_selectAdd( &sel );
                  else {
                     sysedit_deselect();
                     sysedit_selectAdd( &sel );
                  }
                  sysedit_tsel.type = SELECT_NONE;

                  /* Start dragging anyway. */
                  sysedit_dragSel   = 1;
                  sysedit_dragTime  = SDL_GetTicks();
                  sysedit_moved     = 0;
                  return 1;
               }
            }

            /* Check jump points. */
            for (i=0; i<sys->njumps; i++) {
               jp = &sys->jumps[i];

               /* Position. */
               x = jp->pos.x * sysedit_zoom;
               y = jp->pos.y * sysedit_zoom;

               /* Set selection. */
               sel.type       = SELECT_JUMPPOINT;
               sel.u.planet   = i;

               /* Threshold. */
               t  = jumppoint_gfx->sw * jumppoint_gfx->sh / 4.; /* Radius^2 */
               t *= pow2(2.*sysedit_zoom);

               /* Can select. */
               if ((pow2(mx-x)+pow2(my-y)) < t) {

                  /* Check if already selected. */
                  for (j=0; j<sysedit_nselect; j++) {
                     if (sysedit_selectCmp( &sel, &sysedit_select[j] )) {
                        sysedit_dragSel   = 1;
			sysedit_tsel      = sel;

                        /* Check modifier. */
                        if (mod & (KMOD_LCTRL | KMOD_RCTRL))
                           sysedit_tadd      = 0;
                        else {
                           /* Detect double click to open planet editor. */
                           if ((SDL_GetTicks() - sysedit_dragTime < SYSEDIT_DRAG_THRESHOLD*2)
                                 && (sysedit_moved < SYSEDIT_MOVE_THRESHOLD)) {
                              sysedit_editJump();
                              sysedit_dragSel = 0;
                              return 1;
                           }
                           sysedit_tadd      = -1;
                        }
                        sysedit_dragTime  = SDL_GetTicks();
                        sysedit_moved     = 0;
                        return 1;
                     }
                  }

                  /* Add the system if not selected. */
                  if (mod & (KMOD_LCTRL | KMOD_RCTRL))
                     sysedit_selectAdd( &sel );
                  else {
                     sysedit_deselect();
                     sysedit_selectAdd( &sel );
                  }
                  sysedit_tsel.type = SELECT_NONE;

                  /* Start dragging anyway. */
                  sysedit_dragSel   = 1;
                  sysedit_dragTime  = SDL_GetTicks();
                  sysedit_moved     = 0;
                  return 1;
               }
            }

            /* Start dragging. */
            if (!(mod & (KMOD_LCTRL | KMOD_RCTRL))) {
               sysedit_drag      = 1;
               sysedit_dragTime  = SDL_GetTicks();
               sysedit_moved     = 0;
               sysedit_tsel.type = SELECT_NONE;
            }
         }
         return 1;

      case SDL_MOUSEBUTTONUP:
         if (sysedit_drag) {
            if ((SDL_GetTicks() - sysedit_dragTime < SYSEDIT_DRAG_THRESHOLD) && (sysedit_moved < SYSEDIT_MOVE_THRESHOLD)) {
               if (sysedit_tsel.type == SELECT_NONE)
                  sysedit_deselect();
               else
                  sysedit_selectAdd( &sysedit_tsel );
            }
            sysedit_drag      = 0;

            if (conf.devautosave)
               for (i=0; i<sysedit_nselect; i++)
                  dpl_savePlanet(sysedit_sys->planets[ sysedit_select[i].u.planet ]);
         }
         if (sysedit_dragSel) {
            if ((SDL_GetTicks() - sysedit_dragTime < SYSEDIT_DRAG_THRESHOLD) &&
                  (sysedit_moved < SYSEDIT_MOVE_THRESHOLD) && (sysedit_tsel.type != SELECT_NONE)) {
               if (sysedit_tadd == 0)
                  sysedit_selectRm( &sysedit_tsel );
               else {
                  sysedit_deselect();
                  sysedit_selectAdd( &sysedit_tsel );
               }
            }
            sysedit_dragSel   = 0;


            /* Save all planets in our selection - their positions might have changed. */
            if (conf.devautosave)
               for (i=0; i<sysedit_nselect; i++)
                  if (sysedit_select[i].type == SELECT_PLANET)
                     dpl_savePlanet( sys->planets[ sysedit_select[i].u.planet ] );
         }
         break;

      case SDL_MOUSEMOTION:
         /* Update mouse positions. */
         sysedit_mx  = mx;
         sysedit_my  = my;

         /* Handle dragging. */
         if (sysedit_drag) {
            /* axis is inverted */
            sysedit_xpos -= event->motion.xrel;
            sysedit_ypos += event->motion.yrel;

            /* Update mouse movement. */
            sysedit_moved += ABS( event->motion.xrel ) + ABS( event->motion.yrel );
         }
         /* Dragging selection around. */
         else if (sysedit_dragSel && (sysedit_nselect > 0)) {
            if ((sysedit_moved > SYSEDIT_MOVE_THRESHOLD) || (SDL_GetTicks() - sysedit_dragTime > SYSEDIT_DRAG_THRESHOLD)) {
               for (i=0; i<sysedit_nselect; i++) {

                  /* Planets. */
                  if (sysedit_select[i].type == SELECT_PLANET) {
                     p = sys->planets[ sysedit_select[i].u.planet ];
                     p->pos.x += ((double)event->motion.xrel) / sysedit_zoom;
                     p->pos.y -= ((double)event->motion.yrel) / sysedit_zoom;
                  }
                  /* Jump point. */
                  else if (sysedit_select[i].type == SELECT_JUMPPOINT) {
                     jp         = &sys->jumps[ sysedit_select[i].u.jump ];
                     jp->flags &= ~(JP_AUTOPOS);
                     jp->pos.x += ((double)event->motion.xrel) / sysedit_zoom;
                     jp->pos.y -= ((double)event->motion.yrel) / sysedit_zoom;
                  }
               }
            }

            /* Update mouse movement. */
            sysedit_moved += ABS( event->motion.xrel ) + ABS( event->motion.yrel );
         }
         break;
   }

   return 0;
}


/**
 * @brief Handles the button zoom clicks.
 *
 *    @param wid Unused.
 *    @param str Name of the button creating the event.
 */
static void sysedit_buttonZoom( unsigned int wid, char* str )
{
   (void) wid;

   /* Transform coords to normal. */
   sysedit_xpos /= sysedit_zoom;
   sysedit_ypos /= sysedit_zoom;

   /* Apply zoom. */
   if (strcmp(str,"btnZoomIn")==0) {
      sysedit_zoom *= SYSEDIT_ZOOM_STEP;
      sysedit_zoom = MIN( pow(SYSEDIT_ZOOM_STEP, SYSEDIT_ZOOM_MAX), sysedit_zoom );
   }
   else if (strcmp(str,"btnZoomOut")==0) {
      sysedit_zoom /= SYSEDIT_ZOOM_STEP;
      sysedit_zoom = MAX( pow(SYSEDIT_ZOOM_STEP, SYSEDIT_ZOOM_MIN), sysedit_zoom );
   }

   /* Transform coords back. */
   sysedit_xpos *= sysedit_zoom;
   sysedit_ypos *= sysedit_zoom;
}


/**
 * @brief Deselects everything.
 */
static void sysedit_deselect (void)
{
   if (sysedit_nselect > 0)
      free( sysedit_select );
   sysedit_select    = NULL;
   sysedit_nselect   = 0;
   sysedit_mselect   = 0;

   /* Button sanity. */
   sysedit_checkButtons();
}


/**
 * @brief Checks to see which buttons should be active and the likes.
 */
static void sysedit_checkButtons (void)
{
   int i, sel_planet, sel_jump;
   Select_t *sel;

   /* See if a planet or jump is selected. */
   sel_planet  = 0;
   sel_jump    = 0;
   for (i=0; i<sysedit_nselect; i++) {
      sel = &sysedit_select[i];
      if (sel->type == SELECT_PLANET)
         sel_planet++;
      else if (sel->type == SELECT_JUMPPOINT)
         sel_jump++;
   }

   /* Planet dependent. */
   if (sel_planet) {
      window_enableButton( sysedit_wid, "btnRemove" );
      window_enableButton( sysedit_wid, "btnRename" );
   }
   else {
      window_disableButton( sysedit_wid, "btnRemove" );
      window_disableButton( sysedit_wid, "btnRename" );
   }

   /* Jump dependent. */
   if (sel_jump)
      window_enableButton( sysedit_wid, "btnReset" );
   else
      window_disableButton( sysedit_wid, "btnReset" );

   /* Editor - just one planet. */
   if (((sel_planet==1) && (sel_jump==0)) || ((sel_planet==0) && (sel_jump==1)))
      window_enableButton( sysedit_wid, "btnEdit" );
   else
      window_disableButton( sysedit_wid, "btnEdit" );
}


/**
 * @brief Adds a system to the selection.
 */
static void sysedit_selectAdd( Select_t *sel )
{
   /* Allocate if needed. */
   if (sysedit_mselect < sysedit_nselect+1) {
      if (sysedit_mselect == 0)
         sysedit_mselect = 1;
      sysedit_mselect  *= 2;
      sysedit_select    = realloc( sysedit_select,
            sizeof(Select_t) * sysedit_mselect );
   }

   /* Add system. */
   sysedit_select[ sysedit_nselect ] = *sel;
   sysedit_nselect++;

   /* Button sanity. */
   sysedit_checkButtons();
}


/**
 * @brief Removes a system from the selection.
 */
static void sysedit_selectRm( Select_t *sel )
{
   int i;
   for (i=0; i<sysedit_nselect; i++) {
      if (sysedit_selectCmp( &sysedit_select[i], sel )) {
         sysedit_nselect--;
         memmove( &sysedit_select[i], &sysedit_select[i+1],
               sizeof(Select_t) * (sysedit_nselect - i) );
         /* Button sanity. */
         sysedit_checkButtons();
         return;
      }
   }
   WARN("Trying to unselect item that is not in selection!");
}


/**
 * @brief Compares two selections to see if they are the same.
 *
 *    @return 1 if both selections are the same.
 */
static int sysedit_selectCmp( Select_t *a, Select_t *b )
{
   return (memcmp(a, b, sizeof(Select_t)) == 0);
}


/**
 * @brief Edits a planet.
 */
static void sysedit_editPnt( void )
{
   unsigned int wid;
   int x, y, w, l, bw;
   char buf[1024], *s, title[100];
   Planet *p;

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];

   /* Create the window. */
   sprintf(title, "Planet Property Editor - %s", p->name);
   wid = window_create( title, -1, -1, SYSEDIT_EDIT_WIDTH, SYSEDIT_EDIT_HEIGHT );
   sysedit_widEdit = wid;

   window_setCancel( wid, sysedit_editPntClose );

   bw = (SYSEDIT_EDIT_WIDTH - 40 - 15 * 3) / 4.;

   /* Rename button. */
   y = -40;
   nsnprintf( buf, sizeof(buf), "Name: " );
   w = gl_printWidthRaw( NULL, buf );
   window_addText( wid, 20, y, 180, 15, 0, "txtNameLabel", &gl_smallFont, &cDConsole, buf );
   nsnprintf( buf, sizeof(buf), "%s", p->name );
   window_addText( wid, 20 + w, y, 180, 15, 0, "txtName", &gl_smallFont, &cBlack, buf );
   window_addButton( wid, -20, y - gl_defFont.h/2. + BUTTON_HEIGHT/2., bw, BUTTON_HEIGHT, "btnRename",
         "Rename", sysedit_btnRename );
   window_addButton( wid, -20 - 15 - bw, y - gl_defFont.h/2. + BUTTON_HEIGHT/2., bw, BUTTON_HEIGHT, "btnFaction",
         "Faction", sysedit_btnFaction );

   y -= gl_defFont.h + 5;

   window_addText( wid, 20, y, 180, 15, 0, "txtFactionLabel", &gl_smallFont, &cDConsole, "Faction: " );
   nsnprintf( buf, sizeof(buf), "%s", p->faction > 0 ? faction_name( p->faction ) : "None" );
   window_addText( wid, 20 + w, y, 180, 15, 0, "txtFaction", &gl_smallFont, &cBlack, buf );
   y -= gl_defFont.h + 5;

   /* Input widgets and labels. */
   x = 20;
   s = "Population";
   l = gl_printWidthRaw( NULL, s );
   window_addText( wid, x, y, l, 20, 1, "txtPop",
         NULL, &cBlack, s );
   window_addInput( wid, x += l + 5, y, 80, 20, "inpPop", 12, 1, NULL );
   window_setInputFilter( wid, "inpPop",
         "abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ[]{}()-=*/\\'\"~<>!@#$%^&|_`." );
   x += 80 + 10;

   s = "Class";
   l = gl_printWidthRaw( NULL, s );
   window_addText( wid, x, y, l, 20, 1, "txtClass",
         NULL, &cBlack, s );
   window_addInput( wid, x += l + 5, y, 30, 20, "inpClass", 1, 1, NULL );
   x += 30 + 10;

   s = "Land";
   l = gl_printWidthRaw( NULL, s );
   window_addText( wid, x, y, l, 20, 1, "txtLand",
         NULL, &cBlack, s );
   window_addInput( wid, x += l + 5, y, 150, 20, "inpLand", 20, 1, NULL );
   y -= gl_defFont.h + 15;

   /* Second row. */
   x = 20;
   s = "Presence";
   l = gl_printWidthRaw( NULL, s );
   window_addText( wid, x, y, l, 20, 1, "txtPresence",
         NULL, &cBlack, s );
   window_addInput( wid, x += l + 5, y, 60, 20, "inpPresence", 5, 1, NULL );
   window_setInputFilter( wid, "inpPresence",
         "abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ[]{}()-=*/\\'\"~<>!@#$%^&|_`" );
   x += 60 + 10;

   s = "Range";
   l = gl_printWidthRaw( NULL, s );
   window_addText( wid, x, y, l, 20, 1, "txtPresenceRange",
         NULL, &cBlack, s );
   window_addInput( wid, x += l + 5, y, 30, 20, "inpPresenceRange", 1, 1, NULL );
   window_setInputFilter( wid, "inpPresenceRange",
         "abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ[]{}()-=*/\\'\"~<>!@#$%^&|_`." );
   x += 30 + 10;

   s = "hide";
   l = gl_printWidthRaw( NULL, s );
   window_addText( wid, x, y, l, 20, 1, "txtHide",
         NULL, &cBlack, s );
   window_addInput( wid, x += l + 5, y, 50, 20, "inpHide", 4, 1, NULL );
   window_setInputFilter( wid, "inpHide",
         "abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ[]{}()-=*/\\'\"~<>!@#$%^&|_`" );
   x += 50 + 10;

   /* Bottom buttons. */
   window_addButton( wid, -20 - bw*3 - 15*3, 35 + BUTTON_HEIGHT, bw, BUTTON_HEIGHT,
         "btnRmService", "Rm Service", sysedit_btnRmService );
   window_addButton( wid, -20 - bw*2 - 15*2, 35 + BUTTON_HEIGHT, bw, BUTTON_HEIGHT,
         "btnAddService", "Add Service", sysedit_btnAddService );
   window_addButton( wid, -20 - bw - 15, 35 + BUTTON_HEIGHT, bw, BUTTON_HEIGHT,
         "btnEditTech", "Edit Tech", sysedit_btnTechEdit );
   window_addButton( wid, -20 - bw*3 - 15*3, 20, bw, BUTTON_HEIGHT,
         "btnDesc", "Description", sysedit_planetDesc );
   window_addButton( wid, -20 - bw*2 - 15*2, 20, bw, BUTTON_HEIGHT,
         "btnLandGFX", "Land GFX", sysedit_planetGFX );
   window_addButton( wid, -20 - bw - 15, 20, bw, BUTTON_HEIGHT,
         "btnSpaceGFX", "Space GFX", sysedit_planetGFX );
   window_addButton( wid, -20, 20, bw, BUTTON_HEIGHT,
         "btnClose", "Close", sysedit_editPntClose );

   /* Load current values. */
   nsnprintf( buf, sizeof(buf), "%"PRIu64, p->population );
   window_setInput( wid, "inpPop", buf );
   nsnprintf( buf, sizeof(buf), "%s", p->class );
   window_setInput( wid, "inpClass", buf );
   window_setInput( wid, "inpLand", p->land_func );
   nsnprintf( buf, sizeof(buf), "%g", p->presenceAmount );
   window_setInput( wid, "inpPresence", buf );
   nsnprintf( buf, sizeof(buf), "%d", p->presenceRange );
   window_setInput( wid, "inpPresenceRange", buf );
   nsnprintf( buf, sizeof(buf), "%g", sqrt(p->hide) );
   window_setInput( wid, "inpHide", buf );

   /* Generate the list. */
   sysedit_genServicesList( wid );
}

/**
 * @brief Updates the jump point checkboxes.
 */
static void jp_type_check_hidden_update( unsigned int wid, char* str )
{
   (void) str;
   if (jp_hidden == 0) {
      jp_hidden = 1;
      jp_exit   = 0;
   }
   else
      jp_hidden = 0;
   window_checkboxSet( wid, "chkHidden", jp_hidden );
   window_checkboxSet( wid, "chkExit",   jp_exit );
}

/**
 * @brief Updates the jump point checkboxes.
 */
static void jp_type_check_exit_update( unsigned int wid, char* str )
{
   (void) str;
   if (jp_exit == 0) {
      jp_exit   = 1;
      jp_hidden = 0;
   }
   else
      jp_exit = 0;
   window_checkboxSet( wid, "chkHidden", jp_hidden );
   window_checkboxSet( wid, "chkExit",   jp_exit );
}

/**
 * @brief Edits a jump.
 */
static void sysedit_editJump( void )
{
   unsigned int wid;
   int x, y, w, l, bw;
   char buf[1024], *s;
   JumpPoint *j;

   j = &sysedit_sys->jumps[ sysedit_select[0].u.jump ];

   /* Create the window. */
   wid = window_create( "Jump Point Editor", -1, -1, SYSEDIT_EDIT_WIDTH, SYSEDIT_EDIT_HEIGHT );
   sysedit_widEdit = wid;

   bw = (SYSEDIT_EDIT_WIDTH - 40 - 15 * 3) / 4.;

   /* Target lable. */
   y = -40;
   nsnprintf( buf, sizeof(buf), "Target: " );
   w = gl_printWidthRaw( NULL, buf );
   window_addText( wid, 20, y, 180, 15, 0, "txtTargetLabel", &gl_smallFont, &cDConsole, buf );
   nsnprintf( buf, sizeof(buf), "%s", j->target->name );
   window_addText( wid, 20 + w, y, 180, 15, 0, "txtName", &gl_smallFont, &cBlack, buf );

   y -= gl_defFont.h + 10;

   /* Input widgets and labels. */
   x = 20;

   /* Initial checkbox state */
   jp_hidden = 0;
   jp_exit   = 0;
   if (jp_isFlag( j, JP_HIDDEN ))
      jp_hidden = 1;
   else if (jp_isFlag( j, JP_EXITONLY ))
      jp_exit   = 1;
   /* Create check boxes. */
   window_addCheckbox( wid, x, y, 100, 20,
         "chkHidden", "Hidden", jp_type_check_hidden_update, jp_hidden );
   y -= 20;
   window_addCheckbox( wid, x, y, 100, 20,
         "chkExit", "Exit only", jp_type_check_exit_update, jp_exit );
   y -= 30;

   s = "Hide"; /* TODO: if inpType == 0 disable hide box */
   l = gl_printWidthRaw( NULL, s );
   window_addText( wid, x, y, l, 20, 1, "txtHide",
         NULL, &cBlack, s );
   window_addInput( wid, x + l + 8, y, 50, 20, "inpHide", 4, 1, NULL );
   window_setInputFilter( wid, "inpHide",
         "abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ[]{}()-=*/\\'\"~<>!@#$%^&|_`" );
   x += 50 + 10;

   /* Bottom buttons. */
   window_addButton( wid, -20, 20, bw, BUTTON_HEIGHT,
         "btnClose", "Close", sysedit_editJumpClose );

   /* Load current values. */
   nsnprintf( buf, sizeof(buf), "%g", sqrt(j->hide) );
   window_setInput( wid, "inpHide", buf );
}

/**
 * @brief Closes the jump editor, saving the changes made.
 */
static void sysedit_editJumpClose( unsigned int wid, char *unused )
{
   (void) unused;
   JumpPoint *j;

   j = &sysedit_sys->jumps[ sysedit_select[0].u.jump ];
   if (jp_hidden == 1) {
      jp_setFlag( j, JP_HIDDEN );
      jp_rmFlag(  j, JP_EXITONLY );
   }
   else if (jp_exit == 1) {
      jp_setFlag( j, JP_EXITONLY );
      jp_rmFlag(  j, JP_HIDDEN );
   }
   else {
      jp_rmFlag( j, JP_HIDDEN );
      jp_rmFlag( j, JP_EXITONLY );
   }
   j->hide  = pow2( atof(window_getInput( sysedit_widEdit, "inpHide" )) );

   window_close( wid, unused );
}

/**
 * @brief Displays the planet landing description and bar description in a separate window.
 */
static void sysedit_planetDesc( unsigned int wid, char *unused )
{
   (void) unused;
   int x, y, h, w, bw;
   Planet *p;
   char *desc, *bardesc, title[100];

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];

   /* Create the window. */
   sprintf(title, "Planet Information - %s", p->name);
   wid = window_create( title, -1, -1, SYSEDIT_EDIT_WIDTH, SYSEDIT_EDIT_HEIGHT );
   window_setCancel( wid, window_close );

   x = 20;
   y = -40;
   w = SYSEDIT_EDIT_WIDTH - 40;
   h = (SYSEDIT_EDIT_HEIGHT - gl_defFont.h * 2 - 30 - 60 - BUTTON_HEIGHT - 10) / 2.;
   desc    = p->description ? p->description : "None";
   bardesc = p->bar_description ? p->bar_description : "None";
   bw = (SYSEDIT_EDIT_WIDTH - 40 - 15 * 3) / 4.;

   window_addButton( wid, -20 - bw*3 - 15*3, 20, bw, BUTTON_HEIGHT,
         "btnProperties", "Properties", sysedit_planetDescReturn );

   window_addButton( wid, -20, 20, bw, BUTTON_HEIGHT,
         "btnClose", "Close", sysedit_planetDescClose );

   /* Description label and text. */
   window_addText( wid, x, y, w, gl_defFont.h, 0, "txtDescriptionLabel", &gl_defFont, &cBlack,
         "Landing Description" );
   y -= gl_defFont.h + 10;
   window_addInput( wid, x, y, w, h, "txtDescription", 1024, 0,
         NULL );
   window_setInputFilter( wid, "txtDescription",
         "[]{}~<>@#$^|_" );
   y -= h + 10;
   /* Load current values. */
   window_setInput( wid, "txtDescription", desc );

   /* Bar description label and text. */
   window_addText( wid, x, y, w, gl_defFont.h, 0, "txtBarDescriptionLabel", &gl_defFont, &cBlack,
         "Bar Description" );
   y -= gl_defFont.h + 10;
   window_addInput( wid, x, y, w, h, "txtBarDescription", 1024, 0,
         NULL );
   window_setInputFilter( wid, "txtBarDescription",
         "[]{}~<>@#$^|_" );
   /* Load current values. */
   window_setInput( wid, "txtBarDescription", bardesc );
}

/**
 * @brief Closes the planet description window and returns to the properties window.
 */
static void sysedit_planetDescReturn( unsigned int wid, char *unused )
{
   Planet *p;
   char *mydesc, *mybardesc;

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];

   mydesc    = window_getInput( wid, "txtDescription" );
   mybardesc = window_getInput( wid, "txtBarDescription" );

   if (p->description)
      free(p->description);
   if (p->bar_description)
      free(p->bar_description);
   p->description     = NULL;
   p->bar_description = NULL;

   if (mydesc != NULL)
      p->description     = strdup( mydesc );
   if (mybardesc != NULL)
      p->bar_description = strdup( mybardesc );

   window_close( wid, unused );
}

/**
 * @brief Closes both the planet description window and the properties window.
 */
static void sysedit_planetDescClose( unsigned int wid, char *unused )
{
   sysedit_planetDescReturn( wid, unused );
   sysedit_editPntClose( sysedit_widEdit, unused );
}

/**
 * @brief Generates the planet services list.
 */
static void sysedit_genServicesList( unsigned int wid )
{
   int i, j, n, nservices;
   Planet *p;
   char **have, **lack;
   int x, y, w, h, hpos, lpos;

   hpos = lpos = -1;

   /* Destroy if exists. */
   if (widget_exists( wid, "lstServicesHave" ) &&
         widget_exists( wid, "lstServicesLacked" )) {
      hpos = toolkit_getListPos( wid, "lstServicesHave" );
      lpos = toolkit_getListPos( wid, "lstServicesLacked" );
      window_destroyWidget( wid, "lstServicesHave" );
      window_destroyWidget( wid, "lstServicesLacked" );
   }

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   x = 20;
   y = 20 + BUTTON_HEIGHT * 2 + 30;
   w = (SYSEDIT_EDIT_WIDTH - 40 - 15 * 3) / 4.;
   h = SYSEDIT_EDIT_HEIGHT - y - 130;

   /* Get all missing services. */
   n = nservices = 0;
   for (i=1; i<PLANET_SERVICES_MAX; i<<=1) {
      if (!planet_hasService(p, i) && (i != PLANET_SERVICE_INHABITED))
         n++;
      nservices++; /* Cheaply track all service types. */
   }

   /* Get all the services the planet has. */
   j = 0;
   have = malloc( sizeof(char*) * MAX(nservices - n, 1) );
   if (nservices == n)
      have[j++] = strdup("None");
   else
      for (i=1; i<PLANET_SERVICES_MAX; i<<=1)
         if (planet_hasService(p, i)  && (i != PLANET_SERVICE_INHABITED))
            have[j++] = strdup( planet_getServiceName( i ) );

   /* Add list. */
   window_addList( wid, x, y, w, h, "lstServicesHave", have, j, 0, NULL );
   x += w + 15;

   /* Add list of services the planet lacks. */
   j = 0;
   lack = malloc( sizeof(char*) * MAX(1, n) );
   if (!n)
      lack[j++] = strdup( "None" );
   else
      for (i=1; i<PLANET_SERVICES_MAX; i<<=1)
         if (!planet_hasService(p, i) && (i != PLANET_SERVICE_INHABITED))
            lack[j++] = strdup( planet_getServiceName(i) );

   /* Add list. */
   window_addList( wid, x, y, w, h, "lstServicesLacked", lack, j, 0, NULL );

   /* Restore positions. */
   if (hpos != -1 && lpos != -1) {
      toolkit_setListPos( wid, "lstServicesHave", hpos );
      toolkit_setListPos( wid, "lstServicesLacked", lpos );
   }
}


/**
 * @brief Adds a service to a planet.
 */
static void sysedit_btnAddService( unsigned int wid, char *unused )
{
   (void) unused;
   char *selected;
   Planet *p;

   selected = toolkit_getList( wid, "lstServicesLacked" );
   if ((selected == NULL) || (strcmp(selected,"None")==0))
      return;

   /* Enable the service. All services imply landability. */
   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   p->services |= planet_getService(selected) | PLANET_SERVICE_INHABITED | PLANET_SERVICE_LAND;

   /* Regenerate the list. */
   sysedit_genServicesList( wid );
}


/**
 * @brief Removes a service from a planet.
 */
static void sysedit_btnRmService( unsigned int wid, char *unused )
{
   (void) unused;
   char *selected;
   Planet *p;

   selected = toolkit_getList( wid, "lstServicesHave" );
   if ((selected==NULL) || (strcmp(selected,"None")==0))
      return;

   /* Flip the bit. Safe enough, as it's always 1 to start with. */
   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   p->services ^= planet_getService(selected);

   /* If landability was removed, the rest must go, too. */
   if (strcmp(selected,"Land")==0)
      p->services = 0;

   sysedit_genServicesList( wid );
}


/**
 * @brief Edits a planet's tech.
 */
static void sysedit_btnTechEdit( unsigned int wid, char *unused )
{
   (void) unused;
   int y, w, bw;

   /* Create the window. */
   wid = window_create( "Planet Tech Editor", -1, -1, SYSEDIT_EDIT_WIDTH, SYSEDIT_EDIT_HEIGHT );
   window_setCancel( wid, window_close );

   w = (SYSEDIT_EDIT_WIDTH - 40 - 15) / 2.;
   bw = (SYSEDIT_EDIT_WIDTH - 40 - 15 * 3) / 4.;

   /* Close button. */
   window_addButton( wid, -20, 20, bw, BUTTON_HEIGHT,
         "btnClose", "Close", window_close );
   y = 20 + BUTTON_HEIGHT + 15;

   /* Remove button. */
   window_addButton( wid, -20-(w+15), y, w, BUTTON_HEIGHT,
         "btnRm", "Rm Tech", sysedit_btnRmTech );

   /* Add button. */
   window_addButton( wid, -20, y, w, BUTTON_HEIGHT,
         "btnAdd", "Add Tech", sysedit_btnAddTech );

   sysedit_genTechList( wid );
}


/**
 * @brief Generates the planet services list.
 */
static void sysedit_genTechList( unsigned int wid )
{
   Planet *p;
   char **have, **lack, **tmp;
   int i, j, n, x, y, w, h, hpos, lpos;

   hpos = lpos = -1;

   /* Destroy if exists. */
   if (widget_exists( wid, "lstTechsHave" ) &&
         widget_exists( wid, "lstTechsLacked" )) {
      hpos = toolkit_getListPos( wid, "lstTechsHave" );
      lpos = toolkit_getListPos( wid, "lstTechsLacked" );
      window_destroyWidget( wid, "lstTechsHave" );
      window_destroyWidget( wid, "lstTechsLacked" );
   }

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   w = (SYSEDIT_EDIT_WIDTH - 40 - 15) / 2.;
   x = -20 - w - 15;
   y = 20 + BUTTON_HEIGHT * 2 + 30;
   h = SYSEDIT_EDIT_HEIGHT - y - 30;

   /* Get all the techs the planet has. */
   n = 0;
   if (p->tech != NULL)
      have = tech_getItemNames( p->tech, &n );
   else {
      have = malloc( sizeof(char*) );
      have[n++] = strdup("None");
   }

   /* Add list. */
   window_addList( wid, x, y, w, h, "lstTechsHave", have, n, 0, NULL );
   x += w + 15;

   /* Omit the techs that the planet already has from the list.  */
   n = 0;
   if (p->tech != NULL) {
      tmp = tech_getAllItemNames( &j );
      for (i=0; i<j; i++)
         if (!tech_hasItem( p->tech, tmp[i] ))
            n++;

      if (!n) {
         lack = malloc( sizeof(char*) );
         lack[n++] = strdup("None");
      }
      else {
         lack = malloc( sizeof(char*) * j );
         n = 0;
         for (i=0; i<j; i++)
            if (!tech_hasItem( p->tech, tmp[i] ))
               lack[n++] = strdup( tmp[i] );
      }

      /* Clean up. */
      for (i=0; i<j; i++)
         free(tmp[i]);

      free(tmp);
   }
   else
      lack = tech_getAllItemNames( &n );

   /* Add list. */
   window_addList( wid, x, y, w, h, "lstTechsLacked", lack, n, 0, NULL );

   /* Restore positions. */
   if (hpos != -1 && lpos != -1) {
      toolkit_setListPos( wid, "lstTechsHave", hpos );
      toolkit_setListPos( wid, "lstTechsLacked", lpos );
   }
}


/**
 * @brief Adds a tech to a planet.
 */
static void sysedit_btnAddTech( unsigned int wid, char *unused )
{
   (void) unused;
   char *selected;
   Planet *p;

   selected = toolkit_getList( wid, "lstTechsLacked" );
   if ((selected == NULL) || (strcmp(selected,"None")==0))
      return;

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   if (p->tech == NULL)
      p->tech = tech_groupCreate();
   tech_addItemTech( p->tech, selected );

   /* Regenerate the list. */
   sysedit_genTechList( wid );
}


/**
 * @brief Removes a tech from a planet.
 */
static void sysedit_btnRmTech( unsigned int wid, char *unused )
{
   (void) unused;
   char *selected;
   Planet *p;
   int n;

   selected = toolkit_getList( wid, "lstTechsHave" );
   if ((selected == NULL) || (strcmp(selected,"None")==0))
      return;

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   if (tech_hasItem( p->tech, selected ))
      tech_rmItemTech( p->tech, selected );

   n = tech_getItemCount( p->tech );
   if (!n)
      p->tech = NULL;

   /* Regenerate the list. */
   sysedit_genTechList( wid );
}


/**
 * @brief Edits a planet's faction.
 */
static void sysedit_btnFaction( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;
   unsigned int wid;
   int i, j, y, h, n, bw, *factions;
   char **str;

   /* Create the window. */
   wid = window_create( "Modify Faction", -1, -1, SYSEDIT_EDIT_WIDTH, SYSEDIT_EDIT_HEIGHT );
   window_setCancel( wid, window_close );

   /* Generate factions list. */
   factions = faction_getAll( &n );
   str = malloc( sizeof(char*) * (n+1));
   j   = 0;
   for (i=0; i<n; i++)
      str[j++] = strdup( faction_name( factions[i] ) );
   str[j++] = strdup( "None" );

   bw = (SYSEDIT_EDIT_WIDTH - 40 - 15 * 3) / 4.;
   y = 20 + BUTTON_HEIGHT + 15;
   h = SYSEDIT_EDIT_HEIGHT - y - 30;
   window_addList( wid, 20, -40, SYSEDIT_EDIT_WIDTH-40, h,
         "lstFactions", str, j, 0, NULL );

   /* Close button. */
   window_addButton( wid, -20, 20, bw, BUTTON_HEIGHT,
         "btnClose", "Close", window_close );

   /* Add button. */
   window_addButton( wid, -20-(bw+15), 20, bw, BUTTON_HEIGHT,
         "btnAdd", "Set", sysedit_btnFactionSet );
}


/**
 * @brief Actually modifies the faction.
 */
static void sysedit_btnFactionSet( unsigned int wid, char *unused )
{
   (void) unused;
   char *selected;
   Planet *p;

   selected = toolkit_getList( wid, "lstFactions" );
   if (selected == NULL)
      return;

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   /* Set the faction. */
   if (strcmp(selected,"None")==0)
      p->faction = -1;
   else
      p->faction = faction_get( selected );

   /* Update the editor window. */
   window_modifyText( sysedit_widEdit, "txtFaction", p->faction > 0 ? faction_name( p->faction ) : "None" );

   window_close( wid, unused );
}


/**
 * @brief Opens the system property editor.
 */
static void sysedit_btnEdit( unsigned int wid_unused, char *unused )
{
   (void) wid_unused;
   (void) unused;
   Select_t *sel;

   sel = &sysedit_select[0];

   if (sel->type==SELECT_PLANET)
      sysedit_editPnt();
   else if (sel->type==SELECT_JUMPPOINT)
      sysedit_editJump();
}


/**
 * @brief Opens the planet landing or space graphic editor.
 */
static void sysedit_planetGFX( unsigned int wid_unused, char *wgt )
{
   (void) wid_unused;
   unsigned int wid;
   uint32_t nfiles, i, j;
   char *path, buf[PATH_MAX];
   char **files, **png_files;
   glTexture **tex, *t;
   int w, h, land;
   Planet *p;
   glColour *bg, c;

   land = !strcmp(wgt,"btnLandGFX");

   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];
   /* Create the window. */
   nsnprintf( buf, sizeof(buf), "%s - Planet Properties", p->name );
   wid = window_create( buf, -1, -1, -1, -1 );
   window_dimWindow( wid, &w, &h );

   window_setCancel( wid, sysedit_btnGFXClose );
   window_setAccept( wid, sysedit_btnGFXApply );

   /* Close button. */
   window_addButton( wid, -20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
         "btnClose", "Close", sysedit_btnGFXClose );

   /* Apply button. */
   window_addButton( wid, -20, 20+(20+BUTTON_HEIGHT), BUTTON_WIDTH, BUTTON_HEIGHT,
         land ? "btnApplyLand" : "btnApplySpace", "Apply", sysedit_btnGFXApply );

   /* Find images first. */
   path           = land ? PLANET_GFX_EXTERIOR_PATH : PLANET_GFX_SPACE_PATH;
   files          = ndata_list( path, &nfiles );
   ndata_sortName( files, nfiles );
   png_files      = malloc( sizeof(char*) * nfiles );
   tex            = malloc( sizeof(glTexture*) * nfiles );
   sysedit_tex    = malloc( sizeof(glTexture*) * nfiles );
   bg             = malloc( sizeof(glColour) * nfiles );
   j              = 0;
   for (i=0; i<nfiles; i++) {
      nsnprintf( buf, sizeof(buf), "%s/%s", path, files[i] );
      t              = gl_newImage( buf, OPENGL_TEX_MIPMAPS );
      if (t != NULL) {
         tex[j]         = t;
         sysedit_tex[j] = tex[j];
         png_files[j]   = strdup( files[i] );
         c = strcmp(files[i], land ? p->gfx_exteriorPath : p->gfx_spacePath)==0 ? cOrange : cBlack;
         bg[j] = c;
         j++;
      }
      free( files[i] );
   }
   free( files );
   sysedit_ntex   = j;

   /* Add image array. */
   window_addImageArray( wid, 20, 20, w-60-BUTTON_WIDTH, h-60, "iarGFX", 128, 128, tex, png_files, j, NULL, NULL );
   toolkit_setImageArray( wid, "iarGFX", path );
   toolkit_setImageArrayBackground( wid, "iarGFX", bg );
}


/**
 * @brief Closes the planet graphic editor.
 */
static void sysedit_btnGFXClose( unsigned int wid, char *wgt )
{
   int i;
   for (i=0; i<sysedit_ntex; i++)
      gl_freeTexture( sysedit_tex[i] );
   if (sysedit_tex != NULL)
      free( sysedit_tex );
   sysedit_tex    = NULL;
   sysedit_ntex   = 0;
   window_close( wid, wgt );
}


/**
 * @brief Apply new graphics.
 */
static void sysedit_btnGFXApply( unsigned int wid, char *wgt )
{
   Planet *p;
   char *str, *path, buf[PATH_MAX];
   int land;

   land = !strcmp(wgt,"btnApplyLand");
   p = sysedit_sys->planets[ sysedit_select[0].u.planet ];

   /* Get output. */
   str = toolkit_getImageArray( wid, "iarGFX" );
   if (str == NULL)
      return;

   /* New path. */
   path = land ? PLANET_GFX_EXTERIOR_PATH : PLANET_GFX_SPACE_PATH;
   nsnprintf( buf, sizeof(buf), "%s/%s", path, str );

   if (land) {
      free( p->gfx_exteriorPath );
      nsnprintf( buf, sizeof(buf), PLANET_GFX_EXTERIOR_PATH"%s", str );
      p->gfx_exteriorPath = strdup( str );
      p->gfx_exterior = strdup( buf );
   }
   else { /* Free old texture, load new. */
      free( p->gfx_spacePath );
      gl_freeTexture( p->gfx_space );
      p->gfx_space     = gl_newImage( buf, OPENGL_TEX_MIPMAPS );
      p->gfx_spacePath = strdup( str );
      planet_setRadiusFromGFX(p);
   }

   /* For now we close. */
   sysedit_btnGFXClose( wid, wgt );
}
