/**
 * @file toolkit.c
 *
 * @brief Handle window and widgets.
 */

#include <stdarg.h>

#include "lephisto.h"
#include "log.h"
#include "pause.h"
#include "opengl.h"
#include "input.h"
#include "toolkit.h"

#define INPUT_DELAY   500
#define INPUT_FREQ    100

typedef enum WidgetType_ {
  WIDGET_NULL,
  WIDGET_BUTTON,
  WIDGET_TEXT,
  WIDGET_IMAGE,
  WIDGET_LIST,
  WIDGET_RECT,
  WIDGET_CUST,
  WIDGET_INPUT,
  WIDGET_IMAGEARRAY
} WidgetType;

typedef enum WidgetStatus_ {
  WIDGET_STATUS_NORMAL,
  WIDGET_STATUS_MOUSEOVER,
  WIDGET_STATUS_MOUSEDOWN,
  WIDGET_STATUS_SCROLLING
} WidgetStatus;

typedef struct Widget_ {
  char* name; /* Widget name. */
  WidgetType type; /* type.. */

  double x,y; /* Position. */
  double w,h; /* Dimensions. */

  WidgetStatus status;

  union {
    /* Widget button. */
    struct {
      void(*fptr) (char*); /* Active callback. */
      char* display; /* Stored text. */
      int   disabled;
    } btn;
    /* Widget text. */
    struct {
      char* text; /* Use printMid for centered printText if not. */
      glFont* font;
      glColour* colour;
      int centered;
    } txt;
    struct {
      /* Widget image. */
      glTexture* image;
      glColour*  colour;
      int border;
    } img;
    struct {
      /* Widget list. */
      char** options; /* Pointer to the options. */
      int noptions;   /* total number of options. */
      int selected;   /* Currently selected option. */
      int pos;    /* Current topmost option (in view). */
      void (*fptr) (char*); /* Modify callback. */
    } lst;
    /* Widget rect. */
    struct {
      glColour* colour; /* Background colour. */
      int border;    /* Border. */
    } rct;
    /* Widget cust. */
    struct {
      int border;
      void(*render) (double bx, double by, double bw, double bh);
      void(*mouse) (SDL_Event* event, double bx, double by);
    } cst;
    struct {
      /* Widget input. */
      char* input;    /* Input buffer. */
      int max;        /* Max length. */
      int oneline;    /* Is it a one-line? '\n' and co. if no. */
      int view, pos;  /* View and cursor position. */
    } inp;
    struct {
      glTexture** images;   /**< Image array. */
      char** captions;      /**< Corresponding caption array. */
      int nelements;        /**< Number of elements. */
      int selected;         /**< Currently selected element. */
      double pos;           /**< Current y position. */
      int iw;               /**< Image width to use. */
      int ih;               /**< Image height to use. */
      void(*fptr)(char*);   /**< Modify callback - triggered on selection. */
    } iar; /**< WIDGET_IMAGEARRAY */
  } dat;
} Widget;

typedef struct Window_ {
  unsigned int id; /* Unique identifier. */
  char* name;

  int hidden;  /* Is it hidden? */
  int focus;  /* Which window is focused. */

  void(*accept_fptr)(char*); /**< Triggered by hitting 'enter' with no widget
                                  that catches the keypress. */
  void(*cancel_fptr)(char*); /**< Triggered by hitting 'escape' with no
                                  widget that catches the keypress. */

  double x,y; /* Position. */
  double w,h; /* Dimensions. */

  Widget* widgets; /* Widget storage. */
  int nwidgets; /* Total number of widgets. */
} Window;

static unsigned int genwid = 0; /* Generate unique id > 0. */

int toolkit = 0;

#define MIN_WINDOWS 3
static Window* windows = NULL;
static int nwindows = 0;
static int mwindows = 0;

/* Default outline colours. */
static glColour* toolkit_colLight = &cGrey90;
static glColour* toolkit_col   = &cGrey70;
static glColour* toolkit_colDark = &cGrey30;

/* Converts absolute mouse events to relative mouse events. */
static double last_x = 0.; /**< Last x mouse position. */
static double last_y = 0.; /**< Last y mouse position. */

/* Widgets. */
static Widget* window_newWidget(Window* w);
static void widget_cleanup(Widget* widget);
static Window* window_wget(const unsigned int wid);
static Widget* window_getwgt(const unsigned int wid, char* name);
static void toolkit_setPos(Window* wdw, Widget* wgt, int x, int y);
/* Input. */
static int  toolkit_inputInput(Uint8 type, Widget* inp, SDLKey key);
static void toolkit_mouseEvent(SDL_Event* event);
static int  toolkit_keyEvent(SDL_Event* event);
static void toolkit_imgarrMove(Widget* iar, double ry);
/* Focus. */
static void toolkit_nextFocus(void);
static int toolkit_isFocusable(Widget* wgt);
static void toolkit_triggerFocus(void);
static Widget* toolkit_getFocus(void);
static void toolkit_listScroll(Widget* wgt, int direction);
static void toolkit_listFocus(Widget* lst, double bx, double by);
static void toolkit_imgarrFocus(Widget* iar, double dx, double by);
/* Render. */
static void window_render(Window* w);
static void toolkit_renderButton(Widget* btn, double bx, double by);
static void toolkit_renderText(Widget* txt, double bx, double by);
static void toolkit_renderImage(Widget* img, double bx, double by);
static void toolkit_renderList(Widget* lst, double bx, double by);
static void toolkit_renderRect(Widget* rct, double bx, double by);
static void toolkit_renderCust(Widget* cst, double bx, double by);
static void toolkit_renderInput(Widget* inp, double bx, double by);
static void toolkit_renderImageArray(Widget* iar, double bx, double by);
static void toolkit_drawOutline(double x, double y, double w,
                                double h, double b, glColour* c, glColour* lc);
static void toolkit_clip(double x, double y, double w, double h);
static void toolkit_unclip(void);
static void toolkit_drawRect(double x, double y, double w, double h,
                             glColour* c, glColour* lc);

/* Set the internal widget position. */
static void toolkit_setPos(Window* wdw, Widget* wgt, int x, int y) {
  if(x < 0) wgt->x = wdw->w - wgt->w + x;
  else wgt->x = (double) x;
  if(y < 0) wgt->y = wdw->h - wgt->h + y;
  else wgt->y = (double) y;
}

/* Add a button that when pressed will trigger call, passing it's name as the */
/* only parameter. */
void window_addButton(const unsigned int wid, const int x, const int y,
                      const int w, const int h, char* name,
                      char* display, void (*call)(char*)) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type = WIDGET_BUTTON;
  wgt->name = strdup(name);

  /* Specific. */
  wgt->dat.btn.display = strdup(display);
  wgt->dat.btn.disabled = 0;  /* Initially enabled. */
  wgt->dat.btn.fptr = call;

  /* Position/Size. */
  wgt->w = (double) w;
  wgt->h = (double) h;
  toolkit_setPos(wdw, wgt, x, y);

  if(wdw->focus == -1) /* Init the focus. */
    toolkit_nextFocus();
}

/* Add text to the window. */
void window_addText(const unsigned int wid, const int x, const int y,
                    const int w, const int h, const int centered, char* name,
                    glFont* font, glColour* colour, char* string) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type = WIDGET_TEXT;
  wgt->name = strdup(name);

  /* Specific. */
  if(font == NULL) wgt->dat.txt.font = &gl_defFont;
  else wgt->dat.txt.font = font;
  if(font == NULL) wgt->dat.txt.font = &gl_defFont;
  else wgt->dat.txt.font = font;
  if(colour == NULL) wgt->dat.txt.colour = &cBlack;
  else wgt->dat.txt.colour = colour;
  wgt->dat.txt.centered = centered;
  if(string) wgt->dat.txt.text = strdup(string);
  else wgt->dat.txt.text = NULL;

  /* Position/Size. */
  wgt->w = (double)w;
  wgt->h = (double)h;
  toolkit_setPos(wdw, wgt, x, y);
}

