/*---------------------------------------------------------------

  File        : pslider.cpp

  Description : Tool for doing presentation from Latex and PS files

  Author      : David Tschumperlé
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  ------------------------------------------------------------*/

#include "../CImg.h"
// Overcome VisualC++ 6.0 and DMC compilers namespace bug
#if ( defined(_MSC_VER) && _MSC_VER<=1200 ) || defined(__DMC__)
#define std
#endif
using namespace cimg_library;

unsigned int width,height,owidth,oheight;

struct mime {
  char ext[16];
  char command[256];
};

struct Link {
  unsigned int page,xmin,xmax,ymin,ymax;
  char url[256];
};

unsigned int load_Links(const char *Link_filename,Link *Links)
{
  std::FILE *file = std::fopen(Link_filename,"r");
  unsigned int nol=0,p,x0,y0,w,h;
  if (file) {
    while (std::fscanf(file,"%d \"%[^\"]\" %d %d %d %d",&p,Links[nol].url,&x0,&y0,&h,&w)==6) {
      Links[nol].page=p-1;
      Links[nol].xmin=x0;
      Links[nol].xmax=x0+w;
      Links[nol].ymin=oheight-y0-h;
      Links[nol++].ymax=oheight-y0;
    }
    std::fclose(file);
  }
  return nol;
}


void save_Links(Link *Links,const unsigned int nol,const char *Link_filename)
{
  std::FILE *file = cimg::fopen(Link_filename,"w");
  if (!file) throw CImgException("Cannot open file '%s' for writing");
  for (unsigned int i=0; i<nol; i++) {
    Link l = Links[i];
    std::fprintf(file,"%d \"%s\" %d %d %d %d\n",l.page+1,l.url,l.xmin,oheight-l.ymax,l.ymax-l.ymin,l.xmax-l.xmin);
  }
  std::fclose(file);
}


/*-----------------------------------

  Main procedure

  -----------------------------------*/

