/* Very basic menu system, inspired from neocdpsp interface 
 * In fact it has become the core of the new gui system for raine.
 *
 * The idea behind this one is to have something as easy to program as
 * possible (1st goal), usable with any peripheral (2nd), and also nice if
 * possible (3rd !) ;-)
 * 
 * To achieve this easy programmation, I took some ideas from the guis on psp:
 * a basic "menu" on screen, you can move ton any option, pressing a button
 * execute it, and moving right or left changes values in a list of values
 * if there is one. It's nice, easy to use, and easy to program. All this stuff
 * is handled by a very basic struct :
 *
 * typedef struct {
 *                const char *label;
 *                int (*menu_func)(int);
 *                int     *value_int;
 *                int     values_list_size;
 *                int values_list[16];
 *                char* values_list_label[16];
 * } menu_item_t;
 *
 * A menu is just a list of these structs, ended by an entry with label=NULL
 * To have something static, just pass a label and menu_func=value_int=NULL
 * To have a simple command, just pass a function in menu_func.
 * And to have list of values where to choose from, just pass an int and
 * some lists. Something to notice about these lists : contrary to what
 * allegro did, here you don't have to worry about list indexes. For example
 * if you want to choose the audio frequency you would put :
 * { "Audio frequency", NULL, &audio_sample_rate, 3, {11025,22050,44100}, NULL}
 * If you pass a menu_func != NULL with a value_int != NULL, then menu_func
 * is called everytime you change the value of value_int in the list.
 *
 * The end result is something which looks a little like the mame's menu system
 * but with quite some difference :
 * 1) it's nicer : transparency effects, true type support, bitmap handling,
 * flashing background for the selected option...
 * 2) it can be controlled with any device (mouse, keyboard, joystick...).
 * 3) it pauses the game when you call the gui ! ;-)
 *
 * The original psp implementation was in pure C. This one is in C++ to add
 * a few features more easily, like bitmap handling, and make the whole thing
 * even easier to use. This file contains the gory details, you shouldn't
 * have to look into it just to use the gui !
 */

#include "raine.h"
#include "sdl/SDL_gfx/SDL_gfxPrimitives.h"
#include "video/palette.h"
#include "version.h"
#include "games.h"
#include "menu.h"
#include "tfont.h"
#include "sdl/SDL_gfx/SDL_framerate.h"
#include "sdl/SDL_gfx/SDL_rotozoom.h"
#include <SDL_image.h>
#include "blit.h"
#include "sdl/display_sdl.h"
#include "sdl/dialogs/gui_options.h"
#include "tlift.h"
#ifdef NEO
#include "neocd/neocd.h"
#endif

static int return_mandatory = 0, use_transparency = 1;
int emulate_mouse_cursor = 0;
static int mouse_erased;

extern int repeat_interval, repeat_delay; // in gui.cpp

static int toggle_mouse(int sel) {
  if (emulate_mouse_cursor) {
    SDL_ShowCursor(SDL_DISABLE);
    mouse_erased = 1;
  }
  gui_options->draw();
  if (!(sdl_screen->flags & SDL_DOUBLEBUF) && !emulate_mouse_cursor)
    SDL_ShowCursor(SDL_ENABLE);
  return 0;
}

menu_item_t menu_options[] =
{
  { "Return mandatory", NULL, &return_mandatory, 2, { 0, 1 }, { "No", "Yes" }},
  { "Use transparency", NULL, &use_transparency, 2, { 0, 1 }, { "No", "Yes" }},
  { "Use custom mouse cursor", &toggle_mouse, &emulate_mouse_cursor, 2,
    { 0,1 }, { "No", "Yes" } },
};

void save_menu_config() {
  raine_set_config_int("GUI", "return_mandatory", return_mandatory);
  raine_set_config_int("GUI", "use_transparency", use_transparency);
}

void read_menu_config() {
  return_mandatory = raine_get_config_int("GUI", "return_mandatory", 0);
  use_transparency = raine_get_config_int("GUI", "use_transparency", 1);
}

int add_menu_options(menu_item_t *menu) {
  menu[0] = menu_options[0];
  menu[1] = menu_options[1];
  menu[2] = menu_options[2];
  return 3;
}

static SDL_Surface *cursor = NULL;

void setup_mouse_cursor(SDL_Surface *mycursor) {
  cursor = mycursor;
}

void sort_menu(menu_item_t *menu) {
  int n,ref,x;
  if (!menu)
    return;
  for (n=0; menu[n].label; n++) {
    if (menu[n].value_int && menu[n].values_list_size > 0 && menu[n].values_list_label[0]) {
      for (ref=0; ref<menu[n].values_list_size-1; ref++) {
	for (x=ref+1; x<menu[n].values_list_size; x++) {
	  if (strcmp(menu[n].values_list_label[ref],
	      menu[n].values_list_label[x]) > 0) {
	    char *s = menu[n].values_list_label[ref];
	    int nb = menu[n].values_list[ref];
	    menu[n].values_list_label[ref] = menu[n].values_list_label[x];
	    menu[n].values_list[ref] = menu[n].values_list[x];
	    menu[n].values_list_label[x] = s;
	    menu[n].values_list[x] = nb;
	  }
	}
      }
    }
  }
}
	   
TMenu::TMenu(char *my_title, menu_item_t *my_menu, char *myfont, int myfg, int mybg, int myfg_frame, int mybg_frame) {
  update_count = top = 0;
  sel = -1; // compute first selection later (compute_nb_items)
  title = my_title;
  menu = my_menu;
  fg_layer = bg_layer = NULL;
  font = NULL;
  menu_disp = NULL;
  
  if (myfg == -1)
    fg = mymakecol(255,255,255);
  else
    fg = myfg; 
  if (mybg == -1)
    bg = makecol_alpha(0x28,0x28,0x78,128);
  else
    bg = mybg; 
  if (myfg_frame == -1)
    fg_frame = mymakecol(255,255,255);
  else
    fg_frame = myfg_frame;
  if (mybg_frame == -1) {
    bg_frame = mymakecol(0,0,255);
  } else
    bg_frame = mybg_frame;
  lift = NULL;
  keybuf[0] = 0;
  SDL_GetMouseState(&mousex[0],&mousey[0]);
  mousex[1] = mousex[0]; mousey[1] = mousey[0];
  flip_page = 0;
  phase_repeat = jmoved = 0;
  work_area.h = 0;
  if (sdl_screen->flags & SDL_HWSURFACE) {
    /* Hw surfaces are very bad to handle transparency because when you
     * blit a transparent surface to it, you need to read from the hw
     * surface to compute the transparency and this is extremely slow !
     * So we'll just use an intermediate surface for that... */
    my_screen = SDL_CreateRGBSurface(SDL_SWSURFACE,
      sdl_screen->w,sdl_screen->h,sdl_screen->format->BitsPerPixel,
      sdl_screen->format->Rmask,
      sdl_screen->format->Gmask,
      sdl_screen->format->Bmask,
      sdl_screen->format->Amask);
    private_screen = 1;
  } else {
    my_screen = sdl_screen;
    private_screen = 0;
  }
}

