/**
 * E-FancyLauncher : icon launcher, a little similar to
 * E-Launch by Ejvend Nielsen, though the code is
 * completely different. Basically I have hacked to
 * draw my own buttons since the epplet API does not
 * provide scaled-image-buttons.
 * The hack for the tooltip is borrowed from E-Xmms
 * written by Tim Ferguson (timf@dgs.monash.edu.au)
 * and is improved (hacked even more).
 *
 * This version does not need a special directory
 * with small icons, images are resized automatically
 * and the icons are mapped to occupy as much space as
 * possible. Custom layouts and sizes are available.
 * A tooltip is displayed and simple key shortcuts are available!
 * Support for transparency and configuration with
 * a dialog window.
 *
 * author: Alexandre David
 * http://www.docs.uu.se/~adavid
 * e-mail: adavid@docs.uu.se
 * License: GPL
 * Version: 0.7
 * Changes: 0.1 creation
 *          0.2 changed the icon drawing
 *              added transparency,
 *              ,custom raiseable buttons
 *              ,inhibit flag for all tooltips
 *              ,general dimensions
 *              ,fixed theme change bug
 *              ,suppressed ugly hack to prefetch dimensions
 *               (and replaced it by a resize hack :) )
 *              ,multiple instances and configurations fixes
 *          0.3 bad behavior fix for transparent background
 *              when no button is defined + bug with the event
 *              handler: bad event filter
 *          0.4 inner button borders configurable,
 *              changed default configuration,
 *              added _QUIT _INFO _CONFIG special commands,
 *              added configuration window
 *              removed MEGAHACK for the tooltips, I noticed Epplet_gadget_move *<8o)
 *              but I hacked a scrollbar manually, I cannot use a button because
 *              I do not have the drag.
 *          0.5 wheel mouse support
 *          0.6 page button fix sorry.
 *          0.7 fixed priority stuff: nice is useless since
 *              the epplet uses little resources and the pb is
 *              applications started inherit this nice.
 *              Pinpointed by: Compudj <compudj@penguinpowered.com>
 *              experimental transparent raise button
 *
 *          todo?: double buffer to avoid flicker when repainting
 *                 the whole list in the dialog window (not disturbing)
 *                 new tooltip implementation
 */

#include<epplet.h>
#include<X11/cursorfont.h>
#include<signal.h>
#include<sys/time.h>
#include<sys/resource.h>

/* when I develop */
#define DEVELOPMENT 0
/* version tag */
#define VERSION "0.7"

/* default dimensions for the epplet */
#define WIDTH_NAME "EPPLET_WIDTH"
#define HEIGHT_NAME "EPPLET_HEIGHT"
#define DEF_WIDTH 48
#define DEF_HEIGHT 144
#define DEFAULT_WIDTH "*3"
#define DEFAULT_HEIGHT "*9"

/* for people upgrading from 0.1 */
#define OLD_WIDTH "EPPLET_WIDTHx16"
#define OLD_HEIGHT "EPPLET_HEIGHTx16"

/* default button layout */
#define COLS_NAME "COLUMNS"
#define ROWS_NAME "ROWS"
#define COLS_DEFAULT "2"
#define ROWS_DEFAULT "6"

/* default fancy stuff */
#define TRANSP_RAISE_NAME "TRANSP_RAISE"
#define TRANSP_RAISE_DEF 0
#define TRANSP_RAISE_DEFAULT "0"
#define TRANSPARENT_NAME "TRANSPARENT"
#define TRANSPARENT_DEF 1
#define TRANSPARENT_DEFAULT "1"
#define RAISE_NAME "RAISE"
#define RAISE_DEF 1
#define RAISE_DEFAULT "1"
#define TOOLTIPS_NAME "TOOLTIPS"
#define TOOLTIPS_DEF 1
#define TOOLTIPS_DEFAULT "1"
#define ICON_BORDER "ICON_BORDER"
#define ICON_BORDER_DEFAULT "0"
#define ICON_BORDER_DEF 0
#define LIST_HEIGHT_NAME "LIST_HEIGHT"
#define LIST_HEIGHT_DEFAULT "6"
#define LIST_HEIGHT_DEF 6

/* version marker */
#define HELLO "v"VERSION":Please"
#define HELLO_TXT \
  "customize (and add) your buttons. Removing this line will display the help again."

/* where to search for icons */
#define DEFAULT_PATH1 "/usr/share/pixmaps/"
#define DEFAULT_PATH2 "/usr/share/icons/"
#define LONGEST_PATH_SIZE sizeof(DEFAULT_PATH1)

/* when a command fails */
#define FAILED "Failed command : "

/* special commands */
#define SWITCH_PAGE "_SWITCH"
#define QUIT "_QUIT"
#define INFO "_INFO"
#define CONFIG "_CONFIG"

/* commands to mount and unmount */
#define MOUNT_FLOPPY \
 "!if [ -z \"`df | grep /mnt/floppy`\" ]; then mount /mnt/floppy; else umount /mnt/floppy; fi"
#define MOUNT_CDROM \
 "!if [ -z \"`df | grep /mnt/cdrom`\" ]; then mount /mnt/cdrom; else umount /mnt/cdrom; fi"

/* some limits for width and height */
#define MAXW 128
#define MAXH 100
/* max col = 170, max width = 133 (MAX*16/12) */

#if DEVELOPMENT
# define ONE "/home/adavid/private/C/Epplets/E-FancyLauncher/1.gif"
# define TWO "/home/adavid/private/C/Epplets/E-FancyLauncher/2.gif"
# define THREE "/home/adavid/private/C/Epplets/E-FancyLauncher/3.gif"
# define show_about() Epplet_spawn_command("dox E-FancyLauncher.ABOUT")
#else
# define ONE EROOT"/epplet_data/E-FancyLauncher/1.gif"
# define TWO EROOT"/epplet_data/E-FancyLauncher/2.gif"
# define THREE EROOT"/epplet_data/E-FancyLauncher/3.gif"
# define show_about() Epplet_show_about("E-FancyLauncher")
#endif

/* the button structure */
typedef struct {
  char *pixmap;
  char *command;
  char *tooltip;
} LButton;

/* default configuration */
#define DEF_SIZE_PAGE 12
static LButton DEF_BUTTONS[3][DEF_SIZE_PAGE]={
  {{ONE,SWITCH_PAGE,"Switch page "},
   {"mc/i-directory.png","rxvt -title \"Midnight Commander\" -e mc","Midnight Commander "},
   {"emacs.png","emacs","Emacs "},
   {"gnome-color-xterm.png","rxvt","rxvt "},
   {"gnome-eterm.png","Eterm --trans --borderless --shade 22","Transparent Eterm "},
   {"gnome-eterm.png","Eterm","Eterm "},
   {"gnome-terminal.png","gnome-terminal","Gnome Terminal "},
   {"netscape.png","netscape","Navigator "},
   {"gnome-balsa2.png","netscape -messenger","Messenger "},
   {"gnome-word.png","netscape -composer","Composer "},
   {"large/java_src.xpm","jassistant","Java Assistant "},
   {"gnome-lockscreen.png","!xscreensaver-command -lock","Lock Screen "}},
  {{TWO,SWITCH_PAGE,"Switch page "},
   {"gnome-ccthemes.png",CONFIG,"E-FancyLauncher Configuration "},
   {"mc/i-floppy.png",MOUNT_FLOPPY,"mount/umount floppy "},
   {"mc/i-cdrom.png",MOUNT_CDROM,"mount/umount cdrom "},
   {"large/pdf.xpm","acroread","Acrobat Reader "},
   {"gproc-icon.png","gproc","GProc "},
   {"gnome-calc2.png","x48","HP48 Emulator "},
   {"gnome-cpu-mem.png","gkrellm","gkrellm "},
   {"gnome-audio2.png","xmms","XMMS "},
   {"gnome-mixer.png","gmix","Gnome Mixer "},
   {"mc/i-cdrom.png","gtcd","Gnome CD Player "},
   {"gnome-warning.png",QUIT,"Quit E-FancyLauncher "}},  
  {{THREE,SWITCH_PAGE,"Switch page "},
   {"gnome-graphics.png","xpaint","xpaint "},
   {"xv2.xpm","xv","XV "},
   {"gnome-gimp.png","gimp","Gimp "},
   {"gnome-ee.png","ee","Electric Eye "},
   {"gqview.png","gqview","GQview "},
   {"gnome-panel.png","panel","Gnome Panel "},
   {"mc/gnome-manpage.png","gnome-help-browser","Gnome Help "},
   {"gnome-favorites.png","soffice","Star Office "},
   {"gnome-info.png",INFO,"Help on E-FancyLauncher "},
   {"control-center.png","gnomecc","Gnome Control Center "},
   {"gnome-color-browser.png","gcolorsel","Gnome Color Browser "}}};