int main(int argc,char **argv)
{
  unsigned int ckey=0,stopflag,nop,nol,nom,current_slide,old_slide,update_flag,twidth,theight,slide_type,itmp,i;
  int mousex,mousey,ooverLink=-1,overLink=-1;
  int xmin,xmax,ymin,ymax;
  char slideformat[1024],thumbformat[1024],Link_filename[1024];
  double gamma=1;
  const char *ext;
  mime mimes[100];
  CImg<unsigned char> slide,nslide,vslide,thumb,menu,layer,menuthumb;
  CImgl<unsigned char> thumbs;
  unsigned char yellow[3]= { 255,255,0 }, white[3]= { 255,255,255 }, green[3]= { 100,255,100 }, gray[3]= { 150,150,150 }, black[3]= { 0,0,0 };
  CImgDisplay *disp=NULL,*dispthumb=NULL;
  Link Links[1024];
  std::FILE *file;
  char c;

/*------------------------------------

  PSlider Initialization Part

  ----------------------------------*/

// Read str line parameters
// Display usage
  char str[1024];
  std::sprintf(str,"Tool for generating and displaying presentations from PS or PS2HTML files\n\n");
  std::sprintf(str+std::strlen(str)," When the window is opened, you can press the key 'H' to open the help menu.\n");
  std::sprintf(str+std::strlen(str)," Additional command line parameters are :");
  cimg_usage(str);

// Read command line parameters
  const char *file_i    = cimg_option("-i",(char*)NULL,"Input PS or PDF file");
  const char *format_o  = cimg_option("-o","png","Output image format ('png' or 'ppm')");
  const char *geometry  = cimg_option("-g","full","Window geometry ('wxh', or 'full')");
  const char *tgeometry = cimg_option("-t","128x96","Thumbnail size (wxh)");
  const char *mimefilename = cimg_option("-m","pslider.mime","Specify file of mime types");
  bool orient       = cimg_option("-invert",false,"Invert landscape orientation");
  bool lorient      = cimg_option("-linvert",false,"Invert links orientation");
  bool linearf      = cimg_option("-linear",false,"Use linear interpolation for image resizing");
  bool noLink       = cimg_option("-nolink",false,"Don't display link contours");
  int dautorun = cimg_option("-autorun",-1,"If >0, determine a time step (in seconds) that is used for an automatic slideshow");
  int nautolink=-1, autorun=dautorun, nautorun=autorun;

  width = 1024; height = 768; twidth = 128; theight = 96;
  if (std::sscanf(geometry,"%dx%d",&width,&height)==0) {
#if cimg_OS==0 || cimg_OS==1
    {
      system("xdpyinfo -display :0 | grep dimensions >/tmp/tmp_pslider");
      file = fopen("/tmp/tmp_pslider","r"); fscanf(file,"%*[^0-9]%dx%d",&width,&height); std::fclose(file);
    }
#elif cimg_OS==2
    {
      HDC dc = GetDC(NULL);
      width = GetDeviceCaps(dc,HORZRES);
      height = GetDeviceCaps(dc,VERTRES);
    }
#endif
  }
  std::sscanf(tgeometry,"%dx%d",&twidth,&theight);
  if (cimg_option("-h",false,"Display this help page")) exit(0); else std::fprintf(stderr,"\n*** PSlider ***\n");
  std::fprintf(stderr,"\n - Input geometry : slides=%dx%d, thumbnails=%dx%d\n",width,height,twidth,theight);

// Try to open an existing presentation
  slide_type = 0;
  std::fprintf(stderr," - Looking for a PS2HTML presentation in current dir ..... "); fflush(stderr);
  if ((file=std::fopen("ps2html_auto/0001.html","r"))!=NULL) { std::fclose(file);
    std::fprintf(stderr, "YES\n"); slide_type = 1; }
  else std::fprintf(stderr,"NO\n");
  std::fprintf(stderr," - Looking for a PSlider presentation in current dir ..... "); fflush(stderr);
  if ((file=std::fopen("pslider_auto/.pslider","r"))!=NULL) { std::fclose(file); std::fprintf(stderr,"YES\n"); slide_type = 2; }
  else std::fprintf(stderr,"NO\n");
  if (file_i) {
    if (cimg::strcasecmp(cimg::filename_split(file_i),"ps") && cimg::strcasecmp(cimg::filename_split(file_i),"pdf"))
      throw CImgException("You have to specify a PS or PDF file as input file. You specified '%s' as the filename",file_i);
    std::FILE *test_file = cimg::fopen(file_i,"r");
    cimg::fclose(test_file);
    switch (slide_type) {
    case 1: cimg::warn(1,"An existing PSHTML presentation has been found.\n Do you really want to create a PSlider one ? [Y]/[N} ..."); break;
    case 2: cimg::warn(1,"An existing PSlider presentation has been found.\n Do you really want to overwrite it ? [Y]/[N] ..."); break;
      default: break;
    }
    if (slide_type) { c = getc(stdin); if (cimg::uncase(c)=='y') slide_type=0; else file_i=0; }
  }
  if (!slide_type && !file_i) throw CImgException("No existing presentations have been found, you must generate one (option '-i')");

// Init presentation (if necessary) and image paths
  switch (slide_type) {
    case 2:
      ext = NULL;
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.ppm","r"); if (file) {
          ext = "ppm"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.PPM","r"); if (file) {
          ext = "PPM"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.gif","r"); if (file) {
          ext = "gif"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.GIF","r"); if (file) {
          ext = "GIF"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.jpg","r"); if (file) {
          ext = "jpg"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.JPG","r"); if (file) {
          ext = "JPG"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.png","r"); if (file) {
          ext = "png"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/slide_1.PNG","r"); if (file) {
          ext = "PNG"; std::fclose(file);
        }
      }
      if (!ext) throw CImgException("I cannot recognize slide format, in directory 'pslider_auto'");
      std::sprintf(slideformat,"pslider_auto/slide_%%d.%s",ext);
      ext = NULL;
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.ppm","r"); if (file) {
          ext = "ppm"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.PPM","r"); if (file) {
          ext = "PPM"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.gif","r"); if (file) {
          ext = "gif"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.GIF","r"); if (file) {
          ext = "GIF"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.jpg","r"); if (file) {
          ext = "jpg"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.JPG","r"); if (file) {
          ext = "JPG"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.png","r"); if (file) {
          ext = "png"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("pslider_auto/thumb_1.PNG","r"); if (file) {
          ext = "PNG"; std::fclose(file);
        }
      }
      if (!ext) throw CImgException("I cannot recognize thumbnails format, in directory 'pslider_auto'");
      std::sprintf(thumbformat,"pslider_auto/thumb_%%d.%s",ext);
      std::sprintf(Link_filename,"pslider_auto/links");
      break;
    case 1:
      ext = NULL;
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.ppm","r"); if (file) {
          ext = "ppm"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.PPM","r"); if (file) {
          ext = "PPM"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.gif","r"); if (file) {
          ext = "gif"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.GIF","r"); if (file) {
          ext = "GIF"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.jpg","r"); if (file) {
          ext = "jpg"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.JPG","r"); if (file) {
          ext = "JPG"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.png","r"); if (file) {
          ext = "png"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/0001.PNG","r"); if (file) {
          ext = "PNG"; std::fclose(file);
        }
      }
      if (!ext) throw CImgException("I cannot recognize slide format, in directory 'ps2html_auto'");
      std::sprintf(slideformat,"ps2html_auto/%%.4d.%s",ext);
      ext = NULL;
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.ppm","r"); if (file) {
          ext = "ppm"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.PPM","r"); if (file) {
          ext = "PPM"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.gif","r"); if (file) {
          ext = "gif"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.GIF","r"); if (file) {
          ext = "GIF"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.jpg","r"); if (file) {
          ext = "jpg"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.JPG","r"); if (file) {
          ext = "JPG"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.png","r"); if (file) {
          ext = "png"; std::fclose(file);
        }
      }
      if (!ext) {
        file = std::fopen("ps2html_auto/mosaic0001.PNG","r"); if (file) {
          ext = "PNG"; std::fclose(file);
        }
      }
      if (!ext) throw CImgException("I cannot recognize thumbnails format, in directory 'ps2html_auto'");
      std::sprintf(thumbformat,"ps2html_auto/mosaic%%.4d.%s",ext);
      std::sprintf(Link_filename,"ps2html_auto/links");
      break;
    case 0: // Creating presentation from a PS file