TMenu::~TMenu() {
  delete font;
  if (menu_disp)
    free(menu_disp);
  if (lift)
    delete lift;
  if (bg_layer)
    SDL_FreeSurface(bg_layer);
  if (fg_layer)
    SDL_FreeSurface(fg_layer);
  if (private_screen)
    SDL_FreeSurface(my_screen);
}

int TMenu::can_be_displayed(int n) {
  // can hide some menu entries at will. By default, show everything
  return 1;
}

int TMenu::can_be_selected(int n) {
  // same thing to say if an item can be selected
  // by default everything which can be displayed can also be selected
  return can_be_displayed(n) && (menu[n].menu_func || (menu[n].value_int && menu[n].values_list_size > 0));
}

void TMenu::compute_nb_items() {
  nb_items = 0;
  nb_disp_items = 0;
  if (menu)  {
    while (menu[nb_items].label) {
      if (can_be_displayed(nb_items)) {
	nb_disp_items++;
	if (sel < 0 && can_be_selected(nb_items))
	  sel = nb_items;
      } 
      nb_items++;
    }
    if (menu_disp)
      free(menu_disp);
    menu_disp = (int*)malloc(sizeof(int)*nb_disp_items);
    int n=0;
    nb_disp_items = 0;
    while (menu[n].label) {
      if (can_be_displayed(n)) {
	menu_disp[nb_disp_items++] = n;
      }
      n++;
    }
  }
}

void TMenu::draw_top_frame() {
  int w_title,h_title;
  font->dimensions(title,&w_title,&h_title);
  boxColor(my_screen,0,0,my_screen->w,h_title-1,bg_frame);
  font->put_string(HMARGIN,0,EMUNAME " " VERSION,fg_frame,bg_frame);
  font->put_string(my_screen->w-w_title,0,title,fg_frame,bg_frame);
}

int TMenu::get_max_bot_frame_dimensions(int &w, int &h) {
#ifdef RAINE_DEBUG
  char game[100];
#ifdef NEO
  sprintf(game,"%s (%03x:%s)",(current_game ? current_game->long_name :"No game loaded"),neocd_id,(current_game ? current_game->main_name : ""));
#else
  sprintf(game,"%s (%s)",(current_game ? current_game->long_name :"No game loaded"),(current_game ? current_game->main_name : ""));
#endif
#else
#ifdef NEO
  char game[100];
  sprintf(game,"%s (%s)",(current_game ? current_game->long_name :"No game loaded"),(current_game ? current_game->main_name : ""));
#else
  const char *game = (current_game ? current_game->long_name : "No game loaded");
#endif
#endif
  if (font) font->dimensions(game,&w,&h);
  return strlen(game);
}

void TMenu::draw_bot_frame() {
  int base = work_area.y+work_area.h;
#ifdef RAINE_DEBUG
  char game[100];
#ifdef NEO
  sprintf(game,"%s (%03x:%s)",(current_game ? current_game->long_name :"No game loaded"),neocd_id,(current_game ? current_game->main_name : ""));
#else
  sprintf(game,"%s (%s)",(current_game ? current_game->long_name :"No game loaded"),(current_game ? current_game->main_name : ""));
#endif
#else
#ifdef NEO
  char game[100];
  sprintf(game,"%s (%s)",(current_game ? current_game->long_name :"No game loaded"),(current_game ? current_game->main_name : ""));
#else
  const char *game = (current_game ? current_game->long_name : "No game loaded");
#endif
#endif
  boxColor(my_screen,0,base,my_screen->w,my_screen->h,bg_frame);
  font->put_string(HMARGIN,base,game,fg_frame,bg_frame);
}

void TMenu::draw_frame(SDL_Rect *r) {
  int w_game, h_game;
  int w_title,h_title;
  int len_top, len_bot;
  char top_string[512];
  len_bot = get_max_bot_frame_dimensions(w_game,h_game);

  sprintf(top_string,EMUNAME " " VERSION" %s",title);
  len_top = strlen(top_string);
  
  if (!font) setup_font((len_top > len_bot ? len_top : len_bot));
  font->dimensions(title,&w_title,&h_title);
  len_bot = get_max_bot_frame_dimensions(w_game,h_game);

  int base = my_screen->h-h_game;

  if (!r) {
    /* Where should the screen be cleared ???
     * This is a simplified gui, with normally only 1 dialog at a time on
     * screen, so there should be a place where the screen is cleared, so that
     * we continue to see something after a few dialogs !
     * It can't be in bg_frame handling because the bg_frame size is supposed
     * to be calculated from work_area, here ! So this must absolutely be
     * handled either by draw() or by draw_frame(). draw_frame() looks like
     * a good choice... */
    clear_surface(my_screen);
    work_area.x = 0;
    work_area.y = h_title;
    work_area.w = my_screen->w;
    work_area.h = (base - (h_title));
  }

  if (!r || r->y < h_title) {
    draw_top_frame();
  }

  if (!r || r->y+r->h > base) {
    draw_bot_frame();
  }
}


void TMenu::setup_bg_layer(SDL_Surface *bitmap) {
  // called after work_area is initialized
  if (bg_layer)
    SDL_FreeSurface(bg_layer);
  if (bitmap) {
    int border;
    if (GameBitmap && bitmap == get_surface_from_bmp(GameBitmap))
      border = current_game->video_info->border_size;
    else
      border = 0;
    double ratio;
    double zoom;
    SDL_Rect src;
    src.x = border;
    src.y = border;
    src.w = bitmap->w - border*2;
    src.h = bitmap->h - border*2;
    ratio = src.w*1.0/src.h;
    if (ratio*work_area.h > work_area.w) {
      bgdst.w = work_area.w;
      bgdst.h = (Uint16)(work_area.w/ratio);
      zoom = bgdst.w*1.0/src.w;
    } else {
      bgdst.w = (Uint16)(ratio*work_area.h);
      bgdst.h = work_area.h;
      zoom = bgdst.h*1.0/src.h;
    }
    bgdst.x = (work_area.w-bgdst.w)/2+work_area.x;
    bgdst.y = (work_area.h-bgdst.h)/2+work_area.y;
    SDL_Surface *zoomed;
    if (border) {
      bg_layer = SDL_CreateRGBSurface(SDL_SWSURFACE,src.w,src.h,display_cfg.bpp,color_format->Rmask,color_format->Gmask,color_format->Bmask,color_format->Amask);
      if (display_cfg.bpp == 8)
	SDL_SetPalette(bg_layer,SDL_LOGPAL,(SDL_Color*)pal,0,256);

      SDL_BlitSurface(bitmap,&src,
	  bg_layer,NULL);
      zoomed = rotozoomSurface(bg_layer,0.0,zoom,1);
      SDL_FreeSurface(bg_layer);
    } else {
      zoomed = rotozoomSurface(bitmap, 0.0, zoom, 0);
    }
    bg_layer = zoomed;
    if (bg_layer->flags & SDL_SRCCOLORKEY) {
      SDL_SetColorKey(bg_layer,0,0); // We don't want transparency in the bg!
    }
  } else {
    bgdst = work_area;
    //bg_layer = SDL_CreateRGBSurface(SDL_SWSURFACE,bgdst.w,bgdst.h,display_cfg.bpp,color_format->Rmask,color_format->Gmask,color_format->Bmask,color_format->Amask);
    // clear_surface(bg_layer);
    bg_layer = NULL;
  }
}