/* Add a graphic to the window. */
void window_addImage(const unsigned int wid, const int x, const int y,
                     char* name, glTexture* image, int border) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type = WIDGET_IMAGE;
  wgt->name = strdup(name);

  /* Specific. */
  wgt->dat.img.image  = image;
  wgt->dat.img.border = border;
  wgt->dat.img.colour = NULL; /* Normal colour. */

  /* Position/Size. */
  wgt->w = (image == NULL) ? 0 : wgt->dat.img.image->sw;
  wgt->h = (image == NULL) ? 0 : wgt->dat.img.image->sh;
  toolkit_setPos(wdw, wgt, x, y);
}

void window_addList(const unsigned int wid, const int x, const int y,
                    const int w, const int h, char* name, char** items, int nitems,
                    int defitem, void(*call) (char*)) {

  Window *wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type = WIDGET_LIST;
  wgt->name = strdup(name);

  /* Specific. */
  wgt->dat.lst.options = items;
  wgt->dat.lst.noptions = nitems;
  wgt->dat.lst.selected = defitem; /* -1 would be none. */
  wgt->dat.lst.pos = 0;
  wgt->dat.lst.fptr = call;

  /* Position/Size. */
  wgt->w = (double) w;
  wgt->h = (double) h - ((h % (gl_defFont.h+2)) + 2);
  toolkit_setPos(wdw, wgt, x, y);

  if(wdw->focus == -1)
    /* Initialize the focus. */
    toolkit_nextFocus();
}

void window_addRect(const unsigned int wid, const int x, const int y,
                    const int w, const int h, char* name, glColour* colour, int border) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type = WIDGET_RECT;
  wgt->name = strdup(name);

  /* Specific. */
  wgt->dat.rct.colour = colour;
  wgt->dat.rct.border = border;

  /* Position/Size. */
  wgt->w = (double)w;
  wgt->h = (double)h;
  toolkit_setPos(wdw, wgt, x, y);
}

void window_addCust(const unsigned int wid, const int x, const int y,
                    const int w, const int h, char* name, const int border,
                    void(*render) (double x, double y, double w, double h),
                    void(*mouse) (SDL_Event* event, double x, double y)) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type  = WIDGET_CUST;
  wgt->name  = strdup(name);

  /* Specific. */
  wgt->dat.cst.border = border;
  wgt->dat.cst.render = render;
  wgt->dat.cst.mouse = mouse;

  /* Position/size. */
  wgt->w = (double)w;
  wgt->h = (double)h;
  toolkit_setPos(wdw, wgt, x, y);
}

/* Add an input widget. */
void window_addInput(const unsigned int wid,
                     const int x, const int y,
                     const int w, const int h,
                     char* name, const int max, const int oneline) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type = WIDGET_INPUT;
  wgt->name = strdup(name);

  /* Specific. */
  wgt->dat.inp.max      = max+1;
  wgt->dat.inp.oneline  = oneline;
  wgt->dat.inp.pos      = 0;
  wgt->dat.inp.view     = 0;
  wgt->dat.inp.input    = malloc(sizeof(char)*wgt->dat.inp.max);
  memset(wgt->dat.inp.input, 0, wgt->dat.inp.max*sizeof(char));

  /* Position/Size. */
  wgt->w = (double)w;
  wgt->h = (double)h;
  toolkit_setPos(wdw, wgt, x, y);
}

/**
 * @brief Add an Image array.
 *    @param wid Window to add to.
 *    @param x X position.
 *    @param y Y position.
 *    @param w Width.
 *    @param h Height.
 *    @param name Internal widget name.
 *    @param iw Image width to use.
 *    @param ih Image height to use.
 *    @param tex Texture array to use (not freed).
 *    @param caption Caption array to use (freed).
 *    @param nelem Elements in tex and caption.
 *    @param call Callback when modified.
 */
void window_addImageArray(const unsigned int wid,
    const int x, const int y, /* Position. */
    const int w, const int h, /* Size. */
    char* name, const int iw, const int ih, /* Name and image sizes. */
    glTexture** tex, char** caption, int nelem, /* Elements. */
    void(*call) (char*)) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_newWidget(wdw);

  /* Generic. */
  wgt->type = WIDGET_IMAGEARRAY;
  wgt->name = strdup(name);

  /* Specific. */
  wgt->dat.iar.images     = tex;
  wgt->dat.iar.captions   = caption;
  wgt->dat.iar.nelements  = nelem;
  wgt->dat.iar.selected   = 0;
  wgt->dat.iar.pos        = 0;
  wgt->dat.iar.iw         = iw;
  wgt->dat.iar.ih         = ih;
  wgt->dat.iar.fptr       = call;

  /* Position/Size. */
  wgt->w = (double)w;
  wgt->h = (double)h;
  toolkit_setPos(wdw, wgt, x, y);

  if(wdw->focus == -1) /* Initialize the focus. */
    toolkit_nextFocus();
}

/* Return pointer to newly allocated widget. */
static Widget* window_newWidget(Window* w) {
  Widget* wgt = NULL;

  w->nwidgets++;
  w->widgets = realloc(w->widgets, sizeof(Widget)*w->nwidgets);
  if(w->widgets == NULL) WARN("Out of memory");

  wgt = &w->widgets[w->nwidgets - 1];

  wgt->type = WIDGET_NULL;
  wgt->status = WIDGET_STATUS_NORMAL;
  return wgt;
}

/* Return the window of id wid. */
static Window* window_wget(const unsigned int wid) {
  int i;
  for(i = 0; i < nwindows; i++)
    if(windows[i].id == wid)
      return &windows[i];
  DEBUG("Window '%d' not found in windows stack", wid);
  return NULL;
}

static Widget* window_getwgt(const unsigned int wid, char* name) {
  int i;
  Window* wdw = window_wget(wid);
  for(i = 0; i < wdw->nwidgets; i++)
    if(strcmp(wdw->widgets[i].name, name)==0)
      return &wdw->widgets[i];

  WARN("Widget '%s' not found in window '%u'!", name, wid);
  return NULL;
}

void window_modifyText(const unsigned int wid, char* name, char* newstring) {
  Widget* wgt = window_getwgt(wid, name);

  if(wgt->dat.txt.text) free(wgt->dat.txt.text);
  wgt->dat.txt.text = (newstring) ? strdup(newstring) : NULL;
}

/* Get a widgets position. */
void window_posWidget(const unsigned int wid,
    char* name, int* x, int* y) {
  Widget* wgt = window_getwgt(wid, name);

  (*x) = wgt->x;
  (*y) = wgt->y;
}

/* Move a widget. */
void window_moveWidget(const unsigned int wid,
    char* name, int x, int y) {

  Window* wdw = window_wget(wid);
  Widget* wgt = window_getwgt(wid, name);

  toolkit_setPos(wdw, wgt, x, y);
}

/* Disable a button. */
void window_disableButton(const unsigned int wid, char* name) {
  Widget* wgt = window_getwgt(wid, name);

  if(wgt->type != WIDGET_BUTTON) {
    DEBUG("Trying to disable a non-button widget '%s'", name);
    return;
  }
  wgt->dat.btn.disabled = 1;
}

/* Enable a button. */
void window_enableButton(const unsigned int wid, char* name) {
  Widget* wgt = window_getwgt(wid, name);

  if(wgt->type != WIDGET_BUTTON) {
    DEBUG("Trying to enable a non-button widget '%s'", name);
    return;
  }
  wgt->dat.btn.disabled = 0;
}

void window_modifyImage(const unsigned int wid, char* name, glTexture* image) {
  Widget* wgt = window_getwgt(wid, name);
  wgt->dat.img.image = image;
}

void window_imgColour(const unsigned int wid, char* name, glColour* colour) {
  Widget* wgt = window_getwgt(wid, name);

  wgt->dat.img.colour = colour;
}

glTexture* window_getImage(const unsigned int wid, char* name) {
  Widget* wgt = window_getwgt(wid, name);
  return (wgt) ? wgt->dat.img.image : NULL;
}

/* Get the input from an input widget. */
char* window_getInput(const unsigned int wid, char* name) {
  Widget* wgt = window_getwgt(wid, name);
  return(wgt) ? wgt->dat.inp.input : NULL;
}

/* Check if a window exists. */
int window_exists(const char* wdwname) {
  int i;
  for(i = 0; i < nwindows; i++)
    if(strcmp(windows[i].name, wdwname)==0)
      return 1; /* Exists. */

  return 0; /* Does not exits! */
}