/* main parameters */
int epplet_width,epplet_height;
int transparent,raiseable,tooltips,transp_raise;
int cols,rows,button_w,button_h,icon_border;

/* col and row to handle the tooltip smoothly */
int current_page,current_col,current_row;

/* all the defined buttons */
LButton *buttons;
/* the hack for the tooltip */
Epplet_gadget tooltip_popup;
/* for correct shape */
XRectangle button_rect,shape_button;

/* low level data */
GC gc;
Display *display;
Window window;
ImlibData *imlibdata;
Pixmap winmap=0;
int font_h,lastx=10000,lasty=10000;

/* configuration dialog stuff:
 * mainly dimension
 */
#define FONT_SIZE 2
#define MAX_COM 36
#define MAX_TOOLTIP 22
#define SIZEBAR 12
#define CHARW 7
#define COM_W (CHARW*MAX_COM)
#define TOOLTIP_W (CHARW*MAX_TOOLTIP)
#define VIEWW (COM_W+TOOLTIP_W+4)
#define OFFSX 5
#define OFFSY 5
#define BUTX (OFFSX+VIEWW+4)
#define CONFIG_WIDTH (BUTX+SIZEBAR+OFFSX)

/* window of the configuration dialog and the button list */
Window dialog_win,dialog_area_window;
/* configuration dialog gadgets */
Epplet_gadget dialog_area,dialog_pixmap,dialog_command,dialog_tooltip;
Epplet_gadget dialog_epplet_width,dialog_epplet_height,dialog_epplet_cols,dialog_epplet_rows;
Epplet_gadget dialog_epplet_border,dialog_epplet_lines;
Epplet_gadget transp_raise_checkbox;
/* for the scrollbar */
int dialog_pressy,dialog_bh,dialog_by,dialog_offset_view,dialog_maxoffset;
int dialog_maxy,dialog_bheight,dialog_in_button;
Pixmap dialog_button_up,dialog_button_down,dialog_button_normal;
/* button list state */
int selected,hilited,list_lines;
/* cursor when dragging */
Cursor exchange_cursor;

/* for rendering mod1: normal, mod2: pressed */
static ImlibColorModifier mod1={256,210,256};
static ImlibColorModifier mod2={256,256,256};

ImlibImage *getImage(ImlibData*,char*);           /* loads an image */
void button_normal(int,int,char*);                /* draw a normal button */
void repaint_button(int,int,float,int);
void button_up(int,int,char*);                    /* draw a hilited button */
void button_down(int,int,char*);                  /* draw a pressed button */
void fill_page();                                 /* fills the epplet page */
void print_label(char*,int,int,int);              /* draw labels in the button list */
void expose_handler(void*,Window,int,int,int,int);/* draw epplet or config. dialog */
void move_handler(void *,Window,int,int,int,int);

void checkflags(void *);    /* consistency check */
int contents(Epplet_gadget);/* read number in a field */
void update_textboxes();    /* select -> fill the fields */
void page_changed();        /* page change -> repaint list */
void enter_pixmap(void*);   /* change pixmap of a button */
void enter_command(void*);  /* change command of a button */
void enter_tooltip(void*);  /* change tooltip of a button */
void enter_width(void*);    /* when width is entered */
void enter_height(void*);   /* when height is entered */
void new_icon_border(void*);/* when icon border is entered */
void failed_command(char*); /* error message when a command fails */

void changed_config(char*,char*,int); /* update a configuration entry */
void update_config(int);              /* commit a new button configuration */
void applyConfig(void*);              /* commit all the configuration changes */
void okConfig(void*);                 /* same as apply but close the window */

void clean_pixmaps(); /* clean pixmaps of the scroll button */
void setup_pixmaps(); /* setup pixmaps of the scroll button */
void fill_textbox(Epplet_gadget,int,int); /* fill textbox with numerical values */
void createConfigDialog(); /* create the configuration window */
void free_buttons();  /* free all the buttons when changing layout */
void cleanup();       /* epplet cleanup */
void update_dim();    /* setup dimensions */
void setup_buttons(); /* setup buttons */
void setup_epplet();  /* general epplet setup */

void checkButtonPixmap(int,int,int); /* paint the right pixmap for the scroll button */
void mouse_motion(void*,Window,int,int); /* motion on epplet or config. dialog */
void button_pressed(void*,Window,int,int,int); /* button pressed on epplet or config. dialog */
void button_released(void*,Window,int,int,int);/* button released ... */
void mouse_leave(void*,Window); /* mouse leaves a window */
void key_released(void*,Window,char*); /* key event handling */
void update_epplet(void*); /* used for theme changes */
void event_handler(XEvent*,void*); /* supposed to detect theme changes */

/****************
 * drawing stuff
 ****************/

ImlibImage *getImage(ImlibData *id,char *image) {
  if (image) {
    /* for an unknown reason, epplet may add spaces
     * to the config resources, so I have to strip
     */
    int n=strlen(image);
    while(n>0 &&
	  (image[n-1]==' ' ||
	   image[n-1]=='\r'||
	   image[n-1]=='\n'||
	   image[n-1]=='\t')) image[--n]='\0';
    while(n>0 &&
	  (*image==' ' ||
	   *image=='\r'||
	   *image=='\n'||
	   *image=='\t')) { image++; n--; }
    if (*image!='/') {
      /* try default paths */
      char buf[n+LONGEST_PATH_SIZE];
      ImlibImage *im;
      strncpy(buf,DEFAULT_PATH1,sizeof(DEFAULT_PATH1)-1);
      strcpy(&buf[sizeof(DEFAULT_PATH1)-1],image);
      im=Imlib_load_image(id,buf);
      if (im) return im;
      strncpy(buf,DEFAULT_PATH2,sizeof(DEFAULT_PATH2)-1);
      strcpy(&buf[sizeof(DEFAULT_PATH2)-1],image);
      im=Imlib_load_image(id,buf);
      if (im) return im;
      /* fail -> last chance, try directly from home */
    }
    return Imlib_load_image(id,image);
  }
  return NULL;
}


void button_normal(int x,int y,char *img) {
  /* reshape if necessary */

  if (raiseable & transparent) {
    Pixmap mask;
    ImlibData *id=imlibdata;
    ImlibImage *im=getImage(id,img);
    if (im) {
      Imlib_render(id,im,button_w-2*icon_border,button_h-2*icon_border);
      mask=Imlib_move_mask(id,im);
      XShapeCombineRectangles(display,window,ShapeBounding,x,y,&button_rect,1,ShapeSubtract,0);
      if (mask)
	XShapeCombineMask(display,window,ShapeBounding,x+icon_border,y+icon_border,mask,ShapeUnion);
      else
	XShapeCombineRectangles(display,window,ShapeBounding,x,y,
				&shape_button,1,ShapeUnion,0);
      Imlib_destroy_image(id,im);
    }
  }
  if (transp_raise && winmap)
    XSetWindowBackgroundPixmap(display,window,winmap);
  XClearArea(display,window,x,y,button_w,button_h,False);
}