void TMenu::compute_width_from_font() {
  width_max = 0;
  int width_max_options = 0,n,w,h;
  // The problem with variable width fonts is that it's not because a string
  // is longer than another that it will require more space on screen
  // so we must restart the loop again and compute the real width on screen
  // for each string !
  for (n=0; n<nb_items; n++) {
    font->dimensions(menu[n].label,&w,&h);
    if (!menu[n].menu_func && menu[n].value_int) {
      if (menu[n].values_list_size == ITEM_PROGRESS_BAR) {
	h += 2;
	w = my_screen->w/2;
      }
    }
    if (w > width_max)
      width_max = w;
    if (menu[n].value_int && menu[n].values_list_size) {
      if (menu[n].values_list_label[0]) { // we have strings for the options
	for (int l=0; l<menu[n].values_list_size; l++) {
	  font->dimensions(menu[n].values_list_label[l],&w,&h);
	  if (w > width_max_options)
	    width_max_options = w;
	}
      } else { // we have no strings...
	for (int l=0; l<menu[n].values_list_size; l++) {
	  char s2[20];
	  sprintf(s2,"%d",menu[n].values_list[l]);
	  font->dimensions(s2,&w,&h);
	  if (w > width_max_options)
	    width_max_options = w;
	}
      }
    }
  }
  width_max += 2*HMARGIN;
  xoptions = width_max;
  if (width_max_options) {
    width_max += width_max_options;
  }
  width_max += HMARGIN;
}

void TMenu::adjust_len_max_options(unsigned int &len_max_options) {
  // Just in case some inheritance needs to adjust this...
}

void TMenu::setup_font(unsigned int len_frame) {
  int w,h,n;
  compute_nb_items();
  unsigned int len_max_options = 0,len_max = 0;
  if (nb_items) {
    for (n=0; n<nb_items; n++) {
      const char *s = menu[n].label;
      unsigned int len = strlen(s);
      if (len > len_max) {
	len_max = len;
      }

      if (menu[n].value_int && menu[n].values_list_size) {
	if (menu[n].values_list_label[0]) { // we have strings for the options
	  for (int l=0; l<menu[n].values_list_size; l++) {
	    const char *s2 = menu[n].values_list_label[l];
	    unsigned int len2 = strlen(s2);
	    if (len2 > len_max_options) {
	      len_max_options = len2;
	    }
	  }
	} else { // we have no strings...
	  for (int l=0; l<menu[n].values_list_size; l++) {
	    char s2[20];
	    sprintf(s2,"%d",menu[n].values_list[l]);
	    unsigned int len2 = strlen(s2);
	    if (len2 > len_max_options) {
	      len_max_options = len2;
	    }
	  }
	}
      }
    }
  }

  adjust_len_max_options(len_max_options);

  if (font) delete font;
  if (len_max + len_max_options + 3 > len_frame) {
    w = my_screen->w/(len_max+len_max_options + 3); // ideal font width & height, with some
  } else
    w = my_screen->w/(len_frame); // ideal font width & height, with some
  h = (work_area.h ? work_area.h-40 : my_screen->h - 40)/(nb_disp_items); // margin
  h = h*4/9; // This 5/11 is just the result of tests, so that the main
  // menu fits on the screen without scrollbar when loading bublbobl !
  // Actually it's probably dependant of the font (size w != size h).
  if (h < w && h >= min_font_size) w=h; // take the minimum for the ttf...
  font = new TFont_ttf(my_screen,w);
  compute_width_from_font();
}

int TMenu::compute_fglayer_height() {
  int h;
  rows = work_area.h / font->get_font_height()-1;
  if (rows > nb_disp_items)
    rows = nb_disp_items;
  h = (rows)*font->get_font_height()+2*HMARGIN;
  reset_top();
  return h;
}

void TMenu::setup_fg_layer() {
  int w,h;

  w = width_max;
  h = compute_fglayer_height();
  fgdst.x = (work_area.w-w)/2;
  fgdst.y = (work_area.h-h)/2+work_area.y;
  if (lift) {
    delete lift;
    lift = NULL;
  }
  if (fg_layer)
    SDL_FreeSurface(fg_layer);
  if (nb_disp_items > rows) {
    width_max += 10;
    w = width_max;
    if (!lift) {
      int y = fgdst.y;
      skip_fglayer_header(y);
      lift = new TLift(width_max-20,y,h-y-fgdst.y,&top,&nb_disp_items,&rows,&update_count,fg_layer,mymakecol(0,0,0),mymakecol(0xc0,0xc0,0xc0),mymakecol(0xff,0xff,0xff));
    }
  } 
  if (use_transparency)
    fg_layer = SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,w,h,
	32,0xff000000,0x00ff0000,0x0000ff00,0xff);
  else
    fg_layer = SDL_CreateRGBSurface(SDL_SWSURFACE,w,h,
      my_screen->format->BitsPerPixel,
      my_screen->format->Rmask,
      my_screen->format->Gmask,
      my_screen->format->Bmask,
      my_screen->format->Amask);
  if (lift)
    lift->set_surface(fg_layer);
  bgsdl = gfx_to_sdlcolor(fg_layer,bg);
  if (!fg_layer) {
    printf("fg layer creation failed: %s\n",SDL_GetError());
    exit(1);
  }
  // SDL_SetAlpha(fg_layer,SDL_SRCALPHA | SDL_RLEACCEL,(fw > 10 ? 128 : 128+48));
  fgdst.w = fg_layer->w; // w & h are not used normaly, but just in case...
  fgdst.h = fg_layer->h;
}

int TMenu::get_list_index(int n) {
  int index;
  for (index=0; index<menu[n].values_list_size; index++) {
    if (menu[n].values_list[index] == *(menu[n].value_int))
      break;
  }
  return index;
}

int TMenu::compute_selcolor() {
  int selcolor;
  if (display_cfg.bpp > 8) {
    selcolor = (update_count<<1) & 0xff;
    if (selcolor > 0x80)
      selcolor = 0x100-selcolor;
    selcolor = mymakecol(0x7f+selcolor,0x7f+selcolor,0x7f+selcolor);
  } else
    selcolor = fg;
  return selcolor;
}

