/**************************************************************************
   printoxx - arrange images and text in a layout, print layout

   Free software licensed under GNU General Public License v.2
   source URL:  kornelix.squarespace.com
***************************************************************************/

#include "zfuncs.h"

#define fversion "printoxx v.2.0.2  2009.07.01"                            //  version and date
#define flicense "Free software - GNU General Public License v.2"
#define fsource "http://kornelix.squarespace.com/printoxx"

#define bilinear GDK_INTERP_BILINEAR                                       //  GDK shortcuts
#define nodither GDK_RGB_DITHER_NONE,0,0
#define colorspace GDK_COLORSPACE_RGB
#define pixbuf_scale gdk_pixbuf_scale_simple
#define maxtext 9999

GtkWidget   *mainWin, *drawWin, *mVbox;                                    //  main and drawing window
GdkPixbuf   *pxb_layout = 0;                                               //  print layout pixbuf
GdkPixbuf   *pxb_drawwin = 0;                                              //  drawing window pixbuf
GdkGC       *gdkgc = 0;                                                    //  graphics context
GError      **gerror = 0;                                                  //  pixbuf error code
int         wwD = 800, hhD = 600;                                          //  main window initial size
typedef     uchar *pixel;                                                  //  3 RGB values, 0-255 each

zdialog     *zdtext = 0;                                                   //  active text dialog
zdialog     *zdrotate = 0;                                                 //  active rotate dialog
zdialog     *zdframe = 0;                                                  //  active frame dialog

char        clfile[maxfcc] = "";                                           //  command line image file
char        imagefile[maxfcc] = "";                                        //  current image file
char        imagedirk[maxfcc] = "";                                        //  current image file directory

//  page layout data and default settings
int         layout_ww, layout_hh;                                          //  layout pixel dimensions
int         layout_pix = 400;                                              //  default image size in layout
int         layout_select = -1;                                            //  last selected image/text index
double      Rscale = 0.5;                                                  //  layout to drawing window
char        pendfile[maxfcc] = "";                                         //  pending image, add to layout
char        pendtext[maxtext+1] = "";                                      //  pending text, add to layout
char        textfont[60] = "sans 40";                                      //  layout text default font
int         textbw = 'b';                                                  //  default text: black on white
int         transp = 0;                                                    //  text background is transparent
int         framepix[3] = { 150, 150, 150 };                               //  default frame color       v.2.0
int         framethick = 8;                                                //  default frame thickness   v.2.0

struct      playout {                                                      //  image and text members of layout
   int         type;                                                       //  1: image image, 2: text image
   int         textbw;                                                     //  text color, 'b' or 'w'
   int         transp;                                                     //  background is transparent
   char        *filespec;                                                  //  image image filespec
   char        *textspec;                                                  //  text image text content
   GdkPixbuf   *pixbuf1;                                                   //  image pixbuf, full size
   GdkPixbuf   *pixbuf2;                                                   //  image pixbuf, rscale size
   int         ww, hh;                                                     //  full image dimensions
   int         xpos, ypos;                                                 //  position in layout
   double      rscale;                                                     //  scale factor, image to layout
   double      angle;                                                      //  rotation, degrees CW      v.2.0
   int         framepix[3];                                                //  frame color               v.2.0
   int         framethick;                                                 //  frame thickness           v.2.0
};
playout     layout[200];                                                   //  layout for all images + text
int         maxNF = 200;                                                   //  max. images + text in layout
int         NPL = 0;                                                       //  current layout count

//  default printer setup
char     printer[60] = "default";                                          //  printer name
char     jobname[60] = "printoxx";                                         //  print job name
char     paper[40] = "A4  21.0 x 29.7 cm";                                 //  paper format and dimensions
char     orientation[20] = "portrait";                                     //  portrait / landscape
double   paperwidth, paperheight;                                          //  paper size, cm

//  known paper types and sizes                                            //  v.1.4
int         nptab = 7;
int         defptab = 1;                                                   //  default is ptab[1] = A4
const char  *ptab[7] = { "letter  21.6 x 27.9 cm" ,
                         "A4  21.0 x 29.7 cm" ,
                         "A5  14.8 x 21.0 cm" ,
                         "A6  10.5 x 14.8 cm" ,
                         "A7  7.4 x 10.5 cm" ,
                         "A8  5.2 x 7.4 cm" ,
                         "A9  3.7 x 5.2 cm"  };
   
//  GTK functions
int   gtkinitfunc(void *data);                                             //  GTK initz. function
void  mwpaint();                                                           //  window repaint - expose event
void  mwpaint2();                                                          //  window repaint - application call
void  destroy();                                                           //  main window destroy signal
void  mouse_event(GtkWidget *, GdkEventButton *, void *);                  //  mouse event function
int   KBpress(GtkWidget *, GdkEventKey *, void *);                         //  KB key press event
int   KBrelease(GtkWidget *, GdkEventKey *, void *);                       //  KB key release event
void  drag_drop(int x, int y, char *text);                                 //  text drag/drop event   v.2.0.2
void  menufunc(GtkWidget *, const char *menu);                             //  menu function, main window

//  toolbar button functions
void  m_index();                                                           //  show thumbnail index window
void  m_image();                                                           //  add images to layout
void  m_text();                                                            //  add text to layout
void  m_rotate();                                                          //  rotate image
void  m_frame();                                                           //  edit image frame
void  m_save();                                                            //  save layout to file
void  m_setup();                                                           //  print setup
void  m_print();                                                           //  print finished layout 
void  m_help();                                                            //  show user guide
void  m_quit();                                                            //  quit

void  init_layout();                                                       //  page layout from paper size
void  set_pendfile(char *file);                                            //  set pending file to be added
void  add_layout_image(int mpx, int mpy);                                  //  add pending image to layout
void  add_layout_text(int mpx, int mpy);                                   //  add pending text to layout
void  save_imagedirk();                                                    //  save for next startup
void  load_imagedirk();                                                    //  load saved image directory

GdkPixbuf * make_layout(int final);                                        //  make layout pixbuf
GdkPixbuf * load_pixbuf(const char *file);                                 //  load pixbuf from file, remove alpha
GdkPixbuf * pixbuf_text(GtkWidget *, cchar *text, cchar *font, int bw);    //  create pixbuf containing text
void pixbuf_copy_alpha(GdkPixbuf *pxb1, int x1, int y1, int ww, int hh,    //  copy pixbuf with transparent color 
                       GdkPixbuf *pxb2, int x2, int y2, int rgb, int ff);
GdkPixbuf * pixbuf_add_frame(GdkPixbuf *pixbuf1, int *rgb, int thick);     //  add frame around a pixbuf


/**************************************************************************/

//  main program