#if cimg_OS==0 || cimg_OS==1 || cimg_OS==2
      std::fprintf(stderr," - Creating PSlider presentation from file '%s' :\n",cimg::basename(file_i));
      if (!cimg::strcasecmp("png",format_o)) {
        std::sprintf(slideformat,"pslider_auto/slide_%%d.png");
        std::sprintf(thumbformat,"pslider_auto/thumb_%%d.png");
      }
      else {
        std::sprintf(slideformat,"pslider_auto/slide_%%d.ppm");
        std::sprintf(thumbformat,"pslider_auto/thumb_%%d.ppm");
      }
      std::sprintf(Link_filename,"pslider_auto/links");
      ext = cimg::filename_split(file_i);
      stopflag = nop = 0;
      std::fprintf(stderr,"   > Creating 'PSlider' directory ... "); fflush(stderr);
#if cimg_OS!=2
      system("rm -rf pslider_auto");
      system("mkdir pslider_auto");
#else
      system("rmdir /s /q pslider_auto");
      system("mkdir pslider_auto");
#endif
      std::fprintf(stderr,"OK\n");

      std::fprintf(stderr,"   > Running Postcript interpreter to generate slides ... "); fflush(stderr);
      std::FILE *files = cimg::fopen(file_i,"r"), *filed = cimg::fopen("pslider_auto/tmp.ps","w");
      while (std::fscanf(files,"%[^\n]\n",str) && cimg::strcasecmp("%%EndComments",str)) 
        std::fprintf(filed,"%s\n",str);
      if (!cimg::strcasecmp(cimg::filename_split(file_i),"ps")) {

        std::fprintf(filed,"%%%%EndComments\n\
    %%%%BeginProcSet: ps2html.pro\n\
    userdict begin\n\
    	/debug false def\n\
    	/lwrites { loutput exch writestring } def\n\
    	/dwrites { doutput exch writestring } def\n\
    	/endline { 10 write } def\n\
    	/conv { cvi 10 string cvs } def\n\
    	/pnum { conv lwrites } def\n\
    	/start-hook { userdict begin\n\
    		(pslider_auto/links) (w) file /loutput exch def\n\
    		(pslider_auto/destinations) (w) file /doutput exch def\n\
    	end } bind def\n\
    	/bop-hook   { dup 1 add /pagenum exch def } bind def\n\
    	/end-hook   { userdict begin\n\
    		loutput closefile\n\
    		doutput closefile\n\
    	end } bind def\n\
    	/filestr {\n");
      // cut the string in multiple part, since VisualStudio doesn't like bug strings
      std::fprintf(filed,"\
    		(0000) 4 string copy dup\n\
    		4 4 -1 roll conv\n\
    		dup length 3 -1 roll exch sub\n\
    		exch putinterval\n\
    	} def\n\
    	/conc {\n\
    		dup length 3 -1 roll dup length dup 4 -1 roll add string\n\
    		dup 0 5 -1 roll putinterval\n\
    		dup 4 -2 roll exch putinterval\n\
    	} def\n\
    	/pattern { 256 string cvs (@) exch conc (@) conc } def\n\
    	/dummy { } def\n\
    	/bbox {\n\
    		cvx /proc exch def\n\
    		dup dup 0 get exch 3 get /proc cvx exec /y1 exch def /x1 exch def\n\
    		dup 2 get exch 1 get /proc cvx exec /y2 exch def /x2 exch def\n\
    		x1 x2 gt { x1 x2 /x1 exch def /x2 exch def } if\n\
    		y1 y2 gt { y1 y2 /y1 exch def /y2 exch def } if\n\
    	} def\n\
    	/rect { newpath x1 y1 moveto x1 y2 lineto x2 y2 lineto x2 y1 lineto closepath stroke } def\n\
    	/pbbox {\n\
    		debug { dup /dummy bbox rect } if\n\
    		/transform bbox\n\
    		y1 pnum ( ) lwrites x1 pnum ( ) lwrites\n\
    		x2 x1 sub pnum ( ) lwrites y2 y1 sub pnum\n\
    	} def\n\
    	/link { pagenum pnum ( \") lwrites exch lwrites (\" ) lwrites pbbox loutput endline } def\n\
    	/DOCINFO { cleartomark } def\n\
    	/DOCVIEW { cleartomark } def\n\
    	/PUT     { cleartomark } def\n\
    	/OUT     { cleartomark } def\n");
      // last part
      std::fprintf(filed,"\
    	%% Destinations\n\
    	/DEST    { >> begin Dest pattern dwrites ( => ) dwrites\n\
    		currentdict /Page known { Page } { pagenum } ifelse filestr (.html) conc dwrites\n\
    		(,) dwrites doutput endline\n\
    	end } def\n\
      %% Annotations\n\
      /ANN     { >> begin currentdict /Subtype known { Subtype } { /Text } ifelse\n\
    		 /Link eq { Link } { Notes } ifelse end } def\n\
      %% SubTypes of annotations, only links are considered.\n\
      /Link    { currentdict /SrcPg\n\
    		known { SrcPg filestr (.html) conc Rect link }\n\
    		{ currentdict /Dest\n\
    			known { Dest pattern Rect link }\n\
    			{ currentdict /Action\n\
    				known { Action begin\n\
    					/URI where { pop URI Rect link }\n\
    					{ currentdict /F known { F Rect link } { } ifelse }\n\
    					ifelse\n\
    				end\n\
    				} if }\n\
    			ifelse }\n\
    		ifelse } def\n\
      /Notes   { } def\n\
      %% Old markers.\n\
      /externalhandler { gsave initmat setmatrix exch 0 get link grestore } def\n\
      /LNK             { >> begin Page filestr (.html) conc Rect link end } def\n\
    	/pdfmark {\n\
    		pstack\n\
    		gsave\n\
    			dup type /stringtype eq\n\
    			{ externalhandler }\n\
    			{ dup type /arraytype eq\n\
    				{ initmat setmatrix setmat 0 get lookuptarget { 0 get filestr (.html) conc } if exch link }\n\
    				{ dup userdict exch known\n\
    					{ cvx exec }\n\
    					{ (Unknown mark ) print 20 string cvs print (\\n) print cleartomark }\n\
    					ifelse }\n\
    				ifelse }\n\
    			ifelse\n\
    		grestore\n\
    	} def\n\
    end\n\
    %%%%EndProcSet\n");
      }
      else std::fprintf(filed,"%%%%EndComments\n");
      int c; while ((c=std::fgetc(files))!=EOF) std::fputc(c,filed);
      std::fclose(files); std::fclose(filed);

      double resx = ((double)height/(210.0/25.4));
      double resy = ((double)width/(297/25.4));
      if (!cimg::strcasecmp(ext,"pdf")) { const double tmp=resx; resx=resy; resy=tmp; }
      if (!cimg::strcasecmp(format_o,"png"))
         std::sprintf(str,"%s -q -dBATCH -dNOPAUSE -sPAPERSIZE=a4 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -r%gx%g -sDEVICE=png16m -sOutputFile=%s pslider_auto/tmp.ps",cimg_OS!=2?"gs":"C:\\gs\\gs8.13\\bin\\gswin32c.exe",resx,resy,slideformat);
      else std::sprintf(str,"%s -q -dBATCH -dNOPAUSE -sPAPERSIZE=a4 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -r%gx%g -sDEVICE=ppm -sOutputFile=%s pslider_auto/tmp.ps",cimg_OS!=2?"gs":"C:\\gs\\gs8.13\\bin\\gswin32c.exe",resx,resy,slideformat);

      system(str); std::fprintf(stderr,"OK\n");

      std::fprintf(stderr,"   > Converting images and creating thumbnails :\n");
      while (!stopflag) {
        std::sprintf(str,slideformat,nop+1);
        file = std::fopen(str,"rb");
        if (file) {
          std::fprintf(stderr,"[%d]",nop+1); fflush(stderr);
          slide = CImg<unsigned char>(str);
          owidth = slide.width; oheight = slide.height;
          if (!cimg::strncmp(ext,"ps",3)) { slide.rotate(90); }
          thumb = slide.get_resize(twidth,theight);
          if (orient) { thumb.rotate(180); slide.rotate(180); }
          slide.save(str);
          std::sprintf(str,thumbformat,nop+1);
          thumb.save(str);
          std::fclose(file);
          nop++;
        } else stopflag=1;
      }