void TMenu::disp_menu(int n,int y,const char *s,int w,int h) {
  /* This is only a simplification function :
   * it's passed y,s,w,h from update_fg_layer so that it can concentrate
   * only on what to display, and forget about the layers (just update
   * fg_layer with what was passed)
   * This is needed because if the entry has list of values attached to it
   * then there is more to display than just the label passed in s, and this
   * function also handles the flashing background on the current selection */

  int fw = HMARGIN;
  int selcolor;
  if (!*s) return;
  int myfg = get_fgcolor(n);
  if (n == sel) {
    selcolor = compute_selcolor();
    font->surf_string(fg_layer,fw,y,s,bg,selcolor);
  } else {
    if (menu[n].value_int && menu[n].values_list_size < 0 && !menu[n].menu_func) { // display an extension
      if (menu[n].values_list_size == ITEM_PROGRESS_BAR) {
	fw = (fg_layer->w-w)/2;
	rectangleColor(fg_layer,fw,y,fw+w-1,y+h-1,mymakecol(255,255,255));
	int pcent;
	int prev = fw+1;
	for (pcent=5; pcent<=*(menu[n].value_int); pcent += 5) {
	  int cur = fw+(pcent * (w-2)/100);
	  int color = 255*pcent/100;
	  boxColor(fg_layer,prev,y+1,cur,y+h-2,mymakecol(color,0,0));
	  prev = cur;
	}
	char buff[8];
	sprintf(buff,"%d %%",*(menu[n].value_int));
	int myw,myh;
	font->dimensions(buff,&myw,&myh);
	font->surf_string(fg_layer,fw+(w-myw)/2,y+1,buff,mymakecol(255,255,255));
      }
    } else {
      font->surf_string(fg_layer,fw,y,s,myfg);
    }
  }

  if (menu[n].value_int && menu[n].values_list_size > 0) {
    // An option to display
    int index;
    if (menu[n].values_list_size != 3 || menu[n].values_list_label[0]) {
      index = get_list_index(n);
      if (index >= menu[n].values_list_size) { // not found !
	// It isn't supposed to happen, but I'd better handle it anyway, just
	// in case
	index = 0;
	*(menu[n].value_int) = menu[n].values_list[0];
      }
    }
    char *disp_string,buff[10];
    if (menu[n].values_list_label[0]) {
      disp_string = menu[n].values_list_label[index];
    } else {
      // no labels -> display the int value directly
      sprintf(buff,"%d",*(menu[n].value_int));
      disp_string = buff;
    }
    if (n == sel) {
      font->surf_string(fg_layer,xoptions,y,disp_string,bg,selcolor);
    } else {
      font->surf_string(fg_layer,xoptions,y,disp_string,myfg);
    }
  } // end display options
}

void TMenu::display_fglayer_header(int &y) {
  // No default header
}

void TMenu::skip_fglayer_header(int &y) {
  // No default header
}

void TMenu::update_fg_layer(int nb_to_update) {
  int n,max;
  int fw = HMARGIN;
  /* Basic scrolling setup... */
  if(top > nb_disp_items-rows) top=nb_disp_items-rows;
  if(top < 0)		  top = 0;
  if(sel >= nb_items)	  sel = 0;

  int y = HMARGIN;
  if (nb_to_update < 0) {
    // Use SDL_FillRect instead of boxColor because we don't want blending here
    SDL_FillRect(fg_layer,NULL,bgsdl);
    display_fglayer_header(y);
  } else {
    skip_fglayer_header(y);
  }

  max = top+rows;
  if (nb_disp_items < max)
    max = nb_disp_items;

  for (n=top; n<max; n++) {
    int index = menu_disp[n];
    const char *s = menu[index].label;
    int w,h;
    font->dimensions(s,&w,&h);

    if (menu[n].value_int && menu[n].values_list_size < 0 && !menu[n].menu_func) {
      if (menu[n].values_list_size == ITEM_PROGRESS_BAR) {
	w = my_screen->w/2;
	h += 2;
      }
    } else {
      w = width_max - HMARGIN;
      if (lift)
	  w -= 20;
    }

    if (nb_to_update < 0 || nb_to_update == index) {
      if (nb_to_update == index) {
	// if there is only 1 indice to update, clear the bg for it
	SDL_Rect dst;
	dst.x = fw; dst.y = y; dst.w = w; dst.h = h;
	SDL_FillRect(fg_layer,&dst,bgsdl);
      } 
      disp_menu(index,y,s,w,h);
      if (nb_to_update == index) {
	/* If we update just 1 entry, then we must explicitely update
	 * 1st the bg layer for this entry and then blit the fg layer over
	 * it */
	SDL_Rect from, to;
	from.x = fw; from.y = y; from.h = h;
	from.w = w;
	to.x = fgdst.x+fw;
	to.y = fgdst.y+y;
	to.w = from.w;
	to.h = from.h;
	if (nb_to_update != sel) {
	  update_bg_layer(&to);
	}
	SDL_BlitSurface(fg_layer,&from,my_screen,&to);
	do_update(&to);
      }
    }
    y += h;
  }
  if (nb_to_update == -1) {
    if (lift) {
      lift->draw();
    }
    // do_update(&fgdst);
  }
}

void TMenu::update_bg_layer(SDL_Rect *region) {
  SDL_Rect tmp;
  if (region) {
    tmp = *region; // otherwise SDL_BlitSurface changes region
    if (tmp.y < work_area.y) tmp.y = work_area.y;
    if (tmp.y+tmp.h > work_area.y+work_area.h)
      tmp.h = work_area.y+work_area.h - tmp.y;
    if (tmp.h > 65000)
      return;
  }
  SDL_Rect src;
  if (!bg_layer) {
    if (region) {
      boxColor(my_screen,tmp.x,tmp.y,tmp.x+tmp.w-1,
	tmp.y+tmp.h-1,mymakecol(0,0,0));
    } else {
      SDL_FillRect(my_screen,&work_area,0);
    }
    return;
  } else if (!region) {
    tmp = work_area;
  }
  if (bgdst.x > tmp.x) {
    // Normally it can't be out in x and y at the same time
    boxColor(my_screen,tmp.x,tmp.y,bgdst.x,tmp.y+tmp.h-1,mymakecol(0,0,0));
  }
  if (bgdst.y > tmp.y) {
    boxColor(my_screen,tmp.x,tmp.y,tmp.x+tmp.w-1,bgdst.y,mymakecol(0,0,0));
  }
  if (bgdst.x+bgdst.w < tmp.x+tmp.w) {
    // out on right side !
    boxColor(my_screen,tmp.x,tmp.y,tmp.x+tmp.w-1,tmp.y+tmp.h-1,mymakecol(0,0,0));
  }
  if (bgdst.y+bgdst.h < tmp.y+tmp.h) {
    boxColor(my_screen,tmp.x,bgdst.y+bgdst.h,tmp.x+tmp.w-1,tmp.y+tmp.h-1,mymakecol(0,0,0));
  }
  src.x = tmp.x-bgdst.x;
  src.y = tmp.y-bgdst.y;
  src.w = tmp.w;
  src.h = tmp.h;
  // with the clipped coordinates !
  SDL_SetClipRect(my_screen,&tmp);
  SDL_BlitSurface(bg_layer,&src,my_screen,&tmp);
  SDL_SetClipRect(my_screen,NULL);
}