int main(int argc, char *argv[])
{
   GtkWidget   *mtbar;
   char        lang[4] = "";

   printf(fversion "\n");                                                  //  print version
   if (argc > 1 && strEqu(argv[1],"-v")) return 0;

   gtk_init(&argc,&argv);                                                  //  GTK command line options
   initz_appfiles("printoxx",null);                                        //  get app directories
   zlockInit();

   for (int ii = 1; ii < argc; ii++)                                       //  command line options
   {
      if (strEqu(argv[ii],"-l") && argc > ii+1)                            //  -l language code
            strncpy0(lang,argv[++ii],3);
      else if (strEqu(argv[ii],"-i") && argc > ii+1)                       //  -i imageDirectory
            strcpy(imagedirk,argv[++ii]);
      else if (strEqu(argv[ii],"-f") && argc > ii+1)                       //  -f imageFile
            strcpy(clfile,argv[++ii]);
      else strcpy(clfile,argv[ii]);                                        //  assume imageFile
   }
   
   ZTXinit(lang);                                                          //  setup translations

   mainWin = gtk_window_new(GTK_WINDOW_TOPLEVEL);                          //  create main window
   gtk_window_set_title(GTK_WINDOW(mainWin),fversion);
   gtk_window_set_position(GTK_WINDOW(mainWin),GTK_WIN_POS_CENTER);
   gtk_window_set_default_size(GTK_WINDOW(mainWin),wwD,hhD);

   mVbox = gtk_vbox_new(0,0);                                              //  add vert. packing box
   gtk_container_add(GTK_CONTAINER(mainWin),mVbox);

   mtbar = create_toolbar(mVbox,24);                                       //  add toolbar and buttons  
   add_toolbar_button(mtbar,ZTX("index"),ZTX("thumbnail index"),"index.png",menufunc);
   add_toolbar_button(mtbar,ZTX("+image"),ZTX("add image"),"image.png",menufunc);
   add_toolbar_button(mtbar,ZTX("+text"),ZTX("add text"),"text.png",menufunc);
   add_toolbar_button(mtbar,ZTX("rotate"),ZTX("rotate image"),"rotate.png",menufunc);
   add_toolbar_button(mtbar,ZTX("frame"),ZTX("edit frame"),"frame.png",menufunc);
   add_toolbar_button(mtbar,ZTX("toolbar::save"),ZTX("save layout to file"),"gtk-save",menufunc);
   add_toolbar_button(mtbar,ZTX("setup"),ZTX("print setup"),"print.png",menufunc);
   add_toolbar_button(mtbar,ZTX("print"),ZTX("print page"),"print.png",menufunc);
   add_toolbar_button(mtbar,ZTX("quit"),ZTX("quit printoxx"),"gtk-quit",menufunc);
   add_toolbar_button(mtbar,ZTX("help"),ZTX("help"),"gtk-help",menufunc);

   drawWin = gtk_drawing_area_new();                                       //  drawing window
   gtk_container_add(GTK_CONTAINER(mVbox),drawWin);                        //  add to main window

   G_SIGNAL(mainWin,"destroy",destroy,0)                                   //  connect events to main window
   G_SIGNAL(drawWin,"expose-event",mwpaint,0)

   G_SIGNAL(mainWin,"key-press-event",KBpress,0)                           //  connect KB events
   G_SIGNAL(mainWin,"key-release-event",KBrelease,0)

   gtk_widget_add_events(drawWin,GDK_BUTTON_PRESS_MASK);                   //  connect mouse events
   gtk_widget_add_events(drawWin,GDK_BUTTON_RELEASE_MASK);
   gtk_widget_add_events(drawWin,GDK_BUTTON_MOTION_MASK);
   gtk_widget_add_events(drawWin,GDK_POINTER_MOTION_MASK);
   G_SIGNAL(drawWin,"button-press-event",mouse_event,0)
   G_SIGNAL(drawWin,"button-release-event",mouse_event,0)
   G_SIGNAL(drawWin,"motion-notify-event",mouse_event,0)

   drag_drop_connect(drawWin,drag_drop);                                   //  connect drag-drop event  v.2.0.2
   
   gtk_widget_show_all(mainWin);                                           //  show all widgets
   gdkgc = gdk_gc_new(drawWin->window);                                    //  initz. graphics context
   
   gtk_init_add((GtkFunction) gtkinitfunc,0);                              //  initz. function
   gtk_main();                                                             //  process window events

   return 0;
}


//  initial function called from gtk_main() at startup

int gtkinitfunc(void * data)
{
   char        *ppv, clfile2[maxfcc], file[200];
   const char  *pp;
   int         ii, ignore;
   FILE        *fid;

   snprintf(file,200,"%s/print-setup",get_zuserdir());                     //  get prior printer setups
   fid = fopen(file,"r");
   if (fid) {
      ignore = fscanf(fid,"printer %s ",printer);
      ignore = fscanf(fid,"jobname %s ",jobname);
      ignore = fscanf(fid,"paper   %[ -z] ",paper);
      ignore = fscanf(fid,"orientation %s ",orientation);
      fclose(fid);
   }
   strTrim(paper);
   
   for (ii = 0; ii < nptab; ii++)                                          //  match paper type with table
      if (strEqu(paper,ptab[ii])) break;
   if (ii == nptab) ii = defptab;                                          //  not found, use default
   strcpy(paper,ptab[ii]);
   pp = strField(paper,' ',2);
   paperwidth = atoi(pp);
   pp = strField(paper,' ',4);
   paperheight = atoi(pp);
   
   if (strNeq(orientation,"portrait") &&                                   //  check/set orientation
       strNeq(orientation,"landscape"))
         strcpy(orientation,"portrait");
   
   init_layout();                                                          //  set up page layout pixbuf
   
   if (! *imagedirk) load_imagedirk();                                     //  recover image directory   v.1.3

   if (*clfile) {                                                          //  command line image file
      if (*clfile != '/') {
         strcpy(clfile2,imagedirk);                                        //  clfile relative to imagedirk
         strcat(clfile2,"/");
         strcat(clfile2,clfile);
      }
      else { 
         strcpy(clfile2,clfile);                                           //  clfile is full pathspec
         strcpy(imagedirk,clfile);
         ppv = (char *) strrchr(imagedirk,'/');
         if (ppv) *ppv = 0;
      }

      set_pendfile(clfile2);                                               //  check if valid image file
      if (*pendfile) add_layout_image(100,100);                            //  add to layout, upper left corner
   }

   return 0;
}


//  paint window when created, exposed, resized

void mwpaint()
{
   GdkPixbuf   *pxbM;
   GdkDrawable *Lwin = drawWin->window;
   int         ww, hh;
   double      scalew, scaleh;
   
   if (pxb_layout) g_object_unref(pxb_layout);                             //  (re)make editing layout
   pxb_layout = make_layout(0);

   wwD = drawWin->allocation.width;                                        //  current drawing window size
   hhD = drawWin->allocation.height;

   scalew = 1.0 * wwD / layout_ww;                                         //  scale layout to window
   scaleh = 1.0 * hhD / layout_hh;
   if (scalew < scaleh) Rscale = scalew;
   else Rscale = scaleh;
   ww = int(Rscale * layout_ww + 0.5);
   hh = int(Rscale * layout_hh + 0.5);
   pxbM = pixbuf_scale(pxb_layout,ww,hh,bilinear);
   if (! pxbM) zappcrash("cannot create pixbuf");

   gdk_draw_pixbuf(Lwin,0,pxbM,0,0,0,0,-1,-1,nodither);
   g_object_unref(pxbM);

   return;
}


//  invalidate window - cause repaint for application changes

void mwpaint2()
{
   gdk_window_invalidate_rect(drawWin->window,0,1);                        //  invalidate whole window   v.1.8
   return;
}


//  main window destroy signal

void destroy()
{
   printf("main window destroyed \n");
   save_imagedirk();
   gtk_main_quit();   
   return;
}


//  mouse event function - capture buttons and drag movements