#if cimg_OS!=2
      system("rm -f pslider_auto/tmp.ps pslider_auto/destinations; echo \"\">pslider_auto/.pslider");
#else
      system("del /q /s pslider_auto\\tmp.ps pslider_auto\\destinations");
      system("echo \"\">pslider_auto\\.pslider");
#endif
      std::fprintf(stderr," OK\n");
      std::fprintf(stderr,"\n** The presentation has been generated, please type 'pslider' again to launch it. **\n\n");
      exit(0);
#endif
      break;
  }

// Check number of pages and original geometry of the presentation
  std::fprintf(stderr," - Geometry of the original presentation ...."); fflush(stderr);
  stopflag=nop=0;
  while (!stopflag) {
    std::sprintf(str,slideformat,nop+1); file = std::fopen(str,"rb"); if (file!=NULL) {
      nop++; std::fclose(file);
    }
    else stopflag=1;
  }
  std::sprintf(str,slideformat,1);
  slide = CImg<unsigned char>(str);
  owidth = slide.width; oheight = slide.height;
  std::fprintf(stderr,"%d slides %dx%d\n",nop,owidth,oheight);

// Load Thumbnails (and optionally slides)
  std::fprintf(stderr," - Loading Thumbnails ('%s' format)... ",cimg::filename_split(thumbformat)); fflush(stderr);
  {
    for (unsigned int i=0; i<nop; i++) {
      std::fprintf(stderr,"[%d]",i+1); fflush(stderr);
      std::sprintf(str,thumbformat,i+1);
      thumb = CImg<unsigned char>(str);
      thumbs.insert(thumb.get_resize(twidth,theight));
      if (orient) thumbs[i].rotate(180);
    }
  }
  std::fprintf(stderr," OK\n");

// Load links
  std::fprintf(stderr," - Loading Links ... "); fflush(stderr);
  std::fprintf(stderr,"%d links\n",nol=load_Links(Link_filename,Links));