/* Return the id of a window. */
unsigned int window_get(const char* wdwname) {
  int i;
  for(i = 0; i < nwindows; i++)
    if(strcmp(windows[i].name, wdwname)==0)
      return windows[i].id;
  DEBUG("Window '%s' not found in windows stack", wdwname);
  return 0;
}

/* Create a window. */
unsigned int window_create(char* name, const int x, const int y,
                           const int w, const int h) {

  Window* wdw;

  if(nwindows >= mwindows) {
    /* We have reached our memory limit. */
    windows = realloc(windows, sizeof(Window)*(++mwindows));
    if(windows == NULL) WARN("Out of memory");
  }

  const int wid = (++genwid); /* Unique id */

  wdw = &windows[nwindows];

  wdw->id = wid;
  wdw->name = strdup(name);

  wdw->hidden       = 0;
  wdw->focus        = -1;
  wdw->accept_fptr  = NULL;
  wdw->cancel_fptr  = NULL;

  wdw->w = (double)w;
  wdw->h = (double)h;

  /* x pos. */
  if(x == -1)
    /* Center. */
    wdw->x = (SCREEN_W - windows[nwindows].w)/2.;
  else if(x < 0)
    wdw->x = SCREEN_W - windows[nwindows].w + (double)x;
  else windows[nwindows].x = (double)x;
  /* y pos. */
  if(y == -1)
    /* Center. */
    wdw->y = (SCREEN_H - windows[nwindows].h)/2.;
  else if(y < 0)
    wdw->y = SCREEN_H - windows[nwindows].h + (double)y;
  else wdw->y = (double)y;

  /* Widgets. */
  wdw->widgets  = NULL;
  wdw->nwidgets = 0;

  nwindows++;

  if(toolkit == 0) {
    /* Toolkit is enabled. */
    SDL_ShowCursor(SDL_ENABLE);
    toolkit = 1; /* Enable it. */
    pause_game();
    gl_defViewport(); /* Reset the default viewport. */
  }

  return wid;
}

/**
 * @fn void window_setAccept(const unsigned int wid, void(*accept)(char*))
 * 
 * @brief Set the default accept function of the window.
 *
 * This function is called whenever 'enter' is pressed and the current widget
 * does not catch it. NULL disables the accept function.
 *    @param wid ID of the window to set the accept function.
 *    @param accept Function to trigger when window is "accepted". Parameter
 *                  passed is window name.
 */
void window_setAccept(const unsigned int wid, void(*accept)(char*)) {
  Window* wdw;

  wdw = window_wget(wid);
  if(wdw != NULL) wdw->accept_fptr = accept;
}

/**
 * @fn void window_setCancel(const unsigned int wid, void(*cancel)(char*))
 *
 * @brief Set the default cancel function of the window.
 *
 * This function is called whenever 'escape' is hit and the current widget
 * does not catch it. NULL disables the cancel function.
 *    @param wid ID of the window to set cancel function.
 *    @param cancel Function to trigger when window is "cancelled".
 *                  Parameter passed is the window name.
 */
void window_setCancel(const unsigned int wid, void(*cancel)(char*)) {
  Window* wdw;

  wdw = window_wget(wid);
  if(wdw != NULL) wdw->cancel_fptr = cancel;
}

/* Destroy a widget. */
static void widget_cleanup(Widget* widget) {
  int i;
  if(widget->name) free(widget->name);

  switch(widget->type) {
  case WIDGET_BUTTON: /* Must clear the button display text. */
    if(widget->dat.btn.display) free(widget->dat.btn.display);
    break;
  case WIDGET_TEXT: /* Must clear the text. */
    if(widget->dat.txt.text) free(widget->dat.txt.text);
    break;
  case WIDGET_LIST: /* Must clear the list. */
    if(widget->dat.lst.options) {
      for(i = 0; i < widget->dat.lst.noptions; i++)
        if(widget->dat.lst.options[i])
          free(widget->dat.lst.options[i]);
      free(widget->dat.lst.options);
    }
    break;
  case WIDGET_INPUT:
    free(widget->dat.inp.input); /* Free the input buffer. */
    break;
  case WIDGET_IMAGEARRAY:
    if(widget->dat.iar.nelements > 0) { /* Free each text individually. */
      for(i = 0; i < widget->dat.iar.nelements; i++)
        if(widget->dat.iar.captions[i]);
      /* Free the arrays. */
      free(widget->dat.iar.captions);
      free(widget->dat.iar.images);
    }
    break;
  default:
    break;
  }
}

/* Destroy a window. */
void window_destroy(const unsigned int wid) {
  int i, j;

  /* Destroy the window. */
  for(i = 0; i < nwindows; i++)
    if(windows[i].id == wid) {
      if(windows[i].name) free(windows[i].name);
      for(j = 0; j < windows[i].nwidgets; j++)
        widget_cleanup(&windows[i].widgets[j]);
      free(windows[i].widgets);
      break;
    }

  if(i == nwindows) { /* Not found. */
    WARN("Window of id '%u' not found in window stack!", wid);
    return;
  }

  /* Move the other windows down a layer. */
  for(; i<(nwindows-1); i++)
    windows[i] = windows[i+1];

  nwindows--;
  if(nwindows == 0) {
    /* No windows left. */
    SDL_ShowCursor(SDL_DISABLE);
    toolkit = 0; /* Disable the toolkit. */
    if(paused) unpause_game();
  }
}

void window_destroyWidget(unsigned int wid, const char* wgtname) {
  Window* w = window_wget(wid);
  int i;

  if(w == NULL) {
    WARN("Window %d does not exist", wid);
    return;
  }

  for(i = 0; i < w->nwidgets; i++)
    if(strcmp(wgtname, w->widgets[i].name)==0)
      break;

  if(i >= w->nwidgets) {
    DEBUG("Widget '%s' not found in window '%d'", wgtname, wid);
    return;
  }

  if(w->focus == i) w->focus = -1;
  widget_cleanup(&w->widgets[i]);
  if(i < w->nwidgets-1)
    /* This isn't the last widget. */
    memmove(&w->widgets[i], &w->widgets[i+1],
        sizeof(Widget) * (w->nwidgets-i-1));

  (w->nwidgets)--; /* Not that we don't actually realloc the space.. */
}

static void toolkit_drawOutline(double x, double y, double w, double h,
                                double b, glColour* c, glColour* lc) {

  glShadeModel((lc == NULL) ? GL_FLAT : GL_SMOOTH);
  if(!lc) COLOUR(*c);
  glBegin(GL_LINE_LOOP);
  /* Left. */
  if(lc) COLOUR(*lc);
  glVertex2d(x-b,     y);
  if(lc) COLOUR(*c);
  glVertex2d(x-b,     y+h);
  /* Top. */
  glVertex2d(x,      y+h+b);
  glVertex2d(x+w,     y+h+b);
  /* Right. */
  glVertex2d(x+w+b,    y+h);
  if(lc) COLOUR(*lc);
  glVertex2d(x+w+b,    y);
  /* Bottom. */
  glVertex2d(x+w,     y-b);
  glVertex2d(x,      y-b);
  glVertex2d(x-b,     y);
  glEnd();
}

static void toolkit_drawRect(double x, double y, double w, double h,
                             glColour* c, glColour* lc) {
  glShadeModel((lc) ? GL_SMOOTH : GL_FLAT);

  glBegin(GL_QUADS);
  COLOUR(*c);
  glVertex2d(x, y);
  glVertex2d(x+w, y);

  COLOUR((lc) ? *lc : *c);
  glVertex2d(x+w, y+h);
  glVertex2d(x, y+h);
  glEnd();
}

/* Set up 2d clipping planes around a rectangle. */
static void toolkit_clip(double x, double y, double w, double h) {
  GLdouble ctop[4] = {  0.,  1.,  0.,  -y  };
  GLdouble cbot[4] = {  0., -1.,  0.,  y+h };
  GLdouble clef[4] = {  1.,  0.,  0.,  -x  };
  GLdouble crig[4] = { -1.,  0.,  0,   x+w };

  glClipPlane(GL_CLIP_PLANE0, ctop);
  glClipPlane(GL_CLIP_PLANE1, cbot);
  glClipPlane(GL_CLIP_PLANE2, clef);
  glClipPlane(GL_CLIP_PLANE3, crig);

  glEnable(GL_CLIP_PLANE0);
  glEnable(GL_CLIP_PLANE1);
  glEnable(GL_CLIP_PLANE2);
  glEnable(GL_CLIP_PLANE3);
}