void repaint_button(int x,int y,float mul,int min) {
  int i,j,nw,nh,min1,min2,pix;
  float border1,border2;
  ImlibImage *im;
  ImlibData *id=Epplet_get_imlib_data();
  Pixmap tmp;

  tmp=XCreatePixmap(display,winmap,epplet_width,epplet_height,id->x.depth);
  XCopyArea(display,winmap,tmp,gc,0,0,epplet_width,epplet_height,0,0);
  im=Imlib_create_image_from_drawable(id,winmap,None,x,y,button_w,button_h);

  if (min==0) {
    border1=0.4;
    border2=2.0;
    min1=0;
    min2=min+min;
  } else {
    border1=2.0;
    border2=0.4;
    min1=min+min;
    min2=0;
  }

  nw=3*button_w;
  nh=button_h;
  for(i=0;i<nw-3;i++) {
    /* 2 first lines */
    pix=im->rgb_data[i]*border1;
    im->rgb_data[i]=(pix>255?255:(pix<min1?min1:pix));
    pix=im->rgb_data[i+nw]*border1;
    im->rgb_data[i+nw]=(pix>255?255:(pix<min1?min1:pix));
  }
  /* 2 last at the right */
  for(;i<nw;i++) {
    pix=im->rgb_data[i]*border1;
    im->rgb_data[i]=(pix>255?255:(pix<min1?min1:pix));
    pix=im->rgb_data[nw+i]*border2;
    im->rgb_data[nw+i]=(pix>255?255:(pix<min2?min2:pix));
  }
  for(j=2;j<nh-2;j++) {
    /* 2 first cols */
    for(i=0;i<6;i++) {
      pix=im->rgb_data[j*nw+i]*border1;
      im->rgb_data[j*nw+i]=(pix>255?255:(pix<min1?min1:pix));
    }
    for(;i<nw-6;i++) {
      /* middle */
      pix=im->rgb_data[j*nw+i]*mul;
      im->rgb_data[j*nw+i]=(pix>255?255:(pix<min?min:pix));
    }
    /* 2 last cols */
    for(;i<nw;i++) {
      pix=im->rgb_data[j*nw+i]*border2;
      im->rgb_data[j*nw+i]=(pix>255?255:(pix<min2?min2:pix));
    }
  }
  /* 2 first at the left */
  for(i=0;i<3;i++) {
    pix=im->rgb_data[(nh-2)*nw+i]*border1;
    im->rgb_data[(nh-2)*nw+i]=(pix>255?255:(pix<min1?min1:pix));
    pix=im->rgb_data[(nh-1)*nw+i]*border2;
    im->rgb_data[(nh-1)*nw+i]=(pix>255?255:(pix<min2?min2:pix));
  }
  /* 2 last lines */
  for(;i<nw;i++) {
    pix=im->rgb_data[(nh-2)*nw+i]*border2;
    im->rgb_data[(nh-2)*nw+i]=(pix>255?255:(pix<min2?min2:pix));
    pix=im->rgb_data[(nh-1)*nw+i]*border2;
    im->rgb_data[(nh-1)*nw+i]=(pix>255?255:(pix<min2?min2:pix));
  }
  
  //n=3*button_w*button_h;
  //for(i=0;i<n;i++) {
  //int pix=im->rgb_data[i]*mul;
  //im->rgb_data[i]=(pix>255?255:(pix<min?min:pix));
  //}

  Imlib_changed_image(id,im);
  Imlib_paste_image(id,im,tmp,x,y,button_w,button_h);
  Imlib_destroy_image(id,im);

  XSetWindowBackgroundPixmap(display,window,tmp);
  XShapeCombineRectangles(display,window,ShapeBounding,x,y,
			  &button_rect,1,ShapeUnion,0);
  //XClearArea(display,window,x,y,button_w,button_h,False);
  XClearWindow(display,window);
  XFreePixmap(display,tmp);
}


void button_up(int x,int y,char *img) {
  /* reshape if necessary , buttons assumed rectangular
   * support for any shaped button is not worth yet
   */
  if (transp_raise) {
    repaint_button(x,y,1.4,16);
  } else {
    if (transparent & raiseable) {
      XShapeCombineRectangles(display,window,ShapeBounding,x,y,
			      &button_rect,1,ShapeUnion,0);
      XClearArea(display,window,x,y,button_w,button_h,False);
    } else {
      ImlibData *id=imlibdata;
      ImlibImage *im;
      Pixmap map,mask;
      
      if (raiseable) {
	Epplet_imageclass_get_pixmaps("EPPLET_BUTTON","hilited",
				      &map,&mask,button_w,button_h);
	XCopyArea(display,map,window,gc,0,0,button_w,button_h,x,y);
	XFreePixmap(display,map);
	XFreePixmap(display,mask);
      }
      
      im=getImage(id,img);
      if (im) {
	Imlib_paste_image(id,im,window,x+icon_border,y+icon_border,
			  button_w-2*icon_border,button_h-2*icon_border);
	Imlib_destroy_image(id,im);
      }
    }
  }
}


void button_down(int x,int y,char *img) {
  /* if we are there, we were in button_up before
   * so the shape is correct
   */

  if (transp_raise) {
    repaint_button(x,y,0.6,0);
  } else {
    Pixmap map,mask;
    ImlibData *id=Epplet_get_imlib_data();
    ImlibImage *im=getImage(id,img);
    if (raiseable) {
      if (transparent) /* if moved, need to do this */
	XShapeCombineRectangles(display,window,ShapeBounding,x,y,
				&button_rect,1,ShapeUnion,0);
      Epplet_imageclass_get_pixmaps("EPPLET_BUTTON","clicked",
				    &map,&mask,button_w,button_h);
      XCopyArea(display,map,window,gc,0,0,button_w,button_h,x,y);
      XFreePixmap(display,map);
      XFreePixmap(display,mask);
    }
    
    if (im) {
      Imlib_set_image_modifier(id,im,&mod1);
      Imlib_paste_image(id,im,window,x+icon_border,y+icon_border,
			button_w-2*icon_border,button_h-2*icon_border);
      Imlib_set_image_modifier(id,im,&mod2);
      Imlib_destroy_image(id,im);
    }
  }
}


void fill_page() {
  LButton *button=buttons+current_page*rows*cols;
  int y,x,max_y=rows*button_h,max_x=cols*button_w,op;
  Pixmap mask=0,himap=0,button_map=0,button_mask=0;
  ImlibImage *im;
  ImlibData *id=imlibdata;

  if (winmap)
    XFreePixmap(display,winmap);

  if (transparent) {
    winmap=XCreatePixmap(display,window,epplet_width,epplet_height,id->x.depth);
    op=ShapeSet;
    if (transp_raise) {
      Window parent,root,*list;
      int i;
      unsigned int n;
      XSetWindowBackgroundPixmap(display,window,ParentRelative);
      XSync(display,False);
      parent=window;
      for(i=0;i<3;i++) {
	XQueryTree(display,parent,&root,&parent,&list,&n);
	if (list) XFree(list);
	if (parent!=root)
	  XSetWindowBackgroundPixmap(display,parent,ParentRelative);
      }
      XClearWindow(display,window);
      Esync();
      /*XCopyArea(display,window,winmap,gc,0,0,epplet_width,epplet_height,0,0);
      * does not work properly */
      im=Imlib_create_image_from_drawable(id,window,None,0,0,epplet_width,epplet_height);
      Imlib_paste_image(id,im,winmap,0,0,epplet_width,epplet_height);
      Imlib_kill_image(id,im);
    } else
      Epplet_imageclass_get_pixmaps("EPPLET_BUTTON","hilited",
				    &himap,&mask,button_w,button_h);
    if (mask) XFreePixmap(display,mask);
  } else {
    Epplet_imageclass_get_pixmaps(( epplet_height>epplet_width ?
				    "EPPLET_BACKGROUND_VERTICAL":
				    "EPPLET_BACKGROUND_HORIZONTAL"),
				  "normal",&winmap,&mask,epplet_width,epplet_height);
    /* actually this should be already set */
    if (mask) {
      XShapeCombineMask(display,window,ShapeBounding,0,0,mask,ShapeSet);
      XFreePixmap(display,mask);
    } else { /* set/restore rectangular mask */
      XRectangle epplet_rect;
      epplet_rect.x=0;
      epplet_rect.y=0;
      epplet_rect.width=epplet_width;
      epplet_rect.height=epplet_height;
      XShapeCombineRectangles(display,window,ShapeBounding,0,0,&epplet_rect,1,ShapeSet,0);
    }
    op=ShapeUnion;
  }

  for(y=0;y<max_y;y+=button_h)
    for(x=0;x<max_x;x+=button_w,button++) {
      if (himap) /* initialize to get button up easily */
	XCopyArea(display,himap,winmap,gc,0,0,button_w,button_h,x,y);
      im=getImage(id,button->pixmap);
      if (im) {
	Imlib_render(id,im,button_w-2*icon_border,button_h-2*icon_border);
	mask=Imlib_move_mask(id,im);
	Imlib_paste_image(id,im,winmap,x+icon_border,y+icon_border,
			  button_w-2*icon_border,button_h-2*icon_border);
	if (mask)
	  XShapeCombineMask(display,window,ShapeBounding,x+icon_border,y+icon_border,mask,op);
	else /* shape to a rectangle */
	  XShapeCombineRectangles(display,window,ShapeBounding,x,y,
				  &shape_button,1,op,0);
	Imlib_destroy_image(id,im);
	op=ShapeUnion;
      } else if (transparent && button->command) {
	/* no image, try empty button when transparent
	* AND there is a command
	*/
	if (!button_map)
	  Epplet_imageclass_get_pixmaps("EPPLET_BUTTON","normal",
					&button_map,&button_mask,button_w,button_h);
	XCopyArea(display,button_map,window,gc,0,0,button_w,button_h,x,y);
	if (button_mask)
	  XShapeCombineMask(display,window,ShapeBounding,
			    x+icon_border,y+icon_border,button_mask,op);
	else /* shape to a rectangle */
	  XShapeCombineRectangles(display,window,ShapeBounding,x,y,
				  &shape_button,1,op,0);
	op=ShapeUnion;
      }
    }

  /* test correctness
  {
    XRectangle r={0,0,epplet_width,epplet_height};
    XShapeCombineRectangles(display,window,ShapeBounding,0,0,
			    &r,1,ShapeSet,0);
  }
  * bug * if the epplet appears first with a bit
  * outside the screen, then the pixmap is shifted (?)
  */

  XSetWindowBackgroundPixmap(display,window,winmap);
  XClearWindow(display,window);
  if (himap) XFreePixmap(display,himap);
  if (button_map) XFreePixmap(display,button_map);
  if (button_mask) XFreePixmap(display,button_mask);
}