void TMenu::do_update(SDL_Rect *region) {
  SDL_Rect r;
  int drawn_mouse = 0;
  if (private_screen) {
    SDL_BlitSurface(my_screen,region,sdl_screen,region);
  }
  if (emulate_mouse_cursor && cursor) {
    if (!mouse_erased) {
      if (!region || (mousex[flip_page] >= region->x &&
	    mousex[flip_page] < region->x+region->w && 
	    mousey[flip_page] >= region->y &&
	    mousey[flip_page] < region->y+region->h))
	mouse_erased = 1;
    }
    if (mouse_erased) {
      r.x = mousex[flip_page];
      r.y = mousey[flip_page];
	
      SDL_BlitSurface(cursor,NULL,sdl_screen,&r);
      mouse_erased = 0;
      drawn_mouse = 1;
    }
  } 
  if ( sdl_screen->flags & SDL_DOUBLEBUF ) {
    if (!emulate_mouse_cursor || !cursor)
      SDL_ShowCursor(SDL_ENABLE);
    SDL_Flip(sdl_screen);
    if (!emulate_mouse_cursor || !cursor) {
      SDL_ShowCursor(SDL_DISABLE);
      mouse_erased = 0;
    }
    flip_page ^= 1;
    if (mousex[flip_page] != mousex[flip_page ^ 1] ||
        mousey[flip_page] != mousey[flip_page ^ 1]) {
      /* Mouse is not handled at all by SDL when double buffering.
       * It's a little surprising, but it's not so hard to do...
       * The size 32x32 is probably too big, but since my converted .cur files
       * are 32x32, it seems to be a safe choice... */
      SDL_Rect r;
      r.x = mousex[flip_page];
      r.y = mousey[flip_page];
      r.w = 32;
      r.h = 32;
      redraw(&r);
      mousex[flip_page] = mousex[flip_page ^ 1];
      mousey[flip_page] = mousey[flip_page ^ 1];
    }
    redraw(region);
  } else {
    if (region) {
      SDL_UpdateRect(sdl_screen, region->x,region->y,region->w,region->h);
    } else
      SDL_UpdateRect(sdl_screen, 0,0,0,0);
    if (emulate_mouse_cursor && cursor && drawn_mouse) {
      if (region && (r.x < region->x || r.x >= region->x+region->w ||
	  r.y < region->y || r.y >= region->y + region->h ||
	  r.x+r.w > region->x+region->w || r.y+r.h > region->y+region->h)) {
	r.w = 32;
	r.h = 32;
	if (r.x + r.w > sdl_screen->w) r.w = sdl_screen->w - r.x;
	if (r.y + r.h > sdl_screen->h) r.h = sdl_screen->h - r.y;
	SDL_UpdateRect(sdl_screen,r.x,r.y,r.w,r.h);
      }
    }
  }
}

void TMenu::draw() {
  if (!private_screen)
    // reset my_screen in case the video mode changed
    my_screen = sdl_screen;
  draw_frame();

  if (GameBitmap)
    setup_bg_layer(get_surface_from_bmp(GameBitmap));
  else
    setup_bg_layer(NULL);
  // SDL_BlitSurface(bg_layer,NULL,sdl_screen,&bgdst);
  update_bg_layer(NULL);
  setup_fg_layer();
  update_fg_layer();
  SDL_BlitSurface(fg_layer,NULL,my_screen,&fgdst);
  do_update(NULL);
}

void TMenu::redraw_fg_layer() {
  // this layer has become tricky to update finally !!!
  update_fg_layer(-1);
  if (lift) {
    lift->draw();
  }
  // update_bg_layer must be called just before calling blitsurface to
  // sdl_screen or otherwise you see bglayer alone on screen during the update
  // (flashing effect), at least without double buffering.
  // Calling it here stops the flashing effect, at least on my machine.
  update_bg_layer(&fgdst);
  SDL_BlitSurface(fg_layer,NULL,my_screen,&fgdst);
  do_update(&fgdst);
}

static int axis_x,axis_y;

void TMenu::reset_top() {
  int seldisp = get_seldisp();
  if (seldisp > top+rows-1)
    top = seldisp-rows+1;
  else if (seldisp < top)
    top = seldisp;
}

void TMenu::next_sel() {
  int old_sel = sel;
  do {
    sel++;
    if (sel > menu_disp[nb_disp_items-1]) {
      sel = 0;
      break;
    }
  } while (sel < nb_items && !can_be_selected(sel));
  if (sel == 0 && !can_be_selected(sel)) {
    sel = old_sel; // failure to change selection
    if (top+rows < nb_disp_items) top++;
  } else {
    reset_top();
  }
}

void TMenu::prev_sel() {
  int old_sel = sel;
  do {
    sel--;
    if (sel < 0) {
      sel = menu_disp[nb_disp_items - 1];
      break;
    }
  } while (sel >= 0 && !can_be_selected(sel));
  if (!can_be_selected(sel))  {
    sel = old_sel; // failure to change selection
    if (top > 0) top--;
  } else {
    int seldisp = get_seldisp();
    if (seldisp > top+rows-1)
      top = seldisp-rows+1;
    else if (seldisp < top)
      top = seldisp;
  }
}

/* About cycling of next_list_item and prev_list_item : this is mandatory with
 * the mouse, or it would be impossible to return to the start of the list
 * (only 1 button to browse since the right button is to exit and the middle
 * one is not convinient at all for this */
void TMenu::next_list_item() {
  if (menu[sel].value_int && menu[sel].values_list_size) {
    if (menu[sel].values_list_size == 3 && !menu[sel].values_list_label[0]) {
      // special interval list
      *(menu[sel].value_int) += menu[sel].values_list[2];
      if (*(menu[sel].value_int) > menu[sel].values_list[1])
        *(menu[sel].value_int) = menu[sel].values_list[0];
    } else {
      int index = get_list_index(sel);
      if (index < menu[sel].values_list_size - 1) 
	index++;
      else
	index = 0; // cycling
      *(menu[sel].value_int) = menu[sel].values_list[index];
    }
    if (menu[sel].menu_func) 
      exit_menu = (*menu[sel].menu_func)(sel);
  }
}
      
void TMenu::prev_list_item() {
  if (menu[sel].value_int && menu[sel].values_list_size) {
    if (menu[sel].values_list_size == 3 && !menu[sel].values_list_label[0]) {
      // special interval list
      *(menu[sel].value_int) -= menu[sel].values_list[2];
      if (*(menu[sel].value_int) < menu[sel].values_list[0])
        *(menu[sel].value_int) = menu[sel].values_list[1];
    } else {
      int index = get_list_index(sel);
      if (index > 0) 
	index--;
      else
	index = menu[sel].values_list_size - 1; // cycling
      *(menu[sel].value_int) = menu[sel].values_list[index];
    }
    if (menu[sel].menu_func) 
      exit_menu = (*menu[sel].menu_func)(sel);
  }
}

void TMenu::find_new_sel() {
  // Find a new selection in the visible entries when top has just changed
  for (int n=top; n<top+rows; n++) {
    int index = menu_disp[n];
    if (can_be_selected(index)) {
      sel = index;
      break;
    }
  }
}

void TMenu::find_new_sel_from_end() {
  // Find a new selection in the visible entries when top has just changed
  for (int n=top+rows-1; n>=top; n--) {
    int index = menu_disp[n];
    if (can_be_selected(index)) {
      sel = index;
      break;
    }
  }
}