static void toolkit_unclip(void) {
  glDisable(GL_CLIP_PLANE0);
  glDisable(GL_CLIP_PLANE1);
  glDisable(GL_CLIP_PLANE2);
  glDisable(GL_CLIP_PLANE3);

}

/* Render a window. */
static void window_render(Window* w) {
  int i;
  double x, y, wid, hei;
  glColour *lc, *c, *dc, *oc;

  /* Position. */
  x = w->x - (double)SCREEN_W/2.;
  y = w->y - (double)SCREEN_H/2.;

  /* Colours. */
  lc  = &cGrey90;
  c   = &cGrey70;
  dc  = &cGrey50;
  oc  = &cGrey30;

  /* Window shaded background. */
  /* Main body. */
  toolkit_drawRect(x+21, y, w->w-42., 0.6*w->h, dc, c);
  toolkit_drawRect(x+21, y+0.6*w->h, w->w-42., 0.4*w->h, c, NULL);

  glShadeModel(GL_SMOOTH);
  /* Left side. */
  glBegin(GL_POLYGON);
  COLOUR(*c);
  glVertex2d(x+21., y+0.6*w->h); /* Center. */
  COLOUR(*dc);
  glVertex2d(x+21., y);
  glVertex2d(x+15., y+1.);
  glVertex2d(x+10., y+3.);
  glVertex2d(x+6.,  y+6.);
  glVertex2d(x+3.,  y+10.);
  glVertex2d(x+1.,  y+15.);
  glVertex2s(x,     y+21.);
  COLOUR(*c);
  glVertex2d(x,     y+0.6*w->h); /* Front of center. */
  glVertex2d(x,     y+w->h-21.);
  glVertex2d(x+1.,  y+w->h-15.);
  glVertex2d(x+3.,  y+w->h-10.);
  glVertex2d(x+6.,  y+w->h-6.);
  glVertex2d(x+10., y+w->h-3.);
  glVertex2d(x+15., y+w->h-1.);
  glVertex2d(x+21., y+w->h);
  glEnd();
  /* Right side. */
  glBegin(GL_POLYGON);
  COLOUR(*c);
  glVertex2d(x+w->w-21., y+0.6*w->h); /* Center. */
  COLOUR(*dc);
  glVertex2d(x+w->w-21.,  y);
  glVertex2d(x+w->w-15.,  y+1.);
  glVertex2d(x+w->w-10.,  y+3.);
  glVertex2d(x+w->w-6.,   y+6.);
  glVertex2d(x+w->w-3.,   y+10.);
  glVertex2d(x+w->w-1.,   y+15.);
  glVertex2d(x+w->w,      y+21.);
  COLOUR(*c);
  glVertex2d(x+w->w,      y+0.6*w->h); /* Front of center. */
  glVertex2d(x+w->w,      y+w->h-21.);
  glVertex2d(x+w->w-1.,   y+w->h-15.);
  glVertex2d(x+w->w-3.,   y+w->h-10.);
  glVertex2d(x+w->w-6.,   y+w->h-6.);
  glVertex2d(x+w->w-10.,  y+w->h-3.);
  glVertex2d(x+w->w-15.,  y+w->h-1.);
  glVertex2d(x+w->w-21.,  y+w->h);
  glEnd();

  /* Inner outline. */
  glShadeModel(GL_SMOOTH);
  glBegin(GL_LINE_LOOP);
  /* Left side. */
  COLOUR(*c);
  glVertex2d(x+21.+1.,   y);
  glVertex2d(x+15.+1.,   y+1.+1.);
  glVertex2d(x+10.+1.,   y+3.+1.);
  glVertex2d(x+6.+1.,    y+6.+1.);
  glVertex2d(x+3.+1.,    y+10.+1.);
  glVertex2d(x+1.+1.,    y+15.+1.);
  glVertex2d(x+1.,       y+21.+1.);
  COLOUR(*lc);
  glVertex2d(x+1.,       y+0.6*w->h); /* Front of center. */
  glVertex2d(x+1.,       y+w->h-21.-1.);
  glVertex2d(x+1.+1.,    y+w->h-15.-1.);
  glVertex2d(x+3.+1.,    y+w->h-10.-1.);
  glVertex2d(x+6.+1.,    y+w->h-6.-1.);
  glVertex2d(x+10.+1.,   y+w->h-2.-1.);
  glVertex2d(x+15.+1.,   y+w->h-1.-1.);
  glVertex2d(x+21.+1.,   y+w->h-1.);
  /* Switch to right via top. */
  glVertex2d(x+w->w-21.-1.,  y+w->h);
  glVertex2d(x+w->w-15.-1.,  y+w->h-1.-1.);
  glVertex2d(x+w->w-10.-1.,  y+w->h-3.-1.);
  glVertex2d(x+w->w-6.-1.,   y+w->h-6.-1.);
  glVertex2d(x+w->w-2.-1.,   y+w->h-10.-1.);
  glVertex2d(x+w->w-1.-1.,   y+w->h-15.-1.);
  glVertex2d(x+w->w-.1,      y+w->h-21.-1.);
  glVertex2d(x+w->w-1.,      y+0.6*w->h); /* Front of center. */
  COLOUR(*c);
  glVertex2d(x+w->w-1.,      y+21.+1.);
  glVertex2d(x+w->w-1.-1.,   y+15.+1.);
  glVertex2d(x+w->w-3.-1.,   y+10.+1.);
  glVertex2d(x+w->w-6.-1.,   y+6.+1.);
  glVertex2d(x+w->w-10.-1.,  y+3.+1.);
  glVertex2d(x+w->w-15.-1.,  y+1.+1.);
  glVertex2d(x+w->w-21.-1.,  y+1.);
  glVertex2d(x+21.+1.,       y+1.);
  glEnd();

  /* Outter outline. */
  glShadeModel(GL_FLAT);
  glBegin(GL_LINE_LOOP);
  /* Left side. */
  COLOUR(*oc);
  glVertex2d(x+21.,    y);
  glVertex2d(x+15.,    y+1.);
  glVertex2d(x+10.,    y+3.);
  glVertex2d(x+6.,     y+6.);
  glVertex2d(x+3.,     y+10.);
  glVertex2d(x+1.,     y+15.);
  glVertex2d(x,        y+21.);
  glVertex2d(x,        y+0.6*w->h); /* Front of center. */
  glVertex2d(x,        y+w->h-21.);
  glVertex2d(x+1.,     y+w->h-15.);
  glVertex2d(x+3.,     y+w->h-10.);
  glVertex2d(x+6.,     y+w->h-6.);
  glVertex2d(x+10.,    y+w->h-3.);
  glVertex2d(x+15.,    y+w->h-1.);
  glVertex2d(x+21.,    y+w->h);
  /* Switch to right via top. */
  glVertex2d(x+w->w-21.,   y+w->h);
  glVertex2d(x+w->w-15.,   y+w->h-1.);
  glVertex2d(x+w->w-10.,   y+w->h-3.);
  glVertex2d(x+w->w-6.,    y+w->h-6.);
  glVertex2d(x+w->w-3.,    y+w->h-10.);
  glVertex2d(x+w->w-1.,    y+w->h-15.);
  glVertex2d(x+w->w,       y+w->h-21.);
  glVertex2d(x+w->w,       y+0.6*w->h);  /* Front of center. */
  glVertex2d(x+w->w,       y+21.);
  glVertex2d(x+w->w-1.,    y+15.);
  glVertex2d(x+w->w-3.,    y+10.);
  glVertex2d(x+w->w-6.,    y+6.);
  glVertex2d(x+w->w-10.,   y+3.);
  glVertex2d(x+w->w-15.,   y+1.);
  glVertex2d(x+w->w-21.,   y);
  glVertex2d(x+21.,        y); /* Back to beginning. */
  glEnd();

  /* Render the window name. */
  gl_printMid(&gl_defFont, w->w,
              x + (double)SCREEN_W/2.,
              y + w->h - 20. + (double)SCREEN_H/2.,
              &cBlack, w->name);

  /* Widgets. */
  for(i = 0; i < w->nwidgets; i++) {
    switch(w->widgets[i].type) {
    case WIDGET_NULL:
      break;
    case WIDGET_BUTTON:
      toolkit_renderButton(&w->widgets[i], x, y);
      break;
    case WIDGET_TEXT:
      toolkit_renderText(&w->widgets[i], x, y);
      break;
    case WIDGET_IMAGE:
      toolkit_renderImage(&w->widgets[i], x, y);
      break;
    case WIDGET_LIST:
      toolkit_renderList(&w->widgets[i], x, y);
      break;
    case WIDGET_RECT:
      toolkit_renderRect(&w->widgets[i], x, y);
      break;
    case WIDGET_CUST:
      toolkit_renderCust(&w->widgets[i], x, y);
      break;
    case WIDGET_INPUT:
      toolkit_renderInput(&w->widgets[i], x, y);
      break;
    case WIDGET_IMAGEARRAY:
      toolkit_renderImageArray(&w->widgets[i], x, y);
      break;
    }
  }
  /* Focus widget. */
  if(w->focus != -1) {
    x += w->widgets[w->focus].x;
    y += w->widgets[w->focus].y;
    wid = w->widgets[w->focus].w;
    hei = w->widgets[w->focus].h;

    toolkit_drawOutline(x, y, wid, hei, 3, &cBlack, NULL);
  }
}