void mouse_event(GtkWidget *, GdkEventButton *event, void *)
{
   static int     bdtime = 0, butime = 0;
   static int     Lmouse = 0, Rmouse = 0;
   static int     Fclick = 0, Fselect = 0, Fcorner = 0;
   static int     elapsed, mpx0 = 0, mpy0 = 0;
   static int     ii, kk, ww, hh;
   int            mpx, mpy, dx = 0, dy = 0;
   int            x1, y1, x2, y2;
   double         rscale;
   playout        templayout;

   mpx = int(event->x);                                                    //  mouse position in window
   mpy = int(event->y);
   mpx = int(mpx / Rscale + 0.5);                                          //  in print layout
   mpy = int(mpy / Rscale + 0.5);

   if (event->type == GDK_BUTTON_PRESS)
   {
      Lmouse = Rmouse = 0;
      if (event->button == 1) Lmouse++;                                    //  left or right mouse button
      if (event->button == 3) Rmouse++;
      bdtime = event->time;
      Fselect = Fcorner = 0;
      layout_select = -1;                                                  //  set no current item    v.2.0

      for (ii = NPL-1; ii >= 0; ii--)                                      //  search from top image down
      {
         rscale = layout[ii].rscale;
         ww = gdk_pixbuf_get_width(layout[ii].pixbuf2);                    //  cursor inside an image?  v.2.0
         hh = gdk_pixbuf_get_height(layout[ii].pixbuf2);
         if (mpx < layout[ii].xpos) continue;
         if (mpx > layout[ii].xpos + ww) continue;
         if (mpy < layout[ii].ypos) continue;
         if (mpy > layout[ii].ypos + hh) continue;
         break;
      }
      if (ii >= 0) {                                                       //  yes, iith image/text selected
         layout_select = ii;                                               //  remember last selected object
         if (layout[ii].type == 1)                                         //  set current image file
            strcpy(imagefile,layout[ii].filespec);                         //  (for thumbnail index anchor)
         else strcpy(pendtext,layout[ii].textspec);                        //  or current text string
         Fselect = 1;
      }
      
      if (Fselect) 
      {
         mpx0 = mpx;                                                       //  set new drag origin
         mpy0 = mpy;
         
         if (layout[ii].type == 1) {                                       //  mouse in image corner?
            if (mpx < layout[ii].xpos + 0.2 * ww  &&
                mpy < layout[ii].ypos + 0.2 * hh) Fcorner = 1;             //  upper left
            if (mpx > layout[ii].xpos + 0.8 * ww  &&
                mpy < layout[ii].ypos + 0.2 * hh) Fcorner = 2;             //  upper right
            if (mpx > layout[ii].xpos + 0.8 * ww  &&
                mpy > layout[ii].ypos + 0.8 * hh) Fcorner = 3;             //  lower right
            if (mpx < layout[ii].xpos + 0.2 * ww  &&
                mpy > layout[ii].ypos + 0.8 * hh) Fcorner = 4;             //  lower left
         }

         if (layout[ii].type == 2) {                                       //  mouse in text corner?
            if (ww > hh) {                                                 //  (broader range for narrow edge)
               if (mpx < layout[ii].xpos + 0.2 * ww  &&
                   mpy < layout[ii].ypos + 0.5 * hh) Fcorner = 1;          //  upper left
               if (mpx > layout[ii].xpos + 0.8 * ww  &&
                   mpy < layout[ii].ypos + 0.5 * hh) Fcorner = 2;          //  upper right
               if (mpx > layout[ii].xpos + 0.8 * ww  &&
                   mpy > layout[ii].ypos + 0.5 * hh) Fcorner = 3;          //  lower right
               if (mpx < layout[ii].xpos + 0.2 * ww  &&
                   mpy > layout[ii].ypos + 0.5 * hh) Fcorner = 4;          //  lower left
            }

            else  {
               if (mpx < layout[ii].xpos + 0.5 * ww  &&
                   mpy < layout[ii].ypos + 0.2 * hh) Fcorner = 1;          //  upper left
               if (mpx > layout[ii].xpos + 0.5 * ww  &&
                   mpy < layout[ii].ypos + 0.2 * hh) Fcorner = 2;          //  upper right
               if (mpx > layout[ii].xpos + 0.5 * ww  &&
                   mpy > layout[ii].ypos + 0.8 * hh) Fcorner = 3;          //  lower right
               if (mpx < layout[ii].xpos + 0.5 * ww  &&
                   mpy > layout[ii].ypos + 0.8 * hh) Fcorner = 4;          //  lower left
            }
         }
      }
      
      return;
   }
   
   if (event->type == GDK_MOTION_NOTIFY)                                   //  capture mouse movement
   {
      if (! Lmouse) return;                                                //  only if left-mouse

      if (Fselect) {
         dx = mpx - mpx0;                                                  //  capture drag motion
         dy = mpy - mpy0;
         mpx0 = mpx;                                                       //  set new drag origin
         mpy0 = mpy;
      }
      
      if (Fselect && ! Fcorner) {                                          //  image move: add mouse drag
         layout[ii].xpos += dx;                                            //    to image position
         layout[ii].ypos += dy;
      }
      
      if (Fcorner)                                                         //  image resize: add mouse drag
      {                                                                    //    to image size
         rscale = layout[ii].rscale;
         ww = layout[ii].ww;
         hh = layout[ii].hh;
         x1 = layout[ii].xpos;
         y1 = layout[ii].ypos;
         x2 = x1 + int(rscale * ww + 0.5);
         y2 = y1 + int(rscale * hh + 0.5);

         if (Fcorner == 1) {                                               //  drag upper left corner
            if (ww > hh) rscale = (rscale * ww - dx) / ww;
            else  rscale = (rscale * hh - dy) / hh;
            x1 = x2 - int(rscale * ww + 0.5);                              //  leave opposite corner fixed
            y1 = y2 - int(rscale * hh + 0.5);
         }

         if (Fcorner == 2) {                                               //  upper right
            if (ww > hh) rscale = (rscale * ww + dx) / ww;
            else  rscale = (rscale * hh - dy) / hh;
            x2 = x1 + int(rscale * ww + 0.5);
            y1 = y2 - int(rscale * hh + 0.5);
         }

         if (Fcorner == 3) {                                               //  lower right
            if (ww > hh) rscale = (rscale * ww + dx) / ww;
            else  rscale = (rscale * hh + dy) / hh;
            x2 = x1 + int(rscale * ww + 0.5);
            y2 = y1 + int(rscale * hh + 0.5);
         }

         if (Fcorner == 4) {                                               //  lower left
            if (ww > hh) rscale = (rscale * ww - dx) / ww;
            else  rscale = (rscale * hh + dy) / hh;
            x1 = x2 - int(rscale * ww + 0.5);
            y2 = y1 + int(rscale * hh + 0.5);
         }
         
         if (rscale < 0.05 || rscale > 4.0) return;                        //  limit extremes
         
         if (ww > hh) rscale = 1.0 * (x2 - x1) / ww;                       //  make precise
         else  rscale = 1.0 * (y2 - y1) / hh;

         if (layout[ii].pixbuf2) g_object_unref(layout[ii].pixbuf2);       //  discard old scaled pixbuf
         layout[ii].pixbuf2 = 0;

         layout[ii].rscale = rscale;                                       //  will recreate from new data
         layout[ii].xpos = x1;
         layout[ii].ypos = y1;
      }
   }
   
   if (event->type == GDK_BUTTON_RELEASE)
   {
      Fclick = 0;
      butime = event->time;
      elapsed = butime - bdtime;                                           //  button down time, milliseconds
      if (elapsed < 500) Fclick = 1;                                       //  mouse clicked, no drag

      if (Fselect && Fclick && Lmouse) {                                   //  image was left-clicked
         templayout = layout[ii];
         for (kk = ii; kk < NPL-1; kk++)                                   //  make topmost (will occlude others)
         {
            if (layout[kk+1].type > layout[ii].type) break;                //  all text above all images   v.1.6
            layout[kk] = layout[kk+1];
         }
         layout[kk] = templayout;
         ii = layout_select = kk;                                          //  new current image index     v.1.7
      }

      if (Fselect && Fclick && Rmouse) {                                   //  image was right-clicked
         if (layout[ii].filespec) zfree(layout[ii].filespec);
         if (layout[ii].textspec) zfree(layout[ii].textspec);
         if (layout[ii].pixbuf1) g_object_unref(layout[ii].pixbuf1);       //  delete image from layout
         if (layout[ii].pixbuf2) g_object_unref(layout[ii].pixbuf2);
         for (kk = ii; kk < NPL-1; kk++)                                   //  cover the hole
            layout[kk] = layout[kk+1];
         NPL--;
         layout_select = -1;                                               //  no selection active    v.2.0
      }

      if (! Fselect && ! Fcorner && Fclick) {                              //  empty space was clicked
         if (*pendfile) add_layout_image(mpx,mpy);                         //  add pending file image to layout
         else if (*pendtext) add_layout_text(mpx,mpy);                     //  add pending text image to layout
      }
      
      if (layout_select >= 0) {                                            //  if item selected and dialog 
         if (zdtext) zdialog_send_event(zdtext,"select");                  //    active, send select event  
         if (zdrotate) zdialog_send_event(zdrotate,"select");              //      to dialog     v.2.0
         if (zdframe) zdialog_send_event(zdframe,"select");
      }

      Fselect = Fcorner = Fclick = 0;                                      //  stop drag/resize
      Lmouse = Rmouse = 0;
   }

   mwpaint2();                                                             //  repaint window
   return;
}


//  prevent propagation of key-press events to toolbar buttons     v.1.7

int KBpress(GtkWidget *win, GdkEventKey *event, void *)
{
   return 1;
}


//  keyboard event - arrow keys >> 1-pixel movement of current image
//  GDK key symbols: /usr/include/gtk-2.0/gdk/gdkkeysyms.h

int KBrelease(GtkWidget *win, GdkEventKey *event, void *)                  //  process KB arrow keys   v.1.7
{
   int         ii, KBkey;

   if (layout_select < 0) return 1;
   ii = layout_select;

   KBkey = event->keyval;
   if (KBkey == GDK_Left) --layout[ii].xpos;                               //  move image in 1-pixel steps
   if (KBkey == GDK_Right) ++layout[ii].xpos;
   if (KBkey == GDK_Up) --layout[ii].ypos;
   if (KBkey == GDK_Down) ++layout[ii].ypos;
   
   mwpaint2();
   return 1;
}


