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

File        : pslider.cpp

Description : Tool for doing presentation from Latex and PS files

Copyright  : David Tschumperle - http://www.greyc.ensicaen.fr/~dtschump/
  
This software is governed by the CeCILL  license under French law and
abiding by the rules of distribution of free software.  You can  use, 
modify and/ or redistribute the software under the terms of the CeCILL
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info". 
  
As a counterpart to the access to the source code and  rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty  and the software's author,  the holder of the
economic rights,  and the successive licensors  have only  limited
liability. 
  
In this respect, the user's attention is drawn to the risks associated
with loading,  using,  modifying and/or developing or reproducing the
software by the user in light of its specific status of free software,
that may mean  that it is complicated to manipulate,  and  that  also
therefore means  that it is reserved for developers  and  experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or 
data to be ensured and,  more generally, to use and operate it in the 
same conditions as regards security. 
  
The fact that you are presently reading this means that you have had
knowledge of the CeCILL license and that you accept its terms.
  
------------------------------------------------------------*/

#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,"%u \"%[^\"]\" %u %u %u %u",&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 mouse_x,mouse_y,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",true,"Invert landscape orientation");
  bool lorient      = cimg_option("-linvert",false,"Invert links orientation");
  bool noLink       = cimg_option("-nolink",false,"Don't display link contours");
  bool fullscreen   = cimg_option("-full",false,"Start in Fullscreen mode");
  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 = CImgDisplay::screen_dimx(); 
  height = CImgDisplay::screen_dimy();
  twidth = 128; theight = 96;
  std::sscanf(geometry,"%ux%u",&width,&height);
  std::sscanf(tgeometry,"%ux%u",&twidth,&theight);
  if (cimg_option("-h",false,"Display this help page")) std::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 ..... "); std::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 ..... "); std::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==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 ... "); std::fflush(stderr);
#if cimg_OS==1
    std::system("rm -rf pslider_auto");
    std::system("mkdir pslider_auto");
#else
    std::system("rmdir /s /q pslider_auto");
    std::system("mkdir pslider_auto");
#endif
    std::fprintf(stderr,"OK\n");

    std::fprintf(stderr,"   > Running Postcript interpreter to generate slides ... "); std::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);

    std::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); std::fflush(stderr);
	slide = CImg<unsigned char>(str);
	owidth = slide.dimx(); oheight = slide.dimy();
	if (!cimg::strncmp(ext,"ps",3)) { slide.rotate(90); }
	thumb = slide.get_resize(twidth,theight,1,3,3);
	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==1
    std::system("rm -f pslider_auto/tmp.ps pslider_auto/destinations; echo \"\">pslider_auto/.pslider");
#else
    std::system("del /q /s pslider_auto\\tmp.ps pslider_auto\\destinations");
    std::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");
    std::exit(0);
#endif
    break;
  }

  // Check number of pages and original geometry of the presentation
  std::fprintf(stderr," - Geometry of the original presentation ...."); std::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.dimx(); oheight = slide.dimy();
  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)); std::fflush(stderr);
  {
    for (unsigned int i=0; i<nop; i++) {
      std::fprintf(stderr,"[%d]",i+1); std::fflush(stderr);
      std::sprintf(str,thumbformat,i+1);
      thumb = CImg<unsigned char>(str);
      thumbs.insert(thumb.get_resize(twidth,theight,1,3,3));
      if (orient) thumbs[i].rotate(180);
    }
  }
  std::fprintf(stderr," OK\n");

  // Load links
  std::fprintf(stderr," - Loading Links ... "); std::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 ... "); std::fflush(stderr);
#if cimg_OS==1
    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,"firefox");
    std::sprintf(mimes[nom].ext,"html"); std::sprintf(mimes[nom++].command,"firefox");
    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); std::fflush(stderr);
      mime mi;
      while (fscanf(file,"%[^ =]%*[ =]%[ -}] ",mi.ext,mi.command)==2) {
        std::fprintf(stderr," '%s'",mi.ext); std::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->is_closed && ckey!=cimg::keyQ)) {
    if (!disp) {
      disp = new CImgDisplay(CImg<unsigned char>(width,height).fill(0),"PSlider",0,2,fullscreen,true);
      disp->move(0,0);
    }
    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->mouse_x>=(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->mouse_x<=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;
    }

    // Toggle Fullscreen mode
    if (ckey==cimg::keyF)  disp->toggle_fullscreen().display(vslide);

    // 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); std::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); std::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 ... "); std::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 ..."); std::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==1) {
        std::fprintf(stderr," > Edit link file : ");
        std::sprintf(str,"xemacs %s",Link_filename);
        std::fprintf(stderr,"'%s'\n",str);
        std::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 F : Toggle Fullscreen\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.dimx()-5,4,white);
      help_win.draw_line(help_win.dimx()-5,4,help_win.dimx()-5,help_win.dimy()-5,white);
      help_win.draw_line(help_win.dimx()-5,help_win.dimy()-5,4,help_win.dimy()-5,white);
      help_win.draw_line(4,help_win.dimy()-5,4,4,white);
      vslide.draw_image(help_win,(vslide.dimx()-help_win.dimx())/2,(vslide.dimy()-help_win.dimy())/2,0,0,0.75);

      disp->key=0;
      vslide.display(*disp);
      while (!disp->is_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->is_closed || dispthumb->key==cimg::keyESC || dispthumb->button&2))) {
      
      if (!dispthumb) {
	// Open menu
        unsigned int fx=thumbs[0].dimx()+8,fy=thumbs[0].dimy()+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 = new CImgDisplay(menuthumb,"Presentation Layout",0,2,fullscreen);
        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].dimx()+1,y+3,white);
          menuthumb.draw_line(x+3+thumbs[k].dimx()+1,y+3,x+3+thumbs[k].dimx()+1,y+3+thumbs[k].dimy()+1,white);
          menuthumb.draw_line(x+3+thumbs[k].dimx()+1,y+3+thumbs[k].dimy()+1,x+3,y+3+thumbs[k].dimy()+1,gray);
          menuthumb.draw_line(x+3,y+3+thumbs[k].dimy()+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].dimx()+8,fy=thumbs[0].dimy()+8,x0,x1,y0,y1;
      CImg<unsigned char> tmp(menuthumb);
      x = dispthumb->mouse_x/fx;
      y = dispthumb->mouse_y/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;
      vslide.resize(width,height,1,3,5);
      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;
    mouse_x = disp->mouse_x;
    mouse_y = disp->mouse_y;
		
    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 && mouse_x>=xmin && mouse_x<=xmax && mouse_y>=ymin && mouse_y<=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,11,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==1) {
	    if (autorun<0) std::sprintf(str+cimg::strlen(str)," &");
	    //else std::sprintf(str+cimg::strlen(str),"");
	  }
          std::fprintf(stderr," > Launching '%s' ... ",str); std::fflush(stderr); std::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.
  if (disp) delete disp;
  std::fprintf(stderr,"\n*** Exit PSlider ***\n\n");
  return 0;
}