/* Renders a button. */
static void toolkit_renderButton(Widget* btn, double bx, double by) {
  glColour* c, *dc, *lc;
  double x, y;

  x = bx + btn->x;
  y = by + btn->y;
  
  if(btn->dat.btn.disabled == 1) {
    lc  = &cGrey60;
    c   = &cGrey20;
    dc  = &cGrey40;
  } else {
    switch(btn->status) {
    /* Set the color. */
    case WIDGET_STATUS_NORMAL:
      lc  = &cGrey80;
      c   = &cGrey60;
      dc  = &cGrey40;
      break;
    case WIDGET_STATUS_MOUSEOVER:
      lc  = &cWhite;
      c   = &cGrey80;
      dc  = &cGrey60;
      break;
    case WIDGET_STATUS_MOUSEDOWN:
      lc  = &cGreen;
      c   = &cGreen;
      dc  = &cGrey40;
      break;
    default:
      break;
    }
  }

  /* Shaded base. */
  if(btn->dat.btn.disabled == 1) {
    toolkit_drawRect(x, y, btn->w, 0.4*btn->h, dc, NULL);
    toolkit_drawRect(x, y+0.4*btn->h, btn->w, 0.6*btn->h, dc, c);  
  } else {
    toolkit_drawRect(x, y, btn->w, 0.6*btn->h, dc, c);
    toolkit_drawRect(x, y+0.6*btn->h, btn->w, 0.4*btn->h, c, NULL);
  }

  /* Inner outline. */
  toolkit_drawOutline(x, y, btn->w, btn->h, 0., lc, c);
  /* Outter outline. */
  toolkit_drawOutline(x, y, btn->w, btn->h, 1., &cBlack, NULL);

  gl_printMid(NULL, (int)btn->w,
              bx + (double)SCREEN_W/2. + btn->x,
              by + (double)SCREEN_H/2. + btn->y + (btn->h - gl_defFont.h)/2.,
              &cDarkRed, btn->dat.btn.display);
}

static void toolkit_renderText(Widget* txt, double bx, double by) {
  if(txt->dat.txt.text == NULL) return;

  if(txt->dat.txt.centered)
    gl_printMid(txt->dat.txt.font, txt->w,
                bx + (double)SCREEN_W/2. + txt->x,
                by + (double)SCREEN_H/2. + txt->y,
                txt->dat.txt.colour, txt->dat.txt.text);
  else
    gl_printText(txt->dat.txt.font, txt->w, txt->h,
                 bx + (double)SCREEN_W/2. + txt->x,
                 by + (double)SCREEN_H/2. + txt->y,
                 txt->dat.txt.colour, txt->dat.txt.text);
}

/* Render the image. */
static void toolkit_renderImage(Widget* img, double bx, double by) {
  double x, y;

  if(img->dat.img.image == NULL) return;

  x = bx + img->x;
  y = by + img->y;

  /* Image. */
  gl_blitStatic(img->dat.img.image,
                x + (double)SCREEN_W/2.,
                y + (double)SCREEN_H/2.,
                img->dat.img.colour);

  if(img->dat.img.border) {
    /* Inner outline (outwards). */
    toolkit_drawOutline(x, y+1, img->dat.img.image->sw-1,
                        img->dat.img.image->sh-1, 1., toolkit_colLight,
                        toolkit_col);
    /* Outter outline. */
    toolkit_drawOutline(x, y+1, img->dat.img.image->sw-1,
                        img->dat.img.image->sh-1, 2., toolkit_colDark, NULL);
  }
}

/* Render the list. */
static void toolkit_renderList(Widget* lst, double bx, double by) {
  int i;
  double x, y, tx, ty;

  x = bx + lst->x;
  y = by + lst->y;

  /* List bg. */
  toolkit_drawRect(x, y, lst->w, lst->h, &cWhite, NULL);

  /* Inner outline. */
  toolkit_drawOutline(x, y, lst->w, lst->h, 0., toolkit_colLight, toolkit_col);
  /* Outter outline. */
  toolkit_drawOutline(x, y, lst->w, lst->h, 1., toolkit_colDark, NULL);

  /* Draw selected. */
  toolkit_drawRect(x,
                   y-1.+lst->h-(1+lst->dat.lst.selected-lst->dat.lst.pos)*(gl_defFont.h+2.),
                   lst->w, gl_defFont.h+2., &cHilight, NULL);

  /* Draw content. */
  tx = (double)SCREEN_W/2. + x+2.;
  ty = (double)SCREEN_H/2. + y+lst->h - 2. - gl_defFont.h;
  y = ty-2.;

  for(i = lst->dat.lst.pos; i < lst->dat.lst.noptions; i++) {
    gl_printMax(&gl_defFont, (int)lst->w-4,
                tx, ty, &cBlack, lst->dat.lst.options[i]);

    ty -= 2 + gl_defFont.h;
    if(ty-y > lst->h) break;
  }
}

/* Render a rectangle. */
static void toolkit_renderRect(Widget* rct, double bx, double by) {
  double x, y;

  x = bx + rct->x;
  y = by + rct->y;

  if(rct->dat.rct.colour)
    /* Draw rect only if it exists. */
    toolkit_drawRect(x, y, rct->w, rct->h, rct->dat.rct.colour, NULL);

  if(rct->dat.rct.border) {
    /* Inner outline. */
    toolkit_drawOutline(x, y, rct->w, rct->h, 0., toolkit_colLight, toolkit_col);
    /* Outter outline. */
    toolkit_drawOutline(x, y, rct->w, rct->h, 1., toolkit_colDark, NULL);
  }
}

/* Render a custom widget. */
static void toolkit_renderCust(Widget* cst, double bx, double by) {
  double x, y;

  x = bx + cst->x;
  y = by + cst->y;

  if(cst->dat.cst.border) {
    /* Inner outline. */
    toolkit_drawOutline(x-1, y+1, cst->w+1, cst->h+1, 0.,
                        toolkit_colLight, toolkit_col);
    /* Outter outline. */
    toolkit_drawOutline(x-1, y, cst->w+1, cst->h+1, 1.,
                        toolkit_colDark, NULL);
  }

  toolkit_clip(x, y, cst->w, cst->h);
  (*cst->dat.cst.render) (x, y, cst->w, cst->h);
  toolkit_unclip();
}