//  text drag and drop event - get filespec or text to add to layout       v.2.0.2

void drag_drop(int mpx, int mpy, char *text)
{
   mpx = int(mpx / Rscale + 0.5);                                          //  position in print layout
   mpy = int(mpy / Rscale + 0.5);
   
   *pendfile = *pendtext = 0;                                              //  cancel prior pending items
   
   if (*text == '/')                                                       //  text is a filespec
   {
      set_pendfile(text);                                                  //  check if valid image file
      if (*pendfile) add_layout_image(mpx,mpy);                            //  add to layout at position
   }
   
   else                                                                    //  text is just text
   {      
      strncpy0(pendtext,text,maxtext);                                     //  save for layout insertion
      add_layout_text(mpx,mpy);
   }

   zfree(text);
   mwpaint2();
   return;
}


//  process main window toolbar events

void menufunc(GtkWidget *, const char *menu)
{
   if (strEqu(menu,ZTX("index"))) { m_index(); return; }
   if (strEqu(menu,ZTX("+image"))) { m_image(); return; }
   if (strEqu(menu,ZTX("+text"))) { m_text(); return; }
   if (strEqu(menu,ZTX("rotate"))) { m_rotate(); return; }
   if (strEqu(menu,ZTX("frame"))) { m_frame(); return; }
   if (strEqu(menu,ZTX("save"))) { m_save(); return; }
   if (strEqu(menu,ZTX("setup"))) { m_setup(); return; }
   if (strEqu(menu,ZTX("print"))) { m_print(); return; }
   if (strEqu(menu,ZTX("quit"))) { m_quit(); return; }
   if (strEqu(menu,ZTX("help"))) { m_help(); return; }
   return;
}


//  display a thumbnail index of images in a separate window

void m_index()                                                             //  v.1.3.2
{
   static char    pimagedirk[maxfcc] = "";
   
   if (strNeq(imagedirk,pimagedirk)) {
      image_xthumbs(imagedirk,"init",0,set_pendfile);
      strcpy(pimagedirk,imagedirk);
   }

   if (*imagefile) image_xthumbs(imagefile,"paint1",0,set_pendfile);
   else  image_xthumbs(0,"paint1");

   return;
}


//  choose image files and add to print layout
//  file-chooser dialog box and response function
//  chosen file is saved for drawing window deposit via mouse-click

void m_image()
{
   int image_compl(GtkDialog *dwin, int arg, void *data);
   void image_preview(GtkFileChooser *, GtkWidget *pvwidget);

   GtkWidget   *dialog, *file_widget;
   GtkWidget   *pvwidget = gtk_image_new();

   dialog = gtk_dialog_new_with_buttons(ZTX("choose files"),GTK_WINDOW(mainWin),
                                         (GtkDialogFlags) 0,ZTX("done"),2,null);
   gtk_window_set_default_size(GTK_WINDOW(dialog),700,500);
   G_SIGNAL(dialog,"response",image_compl,0)

   file_widget = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_OPEN);
   gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),file_widget);
   gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_widget),imagedirk);
   gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(file_widget),0);

   gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_widget),pvwidget);
   G_SIGNAL(file_widget,"update-preview",image_preview,pvwidget);

   gtk_widget_show_all(dialog);
   return;
}

int image_compl(GtkDialog *dwin, int arg, void *data)                      //  done or cancel
{
   gtk_widget_destroy(GTK_WIDGET(dwin));
   return 0;
}

void image_preview(GtkFileChooser *file_widget, GtkWidget *pvwidget)       //  get file preview thumbnail
{
   GdkPixbuf   *thumbnail;
   char        *filename;

   filename = gtk_file_chooser_get_preview_filename(file_widget);
   if (! filename) return;

   thumbnail = image_thumbnail(filename,128);                              //  get 128x128 thumbnail   v.1.7
   if (thumbnail) {                                                        //  (cached or created)
      gtk_image_set_from_pixbuf(GTK_IMAGE(pvwidget),thumbnail);
      gtk_file_chooser_set_preview_widget_active(file_widget,1);
      g_object_unref(thumbnail);
      set_pendfile(filename);                                              //  save for layout insertion   v.2.0
   }
   else
      gtk_file_chooser_set_preview_widget_active(file_widget,0);

   g_free(filename);
   return;
}


//  add text to print layout
//  get text dialog box and response function
//  entered text is saved for drawing window deposit via mouse-click

void m_text()
{
   int text_dialog_event(zdialog *, const char *event);
   int text_dialog_compl(zdialog *, int zstat);

   int         ii;
   const char  *helptext = ZTX(" enter text, select font, \n"
                               " click empty space on layout ");
   if (zdtext) return;

   zdtext = zdialog_new(ZTX("enter text"),mainWin,ZTX("cancel"),null);
   zdialog_add_widget(zdtext,"label","lab1","dialog",helptext,"space=10");       //  text [_______________]
   zdialog_add_widget(zdtext,"hbox","hb1","dialog",0,"space=5");
   zdialog_add_widget(zdtext,"label","lab2","hb1",ZTX("text"),"space=3");
   zdialog_add_widget(zdtext,"frame","fr1","hb1",0,"expand");
   zdialog_add_widget(zdtext,"edit","text","fr1",0,"space=5");
   zdialog_add_widget(zdtext,"hbox","hb2","dialog");                             //  (o) black   (o) white
   zdialog_add_widget(zdtext,"radio","black","hb2",ZTX("black"),"space=5");
   zdialog_add_widget(zdtext,"radio","white","hb2",ZTX("white"),"space=10");
   zdialog_add_widget(zdtext,"hbox","hb3","dialog");                             //  transparent [_]
   zdialog_add_widget(zdtext,"label","lab3","hb3",ZTX("transparent"),"space=8");
   zdialog_add_widget(zdtext,"check","transp","hb3",0);
   zdialog_add_widget(zdtext,"hbox","hb4","dialog",0,"space=10");                //  [erase text]  [select font] 
   zdialog_add_widget(zdtext,"button","erase","hb4",ZTX("erase text"));
   zdialog_add_widget(zdtext,"button","font","hb4",ZTX("select font"));

   ii = layout_select;                                                     //  last selected text image
   if (ii >= 0 && layout[ii].type == 2) {
      textbw = layout[ii].textbw;
      transp = layout[ii].transp;
      strcpy(pendtext,layout[ii].textspec);
      *pendfile = 0;
   }

   if (textbw == 'b') zdialog_stuff(zdtext,"black",1);                     //  use prior text color and
   else  zdialog_stuff(zdtext,"white",1);                                  //    transparency options
   if (transp) zdialog_stuff(zdtext,"transp",1);
   zdialog_stuff(zdtext,"text",pendtext);                                  //  prior or pending text

   zdialog_run(zdtext,text_dialog_event,text_dialog_compl);                //  run dialog, parallel
   return;
}

int text_dialog_event(zdialog *zd,const char *event)
{
   GtkWidget   *font_dialog;
   char        *pp, text[1000] = "";
   int         ii, acolor;
   
   if (strEqu(event,"erase")) {                                            //  erase text entry box
      zdialog_stuff(zd,"text","");
      *pendtext = 0;
      return 0;
   }
   
   if (strEqu(event,"font")) {                                             //  select a new font
      font_dialog = gtk_font_selection_dialog_new(ZTX("select font"));
      gtk_font_selection_dialog_set_font_name(GTK_FONT_SELECTION_DIALOG(font_dialog),textfont);
      gtk_dialog_run(GTK_DIALOG(font_dialog));
      pp = gtk_font_selection_dialog_get_font_name(GTK_FONT_SELECTION_DIALOG(font_dialog));
      if (pp) strncpy0(textfont,pp,59);
      gtk_widget_destroy(GTK_WIDGET(font_dialog));
   }
   
   if (strEqu(event,"select")) {
      ii = layout_select;                                                  //  last selected text image
      if (ii < 0 || layout[ii].type != 2) return 0;
      zdialog_stuff(zd,"text",layout[ii].textspec);
   }

   zdialog_fetch(zd,"text",text,999);                                      //  capture entered text
   zdialog_fetch(zd,"transp",transp);                                      //  and transparency choice
   zdialog_fetch(zd,"black",acolor);                                       //  and black/white choice
   
   if (acolor) textbw = 'b';                                               //  black or white
   else textbw = 'w';

   if (*text) {
      if (NPL == maxNF-1) {
         zmessageACK(ZTX("exceed %d files"),maxNF);
         return 0;
      }
      strncpy0(pendtext,text,maxtext);                                     //  save for layout insertion
      *pendfile = 0;
   }

   return 0;
}