void TMenu::next_page() {
  if (top+rows < nb_disp_items) {
    top += rows;
    if (top + rows > nb_disp_items)
      top = nb_disp_items - rows;
    find_new_sel();
  } else if (sel < menu_disp[nb_disp_items-1])
    find_new_sel_from_end();
}

void TMenu::prev_page() {
  if (top > 0) {
    top -= rows;
    if (top < 0)
      top = 0;
    find_new_sel();
  } else if (sel > menu_disp[top])
    find_new_sel();
}

void TMenu::goto_top() {
  if (top > 0) {
    top = 0;
    find_new_sel();
  } else if (sel > menu_disp[0])
    find_new_sel();
}

void TMenu::goto_end() {
  if (top < nb_disp_items - rows) {
    top = nb_disp_items - rows;
    find_new_sel();
  } else if (sel < menu_disp[nb_disp_items-1])
    find_new_sel_from_end();
}

int TMenu::get_seldisp() {
  int n;
  if (sel == -1) return -1;
  for (n=0; n<nb_disp_items; n++) {
    if (menu_disp[n] == sel) break;
  }
  return n;
}

void TMenu::handle_key(SDL_Event *event) {
  int sym;
  switch (event->type) {
    case SDL_KEYDOWN:
      sym = event->key.keysym.sym;
      switch(sym) {
	case SDLK_UP:
	  prev_sel();
	  break;
	case SDLK_DOWN:
	  next_sel();
	  break;
	case SDLK_PAGEDOWN:
	  next_page();
	  break;
	case SDLK_PAGEUP:
	  prev_page();
	  break;
	case SDLK_HOME:
	  goto_top();
	  break;
	case SDLK_END:
	  goto_end();
	  break;
	case SDLK_RIGHT:
	  next_list_item();
	  break;
	case SDLK_LEFT:
	  prev_list_item();
	  break;
	case SDLK_RETURN:
	  exec_menu_item();
	  break;
	case SDLK_ESCAPE:
	  exit_menu = 1;
	  break;
	default:
          if (sym >= ' ' && sym <= 'z' && sel >= 0) {
	    int index = 0;
	    while (index < MAX_KEYBUF-1 && keybuf[index])
	      index++;
	    if (index < MAX_KEYBUF - 1) {
	      int n,seldisp;
	      keybuf[index++] = sym;
	      keybuf[index] = 0;
	      if (menu[sel].value_int && menu[sel].values_list_size > 0 &&
		menu[sel].values_list_label[0]) {
		// If we are on a list of values, then use the keys to browse
		// the list
                for (n=0; n<menu[sel].values_list_size; n++) {
		  if (!strnicmp(menu[sel].values_list_label[n],keybuf,index))
		    break;
		}
		if (n<menu[sel].values_list_size) { // found
		  *(menu[sel].value_int) = n;
		  update_fg_layer(sel);
		  if (menu[sel].menu_func) 
		    exit_menu = (*menu[sel].menu_func)(sel);
		  break;
		} else { // Otherwise just clear keybuf
		  keybuf[0] = 0;
		  break;
		}
	      } else {
		// look for the new selection then, starting at sel
		// 1 : find sel in the menu_disp array
		n = get_seldisp();
		for (; n<nb_disp_items; n++) {
		  if (can_be_selected(menu_disp[n]) &&
		      !strnicmp(menu[menu_disp[n]].label,keybuf,index))
		    break;
		}
		if (n == nb_disp_items) { // not found -> restart from 0 then
		  for (n=0; n<nb_disp_items; n++) {
		    if (can_be_selected(menu_disp[n]) &&
			!strnicmp(menu[menu_disp[n]].label,keybuf,index))
		      break;
		  }
		  if (n == nb_disp_items) { // still not found !
		    // Let's say that the new key is the start of a new selection
		    // then...
		    if (index == 1) { // if it's already the 1st one, forget it
		      keybuf[0] = 0;
		      break;
		    }
		    // Otherwise just clear keybuf and process this event again
		    keybuf[0] = 0;
		    handle_key(event);
		    break;
		  }
		}
		sel = menu_disp[n];
		while (sel < menu_disp[top]) {
		  top--;
		}
		while (top+rows < nb_disp_items && sel >= menu_disp[top+rows]) {
		  top++;
		}
		if (!return_mandatory) {
		  // check if another entry matches keybuf
		  seldisp = n;
		  for (n=seldisp+1; n<nb_disp_items; n++) {
		    if (can_be_selected(menu_disp[n]) &&
			!strnicmp(menu[menu_disp[n]].label,keybuf,index))
		      break;
		  }
		  if (n == nb_disp_items) {
		    for (n=0; n<seldisp; n++) {
		      if (can_be_selected(menu_disp[n]) &&
			  !strnicmp(menu[menu_disp[n]].label,keybuf,index))
			break;
		    }
		    if (n == seldisp) { // no other entry
		      exec_menu_item();
		    }
		  }
		}
	      }
	    }
	  }
      }
      if (sym < ' ' || sym > 'z') 
	keybuf[0] = 0; // reset keybuf if a non alpanum key is pressed
      break;
  }
}

void TMenu::redraw(SDL_Rect *r) {
  if (private_screen) {
    SDL_BlitSurface(my_screen,r,sdl_screen,r);
    return;
  }
  draw_frame(r);
  update_bg_layer(r);
  if (!r) {
    SDL_BlitSurface(fg_layer,NULL,sdl_screen,&fgdst);
    return;
  }
  if (r->x < fgdst.x+fgdst.w && r->y < fgdst.y+fgdst.h &&
      r->x+r->w >= fgdst.x && r->y+r->h >= fgdst.y) {
    SDL_Rect from;
    from.x = r->x - fgdst.x;
    from.y = r->y - fgdst.y;
    from.w = r->w;
    from.h = r->h;
    if (from.x < 0) {
      r->x -= from.x;
      from.w += from.x;
      r->w = from.w;
      from.x = 0;
    }
    if (from.y < 0) {
      r->y -= from.y;
      from.h += from.y;
      r->h = from.h;
      from.y = 0;
    }
    SDL_BlitSurface(fg_layer,&from,sdl_screen,r);
  } 
}