void print_label(char *label,int x,int y,int wmax) {
  if (label) {
    int w,h;
    Epplet_textclass_get_size("EPPLET_TEXT_MEDIUM",&w,&h,label);
    if (w<=wmax)
      Epplet_textclass_draw("EPPLET_TEXT_MEDIUM","normal",
			    dialog_area_window,x,y,label);
    else {
      char *s1=label;
      char *s3=label+strlen(label);
      char *s2;
      int w1;
      /* find the right size in log(n) iterations */
      while(1) {
	s2=s1+(s3-s1)/2;
	if (s3-s1<2) break;
	Epplet_textclass_get_size("EPPLET_TEXT_MEDIUM",&w1,&h,s2);
	if (w-w1>wmax)
	  s3=s2;
	else
	  s1=s2;
      }
      w1=s2-label;
      { /* and truncate at the right position */
	char buf[w1+1];
	strncpy(buf,label,w1-3);
	buf[w1-3]='.';
	buf[w1-2]='.';
	buf[w1-1]='.';
	buf[w1]='\0';
	Epplet_textclass_draw("EPPLET_TEXT_MEDIUM","normal",
			      dialog_area_window,x,y,buf);
      }
    }
  }
}


void expose_handler(void *data, Window win, int x, int y, int w, int h) {
  XClearArea(display,win,x,y,w,h,False);
  if (win==dialog_win) {
    if (dialog_bheight<cols*rows*button_h)
      XCopyArea(display,dialog_button_normal,win,gc,
		0,0,SIZEBAR,dialog_bh,BUTX+button_w,dialog_by);
  }
  else if (win==dialog_area_window) {
    int i=(y+=dialog_offset_view)/button_h; /* first line to redraw */
    int n=cols*rows;                        /* # of lines */
    int p=n*current_page;                   /* page index offset */
    int yl;
    ImlibData *id=imlibdata;
    ImlibImage *im;
    while(i<0) i++;
    while (i<n && !(y+h<=(yl=i*button_h) || y>=yl+button_h)) {
      im=getImage(id,buttons[p+i].pixmap);
      yl-=dialog_offset_view;
      if (selected==i || hilited==i) {
	Pixmap map,mask;
	Epplet_imageclass_get_pixmaps("EPPLET_BUTTON",(selected==i?"clicked":"normal"),
				      &map,&mask,button_w+VIEWW,button_h);
	XCopyArea(display,map,dialog_area_window,gc,
		  0,0,button_w+VIEWW,button_h,0,yl);
	XFreePixmap(display,map);
	XFreePixmap(display,mask);
      }
      if (im) {
	Imlib_paste_image(id,im,win,icon_border,icon_border+yl,
			  button_w-2*icon_border,button_h-2*icon_border);
	Imlib_destroy_image(id,im);
      }
      print_label(buttons[p+i].command,
		  button_w+2,yl+=(button_h-font_h)/2,COM_W);
      print_label(buttons[p+i].tooltip,
		  button_w+COM_W+4,yl,TOOLTIP_W);
      i++;
    }
  }
  Esync();
}

void move_handler(void *data, Window win, int x, int y, int w, int h) {
  if (win==window && transp_raise && (x!=lastx || y!=lasty)) {
    lastx=x;
    lasty=y;
    fill_page();
  }
}


/*********************************
 * configuration dialog behaviour
 *********************************/

void checkflags(void *data) {
  transp_raise&=(transparent&raiseable);
  if (transp_raise_checkbox)
    Epplet_gadget_data_changed(transp_raise_checkbox);
  if ((int)data)
    fill_page();
}

int contents(Epplet_gadget tb) {
  char *s=Epplet_textbox_contents(tb);
  if (*s=='*')
    return 16*atoi(s+1);
  else
    return atoi(s);
}


void update_textboxes() {
  if (selected>=0) {
    LButton *button=buttons+cols*rows*current_page+selected;
    Epplet_change_textbox(dialog_pixmap,button->pixmap);
    Epplet_change_textbox(dialog_command,button->command);
    Epplet_change_textbox(dialog_tooltip,button->tooltip);
  }
}


void page_changed() {
  if (dialog_area_window) {
    expose_handler(NULL,dialog_area_window,0,0,VIEWW+button_w,dialog_bheight);
    update_textboxes();
  }
}


void enter_pixmap(void *onlypix) {
  if (selected>=0) {
    LButton *button=buttons+rows*cols*current_page+selected;
    char *s=Epplet_textbox_contents(dialog_pixmap);
    if (button->pixmap) free(button->pixmap);
    button->pixmap=(strlen(s)>0?strdup(s):NULL);
    changed_config(button->pixmap,"PAGE%d_ICON%d",selected);
    expose_handler(NULL,dialog_area_window,0,selected*button_h-dialog_offset_view,
		   ((int)onlypix?button_w:VIEWW+button_w),button_h);
    fill_page();
  }
}


void enter_command(void *update) {
  if (selected>=0) {
    LButton *button=buttons+rows*cols*current_page+selected;
    char *s=Epplet_textbox_contents(dialog_command);
    if (button->command) free(button->command);
    button->command=(strlen(s)>0?strdup(s):NULL);
    changed_config(button->command,"PAGE%d_PROG%d",selected);
    if ((int)update)
      expose_handler(NULL,dialog_area_window,button_w,selected*button_h-dialog_offset_view,
		     VIEWW,button_h);
  }
}


void enter_tooltip(void *update) {
  if (selected>=0) {
    LButton *button=buttons+rows*cols*current_page+selected;
    char *s=Epplet_textbox_contents(dialog_tooltip);
    if (button->tooltip) free(button->tooltip);
    button->tooltip=(strlen(s)>0?strdup(s):NULL);
    changed_config(button->tooltip,"PAGE%d_TOOLTIP%d",selected);
    if ((int)update)
      expose_handler(NULL,dialog_area_window,button_w,selected*button_h-dialog_offset_view,
		     VIEWW,button_h);
  }
}


/* will update height */
void enter_width(void *data) {
  int w=contents(dialog_epplet_width);
  int r=contents(dialog_epplet_rows);
  int c=contents(dialog_epplet_cols);
  if (w>=16 && r>0 && c>0)
    fill_textbox(dialog_epplet_height,(r*w)/c,1);
}


/* will update width */
void enter_height(void *data) {
  int h=contents(dialog_epplet_height);
  int r=contents(dialog_epplet_rows);
  int c=contents(dialog_epplet_cols);
  if (h>=16 && r>0 && c>0)
    fill_textbox(dialog_epplet_width,(c*h)/r,1);
}


void new_icon_border(void *data) {
  int b=contents(dialog_epplet_border);
  if (b>=0) {
    icon_border=b;
    Epplet_modify_config(ICON_BORDER,Epplet_textbox_contents(dialog_epplet_border));
    update_dim();
    fill_page();
    page_changed();
  }
}

void failed_command(char *command) {
  char buf[strlen(command)+sizeof(FAILED)];
  strncpy(buf,FAILED,sizeof(FAILED)-1);
  strcpy(&buf[sizeof(FAILED)-1],command);
  Epplet_dialog_ok(buf);
}


/***************************
 * configuration management
 ***************************/

void changed_config(char *val,char *code,int index) {
  char buf[20];
  Esnprintf(buf,20,code,current_page,index);
  if (val) {
    char *s=val;
    while(*s) {
      if (*s!=' ' &&
	  *s!='\r' &&
	  *s!='\n' &&
	  *s!='t') break;
      s++;
    }
    /* after stripping, check if something left */
    if (!*s) val="-";
  }
  else val="-";
  Epplet_modify_config(buf,val);
}