// Load Mimetypes
  nom = 0;
  if (nol) {
    std::fprintf(stderr," - Loading mimetypes ... "); fflush(stderr);
#if cimg_OS!=2
    std::sprintf(mimes[nom].ext,"mpg");  std::sprintf(mimes[nom++].command,"mtvp -z -l");
    std::sprintf(mimes[nom].ext,"mpeg"); std::sprintf(mimes[nom++].command,"mtvp -z -l");
    std::sprintf(mimes[nom].ext,"avi");  std::sprintf(mimes[nom++].command,"xanim +Sr");
    std::sprintf(mimes[nom].ext,"png");  std::sprintf(mimes[nom++].command,"display");
    std::sprintf(mimes[nom].ext,"jpg");  std::sprintf(mimes[nom++].command,"xv");
    std::sprintf(mimes[nom].ext,"ppm");  std::sprintf(mimes[nom++].command,"ppm");
    std::sprintf(mimes[nom].ext,"inr");  std::sprintf(mimes[nom++].command,"inrcast -i");
    std::sprintf(mimes[nom].ext,"htm");  std::sprintf(mimes[nom++].command,"netscape");
    std::sprintf(mimes[nom].ext,"html"); std::sprintf(mimes[nom++].command,"netscape");
    std::sprintf(mimes[nom].ext,"pdf");  std::sprintf(mimes[nom++].command,"acroread");
#else
    std::sprintf(mimes[nom].ext,"mpg");  std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\Windows Media Player\\mplayer2.exe\"");
    std::sprintf(mimes[nom].ext,"mpeg"); std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\Windows Media Player\\mplayer2.exe\"");
    std::sprintf(mimes[nom].ext,"avi");  std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\Windows Media Player\\mplayer2.exe\"");
    std::sprintf(mimes[nom].ext,"png");  std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\IrfanView\\i_view32.exe\"");
    std::sprintf(mimes[nom].ext,"jpg");  std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\IrfanView\\i_view32.exe\"");
    std::sprintf(mimes[nom].ext,"ppm");  std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\IrfanView\\i_view32.exe\"");
    std::sprintf(mimes[nom].ext,"htm");  std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\Internet Explorer\\IEXPLORE.EXE\"");
    std::sprintf(mimes[nom].ext,"html"); std::sprintf(mimes[nom++].command,"\"C:\\Program Files\\Internet Explorer\\IEXPLORE.EXE\"");
#endif
    file = std::fopen(mimefilename,"r");
    if (file) {               // add user specified mimetypes
      std::fprintf(stderr,"from file '%s' :",mimefilename); fflush(stderr);
      mime mi;
      while (fscanf(file,"%[^ =]%*[ =]%[ -}] ",mi.ext,mi.command)==2) {
        std::fprintf(stderr," '%s'",mi.ext); fflush(stderr);
        for (itmp=0; itmp<nom; itmp++) if (!cimg::strcasecmp(mi.ext,mimes[itmp].ext)) { strcpy(mimes[itmp].command,mi.command); itmp=nom+1; }
        if (itmp==nom) { strcpy(mimes[nom].command,mi.command); strcpy(mimes[nom++].ext,mi.ext); }
      }
      std::fclose(file);
      std::fprintf(stderr,"\n");
    } else std::fprintf(stderr,"no file '%s' found, default used\n",mimefilename);
  }