int text_dialog_compl(zdialog *, int zstat)
{
   zdialog_free(zdtext);
   zdtext = 0;
   return 0;
}


//  rotate last-selected image or text through arbitrary angle             //  new  v.2.0

void m_rotate()
{
   int    rotate_dialog_event(zdialog *, const char * event);
   int    rotate_dialog_compl(zdialog *, int zstat);

   char        text[20];
   double      angle = 0;
   
   if (zdrotate) return;

   zdrotate = zdialog_new(ZTX("rotate image"),mainWin,ZTX("done"),ZTX("cancel"),null);
   zdialog_add_widget(zdrotate,"label","labdeg","dialog",ZTX("degrees"),"space=5");
   zdialog_add_widget(zdrotate,"hbox","hb1","dialog",0,"homog|space=5");
   zdialog_add_widget(zdrotate,"vbox","vb1","hb1",0,"space=5");
   zdialog_add_widget(zdrotate,"vbox","vb2","hb1",0,"space=5");
   zdialog_add_widget(zdrotate,"vbox","vb3","hb1",0,"space=5");
   zdialog_add_widget(zdrotate,"vbox","vb4","hb1",0,"space=5");
   zdialog_add_widget(zdrotate,"button"," +0.1  ","vb1"," + 0.1 ");        //  button name is increment to use
   zdialog_add_widget(zdrotate,"button"," -0.1  ","vb1"," - 0.1 ");
   zdialog_add_widget(zdrotate,"button"," +1.0  ","vb2"," + 1   ");
   zdialog_add_widget(zdrotate,"button"," -1.0  ","vb2"," - 1   ");
   zdialog_add_widget(zdrotate,"button"," +10.0 ","vb3"," + 10  ");
   zdialog_add_widget(zdrotate,"button"," -10.0 ","vb3"," - 10  ");
   zdialog_add_widget(zdrotate,"button"," +90.0 ","vb4"," + 90  ");
   zdialog_add_widget(zdrotate,"button"," -90.0 ","vb4"," - 90  ");
   zdialog_add_widget(zdrotate,"hbox","hb2","dialog",0,"space=10");

   if (layout_select >= 0) angle = layout[layout_select].angle;
   sprintf(text,ZTX("degrees: %.1f"),angle);                               //  update dialog angle display
   zdialog_stuff(zdrotate,"labdeg",text);

   zdialog_run(zdrotate,rotate_dialog_event,rotate_dialog_compl);          //  run dialog - parallel
   return;
}


int rotate_dialog_compl(zdialog *zd, int zstat)
{
   zdialog_free(zdrotate);
   zdrotate = 0;
   
   if (zstat != 1 && layout_select >= 0) 
      layout[layout_select].angle = 0;

   mwpaint2();
   return 0;
}


int rotate_dialog_event(zdialog *zd, const char * event)
{
   int         ii, err;
   char        text[20];
   double      angle, incr;

   ii = layout_select;                                                     //  use last selected object
   if (ii < 0) return 0;

   if (strpbrk(event,"+-")) {
      err = convSD(event,incr);                                            //  button name is increment to use
      if (err) return 0;
   }
   else return 0;

   angle = layout[ii].angle + incr;

   sprintf(text,ZTX("degrees: %.1f"),angle);                               //  update dialog angle display
   zdialog_stuff(zd,"labdeg",text);
   
   if (angle >= 360) angle -= 360;
   if (angle <= -360) angle += 360;
   if (fabs(angle) < 0.01) angle = 0;
   
   layout[ii].angle = angle;
   if (layout[ii].pixbuf2) g_object_unref(layout[ii].pixbuf2);
   layout[ii].pixbuf2 = 0;

   mwpaint2();
   return 1;
}


//  edit the frame around an image

void m_frame()
{
   int    frame_dialog_event(zdialog *, const char * event);
   int    frame_dialog_compl(zdialog *, int zstat);

   int      ii, red, green, blue;
   char     color[20];

   if (zdframe) return;
   ii = layout_select;                                                     //  use last selected image
   if (ii < 0) return;
   if (layout[ii].type != 1) return;

   zdframe = zdialog_new(ZTX("edit frame"),mainWin,ZTX("done"),ZTX("cancel"),null);
   zdialog_add_widget(zdframe,"hbox","hb1","dialog",0,"space=10");
   zdialog_add_widget(zdframe,"label","lab1","hb1",ZTX("thickness"));
   zdialog_add_widget(zdframe,"spin","thick","hb1","0|20|1|8","scc=2");
   zdialog_add_widget(zdframe,"label","lab2","hb1",ZTX("color"),"space=10");
   zdialog_add_widget(zdframe,"colorbutt","color","hb1","100|100|100");
   
   zdialog_stuff(zdframe,"thick",layout[ii].framethick);

   red = layout[ii].framepix[0];
   green = layout[ii].framepix[1];
   blue = layout[ii].framepix[2];
   snprintf(color,19,"%d|%d|%d",red,green,blue);
   zdialog_stuff(zdframe,"color",color);

   zdialog_run(zdframe,frame_dialog_event,frame_dialog_compl);             //  run dialog - parallel
   return;
}


int frame_dialog_compl(zdialog *zd, int zstat)
{
   zdialog_free(zdframe);
   zdframe = 0;
   mwpaint2();
   return 0;
}


int frame_dialog_event(zdialog *zd, const char * event)
{
   int         ii, thick, red, green, blue;
   char        color[20];
   const char  *pp;

   ii = layout_select;                                                     //  use last selected image 
   if (ii < 0) return 0;
   if (layout[ii].type != 1) return 0;

   if (strEqu(event,"select")) {
      zdialog_stuff(zd,"thick",layout[ii].framethick);
      red = layout[ii].framepix[0];
      green = layout[ii].framepix[1];
      blue = layout[ii].framepix[2];
      snprintf(color,19,"%d|%d|%d",red,green,blue);
      zdialog_stuff(zd,"color",color);
   }

   if (strEqu(event,"thick")) {
      zdialog_fetch(zd,"thick",thick);
      layout[ii].framethick = thick;
   }
   
   if (strEqu(event,"color")) {
      zdialog_fetch(zd,"color",color,19);
      pp = strField(color,"|",1);
      if (pp) layout[ii].framepix[0] = atoi(pp);
      pp = strField(color,"|",2);
      if (pp) layout[ii].framepix[1] = atoi(pp);
      pp = strField(color,"|",3);
      if (pp) layout[ii].framepix[2] = atoi(pp);
   }

   if (layout[ii].pixbuf2) g_object_unref(layout[ii].pixbuf2);
   layout[ii].pixbuf2 = 0;

   mwpaint2();
   return 1;
}


//  save page layout to a file

void m_save()
{
   GdkPixbuf   *layout2;
   char        printfile[200], command[maxfcc+100];
   char        file1[maxfcc], *file2;
   int         ignore;
   
   layout2 = make_layout(1);                                               //  make 200% layout
   snprintf(printfile,199,"%s/printfile.jpg",get_zuserdir());              //  jpeg print file
   gdk_pixbuf_save(layout2,printfile,"jpeg",gerror,null);                  //  layout >> print file
   g_object_unref(layout2);
   
   snprintf(file1,maxfcc,"%s/printoxx.jpg",imagedirk);
   
   file2 = zgetfile(ZTX("save file"),file1,"save");                        //  query user for file
   if (! file2) return;

   snprintf(command,maxfcc+100,"cp -f \"%s\" \"%s\" ",printfile,file2);
   ignore = system(command);
   return;
}


//  printer setup (printer, paper size, etc.)
//  print setup dialog