void TMenu::handle_mouse(SDL_Event *event) {
  int mx,my;
  if (event->type == SDL_MOUSEMOTION) {
    mx = event->motion.x;
    my = event->motion.y;
    if (emulate_mouse_cursor || (sdl_screen->flags & SDL_DOUBLEBUF)) {
      if (!mouse_erased) {
	SDL_Rect r;
	r.x = mousex[flip_page]; // mx - event->motion.xrel;
	r.y = mousey[flip_page]; // my - event->motion.yrel;
	r.w = 32;
	r.h = 32;
	redraw(&r);
	mouse_erased = emulate_mouse_cursor;
	if (!(sdl_screen->flags & SDL_DOUBLEBUF)) {
	  // redraw can change r values, must be reset
	  r.x = mousex[flip_page]; // mx - event->motion.xrel;
	  r.y = mousey[flip_page]; // my - event->motion.yrel;
	  r.w = 32;
	  r.h = 32;
	  if (r.x + r.w > sdl_screen->w) r.w = sdl_screen->w - r.x;
	  if (r.y + r.h > sdl_screen->h) r.h = sdl_screen->h - r.y;
	  SDL_UpdateRect(sdl_screen,r.x,r.y,r.w,r.h);
	}
	mousex[flip_page] = mx;
	mousey[flip_page] = my;
      }
    }
  } else {
    mx = event->button.x;
    my = event->button.y;
  }
  if (lift && mx > fgdst.x+fgdst.w-lift->get_width()) {
    // fix event coordinates for the lift...
    if (event->type == SDL_MOUSEMOTION)  {
      event->motion.x -= fgdst.x;
      event->motion.y -= fgdst.y;
    } else {
      event->button.x -= fgdst.x;
      event->button.y -= fgdst.y;
    }
    lift->handle_mouse(event);
    return; 
  }
  switch (event->type) {
    case SDL_MOUSEMOTION:
    case SDL_MOUSEBUTTONUP:
      if (my >= fgdst.y && my < fgdst.y+fgdst.h && mx >= fgdst.x && mx < fgdst.x+fgdst.w) {
	int ystart = fgdst.y + HMARGIN;
	skip_fglayer_header(ystart);
	int index = (my - (ystart))/font->get_font_height();
	if (index >= 0 && top+index < nb_disp_items)
	  index = menu_disp[top + index];
	else
	  index = -1;
	if (index >= 0 && index != sel && can_be_selected(index))
	  sel = index;
	if (event->type == SDL_MOUSEBUTTONUP ) {
	  switch(event->button.button) {
	    case 1: if (index == sel) exec_menu_item(); break;
	    // Apparently in sdl button 2 is center button, and 3 is right
	    // (hope it's standard in every os !)
	    case 2: if (index == sel) prev_list_item(); break;
	    case 3: exit_menu = 1; break;
	    case 4: prev_page(); break;
	    case 5: next_page(); break;
	  }
	}
      }
      break;
  }
}

void TMenu::produce_joystick_event() {
  // Produce a joystick event based on axis_x & axis_y
  if (axis_x == -1) {
    if (menu[sel].value_int)
      prev_list_item();
    else {
      prev_page();
    }
  } else if (axis_x == 1) {
    if (menu[sel].value_int)
      next_list_item();
    else
      next_page();
  }
  if (axis_y == -1) {
    prev_sel();
  } else if (axis_y == 1) {
    next_sel();
  }
}

void TMenu::handle_joystick(SDL_Event *event) {
  int hat;
  switch (event->type) {
    case SDL_JOYHATMOTION:
      /* Emulate joystick movement with a hat - what linux does automatically
       * but it's for windows... */
      hat = event->jhat.value;
      axis_x = 0;
      axis_y = 0;
      jmoved = 0;

      if (hat == SDL_HAT_UP) 
	axis_y = -1;
      if (hat == SDL_HAT_DOWN)
	axis_y = 1;
      if (hat == SDL_HAT_LEFT)
	axis_x = -1;
      if (hat == SDL_HAT_RIGHT) 
	axis_x = 1;
      if (hat) {
	jmoved = 1;
	timer = update_count;
	produce_joystick_event();
      } else
	phase_repeat = 0;
      break;
  case SDL_JOYAXISMOTION:
    switch(event->jaxis.axis) {
      case 0: // x axis normally
        if (event->jaxis.value < -16000) {
	  if (axis_x > -1) {
	    axis_x = -1;
	    jmoved = 1;
	    timer = update_count;
	    produce_joystick_event();
	  }
	} else if (event->jaxis.value > 16000) {
	  if (axis_x < 1) {
	    axis_x = 1;
	    jmoved = 1;
	    timer = update_count;
	    produce_joystick_event();
	  }
	} else if ((axis_x == -1 && event->jaxis.value > -16000) ||
	           (axis_x == 1 && event->jaxis.value < 16000)) {
	  axis_x = 0;
	  jmoved = 0;
	  phase_repeat = 0;
	}
	break;
      case 1: // y axis
        if (event->jaxis.value < -16000) {
	  if (axis_y > -1) {
	    axis_y = -1;
	    jmoved = 1;
	    timer = update_count;
	    produce_joystick_event();
	  }
	} else if (event->jaxis.value > 16000) {
	  if (axis_y < 1) {
	    axis_y = 1;
	    jmoved = 1;
	    timer = update_count;
	    produce_joystick_event();
	  }
	} else if ((axis_y == -1 && event->jaxis.value > -16000) ||
	           (axis_y == 1 && event->jaxis.value < 16000)) {
	  axis_y = 0;
	  jmoved = 0;
	  phase_repeat = 0;
	}
	break;
    } // end processing axis
    break;
  case SDL_JOYBUTTONUP:
    switch (event->jbutton.button) {
      case 0: exec_menu_item(); break;
      case 1: exit_menu = 1; break;
    }
    break;
  } // end processing event type
}

void TMenu::exec_menu_item() {
  if (menu[sel].value_int)
    next_list_item();
  else if (menu[sel].menu_func) {
    exit_menu = (*menu[sel].menu_func)(sel);
    compute_nb_items();
    draw();
    SDL_initFramerate(&fpsm);
    fpsm.use_cpu_frame_count = 0;
    SDL_setFramerate(&fpsm,30);
  }
}