void update_config(int index) {
  LButton *button=buttons+rows*cols*current_page+index;
  changed_config(button->pixmap,"PAGE%d_ICON%d",index);
  changed_config(button->command,"PAGE%d_PROG%d",index);
  changed_config(button->tooltip,"PAGE%d_TOOLTIP%d",index);
}


void applyConfig(void* destroywin) {
  int epplet_w,epplet_h,lines,epplet_cols,epplet_rows;

  if (selected>=0) {
    enter_command(NULL);
    enter_tooltip(NULL);
    enter_pixmap(NULL);
  }
  Epplet_modify_config(TRANSPARENT_NAME,(transparent?"1":"0"));
  Epplet_modify_config(RAISE_NAME,(raiseable?"1":"0"));
  Epplet_modify_config(TOOLTIPS_NAME,(tooltips?"1":"0"));
  Epplet_modify_config(TRANSP_RAISE_NAME,(transp_raise?"1":"0"));

  epplet_w=contents(dialog_epplet_width);
  if (epplet_w>=16 && epplet_w<=1600 && epplet_w!=epplet_width)
    Epplet_modify_config(WIDTH_NAME,Epplet_textbox_contents(dialog_epplet_width));
  else
    epplet_w=epplet_width; /* forget change */

  epplet_h=contents(dialog_epplet_height);
  if (epplet_h>=16 && epplet_h<=1200 && epplet_h!=epplet_height)
    Epplet_modify_config(HEIGHT_NAME,Epplet_textbox_contents(dialog_epplet_height));
  else
    epplet_h=epplet_height; /* forget change */

  epplet_cols=contents(dialog_epplet_cols);
  if (epplet_cols>0 && epplet_cols<1000)
    Epplet_modify_config(COLS_NAME,Epplet_textbox_contents(dialog_epplet_cols));
  else
    epplet_cols=cols; /* forget change */

  epplet_rows=contents(dialog_epplet_rows);
  if (epplet_rows>0 && epplet_rows<1000)
    Epplet_modify_config(ROWS_NAME,Epplet_textbox_contents(dialog_epplet_rows));
  else
    epplet_rows=rows; /* forget change */

  lines=contents(dialog_epplet_lines);
  if (lines<3 || lines>64 || lines==list_lines)
    lines=0; /* forget change */
  else {
    Epplet_modify_config(LIST_HEIGHT_NAME,Epplet_textbox_contents(dialog_epplet_lines));
    list_lines=lines;
  }

  if (epplet_h!=epplet_height || epplet_w!=epplet_width) {
    epplet_width=epplet_w;
    epplet_height=epplet_h;
    XResizeWindow(display,window,epplet_width,epplet_height);
    if (cols==epplet_cols && rows==epplet_rows) {
      update_dim();
      fill_page();
      lines=1;
    }
  }

  if (epplet_cols!=cols || epplet_rows!=rows) {
    free_buttons();
    cols=epplet_cols;
    rows=epplet_rows;
    update_dim();
    setup_buttons();
    fill_page();
    lines=1;
  }

  if (lines && (int)destroywin) {
    int x,y;
    Window dummy;
    XTranslateCoordinates(display,dialog_win,
			  XDefaultRootWindow(display),
			  0,0,&x,&y,&dummy);   
    Epplet_window_destroy(dialog_win);
    createConfigDialog();
    Epplet_window_show(dialog_win);
    XMoveWindow(display,dialog_win,x,y);
  }

  Epplet_save_config();
}


void okConfig(void *data) {
  applyConfig(NULL);
  Epplet_window_destroy(dialog_win);
  dialog_win=0;
  transp_raise_checkbox=0;
  clean_pixmaps();
}


/**************************
 * cleanup and setup stuff
 **************************/

void clean_pixmaps() {
  if (dialog_button_up) XFreePixmap(display,dialog_button_up);
  if (dialog_button_down) XFreePixmap(display,dialog_button_down);
  if (dialog_button_normal) XFreePixmap(display,dialog_button_normal);
  dialog_button_up=0;
  dialog_button_down=0;
  dialog_button_normal=0;
}


void setup_pixmaps() {
  Pixmap mask;

  clean_pixmaps();
  Epplet_imageclass_get_pixmaps("EPPLET_BUTTON","hilited",
                                &dialog_button_up,&mask,SIZEBAR,dialog_bh);
  if (mask) XFreePixmap(display,mask);
  Epplet_imageclass_get_pixmaps("EPPLET_BUTTON","clicked",
                                &dialog_button_down,&mask,SIZEBAR,dialog_bh);
  if (mask) XFreePixmap(display,mask);
  Epplet_imageclass_get_pixmaps("EPPLET_BUTTON","normal",
                                &dialog_button_normal,&mask,SIZEBAR,dialog_bh);
  if (mask) XFreePixmap(display,mask);
}


void fill_textbox(Epplet_gadget tb,int val,int multip16) {
  char buf[16];
  if (multip16 && val%16==0)
    Esnprintf(buf,16,"*%d",val/16);
  else
    Esnprintf(buf,16,"%d",val);
  Epplet_change_textbox(tb,buf);
}


void createConfigDialog() {
  int bh=list_lines*button_h;
  int yoffs=bh+2*OFFSY+4;

  dialog_bheight=bh;
  bh=(bh*bh)/(cols*rows*button_h);
  if (bh>dialog_bheight) bh=dialog_bheight;
  dialog_by=OFFSY+2;
  dialog_bh=bh;
  dialog_maxy=OFFSY+2+dialog_bheight-bh;
  dialog_pressy=-1;
  dialog_offset_view=0;
  dialog_maxoffset=(cols*rows-list_lines)*button_h;
  if (dialog_maxoffset<0) dialog_maxoffset=0;
  dialog_in_button=0;
  selected=-1;
  hilited=-1;

  dialog_win=Epplet_create_window_config(CONFIG_WIDTH+button_w,yoffs+200+OFFSY,
					 "E-FancyLauncher Configuration",
					 okConfig,NULL,
					 applyConfig,(void*)1,
					 NULL,NULL);

  dialog_area=Epplet_create_drawingarea(OFFSX,OFFSY,VIEWW+4+button_w,dialog_bheight+4);
  dialog_area_window=Epplet_get_drawingarea_window(dialog_area);  
  Epplet_gadget_show(dialog_area); 
  setup_pixmaps();

  Epplet_gadget_show(Epplet_create_label(OFFSX,yoffs,"Pixmap:",FONT_SIZE));
  Epplet_gadget_show(dialog_pixmap=Epplet_create_textbox(NULL,NULL,OFFSX+70,yoffs-2,
							 CONFIG_WIDTH-2*OFFSX-70-SIZEBAR+button_w,20,
							 FONT_SIZE,enter_pixmap,(void*)1));
  yoffs+=OFFSY+20;
  Epplet_gadget_show(Epplet_create_label(OFFSX,yoffs,"Command:",FONT_SIZE));
  Epplet_gadget_show(dialog_command=Epplet_create_textbox(NULL,NULL,OFFSX+70,yoffs-2,
							  CONFIG_WIDTH-2*OFFSX-70-SIZEBAR+button_w,20,
							  FONT_SIZE,enter_command,(void*)1));
  yoffs+=OFFSY+20;
  Epplet_gadget_show(Epplet_create_label(OFFSX,yoffs,"Tooltip:",2));
  Epplet_gadget_show(dialog_tooltip=Epplet_create_textbox(NULL,NULL,OFFSX+70,yoffs-2,
							  CONFIG_WIDTH-2*OFFSX-70-SIZEBAR+button_w,20,
							  FONT_SIZE,enter_tooltip,(void*)1));
  yoffs+=OFFSY+20;
  Epplet_gadget_show(Epplet_create_label(OFFSX,yoffs,"Epplet width:",FONT_SIZE));
  Epplet_gadget_show(dialog_epplet_width=Epplet_create_textbox(NULL,NULL,OFFSX+90,yoffs-2,
							       30,20,FONT_SIZE,enter_width,NULL));
  fill_textbox(dialog_epplet_width,epplet_width,1);
  Epplet_gadget_show(Epplet_create_label(OFFSX+130,yoffs,"Epplet height:",FONT_SIZE));
  Epplet_gadget_show(dialog_epplet_height=Epplet_create_textbox(NULL,NULL,OFFSX+224,yoffs-2,
								30,20,FONT_SIZE,enter_height,NULL));
  fill_textbox(dialog_epplet_height,epplet_height,1);
  Epplet_gadget_show(Epplet_create_togglebutton("Transparency",NULL,300,yoffs-2,
						100,20,&transparent,checkflags,(void*)1));
  yoffs+=OFFSY+20;
  Epplet_gadget_show(Epplet_create_label(OFFSX,yoffs,"Columns:",FONT_SIZE));
  Epplet_gadget_show(dialog_epplet_cols=Epplet_create_textbox(NULL,NULL,OFFSX+90,yoffs-2,
							      30,20,FONT_SIZE,enter_height,NULL));
  fill_textbox(dialog_epplet_cols,cols,0);
  Epplet_gadget_show(Epplet_create_label(OFFSX+130,yoffs,"Rows:",FONT_SIZE));
  Epplet_gadget_show(dialog_epplet_rows=Epplet_create_textbox(NULL,NULL,OFFSX+224,yoffs-2,
							      30,20,FONT_SIZE,enter_width,NULL));
  fill_textbox(dialog_epplet_rows,rows,0);
  Epplet_gadget_show(Epplet_create_togglebutton("Raise buttons",NULL,300,yoffs-2,
						100,20,&raiseable,checkflags,(void*)1));
  yoffs+=OFFSY+20;
  Epplet_gadget_show(Epplet_create_label(OFFSX,yoffs,"List height:",FONT_SIZE));
  Epplet_gadget_show(dialog_epplet_lines=Epplet_create_textbox(NULL,NULL,OFFSX+90,yoffs-2,
							       30,20,FONT_SIZE,NULL,NULL));
  fill_textbox(dialog_epplet_lines,list_lines,0);
  Epplet_gadget_show(Epplet_create_label(OFFSX+130,yoffs,"Inner border:",FONT_SIZE));
  Epplet_gadget_show(dialog_epplet_border=Epplet_create_textbox(NULL,NULL,OFFSX+224,yoffs-2,
								30,20,FONT_SIZE,new_icon_border,NULL));
  fill_textbox(dialog_epplet_border,icon_border,0);
  Epplet_gadget_show(Epplet_create_togglebutton("Tooltips",NULL,300,yoffs-2,
						100,20,&tooltips,checkflags,NULL));
  yoffs+=OFFSY+20;
  Epplet_gadget_show(transp_raise_checkbox=Epplet_create_togglebutton("Experimental transparent raise",NULL,300,yoffs-2,
								      150,20,&transp_raise,checkflags,(void*)1));
  Epplet_window_pop_context();
}