/* Render an input widget. */
static void toolkit_renderInput(Widget* inp, double bx, double by) {
  double x, y, ty;

  x = bx + inp->x;
  y = by + inp->y;

  /* Main background. */
  toolkit_drawRect(x, y, inp->w, inp->h, &cWhite, NULL);

  /* Center vertically. */
  if(inp->dat.inp.oneline) ty = y - (inp->h - gl_smallFont.h)/2.;

  gl_printText(&gl_smallFont, inp->w-10., inp->h,
               x+5. + SCREEN_W/2., ty + SCREEN_H/2.,
               &cBlack, inp->dat.inp.input+inp->dat.inp.view);

  /* Inner outline. */
  toolkit_drawOutline(x, y, inp->w, inp->h, 0.,
                      toolkit_colLight, toolkit_col);
  /* Outter outline. */
  toolkit_drawOutline(x, y, inp->w, inp->h, 1.,
                      toolkit_colDark, NULL);
}

/**
 * @fn static void toolkit_renderImageArray(Widget* iar, double bx, double by)
 *
 * @brief Renders an image array.
 */
static void toolkit_renderImageArray(Widget* iar, double bx, double by) {
  int i, j;
  double x, y, w, h, xcurs, ycurs;
  double scroll_pos, sx, sy;
  int xelem, yelem, xspace;
  glColour* c, *dc, *lc;
  int is_selected;

  x = bx + iar->x;
  y = by + iar->y;

  /* Calculations. */
  w = iar->dat.iar.iw + 5.*2.; /* Includes border. */
  h = iar->dat.iar.ih + 5.*2. + 2. + gl_smallFont.h;

  /* Number of elements. */
  xelem   = (int)((iar->w - 10.) / w);
  xspace  = (((int)iar->w - 10) % (int)w) / (xelem + 1);
  yelem   = (int)iar->dat.iar.nelements / xelem + 1;

  /* Background. */
  toolkit_drawRect(x, y, iar->w, iar->h, &cBlack, NULL);

  /* Scrollbar. */

  /* scrollbar background. */
  toolkit_drawRect(x+iar->w - 10., y, 10., iar->h,
      toolkit_colDark, toolkit_col);
  toolkit_drawOutline(x + iar->w - 10., y, 10., iar->h, 1.,
      toolkit_colLight, toolkit_col);
  toolkit_drawOutline(x + iar->w - 10., y, 10., iar->h, 2.,
      toolkit_colDark, NULL);

  /* The bar */
  scroll_pos = iar->dat.iar.pos / (h * (yelem - (int)(iar->h / h)));
  sx = x + iar->w - 10.;
  sy = y + iar->h - (iar->h - 30.) * scroll_pos - 30.;
  toolkit_drawRect(sx, sy, 10., 30., toolkit_colLight, toolkit_col);
  toolkit_drawOutline(sx, sy, 10., 30., 0., toolkit_colDark, NULL);

  /* Main drawing loop. */
  toolkit_clip(x, y, iar->w, iar->h);
  ycurs = y + iar->h + (double)SCREEN_H/2. - h + iar->dat.iar.pos;
  for(j = 0; j < yelem; j++) {
    xcurs = x + xspace + (double)SCREEN_W/2.;
    for(i = 0; i < xelem; i++) {
      /* Out of elements. */
      if((j*xelem + i) >= iar->dat.iar.nelements)
        break;

      is_selected = (iar->dat.iar.selected == j*xelem + i) ? 1 : 0;

      if(is_selected)
        toolkit_drawRect(xcurs-(double)SCREEN_W/2. + 2.,
            ycurs-(double)SCREEN_H/2. + 2.,
            w - 4., h - 4., &cDConsole, NULL);

      /* Image. */
      if(iar->dat.iar.images[j*xelem + i] != NULL)
        gl_blitScale(iar->dat.iar.images[j*xelem + i],
            xcurs + 5., ycurs + gl_smallFont.h + 7.,
            iar->dat.iar.iw, iar->dat.iar.ih, NULL);

      /* Caption. */
      gl_printMid(&gl_smallFont, iar->dat.iar.iw, xcurs + 5., ycurs + 5.,
          (is_selected) ? &cBlack : &cWhite,
          iar->dat.iar.captions[j*xelem + i]);

      /* Outline. */
      if(is_selected) {
        lc  = &cWhite;
        c   = &cGrey80;
        dc  = &cGrey60;
      } else {
        lc  = toolkit_colLight;
        c   = toolkit_col;
        dc  = toolkit_colDark;
      }
      toolkit_drawOutline(xcurs-(double)SCREEN_W/2. + 2.,
          ycurs-(double)SCREEN_H/2. + 2.,
          w - 4., h - 4., 1., lc, c);
      toolkit_drawOutline(xcurs-(double)SCREEN_W/2. + 2.,
          ycurs-(double)SCREEN_H/2. +2.,
          w - 4., h - 4., 2., dc, NULL);

      xcurs += w + xspace;
    }
    ycurs -= h;
  }
  toolkit_unclip();

  /* Final outline. */
  toolkit_drawOutline(x, y+1, iar->w-1, iar->h-1, 1., toolkit_colLight, toolkit_col);
  toolkit_drawOutline(x, y+1, iar->w-1, iar->h-1, 2., toolkit_colDark, NULL);
}

/* Handle input for input widget. */
static int toolkit_inputInput(Uint8 type, Widget* inp, SDLKey key) {
  int n;
  SDLMod mods;

  if(inp->type != WIDGET_INPUT) return 0;

  mods = SDL_GetModState();
  if(inp->dat.inp.oneline && isascii(key)) {
    /* Backspace -> delete text. */
    if((type == SDL_KEYDOWN) && (key == '\b') && (inp->dat.inp.pos > 0)) {
      inp->dat.inp.input[--inp->dat.inp.pos] = '\0';

      if(inp->dat.inp.view > 0) {
        n = gl_printWidth(&gl_smallFont,
            inp->dat.inp.input + inp->dat.inp.view - 1);
        if(n+10 < inp->w)
          inp->dat.inp.view--;
      }
    }
    else if((type == SDL_KEYDOWN) && (inp->dat.inp.pos < inp->dat.inp.max-1)) {
      if((key == SDLK_RETURN) && !inp->dat.inp.oneline)
        inp->dat.inp.input[inp->dat.inp.pos++] = '\n';

      /* Uppder case characters. */
      else if(isalpha(key) && (mods & (KMOD_LSHIFT | KMOD_RSHIFT)))
        inp->dat.inp.input[inp->dat.inp.pos++] = toupper(key);

      /* Rest. */
      else if(!iscntrl(key))
        inp->dat.inp.input[inp->dat.inp.pos++] = key;

      /* Didn't get a useful key. */
      else return 0;

      n = gl_printWidth(&gl_smallFont, inp->dat.inp.input+inp->dat.inp.view);
      if(n + 10 > inp->w) inp->dat.inp.view++;
      return 1;
    }
  }
  return 0;
}

/* Render the window. */
void toolkit_render(void) {
  int i;

  if(gl_has(OPENGL_AA_LINE))    glEnable(GL_LINE_SMOOTH);
  if(gl_has(OPENGL_AA_POLYGON)) glEnable(GL_POLYGON_SMOOTH);

  for(i = 0; i < nwindows; i++)
    window_render(&windows[i]);

  if(gl_has(OPENGL_AA_LINE))    glDisable(GL_LINE_SMOOTH);
  if(gl_has(OPENGL_AA_POLYGON)) glDisable(GL_POLYGON_SMOOTH);

}

/* Toolkit input is handled here. */
/* If we return 1, the input isn't passed. */
int toolkit_input(SDL_Event* event) {
  switch(event->type) {
  case SDL_MOUSEMOTION:
  case SDL_MOUSEBUTTONDOWN:
  case SDL_MOUSEBUTTONUP:
    toolkit_mouseEvent(event);
    return 1; /* Block input. */
  case SDL_KEYDOWN:
  case SDL_KEYUP:
    return toolkit_keyEvent(event);
  }
  return 0; /* Don't block unput. */
}