void m_setup()
{
   void  print_dialog_stuff(zdialog *zd);
   void  print_dialog_fetch(zdialog *zd);

   int         zstat, contx = 0;
   const char  *pp1, *pp2;
   zdialog     *zd;

   zd = zdialog_new(ZTX("print setup"),mainWin,ZTX("done"),ZTX("cancel"),null);
   zdialog_add_widget(zd,"vbox","vb1","dialog",0,"space=5");
   zdialog_add_widget(zd,"hbox","hb1","vb1",0,"space=5");
   zdialog_add_widget(zd,"vbox","vb11","hb1",0,"homog");                   //  job name    [__________]
   zdialog_add_widget(zd,"vbox","vb12","hb1",0,"homog");                   //  printer     [__________][v]
   zdialog_add_widget(zd,"label","labjob","vb11",ZTX("job name"));         //  paper type  [__________][v]
   zdialog_add_widget(zd,"label","labpri","vb11",ZTX("printer")); 
   zdialog_add_widget(zd,"label","labpap","vb11",ZTX("paper type"));
   zdialog_add_widget(zd,"entry","entjob","vb12","printoxx","scc=20");
   zdialog_add_widget(zd,"combo","entpri","vb12",0,"scc=20");
   zdialog_add_widget(zd,"combo","entpap","vb12",0,"scc=20");
   zdialog_add_widget(zd,"hbox","hb3","vb1",0,"space=10");                 //  (o) portrait  (o) landscape
   zdialog_add_widget(zd,"radio","port","hb3",ZTX("portrait"));
   zdialog_add_widget(zd,"radio","land","hb3",ZTX("landscape"));

   zdialog_cb_app(zd,"entpri","default");                                  //  list avail. printers  v.1.5
   while (true) {   
      pp1 = command_output(contx,"lpstat -a");
      if (! pp1) break;
      if (*pp1 <= ' ') continue;
      pp2 = strField(pp1,' ',1);
      zdialog_cb_app(zd,"entpri",pp2);
   }

   for (int ii = 0; ii < nptab; ii++)                                      //  stuff combobox with paper types
      zdialog_cb_app(zd,"entpap",ptab[ii]);
   
   print_dialog_stuff(zd);                                                 //  old data >> dialog
   zstat = zdialog_run(zd);                                                //  run dialog, blocking
   if (zstat == 1) print_dialog_fetch(zd);                                 //  dialog >> new data >> file
   zdialog_free(zd);

   init_layout();                                                          //  setup new print layout
   return;
}


//  prior setup data >> dialog widgets

void print_dialog_stuff(zdialog *zd)                                       //  v.1.4
{
   zdialog_stuff(zd,"entpri",printer);                                     //  load dialog widgets with
   zdialog_stuff(zd,"entjob",jobname);                                     //    prior setup data
   zdialog_stuff(zd,"entpap",paper);
   zdialog_stuff(zd,"port",1);
   if (strEqu(orientation,"landscape")) zdialog_stuff(zd,"land",1);
   return;
}


//  dialog widgets >> new setup data >> file

void print_dialog_fetch(zdialog *zd)                                       //  v.1.4
{
   char        file[200];
   const char  *pp;
   int         ii, port;
   FILE        *fid;

   zdialog_fetch(zd,"entpri",printer,60);                                  //  retrieve data from widgets
   zdialog_fetch(zd,"entjob",jobname,60);
   zdialog_fetch(zd,"entpap",paper,40);
   zdialog_fetch(zd,"port",port);
   if (port) strcpy(orientation,"portrait");
   else strcpy(orientation,"landscape");

   snprintf(file,200,"%s/print-setup",get_zuserdir());
   fid = fopen(file,"w");                                                  //  write to file
   if (! fid) {
      zmessageACK(ZTX("cannot write print setup file: %s"),file);
      return;
   }

   fprintf(fid,"printer       %s \n",printer);
   fprintf(fid,"jobname       %s \n",jobname);
   fprintf(fid,"paper         %s \n",paper);
   fprintf(fid,"orientation   %s \n",orientation);
   fclose(fid);
   
   for (ii = 0; ii < nptab; ii++)                                          //  match paper type with table
      if (strEqu(paper,ptab[ii])) break;
   if (ii == nptab) ii = defptab;                                          //  not found, use default
   pp = strField(paper,' ',2);
   paperwidth = atoi(pp);
   pp = strField(paper,' ',4);
   paperheight = atoi(pp);
   return;
}


//  print the completed print layout

void m_print()
{
   GdkPixbuf   *layout2;
   char        printfile[200], command[200], papertype[100];
   const char  *pformat;
   int         ignore;
   
   layout2 = make_layout(1);                                               //  make 200% layout
   snprintf(printfile,199,"%s/printfile.jpg",get_zuserdir());              //  jpeg print file
   gdk_pixbuf_save(layout2,printfile,"jpeg",gerror,null);                  //  layout >> print file
   g_object_unref(layout2);
   
   strcpy(command,"lp ");
   if (strNeq(printer,"default")) 
      strncatv(command,199,"-d ",printer," ",0);                           //  v.1.5
   
   pformat = strField(paper,' ',1);
   snprintf(papertype,99," -o PageSize=%s -o Media=%s ",pformat,pformat);
   strcat(command,papertype);
   strcat(command," -o scaling=98 ");                                      //  fitplot fails   v.1.4.1
   strcat(command,printfile);
   ignore = system(command);
   printf("printoxx: %s \n",command);
   return;
}


//  thread functions to display help file

void m_help()
{
   showz_helpfile();
   return;
}


//  quit - exit program

void m_quit()
{
   save_imagedirk();
   gtk_main_quit();
   return;
}


//  set up page layout pixbuf based on printer paper dimensions

void init_layout()
{
   if (strEqu(orientation,"portrait")) {
      layout_ww = 1000;
      layout_hh = int(paperheight * 1000 / paperwidth);
   }
   else {
      layout_ww = 1500;
      layout_hh = int(paperwidth * 1500 / paperheight);
   }
      
   pxb_layout = gdk_pixbuf_new(colorspace,0,8,layout_ww,layout_hh);        //  pixbuf for page layout
   if (! pxb_layout) zappcrash("cannot create pixbuf");

   wwD = int(Rscale * layout_ww);                                          //  matching drawing window size
   hhD = int(Rscale * layout_hh + 60);
   gtk_window_resize(GTK_WINDOW(mainWin),wwD,hhD);

   return;
}


//  verify chosen file and make pending add to layout
 
void set_pendfile(char *file)
{
   GdkPixbuf      *pixbuf;
   char           *pp;

   if (NPL == maxNF-1) {
      zmessageACK(ZTX("exceed %d files"),maxNF);
      return;
   }
   
   pixbuf = load_pixbuf(file);                                             //  test file is legit. image file
   if (! pixbuf) {
      *pendfile = 0;                                                       //  simply ignore    v.2.0
      return;
   }
   g_object_unref(pixbuf);

   strcpy(pendfile,file);                                                  //  save filespec
   *pendtext = 0;

   strcpy(imagefile,file);                                                 //  set curr. image file
   strcpy(imagedirk,file);                                                 //  and image directory
   pp = (char *) strrchr(imagedirk,'/');
   if (pp) *pp = 0;
   
   gtk_window_present(GTK_WINDOW(mainWin));                                //  bring to foreground
   return;
}


//  add a new image file to the layout at designated position

void add_layout_image(int mpx, int mpy)
{
   GdkPixbuf   *pixbuf;
   int         ii, kk;

   pixbuf = load_pixbuf(pendfile);
   if (! pixbuf) zappcrash("cannot create pixbuf");
   for (ii = 0; ii < NPL; ii++)                                            //  insert at end of images
      if (layout[ii].type != 1) break;                                     //  (topmost file image)
   for (kk = NPL; kk > ii; kk--)
      layout[kk] = layout[kk-1];
   NPL++;
   layout_select = ii;
   layout[ii].type = 1;
   layout[ii].textbw = 0;
   layout[ii].transp = 0;
   layout[ii].filespec = strdupz(pendfile);
   layout[ii].textspec = 0;
   layout[ii].pixbuf1 = pixbuf;
   layout[ii].pixbuf2 = 0;
   layout[ii].ww = gdk_pixbuf_get_width(layout[ii].pixbuf1);
   layout[ii].hh = gdk_pixbuf_get_height(layout[ii].pixbuf1);
   layout[ii].xpos = mpx;
   layout[ii].ypos = mpy;
   layout[ii].rscale = 1.0 * layout_pix / layout[ii].ww;
   layout[ii].angle = 0;                                                   //  v.2.0
   memcpy(layout[ii].framepix,framepix,3*sizeof(int));                     //  v.2.0
   layout[ii].framethick = framethick;                                     //  v.2.0
   *pendfile = 0;                                                          //  v.2.0
   return;
}