void free_buttons() {
  if (buttons) {
    int n=rows*cols*3;
    LButton *button=buttons;
    while(n>0) {
      /*printf("free %d\n %s\n %s\n %s\n",
	rows*cols*3-n,button->pixmap,
	button->command,button->tooltip);*/
      if (button->pixmap) free(button->pixmap);
      if (button->command) free(button->command);
      if (button->tooltip) free(button->tooltip);
      button++;
      n--;
    }
    free(buttons);
    buttons=NULL;
  }
}


void cleanup() {
  if (gc) XFreeGC(display,gc);
  if (winmap) XFreePixmap(display,winmap);
  free_buttons();
  if (dialog_button_up) XFreePixmap(display,dialog_button_up);
  if (dialog_button_normal) XFreePixmap(display,dialog_button_normal);
  if (dialog_button_down) XFreePixmap(display,dialog_button_down);
  Epplet_cleanup();
}


void update_dim() {
  button_w=epplet_width/cols;
  button_h=epplet_height/rows;
  button_rect.x=0;
  button_rect.y=0;
  button_rect.width=button_w;
  button_rect.height=button_h;
  shape_button.x=icon_border;
  shape_button.y=icon_border;
  shape_button.width=button_w-2*icon_border;
  shape_button.height=button_h-2*icon_border;
}


void setup_buttons() {
  int n,p;
  char *val;
  char buf[20];
  LButton* button=buttons=(LButton*)realloc(buttons,3*cols*rows*sizeof(LButton));
  if (!button) exit(1);
  for(p=0;p<3;p++) {
    n=0;
    for(n=0;n<cols*rows;n++,button++) {
      /* icon */
      Esnprintf(buf,20,"PAGE%d_ICON%d",p,n);
      if (!(val=Epplet_query_config(buf))) {
	if (n<DEF_SIZE_PAGE)
	  Epplet_add_config(buf,val=strdup(DEF_BUTTONS[p][n].pixmap));
      } else if (!strcmp(val,"-"))
	val=NULL;
      else val=strdup(val);
      button->pixmap=val;
      /* printf("PAGE%d_ICON%d %s\n",p,n,val); */

      /* command */
      Esnprintf(buf,20,"PAGE%d_PROG%d",p,n);
      if (!(val=Epplet_query_config(buf))) {
	if (n<DEF_SIZE_PAGE)
	  Epplet_add_config(buf,val=strdup(DEF_BUTTONS[p][n].command));
      } else if (!strcmp(val,"-"))
	val=NULL;
      else val=strdup(val);
      button->command=val;
      /* printf("PAGE%d_PROG%d %s\n",p,n,val); */

      /* tooltip */
      Esnprintf(buf,20,"PAGE%d_TOOLTIP%d",p,n);
      if (!(val=Epplet_query_config(buf))) {
	if (n<DEF_SIZE_PAGE)
	  Epplet_add_config(buf,val=strdup(DEF_BUTTONS[p][n].tooltip));
      } else if (!strcmp(val,"-"))
	val=NULL;
      else val=strdup(val);
      button->tooltip=val;
      /* printf("PAGE%d_TOOLTIP%d %s\n",p,n,val); */
    }
  }
}


void setup_epplet() {
  char buf[20];
  int p,n;
  char *val;
  XGCValues gcval;

  /* hello marker */
  if (!Epplet_query_config(HELLO)) {
    show_about();
    Epplet_add_config(HELLO,HELLO_TXT);
  }
  
  /* width */
  epplet_width=0;
  p=0;
  val=Epplet_query_config(WIDTH_NAME);
  if (val) {
    n=(*val=='*');
    epplet_width=atoi(val+n);
    if (n) epplet_width*=16;
  } else if ((val=Epplet_query_config(OLD_WIDTH))!=NULL) {
    /* keep compatibility with 0.1 */
    epplet_width=16*atoi(val);
    p=1;
  }
  if (epplet_width<16 || epplet_width>1600) {
    epplet_width=DEF_WIDTH;
    Epplet_modify_config(WIDTH_NAME,DEFAULT_WIDTH);
  }
  if (p) {
    /* convert format to 0.2 */
    Esnprintf(buf,20,"*%d",epplet_width/16);
    Epplet_add_config(WIDTH_NAME,buf);
  }
  /* printf(WIDTH_NAME" %d\n",epplet_width); */
  
  /* height */
  epplet_height=0;
  p=0;
  val=Epplet_query_config(HEIGHT_NAME);
  if (val) {
    n=(*val=='*');
    epplet_height=atoi(val+n);
    if (n) epplet_height*=16;
  } else if ((val=Epplet_query_config(OLD_HEIGHT))!=NULL) {
    /* keep compatibility with 0.1 */
    epplet_height=16*atoi(val);
    p=1;
  }
  if (epplet_height<16 || epplet_height>1200) {
    epplet_height=DEF_HEIGHT;
    Epplet_modify_config(HEIGHT_NAME,DEFAULT_HEIGHT);
  }
  if (p) {
    /* convert format to 0.2 */
    Esnprintf(buf,20,"*%d",epplet_height/16);
    Epplet_add_config(HEIGHT_NAME,buf);
  }
  /* printf(HEIGHT_NAME" %d\n",epplet_height); */
  
  /* columns */
  cols=atoi(Epplet_query_config_def(COLS_NAME,COLS_DEFAULT));
  if (cols<1 || cols>epplet_width/12) cols=epplet_width/12;
  Esnprintf(buf,20,"%d",cols);
  Epplet_modify_config(COLS_NAME,buf);
  /* printf(COLS_NAME" %d\n",cols); */

  /* rows */
  rows=atoi(Epplet_query_config_def(ROWS_NAME,ROWS_DEFAULT));
  if (rows<1 || rows>epplet_height/12) rows=epplet_height/12;
  Esnprintf(buf,20,"%d",rows);
  Epplet_modify_config(ROWS_NAME,buf);
  /* printf(ROWS_NAME" %d\n",rows); */

  /* list height */
  list_lines=atoi(Epplet_query_config_def(LIST_HEIGHT_NAME,LIST_HEIGHT_DEFAULT));
  if (list_lines<3 || list_lines>64) list_lines=LIST_HEIGHT_DEF;
  Esnprintf(buf,20,"%d",list_lines);
  Epplet_modify_config(LIST_HEIGHT_NAME,buf);

  update_dim();

  /* transparency */
  val=Epplet_query_config(TRANSPARENT_NAME);
  if (val) transparent=atoi(val);
  else {
    transparent=TRANSPARENT_DEF;
    Epplet_add_config(TRANSPARENT_NAME,TRANSPARENT_DEFAULT);
  }

  val=Epplet_query_config(TRANSP_RAISE_NAME);
  if (val) transp_raise=atoi(val);
  else {
    transp_raise=TRANSP_RAISE_DEF;
    Epplet_add_config(TRANSP_RAISE_NAME,TRANSP_RAISE_DEFAULT);
  }

  /* raise buttons */
  val=Epplet_query_config(RAISE_NAME);
  if (val) raiseable=atoi(val);
  else {
    raiseable=RAISE_DEF;
    Epplet_add_config(RAISE_NAME,RAISE_DEFAULT);
  }

  /* icon border */
  val=Epplet_query_config(ICON_BORDER);
  if (val) {
    icon_border=atoi(val);
    if (icon_border>button_w/2 || icon_border>button_h/2) {
      Epplet_modify_config(ICON_BORDER,ICON_BORDER_DEFAULT);
      icon_border=ICON_BORDER_DEF;
    }
  } else {
    Epplet_add_config(ICON_BORDER,ICON_BORDER_DEFAULT);
    icon_border=ICON_BORDER_DEF;
  }

  /* tooltips */
  val=Epplet_query_config(TOOLTIPS_NAME);
  if (val) tooltips=atoi(val);
  else {
    tooltips=TOOLTIPS_DEF;
    Epplet_add_config(TOOLTIPS_NAME,TOOLTIPS_DEFAULT);
  }
  buttons=NULL;
  setup_buttons();

  current_page=0;
  current_col=-1;
  current_row=-1;
  dialog_win=0;
  transp_raise_checkbox=0;
  checkflags(NULL);
  dialog_button_up=0;
  dialog_button_down=0;
  dialog_button_normal=0;
  display=Epplet_get_display();
  window=Epplet_get_main_window();
  imlibdata=Epplet_get_imlib_data();
  Epplet_textclass_get_size("EPPLET_TEXT_LARGE",&p,&font_h,"W");
  gcval.function=GXcopy;
  gcval.subwindow_mode=IncludeInferiors;
  gc=XCreateGC(display,window,GCFunction|GCSubwindowMode,&gcval);
  XResizeWindow(display,window,epplet_width,epplet_height);
  exchange_cursor=XCreateFontCursor(display,XC_exchange);
  fill_page();
}