void TMenu::execute() {
  exit_menu = 0;
  if (font) {
    delete font;
    font = NULL;
  }
  draw();

  SDL_initFramerate(&fpsm);
  fpsm.use_cpu_frame_count = 0;
  SDL_setFramerate(&fpsm,30);
  while (!exit_menu) {
    SDL_Event event;
    int oldsel = sel;
    int oldtop = top;
    while (SDL_PollEvent(&event)) {
      switch(event.type) {
      case SDL_KEYDOWN:
      case SDL_KEYUP:
	handle_key(&event);
	break;
      case SDL_MOUSEMOTION:
      case SDL_MOUSEBUTTONUP:
      case SDL_MOUSEBUTTONDOWN:
        handle_mouse(&event);
	break;
      case SDL_JOYAXISMOTION:
      case SDL_JOYBALLMOTION:
      case SDL_JOYHATMOTION:
      case SDL_JOYBUTTONDOWN:
      case SDL_JOYBUTTONUP:
        handle_joystick(&event);
	break;
      case SDL_VIDEORESIZE:
        display_cfg.screen_x = event.resize.w;
	display_cfg.screen_y = event.resize.h;
	if (display_cfg.screen_x < 640)
	  display_cfg.screen_x = 640;
	if (display_cfg.screen_y < 480)
	  display_cfg.screen_y = 480;
        resize();
	if (font) {
	  delete font;
	  font = NULL;
	}
	draw();

	SDL_initFramerate(&fpsm);
	fpsm.use_cpu_frame_count = 0;
	SDL_setFramerate(&fpsm,30);
	break;
      case SDL_VIDEOEXPOSE:
        /* Strangely the VideoExposeEvent does not give any region to be
	 * redrawn, so we are forced to update everything.
	 * To be verified : with screen = SWSURFACE, just a do_update should
	 * be enough */
	 redraw(NULL);
	do_update(NULL);
	break;
      case SDL_QUIT:
        sdl_done();
	break;
      }
    }
    if (lift) {
      lift->update();
    }
    if (jmoved) { // joystick moved, handle auto-repeat...
      if (!phase_repeat) {
        if ((update_count - timer)*fpsm.rateticks >= repeat_delay) {
	  phase_repeat = 1;
	  produce_joystick_event();
	  timer = update_count;
        }
      } else if ((update_count - timer)*fpsm.rateticks >= repeat_interval) {
	produce_joystick_event();
	timer = update_count;
      }
    }
    update_count++;
    // if we want to handle animations on the bg layer 1 day...
    // update_bg_layer();
    if (top != oldtop) 
      redraw_fg_layer();
    else if (sel != oldsel) {
      update_fg_layer(oldsel);
      if (sel >= 0)
	update_fg_layer(sel);
    } else if (sel >= top && sel <= menu_disp[top+rows-1]) {
      update_fg_layer(sel);
    } else if ((sdl_screen->flags & SDL_DOUBLEBUF) || (emulate_mouse_cursor && cursor)) {
      // If we are using double buffering, then update fg layer even if nothing
      // changes, or we wouldn't be able to see the mouse moving !!!
      update_fg_layer(top);
    }
    SDL_framerateDelay(&fpsm);
  }
  clear_surface(my_screen);
  do_update(NULL);
  if (sdl_screen->flags & SDL_DOUBLEBUF)
    clear_surface(sdl_screen);
}

// TBitmap_menu : a menu with a bitmap on top of it
// Is it better to handle bitmaps with a child of TMenu or with a type
// of menu_item ???
// For now it seems that it's easier to do it with a child, maybe I'll have
// to change my mind later

TBitmap_menu::TBitmap_menu(char *my_title, menu_item_t *mymenu, char *bitmap_path) :
  TMenu(my_title,mymenu)
{
  bmp = IMG_Load(bitmap_path);
  if (!bmp) {
    printf("TBitmap_menu: couldn't load %s\n",bitmap_path);
  }
}

TBitmap_menu::~TBitmap_menu() {
  if (bmp) {
    SDL_FreeSurface(bmp);
  }
}

void TBitmap_menu::setup_font(unsigned int len_frame) {
  TMenu::setup_font(len_frame);
  if (bmp) {
    if (bmp->w+2*HMARGIN > width_max)
      width_max = bmp->w+2*HMARGIN;
    int h;
    h = (sdl_screen->h-bmp->h-2)/(nb_items + 4 + 6); // margin
    if (h < font->get_font_height() && h < font->get_font_width()) {
      delete font;
      font = new TFont_ttf(my_screen,h);
      compute_width_from_font();
    }
  }
}

int TBitmap_menu::compute_fglayer_height() {
  int h = TMenu::compute_fglayer_height();
  if (bmp) 
    h+=bmp->h+2;
  return h;
}

void TBitmap_menu::display_fglayer_header(int &y) {
  if (bmp) {
    SDL_Rect dest;
    dest.x = (fg_layer->w - bmp->w)/2;
    dest.y = y;

    SDL_BlitSurface(bmp,NULL, fg_layer,&dest);
    y += bmp->h+2;
  }
}

void TBitmap_menu::skip_fglayer_header(int &y) {
  if (bmp) {
    y += bmp->h+2;
  }
}

// TDialog : a menu without frames (top & bottom) but with a title bar

void TDialog::setup_font(unsigned int len_frame) {
  TMenu::setup_font(len_frame);
  if (*title) {
    int w;
    font->dimensions(title,&w,&htitle);
    w+=2; htitle+=2; // with border
    if (w > width_max)
      width_max = w;
    int htotal = (sdl_screen->h-htitle)/(nb_items + 4 + 6); // margin
    if (htotal < font->get_font_height() && htotal < font->get_font_width()) {
      delete font;
      font = new TFont_ttf(my_screen,htotal);
      compute_width_from_font();
    }
  }
}

int TDialog::compute_fglayer_height() {
  int h = TMenu::compute_fglayer_height();
  h+=htitle;
  return h;
}

void TDialog::display_fglayer_header(int &y) {
  if (*title) {
    boxColor(fg_layer,1,1,fg_layer->w-2,htitle-1,fg ^ 0xffffff00);
    font->surf_string(fg_layer,1,1,title,fg);
    rectangleColor(fg_layer,0,0,fg_layer->w-1,htitle,fg);
    y += htitle + 2;
  }
}

void TDialog::skip_fglayer_header(int &y) {
  if (*title) {
    y += htitle + 2;
  }
}

void TDialog::draw_frame(SDL_Rect *r) {
  if (!font) setup_font(0);
  if (use_bglayer && !r)
    clear_surface(my_screen);
  work_area.x = 0;
  work_area.y = 0;
  work_area.w = sdl_screen->w;
  work_area.h = sdl_screen->h;
}

// TMenuMultiCol : a multi colomn version of TMenu

TMenuMultiCol::TMenuMultiCol(char *my_title, menu_item_t *mymenu, int nbcol, char **mycols) : TMenu(my_title,mymenu) {
  nb_cols = nbcol;
  cols = mycols;
  colpos = (int*)malloc(sizeof(int)*nb_cols);
}

TMenuMultiCol::~TMenuMultiCol() {
  if (colpos) free(colpos);
}

void TMenuMultiCol::adjust_len_max_options(unsigned int &len_max_options) {
  len_max_options += nb_cols;
  for (int c=0; c< nb_cols; c++) {
    int len_max = 0;
    for (int n=0; n<nb_disp_items; n++) {
      int index = menu_disp[n];
      int len = strlen(cols[index*nb_cols+c]);
      if (len > len_max) {
	len_max = len;
      }
    }
    len_max_options += len_max;
  }
}

void TMenuMultiCol::compute_width_from_font() {
  TMenu::compute_width_from_font();
  for (int c=0; c<nb_cols; c++) {
    colpos[c] = width_max;
    int wmax = 0;
    for (int n=0; n<nb_disp_items; n++) {
      int index = menu_disp[n],w,h;
      font->dimensions(cols[index*nb_cols+c],&w,&h);
      if (w > wmax) {
	wmax = w;
      }
    }
    width_max += wmax+HMARGIN;
  }
  width_max += HMARGIN;
}

void TMenuMultiCol::disp_menu(int n,int y,const char *s,int w,int h) {
  // Just need to add the columns after the normal line
  TMenu::disp_menu(n,y,s,w,h);
  for (int c=0; c<nb_cols; c++) {
    if (n == sel) {
      int selcolor = compute_selcolor();
      font->surf_string(fg_layer,colpos[c],y,cols[n*nb_cols+c],bg,selcolor);
    } else
      font->surf_string(fg_layer,colpos[c],y,cols[n*nb_cols+c],fg);
  }
}