//  add a new text image to the layout at designated position

void add_layout_text(int mpx, int mpy)
{
   GdkPixbuf   *pixbuf;
   int         ii;

   pixbuf = pixbuf_text(drawWin,pendtext,textfont,textbw);
   if (! pixbuf) zappcrash("cannot create text pixbuf");
   ii = NPL++;                                                             //  topmost text image
   layout_select = ii;
   layout[ii].type = 2;
   layout[ii].textbw = textbw;
   layout[ii].transp = transp;
   layout[ii].textspec = strdupz(pendtext);
   layout[ii].filespec = 0;
   layout[ii].pixbuf1 = pixbuf;
   layout[ii].pixbuf2 = 0;
   layout[ii].ww = gdk_pixbuf_get_width(layout[ii].pixbuf1);
   layout[ii].hh = gdk_pixbuf_get_height(layout[ii].pixbuf1);
   layout[ii].xpos = mpx;
   layout[ii].ypos = mpy;
   layout[ii].rscale = 0.5;
   layout[ii].angle = 0;                                                   //  v.2.0
   layout[ii].framethick = 0;                                              //  v.2.0
   *pendtext = 0;                                                          //  v.2.0
   return;
}


//  save current image directory upon exit, reload upon startup            //  v.1.2
//  directory is saved in file $HOME/.printoxx/image_directory

void save_imagedirk()
{
   char     command[maxfcc+100];
   int      ignore;
   
   snprintf(command,maxfcc+99,"echo %s > %s/image_directory",              //  save imagedirk to file
                                    imagedirk, get_zuserdir());
   ignore = system(command);
   return;
}

void load_imagedirk()
{
   int            err;
   FILE           *fid;
   struct stat    statdat;
   char           dirbuff[maxfcc], *pp;

   pp = getcwd(imagedirk,maxfcc-1);                                        //  default is current directory

   snprintf(dirbuff,maxfcc-1,"%s/image_directory",get_zuserdir());         //  read saved file
   fid = fopen(dirbuff,"r");
   if (! fid) return;
   pp = fgets_trim(dirbuff,maxfcc-1,fid,1);
   fclose(fid);
   if (! pp) return;
   err = stat(dirbuff,&statdat);                                           //  contains valid directory name?
   if (err) return;
   if (! S_ISDIR(statdat.st_mode)) return;
   strcpy(imagedirk,dirbuff);                                              //  yes, use it
   return;
}


//  make a layout pixbuf containing all scaled images
//  final = 0 = edit pixbuf - 1x size and scaled image pixbufs are saved and reused
//  final = 1 = pixbuf to print or save - 2x size and scaled image pixbufs are discarded

GdkPixbuf * make_layout(int final)
{
   GdkPixbuf   *pixbuf1, *pixbuf2, *pixbuf3;
   int         type, textbw, transp, ftext;
   int         ii, lscale, lww, lhh, iww, ihh;
   int         xpos, ypos, orgx, orgy;
   int         rgb, rcolor, acolor;
   int         *framepix;
   int         framethick;
   double      angle, rscale;
   
   lscale = 1;
   if (final) lscale = 2;                                                  //  use 2x size for final layout
   
   lww = lscale * layout_ww;                                               //  layout size
   lhh = lscale * layout_hh;

   pixbuf1 = gdk_pixbuf_new(colorspace,0,8,lww,lhh);                       //  layout pixbuf
   if (! pixbuf1) zappcrash("cannot create pixbuf");
   gdk_pixbuf_fill(pixbuf1,(unsigned) 0xffffffff);
   
   for (ii = 0; ii < NPL; ii++)                                            //  copy image pixbufs
   {                                                                       //    into layout pixbuf
      type = layout[ii].type;
      textbw = layout[ii].textbw;
      transp = layout[ii].transp;
      rscale = layout[ii].rscale;
      angle = layout[ii].angle;
      framepix = layout[ii].framepix;
      framethick = layout[ii].framethick;
      pixbuf2 = layout[ii].pixbuf2;                                        //  image pixbuf
      iww = lscale * int(rscale * layout[ii].ww + 0.5);                    //  image size
      ihh = lscale * int(rscale * layout[ii].hh + 0.5);
      
      if (type == 1) {                                                     //  image                  v.2.0
         if (angle == 0) rcolor = acolor = 0;                              //  no angle, no rotate border
         else rcolor = acolor = 'w';                                       //  border and alpha color = white
         ftext = 0;
      }
      else {                                                               //  text
         if (textbw == 'b') rcolor = acolor = 'w';                         //  rotate border and alpha color
         else  rcolor = acolor = 'b';                                      //    opposite of text color
         if (! transp) acolor = 0;
         ftext = 1;
      }

      if (! pixbuf2 || final) {
         pixbuf2 = pixbuf_scale(layout[ii].pixbuf1,iww,ihh,bilinear);      //  scale image to layout

         if (type == 1) {
            pixbuf3 = pixbuf_add_frame(pixbuf2,framepix,framethick);       //  add frame around image    v.2.0
            g_object_unref(pixbuf2);
            pixbuf2 = pixbuf3;
         }

         if (angle) {                                                      //  rotate if needed    v.2.0
            if (rcolor == 'b') rgb = 0;
            else rgb = 255;
            pixbuf3 = gdk_pixbuf_rotate(pixbuf2,angle,rgb);
            g_object_unref(pixbuf2);
            pixbuf2 = pixbuf3;
         }

         if (! final) layout[ii].pixbuf2 = pixbuf2;                        //  retain for efficiency
      }

      iww = gdk_pixbuf_get_width(pixbuf2);                                 //  (rotated) image size   v.2.0
      ihh = gdk_pixbuf_get_height(pixbuf2);

      xpos = lscale * layout[ii].xpos;                                     //  image position in layout
      ypos = lscale * layout[ii].ypos;

      orgx = orgy = 0;                                                     //  cut-off beyond layout edge
      if (xpos < 0) {
         orgx = -xpos;
         xpos = 0;
      }
      if (ypos < 0) {
         orgy = -ypos;
         ypos = 0;
      }

      if (xpos + iww > lww) iww = lww - xpos;
      if (ypos + ihh > lhh) ihh = lhh - ypos;

      if (acolor) {                                                        //  paste image into layout
         if (acolor == 'b') rgb = 0;                                       //    with transparency
         else rgb = 255;
         pixbuf_copy_alpha(pixbuf2,orgx,orgy,iww-orgx,ihh-orgy,pixbuf1,xpos,ypos,rgb,ftext);
      }
      else gdk_pixbuf_copy_area(pixbuf2,orgx,orgy,iww-orgx,ihh-orgy,pixbuf1,xpos,ypos);
      
      if (final) g_object_unref(pixbuf2);
   }
   
   return pixbuf1;
}


//  validate an image file and load pixbuf from file                       //  v.1.2
//  if an alpha channel is present, remove it

GdkPixbuf * load_pixbuf(const char *file)
{
   GdkPixbuf   *pxb91 = 0, *pxb92 = 0;
   int         ww91, hh91, rs91, rs92;
   int         nch, nbits, px, py, alfa;
   pixel       ppix91, pix91, ppix92, pix92;

   pxb91 = gdk_pixbuf_new_from_file(file,gerror);                          //  validate file and load pixbuf
   if (! pxb91) return 0;

   nch = gdk_pixbuf_get_n_channels(pxb91);
   nbits = gdk_pixbuf_get_bits_per_sample(pxb91);
   alfa = gdk_pixbuf_get_has_alpha(pxb91);

   if (nch < 3 || nbits != 8) {                                            //  must be 3 or 4 channels
      g_object_unref(pxb91);                                               //  and 8 bits per channel
      return 0;
   }
   
   if (! alfa) return pxb91;                                               //  no alpha channel, 3 channels

   ww91 = gdk_pixbuf_get_width(pxb91);                                     //  copy without alpha    v.1.2
   hh91 = gdk_pixbuf_get_height(pxb91);
   rs91 = gdk_pixbuf_get_rowstride(pxb91);
   ppix91 = gdk_pixbuf_get_pixels(pxb91);

   pxb92 = gdk_pixbuf_new(colorspace,0,8,ww91,hh91);
   if (! pxb92) zappcrash("cannot create pixbuf");
   rs92 = gdk_pixbuf_get_rowstride(pxb92);
   ppix92 = gdk_pixbuf_get_pixels(pxb92);

   for (py = 0; py < hh91; py++)
   for (px = 0; px < ww91; px++)
   {
      pix91 = ppix91 + rs91 * py + 4 * px;
      pix92 = ppix92 + rs92 * py + 3 * px;
      pix92[0] = pix91[0];
      pix92[1] = pix91[1];
      pix92[2] = pix91[2];
   }

   g_object_unref(pxb91);
   return pxb92;
}