/*-----------------------------------

  Begin User Interaction

  ---------------------------------*/

  old_slide = nop;
  current_slide = 0;
  update_flag = 1;
	unsigned int time0 = (unsigned int)time(NULL);
	bool links_done = false;
 
  while (!disp || (!disp->closed && ckey!=cimg::keyQ)) {
    if (!disp) disp = CImg<unsigned char>(width,height).fill(0).new_display("PSlider",0,cimg_OS!=2?2:6);
    else { if (!dispthumb && !disp->key && !disp->button && autorun<0) disp->wait(); }
    if (disp->key) { ckey=disp->key; disp->key=0; }
    else ckey=0;

// Go the next slide
    if (ckey==cimg::keyARROWRIGHT || ckey==cimg::keySPACE || ckey==cimg::keyN || ckey==cimg::keyPAGEDOWN || ckey==cimg::keyENTER
				|| (disp->mousex>=(int)disp->width-8 && disp->button&1) || (autorun>=0 && (unsigned int)time(NULL)>time0+autorun)) {
      current_slide=(current_slide+1)%nop;
      update_flag=1;
      disp->button&=~1;
			time0 = (unsigned int)time(NULL);
			links_done = false;
			if (dautorun>=0 && autorun>=0) {
				autorun = nautorun;
				nautorun = dautorun;
			}
    }
// Go to previous slide
    if (ckey==cimg::keyARROWLEFT || ckey==cimg::keyB || ckey==cimg::keyPAGEUP || ckey==cimg::keyBACKSPACE || (disp->mousex<=8 && disp->button&1)) {
      current_slide=(current_slide+nop-1)%nop;
      update_flag = 1;
      disp->button&=~1;
    }
// Go to first or last slide
    if (ckey==cimg::keyHOME)  { current_slide=0;     update_flag=1; }
    if (ckey==cimg::keyEND)   { current_slide=nop-1; update_flag=1; }

// Swap Landscape orientation
    if (ckey==cimg::keyARROWDOWN) {
      for (i=0; i<nop; i++) thumbs[i].rotate(180);
      orient=!orient;
      lorient=!lorient;
      update_flag=1;
    }
// Swap links orientation
    if (ckey==cimg::keyARROWUP) { lorient=!lorient; update_flag=1; }
// Swap link visualization
    if (ckey==cimg::keyL) {   // key 'L'
      noLink=!noLink; update_flag=1;
    }

		// Pause autorun
		if (dautorun>=0 && ckey==cimg::keyP) {
			if (autorun>=0) autorun=-1;
			else autorun = dautorun;
			update_flag = 1;
		}
		
		// Add a link
    if (ckey==cimg::keyF1) {
      int bbox[6];
      std::fprintf(stderr," > Add link #%d : ",nol+1); fflush(stderr);
      vslide.feature_selection(bbox,2,*disp);
      int x0=bbox[0],y0=bbox[1],x1=bbox[3],y1=bbox[4];
      x0 = x0*owidth/width;
      y0 = y0*oheight/height;
      x1 = x1*owidth/width;
      y1 = y1*oheight/height;
      std::fprintf(stderr,"Page %d, Box = (%d,%d)-(%d,%d)",current_slide+1,x0,y0,x1,y1);
      if (x0<0 || x1<0 || y0<0 || y1<0 || x0==x1 || y0==y1 || x1>=(int)owidth || y1>=(int)oheight) std::fprintf(stderr,", Range not valid\n");
      else {
        Links[nol].page = current_slide;
        Links[nol].xmin = x0;
        Links[nol].xmax = x1;
        Links[nol].ymin = y0;
        Links[nol].ymax = y1;
        std::sprintf(Links[nol].url,"file:Edit Link_#%d",nol+1);
        nol++;
        save_Links(Links,nol,Link_filename);
        std::fprintf(stderr,".. '%s' updated\n",Link_filename);
      }
      update_flag=1;
    }
    // Remove a Link
    if (overLink>=0 && ckey==cimg::keyF2) {
      std::fprintf(stderr," > Remove link %d...",overLink+1); fflush(stderr);
      for (unsigned int i=overLink; i<nol-1; i++) Links[i]=Links[i+1];
      nol--;
      save_Links(Links,nol,Link_filename);
      std::fprintf(stderr," '%s' updated\n",Link_filename);
      update_flag=1;
    }
    // Insert a new link page
    if (ckey==cimg::keyF3) {
      std::fprintf(stderr," > Insert a new link page ... "); fflush(stderr);
      for (unsigned int i=0; i<nol; i++) if (Links[i].page>=current_slide) Links[i].page++;
      save_Links(Links,nol,Link_filename);
      std::fprintf(stderr," '%s' updated\n",Link_filename);
      update_flag=1;
    }
    // Remove a link page
    if (ckey==cimg::keyF4) {
      std::fprintf(stderr," > Remove a link page ..."); fflush(stderr);
      for (unsigned int i=0; i<nol; i++) {
        if (Links[i].page==current_slide) { for (unsigned int k=i; k<nol-1; k++) Links[k]=Links[k+1]; nol--; i--; }
        else { if (Links[i].page>current_slide) Links[i].page--; }
      }
      save_Links(Links,nol,Link_filename);
      std::fprintf(stderr," '%s' updated\n",Link_filename);
      update_flag=1;
    }
    // Edit link file
    if (ckey==cimg::keyF5) {
      if (cimg_OS!=2) {
        std::fprintf(stderr," > Edit link file : ");
        std::sprintf(str,"xemacs %s",Link_filename);
        std::fprintf(stderr,"'%s'\n",str);
        system(str);
        nol=load_Links(Link_filename,Links);
        update_flag=1;
      }
    }
    // Histogram normalization and gamma correction
    if (ckey==cimg::key1) { gamma=-1; update_flag=1; }
    if (ckey==cimg::key2) { if (gamma<0) gamma=1; gamma+=0.15; update_flag=1; }
    if (ckey==cimg::key3) { if (gamma<0) gamma=1; gamma-=0.15; update_flag=1; }
    if (ckey==cimg::key4) { gamma=1; update_flag=1; }
    
    // Help menu
    if (ckey==cimg::keyH) {
      CImg<unsigned char> help_win(550,450,1,3);
      const unsigned char white[3]={255,255,255};
      help_win.fill(0);
      help_win.draw_text(
        "PSlider -- Keyboard reference --\n\n\
\t H : Display this help\n\
\t Q : Exit PSlider\n\
\t ESC, or Right mousebutton : Open Thumbnail menu\n\
\t RIGHTARROW, PAGEDOWN, SPACE, ENTER : Go to the next slide\n\
\t LEFTTARROW, PAGEUP, B, BACKSPACE : Go to the previous slide\n\
\t HOME, END : Go to the first/last slide\n\
\t DOWNARROW : Swap landscape orientation\n\
\t UPARROW   : Swap links orientation\n\
\t L         : Swap link visualization\n\
\t P         : Pause on/off in autorun mode\n\
\t F1        : Add a link\n\
\t F2        : Remove a link\n\
\t F3        : Insert a new link page\n\
\t F4        : Remove a link page\n\
\t F5        : Edit the link file\n\
\t  1        : Color normalization\n\
\t  2        : Darken the slides\n\
\t  3        : Lighten the slides\n\
\t  4        : Reset the gamma correction\n\
        ",20,20,white);
      help_win.draw_line(4,4,help_win.width-5,4,white);
      help_win.draw_line(help_win.width-5,4,help_win.width-5,help_win.height-5,white);
      help_win.draw_line(help_win.width-5,help_win.height-5,4,help_win.height-5,white);
      help_win.draw_line(4,help_win.height-5,4,4,white);
      vslide.draw_image(help_win,(vslide.width-help_win.width)/2,(vslide.height-help_win.height)/2,0,0,0.75);

      disp->key=0;
      vslide.display(*disp);
      while (!disp->closed && !disp->key && !disp->button) disp->wait();
      disp->key=disp->button=0;
      update_flag = 1;
			time0 = (unsigned int)time(NULL);
    }

// Thumbnails menu
//------------------
    if (ckey==cimg::keyESC || disp->button&2 || (dispthumb && (dispthumb->closed || dispthumb->key==cimg::keyESC || dispthumb->button&2))) {

      if (!dispthumb) {
// Open menu
        unsigned int fx=thumbs[0].width+8,fy=thumbs[0].height+8,x,y;
        menuthumb = CImg<unsigned char>((nop<8?nop*fx:8*fx),fy*(1+(nop-1)/8),1,3).fill(0);
        { cimg_mapXY(menuthumb,x,y) menuthumb(x,y,0)=menuthumb(x,y,1)=menuthumb(x,y,2)=(unsigned char)(std::cos(x/30.0)*std::sin(y/30.0)*64)+64; }
        dispthumb = menuthumb.new_display("Presentation Layout",0,2);
        x=y=0;
        for (unsigned int k=0; k<nop; k++) {
          menuthumb.draw_image(thumbs[k],x+4,y+4,0,0);
          menuthumb.draw_line(x+3,y+3,x+3+thumbs[k].width+1,y+3,white);
          menuthumb.draw_line(x+3+thumbs[k].width+1,y+3,x+3+thumbs[k].width+1,y+3+thumbs[k].height+1,white);
          menuthumb.draw_line(x+3+thumbs[k].width+1,y+3+thumbs[k].height+1,x+3,y+3+thumbs[k].height+1,gray);
          menuthumb.draw_line(x+3,y+3+thumbs[k].height+1,x+3,y+3,gray);
          x+=fx;
          if (x>=menuthumb.width) { x=0; y+=fy; }
        }
        menuthumb.display(*dispthumb);
        disp->button&=~2;
      }
      else {
// Close menu
        delete dispthumb;
        dispthumb=NULL;
        disp->button&=~2;
        update_flag=1;
      }
    }

    if (dispthumb) {
      int next=current_slide,x,y,fx=thumbs[0].width+8,fy=thumbs[0].height+8,x0,x1,y0,y1;
      CImg<unsigned char> tmp(menuthumb);
      x = dispthumb->mousex/fx;
      y = dispthumb->mousey/fy;
      if (x>=0 && y>=0 && y*8+x<(int)nop) {
        x0 = x*fx; y0 = y*fy;
        x1 = (x+1)*fx-1; y1 = (y+1)*fy-1;
        tmp.draw_line(x0,y0,x1,y0,yellow);
        tmp.draw_line(x1,y0,x1,y1,yellow);
        tmp.draw_line(x1,y1,x0,y1,yellow);
        tmp.draw_line(x0,y1,x0,y0,yellow);
        { for (int y=y0; y<=y1; y+=2) for (int x=x0; x<=x1; x+=2) tmp(x,y,0)=200; }
        tmp.display(*dispthumb);
        next = y*8+x;
      } else menuthumb.display(*dispthumb);
      if (dispthumb->button&1) {
        current_slide = next;
        update_flag = 1;
        dispthumb->button&=~1;
      }
    }

// Display a slide
//-----------------
    if (update_flag) {
      if (current_slide!=old_slide) {
        if (current_slide==(old_slide-1)%nop) nslide=slide;
        if (current_slide==(old_slide+1)%nop) slide=nslide;
        else { std::sprintf(str,slideformat,current_slide+1); slide = CImg<unsigned char>(str); }
      }
      vslide = slide;
      if (!linearf || (vslide.width>=width && vslide.height>=height)) vslide.resize(width,height,1,3); else vslide.resize(width,height,1,3,3);
      if (orient) vslide.rotate(180);
      if (gamma!=1) {
        if (gamma<0) vslide.equalize_histogram();
        else cimg_mapXYV(vslide,x,y,k) vslide(x,y,k) = (unsigned char)(std::pow(vslide(x,y,k)/256.0,gamma)*256);
      }
      if (dautorun>0 && autorun<0) vslide.draw_text("Paused",2,2,white,black);
      if (!noLink)
      for (i=0; i<nol; i++) {
        if (Links[i].page==current_slide) {
          xmin = Links[i].xmin*width/owidth;   xmax = Links[i].xmax*width/owidth;
          ymin = Links[i].ymin*height/oheight; ymax = Links[i].ymax*height/oheight;
          if (lorient) {
            xmin = width-xmin;
            xmax = width-xmax;
            ymin = height-ymin;
            ymax = height-ymax;
          }
          vslide.draw_line(xmin,ymin,xmax,ymin,green,0x88888888);
          vslide.draw_line(xmax,ymin,xmax,ymax,green,0x88888888);
          vslide.draw_line(xmax,ymax,xmin,ymax,green,0x88888888);
          vslide.draw_line(xmin,ymax,xmin,ymin,green,0x88888888);
        }
      }
      vslide.display(*disp);
      update_flag=0;
      overLink=-1;
      if (current_slide!=old_slide && current_slide!=(old_slide-1)%nop) {
        std::sprintf(str,slideformat,((current_slide+1)%nop)+1); nslide = CImg<unsigned char>(str);
      }
      old_slide=current_slide;
			nautolink=-1;
		}

// Check mouse over links
//------------------------
    ooverLink = overLink;
    overLink = -1;
    layer = vslide;
    mousex = disp->mousex;
    mousey = disp->mousey;
		
    if (autorun<0 || (autorun>=0 && (unsigned int)time(NULL)>time0+3*autorun/4)) {
			for (i=0; i<nol; i++) {
				xmin = Links[i].xmin*width/owidth;   xmax = Links[i].xmax*width/owidth;
				ymin = Links[i].ymin*height/oheight; ymax = Links[i].ymax*height/oheight;
				if (lorient) {
					int tmp = xmin;
					xmin = width-xmax;
					xmax = width-tmp;
					tmp = ymin;
					ymin = height-ymax;
					ymax = height-tmp;
				}
				if (autorun>=0 && Links[i].page==(current_slide+1)%nop && !cimg::strncasecmp(cimg::filename_split(Links[i].url),"settimer",8)) {
                                  std::sscanf(Links[i].url,"file:%d.settimer",&nautorun);
                                  std::fprintf(stderr," > Next slide timer will be %d seconds\n",nautorun);
				}
				if (autorun<0 && Links[i].page==current_slide && mousex>=xmin && mousex<=xmax && mousey>=ymin && mousey<=ymax
					&& cimg::strncasecmp(Links[i].url,"settimer",8) ) {
					overLink = i;
					if (!noLink) {
						for (int y=ymin; y<=ymax; y+=2) for (int x=xmin; x<=xmax; x+=2) { layer(x,y,1)=128; layer(x,y,0)=layer(x,y,2)=0; }
						layer.draw_text(2,2,white,black,1,Links[i].url);
					}
					i = nol;
				}
				if (autorun>=0 && Links[i].page==current_slide && nautolink<(int)i) { overLink = nautolink = i; i=nol; }
			}
			if (overLink<0) links_done = true;
		}

    if (autorun<0 && overLink!=ooverLink) layer.display(*disp);
    if (overLink>=0 && (disp->button&1 || autorun>=0)) {
      disp->button&=~1;
      const Link l = Links[overLink];
      ext = cimg::filename_split(l.url);
      for (unsigned int m=0; m<nom; m++) {
        if (!cimg::strcasecmp(ext,mimes[m].ext) ||
          (( !cimg::strncasecmp(l.url,"http://",7) || !cimg::strncasecmp(l.url,"ftp://",6)
          || !cimg::strncasecmp(l.url,"www.",4) || !cimg::strncasecmp(l.url,"ftp.",4) )
          &&
          ( !cimg::strcasecmp(mimes[m].ext,"htm") || !cimg::strcasecmp(mimes[m].ext,"html")))
        ) {
          if (slide_type==1) std::sprintf(str,"%s ../%s",mimes[m].command,l.url+1+cimg::strfind(l.url,':'));
          else std::sprintf(str,"%s %s",mimes[m].command,l.url+1+cimg::strfind(l.url,':'));
          if (!cimg::strncasecmp(l.url,"http:",5)) {
            if (slide_type==1) std::sprintf(str,"%s ../%s",mimes[m].command,l.url+5);
            else std::sprintf(str,"%s %s",mimes[m].command,l.url+5);
          }
          if (!cimg::strncasecmp(l.url,"file:",5)) {
            if (slide_type==1) std::sprintf(str,"%s ../%s",mimes[m].command,l.url+5);
            else std::sprintf(str,"%s %s",mimes[m].command,l.url+5);
          }
          if (!cimg::strncasecmp(l.url,"file://",7)) {
            if (slide_type==1) std::sprintf(str,"%s ../%s",mimes[m].command,l.url+7);
            else std::sprintf(str,"%s %s",mimes[m].command,l.url+7);
          }
          if (!cimg::strncasecmp(l.url,"http://",7) || !cimg::strncasecmp(l.url,"ftp://",6) ||
            !cimg::strncasecmp(l.url,"www.",4)
            ) std::sprintf(str,"%s %s",mimes[m].command,l.url);
          if (cimg_OS!=2) {
						if (autorun<0) std::sprintf(str+cimg::strlen(str)," &");
						//else std::sprintf(str+cimg::strlen(str),"");
					}
          std::fprintf(stderr," > Launching '%s' ... ",str); fflush(stderr); system(str);
					if (autorun>=0) time0 = (unsigned int)time(NULL)-(unsigned int)3*autorun/4;
					fprintf(stderr,"Done\n");
          m=nom;
        }
      }
    }
    if (overLink<0 && disp->button&1) disp->button&=~1;
  }

// Exit pslider.
  std::fprintf(stderr,"\n*** Exit PSlider ***\n\n");

  exit(0);
  return 0;
}