/* Input. */
static int mouse_down = 0;
static void toolkit_mouseEvent(SDL_Event* event) {
  int i;
  double x, y, rel_x, rel_y;
  Window* w;
  Widget* wgt, *wgt_func;

  /* Set mouse button status. */
  if(event->type == SDL_MOUSEBUTTONDOWN) mouse_down = 1;
  else if(event->type == SDL_MOUSEBUTTONUP) mouse_down = 0;

  /* Absolute positions. */
  if(event->type == SDL_MOUSEMOTION) {
    x = (double)event->motion.x;
    y = SCREEN_H - (double)event->motion.y;
    /* Create relative events. */
    rel_x = x - last_x;
    rel_y = y - last_y;
    last_x = x;
    last_y = y;
  }
  else if((event->type == SDL_MOUSEBUTTONDOWN) || (event->type == SDL_MOUSEBUTTONUP)) {
    x = (double)event->button.x;
    y = SCREEN_H - (double)event->motion.y;
  }

  w = &windows[nwindows-1];

  /* Always treat button ups to stop hanging states. */
  if((event->type != SDL_MOUSEBUTTONUP) &&
     ((x < w->x) || (x > (w->x + w->w)) || (y < w->y) || (y > (w->y + w->h))))
    return; /* Not in current window. */

  /* Relative positions. */
  x -= w->x;
  y -= w->y;

  wgt_func = NULL;
  for(i = 0; i < w->nwidgets; i++) {
    wgt = &w->widgets[i];
    /* Widget in range? */
    if((x > wgt->x) && (x < (wgt->x + wgt->w)) &&
       (y > wgt->y) && (y < (wgt->y + wgt->h))) {
      /* Custom widgets take it from here. */
      if((wgt->type == WIDGET_CUST) && wgt->dat.cst.mouse)
        (*wgt->dat.cst.mouse)(event, x-wgt->x, y-wgt->y);
      else
        switch(event->type) {
        case SDL_MOUSEMOTION:
          if(!mouse_down)
            wgt->status = WIDGET_STATUS_MOUSEOVER;
          else {
            if(wgt->type == WIDGET_IMAGEARRAY)
              toolkit_imgarrMove(wgt, rel_y);
          }
          break;
        case SDL_MOUSEBUTTONDOWN:
          wgt->status = WIDGET_STATUS_MOUSEDOWN;

          /* Handle mouse wheel. */
          if(event->button.button == SDL_BUTTON_WHEELUP) {
            toolkit_listScroll(wgt, +1);
            break;
          }
          if(event->button.button == SDL_BUTTON_WHEELDOWN) {
            toolkit_listScroll(wgt, -1);
            break;
          }

          if(toolkit_isFocusable(wgt))
            w->focus = i;

          if(wgt->type == WIDGET_LIST) {
            toolkit_listFocus(wgt, x-wgt->x, y-wgt->y);
            //input_key = 0; /* Hack to avoid weird bug with permascroll. */
          }
          
          if(wgt->type == WIDGET_IMAGEARRAY)
            toolkit_imgarrFocus(wgt, x-wgt->x, y-wgt->y);
          break;

        case SDL_MOUSEBUTTONUP:
          /* Since basically only buttons are handled here, we ignore
           * it all except the left mouse button. */
          if(event->button.button != SDL_BUTTON_LEFT)
            break;

          if(wgt->status == WIDGET_STATUS_MOUSEDOWN) {
            if((wgt->type == WIDGET_BUTTON) &&
                  (wgt->dat.btn.disabled==0)) {
              if(wgt->dat.btn.fptr == NULL)
                DEBUG("Toolkit: Button '%s' of Window '%s'"
                      "Does not have a function trigger",
                      wgt->name, w->name);
              else
                wgt_func = wgt; /* Run it at the end in case of close. */
            }
          }
          wgt->status = WIDGET_STATUS_NORMAL;
          break;
        }
    }
    else if((wgt->type == WIDGET_CUST) &&
            (event->type == SDL_MOUSEBUTTONUP) && wgt->dat.cst.mouse)
      (*wgt->dat.cst.mouse) (event, x-wgt->x, y-wgt->y);
    else if(!mouse_down)
      wgt->status = WIDGET_STATUS_NORMAL;
  }

  /* We trigger this at the end in case it destroys the window that is calling
   * this function. Otherwise ugly segfaults appear. */
  if(wgt_func) (*wgt_func->dat.btn.fptr)(wgt_func->name);
}

/* Handle the key events. */
static SDLKey input_key;
static unsigned int input_keyTime;
static int input_keyCounter;
static void toolkit_regKey(SDLKey key) {
  if((input_key == 0) && (input_keyTime == 0)) {
    input_key = key;
    input_keyTime = SDL_GetTicks();
    input_keyCounter = 0;
  }
}

static void toolkit_unregKey(SDLKey key) {
  if(input_key == key) {
    input_key = 0;
    input_keyTime = 0;
    input_keyCounter = 0;
  }
}

static int toolkit_keyEvent(SDL_Event* event) {
  Window* wdw;
  Widget* wgt;
  SDLKey  key;

  if(nwindows <= 0) return 0;

  wdw = &windows[nwindows-1];
  wgt = (wdw->focus != -1) ? &wdw->widgets[wdw->focus] : NULL;
  key = event->key.keysym.sym;

  /* Hack to simulate key repetition. */
  if((key == SDLK_BACKSPACE) || isalnum(key)) {
    if(event->type == SDL_KEYDOWN)    toolkit_regKey(key);
    else if(event->type == SDL_KEYUP) toolkit_unregKey(key);
  }

  /* Handle input widgets. */
  if(wgt && (wgt->type == WIDGET_INPUT))
    /* Grab all the events it wants. */
    if(toolkit_inputInput(event->type, wgt, key)) return 1;

  switch(key) {
  case SDLK_TAB:
    if(event->type == SDL_KEYDOWN)
      toolkit_nextFocus();
    return 1;
  case SDLK_RETURN:
    if(event->type == SDL_KEYDOWN)
      toolkit_triggerFocus();
    return 1;
  case SDLK_ESCAPE:
    if(event->type == SDL_KEYDOWN)
      if(wdw->cancel_fptr != NULL) {
        (*wdw->cancel_fptr)(wdw->name);
        return 1;
      }
    return 0;
  case SDLK_UP:
    if(event->type == SDL_KEYDOWN) {
      toolkit_regKey(SDLK_UP);
      toolkit_listScroll(toolkit_getFocus(), +1);
    }
    else if(event->type == SDL_KEYUP)
      toolkit_unregKey(SDLK_UP);
    return 0;
  case SDLK_DOWN:
    if(event->type == SDL_KEYDOWN) {
      toolkit_regKey(SDLK_DOWN);
      toolkit_listScroll(toolkit_getFocus(), -1);
    }
    else if(event->type == SDL_KEYUP)
      toolkit_unregKey(SDLK_DOWN);
    return 0;
  default:
    return 0;
  }
}

void toolkit_update(void) {
  unsigned int t;
  Window* wdw;
  Widget* wgt;

  t = SDL_GetTicks();

  if(input_key == 0) return;

  if(input_keyTime + INPUT_DELAY + input_keyCounter*INPUT_FREQ > t)
    return;

  input_keyCounter++;

  if(nwindows > 0) {
    wdw = &windows[nwindows-1];
    wgt = (wdw->focus >= 0) ? &wdw->widgets[wdw->focus] : NULL;
    if(wgt && (wgt->type == WIDGET_INPUT) &&
        (input_key == SDLK_BACKSPACE || isalnum(input_key)))
      toolkit_inputInput(SDL_KEYDOWN, wgt, input_key);
  }

  switch(input_key) {
  case SDLK_UP:
    toolkit_listScroll(toolkit_getFocus(), +1);
    break;
  case SDLK_DOWN:
    toolkit_listScroll(toolkit_getFocus(), -1);
    break;
  default:
    break;
  }
}

/* Focus next widget. */
static void toolkit_nextFocus(void) {
  Window* wdw = &windows[nwindows-1]; /* Get active window. */

  if(wdw->nwidgets==0) /* Special case no widgets. */
    wdw->focus = -1;
  else if(wdw->focus+1 >= wdw->nwidgets)
    wdw->focus = -1;
  else if((++wdw->focus+1) && /* Just increment. */
          toolkit_isFocusable(&wdw->widgets[wdw->focus]))
    return;
  else
    toolkit_nextFocus();
}

/* Return 1 if the window is focusable. */
static int toolkit_isFocusable(Widget* wgt) {
  if(wgt == NULL) return 0;

  switch(wgt->type) {
  case WIDGET_BUTTON:
    if(wgt->dat.btn.disabled == 1) return 0;
  case WIDGET_LIST:
  case WIDGET_INPUT:
  case WIDGET_IMAGEARRAY:
    return 1;
  default:
    return 0;
  }
}