/*************************************
 * low level GUI functions and events
 *************************************/

void checkButtonPixmap(int x,int y,int flushpix) {
  Pixmap map=0;
  int inside=(x>=BUTX+button_w && x<=BUTX+SIZEBAR+button_w
	      && y>=dialog_by && y<=dialog_by+dialog_bh);
  if (inside && (flushpix || !dialog_in_button)) {
    dialog_in_button=1;
    map=dialog_button_up;
  }
  else if (!inside && (flushpix || dialog_in_button)) {
    dialog_in_button=0;
    map=dialog_button_normal;
  }
  if (map)
    XCopyArea(display,map,dialog_win,gc,
	      0,0,SIZEBAR,dialog_bh,BUTX+button_w,dialog_by);
}


void mouse_motion(void *data, Window win, int x, int y) {
  if (win==window) {
    x/=button_w;
    y/=button_h;
    /* printf("motion %d,%d\n",x,y); */
    if (x<cols && y<rows && (x!=current_col || y!=current_row)) {
      LButton *button=buttons+current_page*rows*cols+y*cols+x;
      /* printf("change pix old(%d) new(%s)\n",(current_col>=0),button->pixmap); */
      if (current_col>=0)
	button_normal(current_col*button_w,current_row*button_h,
		      buttons[current_page*rows*cols+current_row*cols+current_col].pixmap);
      
      current_col=x;
      current_row=y;
      if (button->pixmap && button->command)
	button_up(x*button_w,y*button_h,button->pixmap);
      
      Epplet_gadget_hide(tooltip_popup);
      if (button->tooltip) {
	Window dw,rootwindow=XDefaultRootWindow(display);
	int w,h,tw,th,px,py;
	unsigned int rw,rh;
	XGetGeometry(display,rootwindow,&dw,&tw,&th,&rw,&rh,&tw,&th);
	XGetGeometry(display,window,&dw,&tw,&th,&w,&h,&tw,&th);
	XTranslateCoordinates(display,window,rootwindow,
			      0,0,&px,&py,&dw);
	/* epplet: px,py w,h */
	/* root: rw,rh */
	if (tooltips) {
	  Epplet_remove_popup_entry(tooltip_popup,0);
	  Epplet_add_popup_entry(tooltip_popup,button->tooltip,NULL,NULL,NULL);
	  /* estimate size: tw,th
	   * because Epplet_popup_arrange_contents is not available
	   */
	  Epplet_textclass_get_size("EPPLET_POPUP",&tw,&th,button->tooltip);
	  if (epplet_height>epplet_width) {
	    py+=y*button_h+(button_h-th)/2-3;
	    if (px+w/2>rw/2)
	      px-=tw+20;
	    else
	      px+=w+10;
	  } else {
	    px+=x*button_w;
	    if (py+h/2>rh/2)
	      py-=th+20;
	    else
	      py+=h+10;
	  }
	  Epplet_gadget_move(tooltip_popup,px,py);
	  Epplet_gadget_show(tooltip_popup);
	}
      }
    }
  }
  else if (win==dialog_win) {
    if (dialog_bheight<cols*rows*button_h) {
      if (dialog_pressy>=0) {
	int newoffset,diffy,newy=y-dialog_pressy+dialog_by;
	if (newy<OFFSY+2)
	  newy=OFFSY+2;
	else if (newy>dialog_maxy)
	  newy=dialog_maxy;
	if (newy>OFFSY+2)
	  XClearArea(display,win,BUTX+button_w,OFFSY+2,SIZEBAR,newy-OFFSY-2,False);
	XCopyArea(display,dialog_button_down,win,gc,
		  0,0,SIZEBAR,dialog_bh,BUTX+button_w,newy);
	if (dialog_maxy>newy)
	  XClearArea(display,win,BUTX+button_w,newy+dialog_bh,
		     SIZEBAR,dialog_maxy-newy,False);
	
	newoffset=(dialog_maxoffset*(newy-OFFSY-2))/(dialog_maxy-OFFSY-2);
	diffy=newoffset-dialog_offset_view;
	if (dialog_offset_view!=newoffset) {
	  dialog_offset_view=newoffset;
	  expose_handler(NULL,dialog_area_window,
			 0,0,VIEWW+button_w,dialog_bheight);
	}
	/* it is not possible to just copy/move the background
	 * because of fancy background pixmaps
	 */
      } else
	checkButtonPixmap(x,y,0);
    }
  } else if (win==dialog_area_window) {
    int b=(y+dialog_offset_view)/button_h;
    if (dialog_pressy>=0) { /* drag */
      XDefineCursor(display,win,exchange_cursor);
      if (selected>=0 && b!=selected) {
	char *s;
	int height,start=cols*rows*current_page;
	LButton *b1=buttons+start+b,*b2=buttons+start+selected;
	s=b1->pixmap;  b1->pixmap =b2->pixmap;  b2->pixmap =s;
	s=b1->command; b1->command=b2->command; b2->command=s;
	s=b1->tooltip; b1->tooltip=b2->tooltip; b2->tooltip=s;
	if (b<selected) {
	  start=b;
	  height=selected-b+1;
	} else {
	  start=selected;
	  height=b-selected+1;
	}
	update_config(selected);
	update_config(b);
	selected=b;
	hilited=-1;
	expose_handler(NULL,win,
		       0,button_h*start-dialog_offset_view,
		       VIEWW+button_w,height*button_h);
	fill_page();
      }
    } else { /* motion */
      if (raiseable && b!=hilited) {
	int start,height;
	if (b<hilited) {
	  start=b;
	  height=hilited-b+1;
	} else if (hilited>=0) {
	  start=hilited;
	  height=b-hilited+1;
	} else {
	  start=b;
	  height=1;
	}
	hilited=b;
	expose_handler(NULL,win,
		       0,button_h*start-dialog_offset_view,
		       VIEWW+button_w,height*button_h);
      }
    }
  }
}