//  create a pixbuf containing text
//  drawWin is from gtk_drawing_area_new()
//  drawWin must be a realized window even though it is not modified
//  bw is 'b'/'w' for black/white text on white/black background

GdkPixbuf * pixbuf_text(GtkWidget *drawWin, cchar *text, cchar *font, int bw)
{
   PangoFontDescription  *pfont;
   static GdkColormap    *colormap = 0;
   static GdkColor       black, white;
   GdkColor              foreground, background;

   PangoLayout    *playout;
   GdkPixmap      *pixmap;
   GdkGC          *gdkgc;
   GdkPixbuf      *pixbuf;
   int            ww, hh;

   if (! colormap) {
      black.red = black.green = black.blue = 0;
      white.red = white.green = white.blue = 0xffff;
      colormap = gtk_widget_get_colormap(drawWin);
      gdk_rgb_find_color(colormap,&black);
      gdk_rgb_find_color(colormap,&white);
   }
   
   if (bw == 'b') {
      foreground = black;
      background = white;
   }
   else {
      foreground = white;
      background = black;
   }

   pfont = pango_font_description_from_string(font);
   playout = gtk_widget_create_pango_layout(drawWin,null);
   pango_layout_set_font_description(playout,pfont);
   pango_layout_set_text(playout,text,-1);
   pango_layout_get_pixel_size(playout,&ww,&hh);
   if (! ww) return 0;

   pixmap = gdk_pixmap_new(drawWin->window,ww,hh,-1);
   if (! pixmap) zappcrash("cannot create pixmap");
   gdkgc = gdk_gc_new(pixmap);
   gdk_gc_set_foreground(gdkgc,&background);
   gdk_draw_rectangle(pixmap,gdkgc,1,0,0,ww,hh);

   gdk_draw_layout_with_colors(pixmap,gdkgc,0,0,playout,&foreground,&background);
   pixbuf = gdk_pixbuf_get_from_drawable(null,pixmap,0,0,0,0,0,ww,hh);
   if (! pixbuf) zappcrash("cannot create text pixbuf");
   
   g_object_unref(playout);
   g_object_unref(pixmap);
   g_object_unref(gdkgc);

   return pixbuf;
}


//  copy pixbuf1 into pixbuf2, treating color "rgb" as transparent
//  rgb = 0 = black, rgb = 255 = white
//  ftext = true to blend anti-aliased text edges (looks better)

void pixbuf_copy_alpha(GdkPixbuf *pxb1, int x1, int y1, int ww, int hh,
                       GdkPixbuf *pxb2, int x2, int y2, int rgb, int ftext)
{
   int         px, py, px1, py1, px2, py2;
   int         ww1, hh1, rs1, ww2, hh2, rs2;
   pixel       ppix1, ppix2, pix1, pix2;
   int         red1, green1, blue1, red2, green2, blue2;
   double      f1, f2;
   
   ww1 = gdk_pixbuf_get_width(pxb1);
   hh1 = gdk_pixbuf_get_height(pxb1);
   rs1 = gdk_pixbuf_get_rowstride(pxb1);
   ppix1 = gdk_pixbuf_get_pixels(pxb1);

   ww2 = gdk_pixbuf_get_width(pxb2);
   hh2 = gdk_pixbuf_get_height(pxb2);
   rs2 = gdk_pixbuf_get_rowstride(pxb2);
   ppix2 = gdk_pixbuf_get_pixels(pxb2);
   
   for (px = 0; px < ww; px++)
   for (py = 0; py < hh; py++)
   {
      px1 = x1 + px;                                                       //  copy-from pixel
      py1 = y1 + py;
      pix1 = ppix1 + rs1 * py1 + 3 * px1;

      red1 = pix1[0];
      green1 = pix1[1];
      blue1 = pix1[2];

      if (red1 == rgb && green1 == rgb && blue1 == rgb)                    //  transparent, do not copy
         continue;
      
      px2 = x2 + px;                                                       //  copy-to pixel
      py2 = y2 + py;
      pix2 = ppix2 + rs2 * py2 + 3 * px2;

      if (ftext)
      {
         red2 = pix2[0];
         green2 = pix2[1];
         blue2 = pix2[2];
         
         f1 = abs(rgb - red1) / 255.0;                                     //  blend pixels (text)
         f2 = 1.0 - f1;
         red2 = int(f1 * red1 + f2 * red2);

         f1 = abs(rgb - green1) / 255.0;
         f2 = 1.0 - f1;
         green2 = int(f1 * green1 + f2 * green2);

         f1 = abs(rgb - blue1) / 255.0;
         f2 = 1.0 - f1;
         blue2 = int(f1 * blue1 + f2 * blue2);
         
         pix2[0] = red2;
         pix2[1] = green2;
         pix2[2] = blue2;
      }

      else                                                                 //  no blending (picture)  v.2.0
      {
         pix2[0] = red1;
         pix2[1] = green1;
         pix2[2] = blue1;
      }
   }

   return;
}


//  add a frame around a pixbuf image                                      //  new v.2.0
//  new pixbuf with added frame is returned
//  rgb: frame color   thick: thickness in pixels

GdkPixbuf * pixbuf_add_frame(GdkPixbuf *pixbuf1, int *rgb, int thick)
{
   GdkPixbuf   *pixbuf2;
   int         px, py;
   int         ww1, hh1, ww2, hh2, rs2;
   pixel       ppix2, pix2;
   int         red, green, blue;
   
   ww1 = gdk_pixbuf_get_width(pixbuf1);                                    //  input pixbuf dimensions
   hh1 = gdk_pixbuf_get_height(pixbuf1);

   ww2 = ww1 + 2 * thick;                                                  //  new pixbuf with space for frame
   hh2 = hh1 + 2 * thick;
   pixbuf2 = gdk_pixbuf_new(colorspace,0,8,ww2,hh2);
   if (! pixbuf2) zappcrash("cannot create pixbuf");
   rs2 = gdk_pixbuf_get_rowstride(pixbuf2);
   ppix2 = gdk_pixbuf_get_pixels(pixbuf2);
   
   gdk_pixbuf_copy_area(pixbuf1,0,0,ww1,hh1,pixbuf2,thick,thick);          //  copy input pixbuf to output
   
   red = rgb[0];
   green = rgb[1];
   blue = rgb[2];
   
   for (py = 0; py < thick; py++)                                          //  top
   for (px = 0; px < ww2; px++)
   {
      pix2 = ppix2 + rs2 * py + 3 * px;
      pix2[0] = red;
      pix2[1] = green;
      pix2[2] = blue;
   }
   
   for (py = 0; py < hh2; py++)                                            //  right
   for (px = ww2 - thick; px < ww2; px++)
   {
      pix2 = ppix2 + rs2 * py + 3 * px;
      pix2[0] = red;
      pix2[1] = green;
      pix2[2] = blue;
   }
   
   for (py = hh2 - thick; py < hh2; py++)                                  //  bottom
   for (px = 0; px < ww2; px++)
   {
      pix2 = ppix2 + rs2 * py + 3 * px;
      pix2[0] = red;
      pix2[1] = green;
      pix2[2] = blue;
   }
   
   for (py = 0; py < hh2; py++)                                            //  left
   for (px = 0; px < thick; px++)
   {
      pix2 = ppix2 + rs2 * py + 3 * px;
      pix2[0] = red;
      pix2[1] = green;
      pix2[2] = blue;
   }
   
   return pixbuf2;
}