static void toolkit_triggerFocus(void) {
  Window* wdw;
  Widget* wgt;

  wdw = &windows[nwindows-1];

  if(wdw->focus == -1) return;

  wgt = &wdw->widgets[wdw->focus];

  switch(wgt->type) {
  case WIDGET_BUTTON:
    if(wgt->dat.btn.fptr)(*wgt->dat.btn.fptr)(wgt->name);
    else DEBUG("Toolkit: Button '%s' of Window '%s'"
        "Doesn't have a function trigger",
        wgt->name, wdw->name);
    break;
  default:
    if(wdw->accept_fptr)(*wdw->accept_fptr)(wgt->name);
    break;
  }
}

/* Try to scroll up/down by direction. */
static void toolkit_listScroll(Widget* wgt, int direction) {
  double w, h;
  int xelem, yelem;
  double hmax;

  if(wgt == NULL) return;

  switch(wgt->type) {
  case WIDGET_LIST:
    wgt->dat.lst.selected -= direction;
    wgt->dat.lst.selected = MAX(0, wgt->dat.lst.selected);
    wgt->dat.lst.selected = MIN(wgt->dat.lst.selected, wgt->dat.lst.noptions-1);
    if(wgt->dat.lst.fptr) (*wgt->dat.lst.fptr)(wgt->name);
    break;

  case WIDGET_IMAGEARRAY:
    /* Element dimensions. */
    w = wgt->dat.iar.iw + 5.*2.;  /* Includes border. */
    h = wgt->dat.iar.ih + 5.*2. + 2 + gl_smallFont.h;

    /* Number of elements. */
    xelem = (int)((wgt->w - 10.) / w);
    yelem = (int)wgt->dat.iar.nelements / xelem + 1;

    /* Maximum. */
    hmax = h * (yelem - (int)(wgt->h / h));
    if(hmax < 0.)
      hmax = 0.;

    /* Move. */
    wgt->dat.iar.pos -= direction * h;

    /* Boundry check. */
    wgt->dat.iar.pos = MAX(wgt->dat.iar.pos, 0.);
    wgt->dat.iar.pos = MIN(wgt->dat.iar.pos, hmax);
    if(wgt->dat.iar.fptr)(*wgt->dat.iar.fptr)(wgt->name);
    break;

  default:
    break;
  }
}

/**
 * @fn char* toolkit_getList(const unsigned int wid, char* name)
 *
 * @brief Get what is selected currently in a list.
 *
 * List included Image Array.
 */
char* toolkit_getList(const unsigned int wid, char* name) {
  Widget* wgt = window_getwgt(wid, name);

  if(wgt == NULL) {
    WARN("Widget '%s' not found", name);
    return NULL;
  }

  switch(wgt->type) {
    case WIDGET_LIST:
      if(wgt->dat.lst.selected == -1)
        return NULL;
      return wgt->dat.lst.options[wgt->dat.lst.selected];

    case WIDGET_IMAGEARRAY:
      if(wgt->dat.iar.selected == -1)
        return NULL;
      return wgt->dat.iar.captions[wgt->dat.iar.selected];

    default:
      return NULL;
  }
}

/* Get the position of current item in the list. */
int toolkit_getListPos(const unsigned int wid, char* name) {
  Widget* wgt = window_getwgt(wid, name);

  if(wgt == NULL) {
    WARN("Widget '%s' not found", name);
    return -1;
  }

  if((wgt->type != WIDGET_LIST) || (wgt->dat.lst.selected == -1))
    return -1;

  return wgt->dat.lst.selected;
}

/* List mouse even focus. */
static void toolkit_listFocus(Widget* lst, double bx, double by) {
  (void)bx;
  int i;

  i = (lst->h - by) / (gl_defFont.h + 2.);
  if(i< lst->dat.lst.noptions) {
    /* Should not be out of boundaries. */
    lst->dat.lst.selected = i;
    toolkit_listScroll(lst, 0); /* Check boundaries and trigger callback. */
  }
}

/**
 * @fn static void toolkit_imgarrFocus(Widget* iar, double bx, double by)
 *
 * @brief Mouse event focus on image array.
 *    @param iar Image array widget.
 *    @param bx X position click.
 *    @param by Y position click.
 */
static void toolkit_imgarrFocus(Widget* iar, double bx, double by) {
  int i, j;
  double x, y, w, h, ycurs, xcurs;
  double scroll_pos, hmax;
  int xelem, xspace, yelem;

  /* Positions. */
  x = bx + iar->x;
  y = by + iar->y;

  /* Element dimensions. */
  w = iar->dat.iar.iw + 5.*2.; /* Includes border. */
  h = iar->dat.iar.ih + 5.*2. + 2. + gl_smallFont.h;

  /* Number of elements. */
  xelem   = (int)((iar->w - 10.) / w);
  xspace  = (((int)iar->w - 10) % (int)w) / (xelem + 1);
  yelem   = (int)iar->dat.iar.nelements / xelem + 1;

  /* Normal click. */
  if(bx < iar->w - 10.) {
    /* Loop through elements until finding collision. */
    ycurs = iar->h - h + iar->dat.iar.pos;
    for(j = 0; j < yelem; j++) {
      xcurs = xspace;
      for(i = 0; i < xelem; i++) {
        /* Out of elements. */
        if((j*xelem + i) >= iar->dat.iar.nelements)
          break;

        /* Check for collision. */
        if((bx > xcurs) && (bx < xcurs+w-4.) &&
            (by > ycurs) && (by < ycurs+h-4.)) {
          iar->dat.iar.selected = j*xelem+i;
          if(iar->dat.iar.fptr != NULL)
            (*iar->dat.iar.fptr)(iar->name);
          return;
        }
        xcurs += xspace + w;
      }
      ycurs -= h;
    }
  } else { /* Scrollbar click. */
    /* Get bar position (center). */
    hmax = h * (yelem - (int)(iar->h / h));
    scroll_pos = iar->dat.iar.pos / hmax;
    y = iar->h - (iar->h - 30.) * scroll_pos - 15.;

    /* Click below the bar. */
    if(by < y-15.)
      toolkit_listScroll(iar, -2);

    /* Click above the bar. */
    else if(by > y+15.)
      toolkit_listScroll(iar, +2);
    /* Click on the bar. */
    else
      iar->status = WIDGET_STATUS_SCROLLING;
  }
}

/**
 * @fn static void toolkit_imgarrMove(Widget* iar, double ry)
 *
 * @brief Handles image array movement.
 *    @param iar Image Array that has mouse movement.
 *    @param ry Relative Y mouse movement.
 */
static void toolkit_imgarrMove(Widget* iar, double ry) {
  double w, h;
  int xelem, yelem;
  double hmax;

  if(iar->status == WIDGET_STATUS_SCROLLING) {
    /* Element dimensions. */
    w = iar->dat.iar.iw + 5.*2.;  /* Includes border. */
    h = iar->dat.iar.ih + 5.*2. + 2. + gl_smallFont.h;

    /* Number of elements. */
    xelem = (int)((iar->w - 10.) / w);
    yelem = (int)iar->dat.iar.nelements / xelem + 1;

    hmax = h * (yelem - (int)(iar->h / h));
  
    iar->dat.iar.pos -= ry*hmax / (iar->h - 30.);

    /* Does boundry checks. */
    toolkit_listScroll(iar, 0);
  }
}

/* Return the focused widget. */
static Widget* toolkit_getFocus(void) {
  Window* wdw;
  wdw = &windows[nwindows-1];

  if(wdw->focus == -1) return NULL;

  return &wdw->widgets[wdw->focus];
}

/* Init. */
int toolkit_init(void) {
  windows = malloc(sizeof(Window)*MIN_WINDOWS);
  nwindows = 0;
  mwindows = MIN_WINDOWS;
  SDL_ShowCursor(SDL_DISABLE);
  return 0;
}

/* Exit the toolkit. */
void toolkit_exit(void) {
  while(nwindows > 0)
    window_destroy(windows[0].id);
  free(windows);
}