void button_pressed(void *data,Window win,int x,int y,int b) {
  if (win==window) { /* epplet button */
    x/=button_w;
    y/=button_h;
    if (x<cols && y<rows) {
      LButton *button=buttons+current_page*rows*cols+y*cols+x;
      if (button->pixmap && button->command)
	button_down(x*button_w,y*button_h,button->pixmap);
    }
  }
  else if (win==dialog_win && dialog_bheight<cols*rows*button_h
	   && ( (x>=BUTX+button_w && x<=BUTX+SIZEBAR+button_w
		 && y>=dialog_by && y<=dialog_by+dialog_bh) )) {
    /* scroll button */
    dialog_pressy=y;
    XCopyArea(display,dialog_button_down,win,gc,
	      0,0,SIZEBAR,dialog_bh,BUTX+button_w,dialog_by);
  } else if (win==dialog_area_window) {
    /* button list */
    if (b<=3) {
      b=(y+dialog_offset_view)/button_h;
      dialog_pressy=y;
      if (b!=selected) {
	y=selected;
	selected=b;
	if (y>=0)
	  expose_handler(NULL,dialog_area_window,
			 0,button_h*y-dialog_offset_view,VIEWW+button_w,button_h);
	expose_handler(NULL,dialog_area_window,
		       0,button_h*b-dialog_offset_view,VIEWW+button_w,button_h);
	update_textboxes();
      }
    } else {
      if (b==5) { /* go down */
	if (dialog_offset_view==dialog_maxoffset) return;
	dialog_offset_view+=button_h;
	if (dialog_offset_view>dialog_maxoffset)
	  dialog_offset_view=dialog_maxoffset;
      } else { /* go up */
	if (dialog_offset_view==0) return;
	dialog_offset_view-=button_h;
	if (dialog_offset_view<0)
	  dialog_offset_view=0;
      }
      dialog_by=OFFSY+2+(dialog_maxoffset>0?
			 ((dialog_maxy-OFFSY-2)*dialog_offset_view)/dialog_maxoffset
			 :0);
      expose_handler(NULL,dialog_win,BUTX+button_w,OFFSY+2,SIZEBAR,dialog_bheight);
      expose_handler(NULL,dialog_area_window,0,0,VIEWW+button_w,dialog_bheight);
    }
  }
}


void button_released(void *data,Window win,int x,int y,int b) {
  if (win==window) { /* epplet button */
    x/=button_w;
    y/=button_h;
    b--;
    if (b==3||b==4) {
      if (b==3) current_page=(current_page+2)%3;
      else current_page=(current_page+1)%3;
      fill_page();
      current_col=-1;
      current_row=-1;
      page_changed();
      mouse_motion(data,win,x*button_w,y*button_h);
    }
    else if (x<cols && y<rows) {
      LButton *button=buttons+current_page*rows*cols+y*cols+x;
      if (button->command) {
	if (button->pixmap)
	  button_up(x*button_w,y*button_h,button->pixmap);
	/* *button_w and button_h to get beginning of buttons */

	if (!strcmp(button->command,SWITCH_PAGE)) {
	  if (b==current_page) return;
	  current_page=b; /* I forgot this for 0.5 !!!! copy paste you know... */

	  if (current_page<0) { current_page=0; return; }
	  else if (current_page>2) { current_page=2; return; }

	  fill_page();
	  current_col=-1;
	  current_row=-1;
	  page_changed();
	  mouse_motion(data,win,x*button_w,y*button_h);

	} else if (!strcmp(button->command,QUIT))
	  exit(0);
	else if (!strcmp(button->command,INFO))
	  show_about();
	else if (!strcmp(button->command,CONFIG)) {
	  if (dialog_win)
	    Epplet_window_destroy(dialog_win);
	  createConfigDialog();
	  Epplet_window_show(dialog_win);
	} else {
	  if (*button->command!='!')
	    Epplet_spawn_command(button->command);
	  else if (Epplet_run_command(&button->command[1]))
	    failed_command(&button->command[1]);
	}
      } 
    }
  }
  else {
    if (win==dialog_win && dialog_bheight<cols*rows*button_h) {
      /* button scroll */
      int newy=y-dialog_pressy+dialog_by;
      if (newy<OFFSY+2)
	newy=OFFSY+2;
      else if (newy>dialog_maxy)
	newy=dialog_maxy;
      if (newy>OFFSY+2)
	XClearArea(display,win,BUTX+button_w,OFFSY+2,SIZEBAR,newy-OFFSY-2,False);
      dialog_by=newy;
      checkButtonPixmap(x,y,1);
      if (dialog_maxy>newy)
	XClearArea(display,win,BUTX+button_w,newy+dialog_bh,
		   SIZEBAR,dialog_maxy-newy,False);
    } else if (win==dialog_area_window) /* button list */
      XUndefineCursor(display,win);
    dialog_pressy=-1;
  }
}


void mouse_leave(void *data, Window win) {
  if (win==window) { /* epplet -> normal button + hide tooltip */
    if (current_col>=0) {
      button_normal(current_col*button_w,current_row*button_h,
		    buttons[current_page*rows*cols+current_row*cols+current_col].pixmap);
      current_col=-1;
      current_row=-1;
    }
    Epplet_gadget_hide(tooltip_popup);
  } else if (win==dialog_area_window && hilited>=0) {
    /* button list -> normal button */
    int  y=button_h*hilited-dialog_offset_view;
    hilited=-1;
    expose_handler(NULL,dialog_area_window,
		   0,y,VIEWW+button_w,button_h);
  } else if (win==dialog_win) { /* dialog -> check for the scroll button */
    checkButtonPixmap(0,0,0);
  }
}


void key_released(void *data,Window win,char *key) {
  if (win==window) {
    /* printf("%s\n",key); */
    if (!strcmp(key,"Escape"))
      exit(0); /* cleanup is done automatically */
    else if (!strcmp(key,"F1"))
      show_about();
    else if (!strcmp(key,"F2")) {
      if (dialog_win) /* because theme may change */
	Epplet_window_destroy(dialog_win);
      createConfigDialog();
      Epplet_window_show(dialog_win);
    }
  }
  if ((win==window || win==dialog_area_window) &&
      (!strcmp(key,"1") || !strcmp(key,"2") || !strcmp(key,"3"))) {
    int p=*key-'1';
    if (p!=current_page) {
      current_page=p;
      fill_page();
      page_changed();
      if (current_col>=0) {
	int x=current_col*button_w;
	int y=current_row*button_h;
	current_col=-1;
	current_row=-1;
	mouse_motion(data,win,x,y);
      }
    }
  }
}


/* cast to work around the bug in Epplets-0.5,
 * one more hack ;) see Epplet_register_event_handler
 * in main
 */
typedef void (*bad_event_cb)(void*,XEvent *);
/* this is the temporary theme change fix */
void update_epplet(void *data) {
  int dummy;
  Epplet_textclass_get_size("EPPLET_TEXT_LARGE",&dummy,&font_h,"W");
  setup_pixmaps();
  fill_page();
}
void event_handler(XEvent *ev,void *data) {
  if (ev->type==ReparentNotify) {
    /* this is the only acceptable alternative I've found */
    Epplet_timer(update_epplet,NULL,1.0,"update");
    /* it is not possible, or very tedious
     * to filter correctly the PropertyNotify
     * we need to use too much of the private
     * functions of Epplets-0.5
     */
  }
}


/***** The main dish *******/

int main(int argc,char *argv[]) {
  /* renice avoided: applications inherit this!
     setpriority(PRIO_PROCESS,getpid(),getpriority(PRIO_PROCESS,getpid())+10);
  */

  /* let's start */
  Epplet_Init("E-FancyLauncher",VERSION,"Enlightenment Fancy Launcher",
	      1,1,argc,argv,1); /* init like this and resize later (only in larger) */
  /* the vertical/horizontal background is reset later anyway */

  /* exit -> cleanup */
  atexit(cleanup);
  /* signals -> exit ... and cleanup done for free */
  signal(SIGINT,exit);
  signal(SIGTERM,exit);

  tooltip_popup=Epplet_create_popup();
  Epplet_add_popup_entry(tooltip_popup,SWITCH_PAGE,NULL,NULL,NULL);
  Epplet_load_config();
  Epplet_show();
  setup_epplet();
  Epplet_save_config();
  Esync();
  Epplet_register_mouse_leave_handler(mouse_leave,NULL);
  Epplet_register_mouse_motion_handler(mouse_motion,NULL);
  Epplet_register_key_release_handler(key_released,NULL);
  Epplet_register_button_press_handler(button_pressed,NULL);
  Epplet_register_button_release_handler(button_released,NULL);
  Epplet_register_event_handler((bad_event_cb)event_handler,NULL);
  Epplet_register_expose_handler(expose_handler,NULL);
  Epplet_register_move_resize_handler(move_handler,NULL);
  Epplet_Loop();
  return 0;
}




