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

   Fotoxx      edit photos and manage collections

   Copyright 2007 2008 2009 2010 2011 2012 Michael Cornelison
   Source URL: http://kornelix.squarespace.com/fotoxx
   Contact: kornelix2@googlemail.com
   
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see http://www.gnu.org/licenses/.

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

#define EX extern                 //  disable extern declarations
#include "fotoxx.h"


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

      Fotoxx image edit - file menu functions

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

//  display image gallery (thumbnails) in a separate window

void m_gallery(GtkWidget *, cchar *)
{
   zfuncs::F1_help_topic = "navigation";
   
   if (curr_file)
      image_gallery(0,"paint1",curr_file_posn,m_gallery2,mWin);            //  overlay main window    v.10.9
   else {
      char *pp = getcwd(command,ccc);                                      //  v.11.09
      if (pp) {
         image_gallery(pp,"init",0,m_gallery2,mWin);                       //  use current directory   v.11.04
         image_gallery(0,"paint1");
      }
      curr_file_posn = 0;                                                  //  v.11.05
   }

   return;
}


//  clicked thumbnail will call this function

void m_gallery2(int Nth, int button)
{
   char     *file;

   zfuncs::F1_help_topic = "navigation";                                   //  v.11.08

   if (Nth == -1) return;                                                  //  gallery window closed

   file = image_gallery(0,"find",Nth);
   if (! file) return;
   
   if (edit_coll_name && button == 3)                                      //  right-click, pass to edit collection
   {                                                                       //  v.11.11
      edit_coll_popmenu(null,file);
      zfree(file);
      return;
   }

   f_open(file,1,Nth);                                                     //  clicked file = current file
   zfree(file);

   gtk_window_present(MWIN);                                               //  bring main window to front   v.10.12
   return;
}


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

//  start a new parallel instance of fotoxx
//  move my window to right half of desktop
//  start new instance to open in left half

void m_clone1(GtkWidget *, cchar *)
{
   GdkScreen   *screen;
   int         ww, hh, err;

   zfuncs::F1_help_topic = "clone";                                        //  v.10.8

   screen = gdk_screen_get_default();                                      //  get desktop screen size
   ww = gdk_screen_get_width(screen);
   hh = gdk_screen_get_height(screen);
   ww = ww / 2 - 20;
   hh = hh - 50;
   gdk_window_move_resize(mWin->window,ww+20,10,ww,hh);                    //  move my window to right half
   
   snprintf(command,ccc,"fotoxx -clone1 -lang %s",zfuncs::zlang);          //  start new instance
   if (curr_file) strncatv(command,ccc," \"",curr_file,"\"",null);
   strcat(command," &");
   err = system(command);
   if (err) printf("error: %s \n",wstrerror(err));
   return;
}


//  start a new parallel instance of fotoxx
//  new window is slightly down and right from old window

void m_clone2(GtkWidget *, cchar *)                                        //  new  v.11.07
{
   int         xx, yy, ww, hh, err;

   zfuncs::F1_help_topic = "clone";

   gtk_window_get_position(MWIN,&xx,&yy);                                  //  get window position and size
   gtk_window_get_size(MWIN,&ww,&hh);

   snprintf(command,ccc,"fotoxx -clone2 %d %d %d %d -lang %s",xx,yy,ww,hh,zfuncs::zlang);
   if (curr_file) strncatv(command,ccc," \"",curr_file,"\"",null);
   strcat(command," &");                                                   //  start new instance and pass
   err = system(command);                                                  //    my window posn and size
   if (err) printf("error: %s \n",wstrerror(err));
   return;
}


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

//  open file menu function

void m_open(GtkWidget *, cchar *)
{
   zfuncs::F1_help_topic = "open_image_file";
   f_open(null,1);
   return;
}


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

//  open drag-drop file

void m_open_drag(int x, int y, char *file)
{
   zfuncs::F1_help_topic = "open_image_file";
   f_open(file,1);
   return;
}


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

//  open a new file in a new parallel instance of fotoxx
//  new window is slightly down and right from old window

void m_open_newin(GtkWidget *, cchar *)                                    //  new  v.11.07
{
   int         xx, yy, ww, hh, err;
   char        *file;

   zfuncs::F1_help_topic = "open_image_file";

   file = zgetfile1(ZTX("Open Image File"),"open",curr_file);              //  dialog to get filespec
   if (! file) return;                                                     //  canceled
   if (image_file_type(file) != 2) return;                                 //  not a supported image file      v.11.05

   gtk_window_get_position(MWIN,&xx,&yy);                                  //  get window position and size
   gtk_window_get_size(MWIN,&ww,&hh);

   snprintf(command,ccc,"fotoxx -c2 %d %d %d %d -l %s",xx,yy,ww,hh,zfuncs::zlang);
   strncatv(command,ccc," \"",file,"\"",null);
   strcat(command," &");                                                   //  start new instance and pass
   err = system(command);                                                  //    my window posn and size
   if (err) printf("error: %s \n",wstrerror(err));
   return;
}


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

//  open the previous file opened (not the same as toolbar [prev] button)
//  repeated use will cycle back and forth between two most recent files

void m_previous(GtkWidget *, cchar *)
{
   int      ii, jj, err;
   
   zfuncs::F1_help_topic = "open_previous_file";

   if (! menulock(1)) return;                                              //  v.11.11
   menulock(0);
   if (mod_keep()) return;                                                 //  v.11.06
   
   if (curr_file) jj = 1;                                                  //  2nd most recent file   v.11.08
   else jj = 0;                                                            //  no current file, use most recent

   for (ii = jj; ii < Nrecentfiles; ii++)
   {
      if (! recentfiles[ii]) continue;                                     //  bugfix  v.12.01
      err = f_open(recentfiles[ii],1);                                     //  get first one that is still there
      if (! err) return;
   }

   return;
}


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

//  open an image file from the list of recent image files

void m_recent(GtkWidget *, cchar *)                                        //  overhauled    v.11.01
{
   void recentfile2(int Nth, int button);

   int            ii, jj, typ;
   FILE           *fid;

   zfuncs::F1_help_topic = "open_recent_file";

   if (! menulock(1)) return;
   menulock(0);
   if (mod_keep()) return;                                                 //  v.11.11

   fid = fopen(recentfiles_file,"w");                                      //  revalidate list of recent files
   if (! fid) return;                                                      //  and update disk file
   
   for (ii = jj = 0; ii < Nrecentfiles; ii++)
   {
      if (! recentfiles[ii]) continue;                                     //  improved      v.11.11
      typ = image_file_type(recentfiles[ii]);
      if (typ != 2) {
         zfree(recentfiles[ii]);
         recentfiles[ii] = 0;
         continue;
      }
      if (jj < ii) {
         recentfiles[jj] = recentfiles[ii];
         recentfiles[ii] = 0;                                              //  bugfix        v.11.11.1
      }
      fprintf(fid,"%s \n",recentfiles[jj]);
      jj++;
   }

   fclose(fid);
   
   if (! jj) return;                                                       //  no files found
   
   image_gallery(recentfiles_file,"initF",0,recentfile2,mWin);             //  generate gallery of recent files
   if (image_navi::galleryname) zfree(image_navi::galleryname);
   image_navi::galleryname = strdupz("Recent Files",0,"recent files");     //  v.12.01
   image_gallery(0,"paint1",0);                                            //  show new image gallery window

   return;
}


//  clicked thumbnail will call this function

void recentfile2(int Nth, int button)
{
   char     *file;

   if (Nth == -1) {                                                        //  gallery window cancelled
      if (curr_file) image_gallery(curr_file,"init");                      //  reset gallery from current file    v.11.05
      return;
   }
   
   file = image_gallery(0,"find",Nth);
   if (! file) return;
   image_gallery(file,"init");                                             //  initz. gallery from chosen file    v.11.05
   image_gallery(0,"close");                                               //  close the gallery
   f_open(file,1);                                                         //  open the file
   zfree(file);
   return;
}


//  add a file to the list of recent files

void add_recent_file(cchar *file)
{
   int      ii;

   for (ii = 0; ii < Nrecentfiles-1 && recentfiles[ii]; ii++)              //  find file in recent list
      if (strEqu(file,recentfiles[ii])) break;                             //    (or find last entry in list)
   if (recentfiles[ii]) zfree(recentfiles[ii]);                            //  free this slot in list
   for (; ii > 0; ii--) recentfiles[ii] = recentfiles[ii-1];               //  move list UP to fill hole
   recentfiles[0] = strdupz(file);                                         //  current file >> first in list
   return;
}


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

//  Open a file and initialize PXM bitmap.
//  If flock and menu is locked, do nothing and return.
//  Otherwise open the file and display in main window.
//  If Nth matches the file position in current file set, curr_file_posn 
//  will be set to Nth, otherwise it is searched and set correctly
//  (a file can be present multiple times in a collection set).
//  If fkeepundo is ON, the edit undo image stack is not purged: current
//  edits are kept after opening the new file (used by m_saveas()).
//  Returns: 0 = OK, +N = error.

int f_open(cchar *filespec, int flock, int Nth, int fkeepundo)
{
   PXM         *temp8;
   int         err, cc, yn, fposn, nfiles, nimages;
   char        *pp, *file, *rawfile, titlebar[250];
   char        fname[100], fdirk[100];
   int         Gwarn = 0, Gnew = 0;
   
   if (flock && Fmenulock) {
      zmessageACK(mWin,ZTX("prior function still active"));                //  v.11.06
      return 1;
   }

   if (mod_keep()) return 2;                                               //  unsaved edits
   
   if (image_navi::gallerytype > 1) Gwarn = 1;                             //  warn if discarding search or
   pp = image_navi::galleryname;                                           //    collection type gallery
   if (pp && strEqu(pp,"Recent Files")) Gwarn = 0;

   if (filespec) 
      file = strdupz(filespec,0,"f_open");                                 //  use passed filespec
   else {
      if (Gwarn) {                                                         //  warn user, gallery will be discarded
         yn = zmessageYN(mWin,Bdiscard,image_navi::galleryname);
         if (! yn) return 6;                                               //  do not discard                  v.11.09
         Gnew = 1;                                                         //  flag, new gallery needed
      }
      pp = curr_file;
      if (! pp) pp = curr_dirk;
      file = zgetfile1(ZTX("Open Image File"),"open",pp);                  //  dialog to get filespec
      if (! file) return 3;                                                //  canceled
   }

   if (image_file_type(file) != 2)                                         //  not a supported image file type
   {
      pp = strrchr(file,'.');
      if (! pp || strlen(pp) != 4 || ! strcasestr(RAWfiles,pp)) {          //  check if a RAW file extension
         zfree(file);                                                      //  v.11.09
         return 3;                                                         //  no
      }
      
      rawfile = file;                                                      //  v.11.09
      file = strdupz(rawfile,5,"f_open");                                  //  filename.raw >> filename.tiff
      pp = file + (pp - rawfile);
      strcpy(pp,".tiff");
      snprintf(command,ccc,"ufraw-batch --wb=camera --out-type=tiff "      //  convert RAW file to tiff-16     v.11.09
                           "--out-depth=16 --overwrite "
                           "--output=\"%s\" \"%s\" ",file,rawfile);
      err = system(command);
      if (err) {
         zmessageACK(mWin,wstrerror(err));                                 //  failed, clean up
         zfree(file);
         zfree(rawfile);
         return 3;
      }
      zfree(rawfile);                                                      //  tiff file will be opened
   }

   temp8 = f_load(file,8);                                                 //  load image as PXM-8 pixmap
   if (! temp8) {
      zfree(file);                                                         //  bad image
      return 5;
   }
                                                                           //  menulock() removed     v.11.11
   free_resources(fkeepundo);                                              //  free resources for old image file

   if (curr_file) zfree(curr_file);                                        //  current image filespec
   curr_file = strdupz(file,8,"curr_file");
   zfree(file);

   if (curr_dirk) zfree(curr_dirk);                                        //  set current directory
   curr_dirk = strdupz(curr_file,0,"curr_dirk");                           //    for new current file
   pp = strrchr(curr_dirk,'/');
   *pp = 0;
   err = chdir(curr_dirk);
   
   Fpxm8 = temp8;                                                          //  pixmap for current image
   Fww = Fpxm8->ww;
   Fhh = Fpxm8->hh;
   
   strcpy(curr_file_type,f_load_type);                                     //  set curr_file_xxx from f_load_xxx
   curr_file_bpc = f_load_bpc;
   curr_file_size = f_load_size;

   fposn = image_gallery_position(curr_file,Nth);                          //  file position in gallery list
   if (fposn < 0 || Gnew) {                                                //  not there or break current gallery    v.11.09
      image_gallery(curr_file,"init",0,m_gallery2);                        //  generate new gallery list             v.12.01
      fposn = image_gallery_position(curr_file,0);                         //  position and count in gallery list
      image_gallery(0,"paint2",fposn);                                     //  refresh gallery window if active      v.11.07
   }

   nfiles = image_navi::nfiles;                                            //  total gallery files (incl. directories)
   nimages = image_navi::nimages;                                          //  total image files               v.11.05

   curr_file_posn = fposn;                                                 //  keep track of file position
   curr_file_count = image_navi::nimages;                                  //  and image gallery count

   add_recent_file(curr_file);                                             //  first in recent files list

   Fzoom = 0;                                                              //  zoom level = fit window
   zoomx = zoomy = 0;                                                      //  no zoom center

   pp = (char *) strrchr(curr_file,'/');
   strncpy0(fname,pp+1,99);                                                //  file name
   cc = pp - curr_file;
   if (cc < 99) strncpy0(fdirk,curr_file,cc+2);                            //  get dirk/path/ if short enough
   else {
      strncpy(fdirk,curr_file,96);                                         //  or use /dirk/path...
      strcpy(fdirk+95,"...");
   }

   fposn = fposn + 1 - nfiles + nimages;                                   //  position among images, 1-based
   snprintf(titlebar,250,"%s   %d/%d  %s  %s",                             //  window title bar
                  fversion,fposn,curr_file_count,fname,fdirk);
   gtk_window_set_title(MWIN,titlebar);

   mwpaint1();                                                             //  immediate paint              v.11.11
   gtk_window_present(MWIN);                                               //  bring main window to front   v.11.04
   zmainloop();             

   if (zdrename) m_rename(0,0);                                            //  update active rename dialog
   if (zdexifview) info_view(0);                                           //    "  EXIF/IPTC view window        v.10.2
   if (zdexifedit) m_info_edit(0,0);                                       //    "  EXIF/IPTC edit window        v.10.11
   if (zdedittags) m_edit_tags(0,0);                                       //    "  edit tags dialog
   if (zdeditcctext) m_edit_cctext(0,0);                                   //    "  edit comments dialog    v.10.10
   
   curr_image_time = get_seconds();                                        //  mark time of file load       v.11.07

   return 0;
}


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

//  open previous or next file in current gallery list

void m_prev(GtkWidget *, cchar *menu)
{
   int      err, Nth;

   if (menu) zfuncs::F1_help_topic = "open_image_file";

   if (Fmenulock) return;
   if (mod_keep()) return;
   if (! curr_file) return;

   for (Nth = curr_file_posn-1; Nth >= 0; Nth--)                           //  v.11.05
   {
      char *pp = image_gallery(0,"find",Nth);
      if (! pp) continue;
      err = f_open(pp,1,Nth);
      zfree(pp);
      if (! err) break;
   }
      
   return;
}

void m_next(GtkWidget *, cchar *menu)
{
   int      err, Nth;
   int      nfiles = image_navi::nfiles;

   if (menu) zfuncs::F1_help_topic = "open_image_file";

   if (Fmenulock) return;
   if (mod_keep()) return;
   if (! curr_file) return;

   for (Nth = curr_file_posn+1; Nth < nfiles; Nth++)                       //  v.11.05
   {
      char *pp = image_gallery(0,"find",Nth);
      if (! pp) continue;
      err = f_open(pp,1,Nth);
      zfree(pp);
      if (! err) break;
   }
      
   return;
}


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

//  save (modified) image to same file

void m_save(GtkWidget *, cchar *menu)
{
   int         Fwarn = 1, zstat, suppress;
   char        *pp;
   zdialog     *zd;
   cchar       *warn_message = ZTX("Overwrite original file?");
   cchar       *suppress_message = ZTX("Do not warn again");

   if (! curr_file) return;
   if (is_syncbusy()) return;                                              //  v.11.11

   if (menu && strNeq(menu,"nowarn"))                                      //  don't change help topic      v.11.11
      zfuncs::F1_help_topic = "save_file";

   if (Fwarnoverwrite) {                                                   //  warn if overwrite original   v.11.10
      pp = strrchr(curr_file,'/');
      if (! pp) return;
      pp = strstr(pp,".v");                                                //  look for version notation .vNN 
      if (pp && pp[2] >= '0' && pp[2] <= '9'
          && pp[3] >= '0' && pp[3] <= '9') Fwarn = 0;                      //  found, file not original
   
      if (Fwarn && ! (menu && strEqu(menu,"nowarn"))) {                    //  no warn if KB rotate save    v.11.11
         zd = zdialog_new(ZTX("Warning"),mWin,Bproceed,Bcancel,null);
         zdialog_add_widget(zd,"label","lab1","dialog",warn_message,"space=5");
         zdialog_add_widget(zd,"check","suppress","dialog",suppress_message,"space=5");
         zdialog_run(zd);
         zstat = zdialog_wait(zd);
         zdialog_fetch(zd,"suppress",suppress);
         zdialog_free(zd);
         if (suppress) {
            Fwarnoverwrite = 0;
            save_params();
         }
         if (zstat != 1) return;
      }
   }
   
   strcpy(jpeg_quality,def_jpeg_quality);                                  //  default jpeg save quality

   if (strEqu(curr_file_type,"other"))                                     //  if gif, bmp, etc. use jpg    v.11.03
      strcpy(curr_file_type,"jpg");

   f_save(curr_file,curr_file_type,curr_file_bpc);                         //  save file
   
   strcpy(curr_file_type,f_save_type);                                     //  update curr_file_xxx from f_save_xxx
   curr_file_size = f_save_size;
   
   return;
}


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

//  save (modified) image to new version of same file
//   - no confirmation of overwrite.

void m_savevers(GtkWidget *, cchar *)                                      //  new  v.11.07
{   
   char           *outfile, *pext, *pvers;
   cchar          *delim;
   int            ii, err, nvers;
   struct stat    fstat;

   if (! curr_file) return;
   if (is_syncbusy()) return;                                              //  v.11.11

   zfuncs::F1_help_topic = "save_file";

   strcpy(jpeg_quality,def_jpeg_quality);                                  //  default jpeg save quality

   outfile = strdupz(curr_file,12,"curr_file");                            //  output file name TBD

   pext = strrchr(outfile,'/');                                            //  find file .ext
   if (pext) pext = strrchr(pext,'.');
   if (pext && strlen(pext) > 5) pext = 0;
   if (! pext) pext = outfile + strlen(outfile);                           //  unknown, none

   pvers = pext - 4;                                                       //  find curr. version: filename.vNN.ext
   if (! strnEqu(pvers,".v",2)) nvers = 0;
   else {
      err = convSI(pvers+2,nvers,1,98,&delim);                             //  convert NN to number 1-98
      if (err > 1) nvers = 0;                                              //  conversion error
      if (delim != pext) nvers = 0;                                        //  check format is .vNN
   }
   if (nvers == 0) {                                                       //  no version in file name
      pvers = pext;
      pext += 4;
      memmove(pext,pvers,6);                                               //  make space for .vNN before .ext
      strncpy(pvers,".vNN",4);
   }

   for (ii = 98; ii > nvers; ii--)                                         //  look for higher file versions
   {
      pvers[2] = ii/10 + '0';                                              //  build filename.vNN.ext
      pvers[3] = ii - 10 * (ii/10) + '0';
      err = stat(outfile,&fstat);
      if (! err) break;
   }

   ii++;                                                                   //  use next version 1-99
   nvers = ii;
   pvers[2] = ii/10 + '0';                                                 //  build filename.vNN.ext
   pvers[3] = ii - 10 * (ii/10) + '0';

   f_save(outfile,curr_file_type,curr_file_bpc);                           //  save file (fails at 99 versions)

   load_fileinfo(outfile);                                                 //  update search index file
   update_search_index(outfile);

   image_gallery(outfile,"init");                                          //  update gallery
   image_gallery(outfile,"paint2",-1);                                     //  refresh gallery window if active

   f_open(outfile,1,0,1);                                                  //  new version = current file      v.11.11.1

   zfree(outfile);
   return;
}


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

//  save (modified) image to new file
//  confirm if overwrite existing file

GtkWidget   *saveas_fchooser;

void m_saveas(GtkWidget *, cchar *menu)
{
   void  saveas_radiobutt(void *, int button);
   void  saveas_kbkey(void *, GdkEventKey *event);

   GtkWidget      *fdialog, *hbox;
   GtkWidget      *tiff8, *tiff16, *jpeg, *png, *jqlab, *jqval;
   GtkWidget      *makecurrent;
   char           *outfile = 0, *outfile2 = 0, *pext;
   int            ii, err, yn, bpc, status, mkcurr = 0;
   struct stat    fstat;
   cchar          *type;
   cchar          *exts = ".jpg.JPG.jpeg.JPEG.tif.TIF.tiff.TIFF.png.PNG";

   if (! curr_file) return;
   if (is_syncbusy()) return;                                              //  v.11.11

   if (menu) zfuncs::F1_help_topic = "save_file";

   fdialog = gtk_dialog_new_with_buttons(ZTX("Save File"),                 //  build file save dialog
                           MWIN, GTK_DIALOG_MODAL,
                           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 
                           GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, null);
   gtk_window_set_default_size(GTK_WINDOW(fdialog),600,500);

   saveas_fchooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_SAVE);
   gtk_container_add(GTK_CONTAINER(GTK_DIALOG(fdialog)->vbox),saveas_fchooser);

   //  set_filename() should select the file but does nothing      GTK bug ?     v.10.9.1       /////
   //  set_current_name() puts file name in input box, but does not select the file
   gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(saveas_fchooser),curr_file);
   char *fname = strrchr(curr_file,'/') + 1;
   gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(saveas_fchooser),fname);
   
   hbox = gtk_hbox_new(0,0);
   gtk_container_add(GTK_CONTAINER(GTK_DIALOG(fdialog)->vbox),hbox);
   gtk_box_set_child_packing(GTK_BOX(GTK_DIALOG(fdialog)->vbox),hbox,0,0,10,GTK_PACK_END);

   tiff8 = gtk_radio_button_new_with_label(null,"tiff-8");
   tiff16 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff8),"tiff-16");
   png = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff8),"png");
   jpeg = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff8),"jpeg");
   jqlab = gtk_label_new(ZTX("quality"));
   jqval = gtk_entry_new();
   makecurrent = gtk_check_button_new_with_label(ZTX("make current"));

   gtk_entry_set_width_chars(GTK_ENTRY(jqval),2);
   gtk_box_pack_start(GTK_BOX(hbox),tiff8,0,0,5);                          //  (o) tiff8  (o) tiff16  (o) png
   gtk_box_pack_start(GTK_BOX(hbox),tiff16,0,0,5);
   gtk_box_pack_start(GTK_BOX(hbox),png,0,0,5);
   gtk_box_pack_start(GTK_BOX(hbox),jpeg,0,0,5);                           //  (o) jpeg  jpeg quality [__]
   gtk_box_pack_start(GTK_BOX(hbox),jqlab,0,0,0);
   gtk_box_pack_start(GTK_BOX(hbox),jqval,0,0,3);
   gtk_box_pack_end(GTK_BOX(hbox),makecurrent,0,0,3);                      //  [x] make current
   
   G_SIGNAL(tiff8,"pressed",saveas_radiobutt,0);                           //  connect file type radio buttons
   G_SIGNAL(tiff16,"pressed",saveas_radiobutt,1);                          //    to handler function
   G_SIGNAL(png,"pressed",saveas_radiobutt,2);
   G_SIGNAL(jpeg,"pressed",saveas_radiobutt,3);
   G_SIGNAL(fdialog,"key-release-event",saveas_kbkey,0);

   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(jpeg),1);                //  set default file type = jpeg
   gtk_entry_set_text(GTK_ENTRY(jqval),def_jpeg_quality);                  //  default jpeg save quality
   
   if (strEqu(curr_file_type,"png"))                                       //  default matches file type
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(png),1);              //    if png or tiff
   if (strEqu(curr_file_type,"tiff"))
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff8),1);
   if (curr_file_bpc == 16)
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff16),1);

dialog_run:

   gtk_widget_show_all(fdialog);                                           //  run dialog

   status = gtk_dialog_run(GTK_DIALOG(fdialog));
   if (status != GTK_RESPONSE_ACCEPT) {                                    //  user cancelled
      gtk_widget_destroy(fdialog);
      return;
   }

   outfile2 = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(saveas_fchooser));
   if (! outfile2) goto dialog_run;
   outfile = strdupz(outfile2,12,"curr_file");                             //  add space for possible .vNN and .ext
   g_free(outfile2);

   type = "jpg";                                                           //  default output type
   bpc = 8;

   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(png)))
      type = "png";
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff8)))
      type = "tif";
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff16))) {
      type = "tif";
      bpc = 16;
   }

   if (strEqu(type,"jpg")) {                                               //  set jpeg save quality
      ii = atoi(gtk_entry_get_text(GTK_ENTRY(jqval)));
      if (ii < 1 || ii > 100) {
         zmessageACK(mWin,ZTX("jpeg quality must be 1-100"));
         goto dialog_run;
      }
      sprintf(jpeg_quality,"%d",ii);
   }
   
   pext = strrchr(outfile,'/');                                            //  locate file .ext
   if (pext) pext = strrchr(pext,'.');
   if (pext && ! strstr(exts,pext)) pext = 0;                              //  keep .ext and append new    v.10.12.1
   if (! pext) {
      pext = outfile + strlen(outfile);                                    //  missing, add one
      strcpy(pext,".");
      strcpy(pext+1,type);
   }

   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(makecurrent)))       //  make saved file the current file?
      mkcurr = 1;
   
   gtk_widget_destroy(fdialog);                                            //  kill dialog    /// gtk bug, can crash

   err = stat(outfile,&fstat);                                             //  check if file exists
   if (! err) {
      yn = zmessageYN(mWin,ZTX("Overwrite file? \n %s"),outfile);          //  confirm overwrite
      if (! yn) {
         zfree(outfile);
         return;
      }
   }

   f_save(outfile,type,bpc);                                               //  save the file
   
   load_fileinfo(outfile);                                                 //  update search index file        v.11.07
   update_search_index(outfile);

   if (mkcurr)                                                             //  use the saved file as new current file
      f_open(outfile,1,0,1);                                               //    and retain edit history       v.11.07
   else if (samedirk(outfile,curr_file)) {                                 //  if same directory, update gallery
      image_gallery(outfile,"init");                                       //  add new file to gallery
      image_gallery(0,"paint2",curr_file_posn);                            //  refresh if active, keep position
   }

   zfree(outfile);
   return;
}


//  set dialog file type from user selection of file type radio button

void saveas_radiobutt(void *, int button)                                  //  v.9.4
{
   cchar    *filetypes[4] = { ".tif", ".tif", ".png", ".jpg" };            //  v.10.9
   char     *filespec;
   char     *filename, *pp;

   filespec = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(saveas_fchooser));
   if (! filespec) return;
   filename = strrchr(filespec,'/');
   if (! filename) return;
   filename = strdupz(filename+1,6,"saveas");
   pp = strrchr(filename,'.');
   if (! pp || strlen(pp) > 5) pp = filename + strlen(filename);           //  bugfix        v.10.9.1
   strcpy(pp,filetypes[button]);
   gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(saveas_fchooser),filename);
   gtk_widget_show_all(saveas_fchooser);
   zfree(filename);
   g_free(filespec);                                                       //  bugfix, leak    v.10.9.1
   return;
}


//  response function for KB key release

void saveas_kbkey(void *, GdkEventKey *event)                              //  v.10.5
{
   if (event->keyval == GDK_F1) 
      showz_userguide(zfuncs::F1_help_topic);
   return;
}


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

//  set new image zoom level or magnification
//  zoom:   "in"        zoom in in steps
//          "out"       zoom out in steps
//          "fit"       zoom out to fit window
//          "100"       toggle 100% and fit window

void m_zoom(GtkWidget *, cchar *zoom)
{
   int      iww, ihh, Dww, Dhh;
   double   scalew, scaleh, fitscale, fzoom2;
   static double ratio = 0;
   
   if (! ratio) {
      if (! zoomratio || strEqu(zoomratio,"undefined"))                    //  bugfix  12.01.1
         zoomratio = strdupz("1.4142136");      
      convSD(zoomratio,ratio);
      if (ratio < 1.1 || ratio > 2.0) ratio = 1.4142136;
   }
   
   Dww = drWin->allocation.width;                                          //  drawing window size
   Dhh = drWin->allocation.height;
   
   if (E3pxm16) {                                                          //  bugfix
      iww = E3ww;
      ihh = E3hh;
   }
   else  {
      iww = Fww;
      ihh = Fhh;
   }

   if (iww > Dww || ihh > Dhh) {                                           //  get window fit scale
      scalew = 1.0 * Dww / iww;
      scaleh = 1.0 * Dhh / ihh;
      if (scalew < scaleh) fitscale = scalew;
      else fitscale = scaleh;                                              //  window/image, < 1.0
   }
   else fitscale = 1.0;                                                    //  if image < window use 100%
   
   if (strEqu(zoom,"Zoom+")) zoom = "in";                                  //  toolbar button, + = zoom in
   if (strEqu(zoom,"Zoom-")) zoom = "fit";                                 //                  - = fit window

   if (strstr("in out",zoom))                                              //  zoom in or out
   {
      convSD(zoomratio,ratio);                                             //  zoom ratio, string to double
      if (! Fzoom) Fzoom = fitscale;                                       //  current zoom scale
      for (fzoom2 = 0.125; fzoom2 < 4.0; fzoom2 *= ratio)                  //  find nearest natural ratio
         if (Fzoom < fzoom2 * sqrt(ratio)) break;
      if (strEqu(zoom,"out")) ratio = 1.0 / ratio;                         //  zoom out, make image smaller
      Fzoom = fzoom2 * ratio;
      if (Fzoom > 0.124 && Fzoom < 0.126) Fzoom = 0.125;
      if (Fzoom > 0.24  && Fzoom < 0.26)  Fzoom = 0.25;
      if (Fzoom > 0.49  && Fzoom < 0.51)  Fzoom = 0.50;
      if (Fzoom > 0.99  && Fzoom < 1.01)  Fzoom = 1.00;
      if (Fzoom > 1.99  && Fzoom < 2.01)  Fzoom = 2.00;
      if (Fzoom > 3.99) Fzoom = 4.0;
      if (Fzoom < fitscale) Fzoom = 0;                                     //  image < window
   }
   
   if (strEqu(zoom,"fit")) Fzoom = 0;                                      //  zoom to fit window

   if (strEqu(zoom,"100")) {
      if (Fzoom != 0) Fzoom = 0;                                           //  toggle 100% and fit window
      else  Fzoom = 1;
   }

   if (! Fzoom) zoomx = zoomy = 0;                                         //  no req. zoom center

   mwpaint2();                                                             //  refresh window
   curr_image_time = get_seconds();                                        //  mark time of image change    v.11.07
   return;
}


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

//  create a new blank image with desired background color

void m_create(GtkWidget *, cchar *)                                        //  v.11.01
{
   int   create_dialog_event(zdialog *zd, cchar *event);

   zdialog     *zd;
   int         zstat;
   char        *prev_file = 0;

   zfuncs::F1_help_topic = "create";
   
   if (is_syncbusy()) return;                                              //  v.11.11
   if (mod_keep()) return;                                                 //  unsaved edits
   if (! menulock(1)) return;                                              //  lock menus

   if (curr_file) prev_file = strdupz(curr_file);

//    file name [___________________________] .jpg                         //  v.11.05
//    width [____]  height [____] (pixels)
//    color [____]
   
   zd = zdialog_new(ZTX("Create Blank Image"),mWin,Bdone,Bcancel,null);
   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labf","hbf",ZTX("file name"),"space=3");
   zdialog_add_widget(zd,"entry","file","hbf","no-name","space=3|expand");
   zdialog_add_widget(zd,"label","ftype","hbf",".jpg  ");
   zdialog_add_widget(zd,"hbox","hbz","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","space","hbz",0,"space=3");
   zdialog_add_widget(zd,"label","labw","hbz",ZTX("width"));
   zdialog_add_widget(zd,"spin","width","hbz","100|9999|1|1600");
   zdialog_add_widget(zd,"label","space","hbz",0,"space=5");
   zdialog_add_widget(zd,"label","labh","hbz",ZTX("height"));
   zdialog_add_widget(zd,"spin","height","hbz","100|9999|1|1000");
   zdialog_add_widget(zd,"label","space","hbz",0,"space=3");
   zdialog_add_widget(zd,"label","labp","hbz","(pixels) ");
   zdialog_add_widget(zd,"hbox","hbc","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","space","hbc",0,"space=3");
   zdialog_add_widget(zd,"label","labc","hbc",ZTX("color"));
   zdialog_add_widget(zd,"colorbutt","color","hbc","200|200|200");
   
   create_dialog_event(zd,"init");

   zdialog_help(zd,"create");                                              //  zdialog help topic     v.11.08
   zdialog_run(zd,create_dialog_event);
   zstat = zdialog_wait(zd);
   zdialog_free(zd);

   if (zstat != 1) {                                                       //  cancel
      if (prev_file) {
         f_open(prev_file,0);                                              //  back to previous file
         zfree(prev_file);
         mwpaint2();
      }
   }

   menulock(0);
   return;
}


//  dialog event and completion function

int create_dialog_event(zdialog *zd, cchar *event)                         //  overhauled    v.11.05
{
   char           color[20], fname[100], *filespec;
   cchar          *pp;
   int            fncc, ww, hh, err;
   static int     red, green, blue;
   uint8          *pixel;
   struct stat    statb;
   
   if (zd->zstat != 1) return 0;                                           //  [done]
   
   zdialog_fetch(zd,"file",fname,100);                                     //  get new file name
   strTrim2(fname);
   if (*fname <= ' ') strcpy(fname,"no-name");
   fncc = strlen(fname);
   
   filespec = strdupz(curr_dirk,fncc+8,"create");                          //  make full filespec
   strcat(filespec,"/");
   strcat(filespec,fname);
   strcat(filespec,".jpg");
   
   err = stat(filespec,&statb);                                            //  make sure it does not exist
   if (! err) {
      zmessageACK(mWin,"file already exists");
      zfree(filespec);
      zd->zstat = 0;                                                       //  keep dialog alive
      return 0;
   }
   
   if (curr_file) zfree(curr_file);                                        //  stop copy of metadata from old file
   curr_file = 0;                                                          //  bugfix     v.11.08

   zdialog_fetch(zd,"width",ww);                                           //  get image dimensions
   zdialog_fetch(zd,"height",hh);

   zdialog_fetch(zd,"color",color,19);                                     //  get image color
   pp = strField(color,"|",1);
   if (pp) red = atoi(pp);
   pp = strField(color,"|",2);
   if (pp) green = atoi(pp);
   pp = strField(color,"|",3);
   if (pp) blue = atoi(pp);
   
   mutex_lock(&Fpixmap_lock);                                              //  lock pixmaps

   PXM_free(Fpxm8);                                                        //  create new PXM image
   Fpxm8 = PXM_make(ww,hh,8);
   Fww = Fpxm8->ww;
   Fhh = Fpxm8->hh;
   pixel = (uint8 *) Fpxm8->bmp;

   for (int ii = 0; ii < ww * hh * 3; ii += 3) {
      pixel[ii] = red;
      pixel[ii+1] = green;
      pixel[ii+2] = blue;
   }

   mutex_unlock(&Fpixmap_lock);

   strcpy(jpeg_quality,def_jpeg_quality);
 
   err = f_save(filespec,"jpg",8);                                         //  save to disk
   if (err) {
      zmessageACK(mWin,"cannot save file");
      zfree(filespec);
      return 0;
   }

   f_open(filespec,0);                                                     //  make it the current file
   zfree(filespec);

   return 0;
}


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

//  Delete image file - move curr_file to trash.
//  Use new Linux standard trash function.                                 //  v.10.8
//  If not available, revert to Desktop folder for fotoxx trash.

void m_trash(GtkWidget *, cchar *)
{
   int            err, yn;
   char           *pp, trashdir[200];
   struct stat    trstat;
   static int     gstat, trashworks = 1;                                   //  assume it works until otherwise
   GError         *gerror = 0;
   GFile          *gfile = 0;
   cchar          *gerrmess = ZTX("Linux standard trash is not supported. \n"
                                  "Desktop trash folder will be created.");
   
   zfuncs::F1_help_topic = "trash";                                        //  v.10.8

   if (! curr_file) return;                                                //  nothing to trash

   if (is_syncbusy()) return;                                              //  v.11.11
   if (! menulock(1)) return;                                              //  check lock but leave unlocked
   menulock(0);

   err = stat(curr_file,&trstat);                                          //  get file status
   if (err) {
      zmessLogACK(mWin,strerror(errno));
      return;
   }

   if (! (trstat.st_mode & S_IWUSR)) {                                     //  check permission
      yn = zmessageYN(mWin,ZTX("Move read-only file to trash?"));
      if (! yn) return;
      trstat.st_mode |= S_IWUSR;
      chmod(curr_file,trstat.st_mode);
   }
   
   if (trashworks)                                                         //  try Linux standard trash
   {
      gfile = g_file_new_for_path(curr_file);
      gstat = g_file_trash(gfile,0,&gerror);
      if (! gstat) {
         printf("no standard trash: %s \n",gerror->message);
         zmessageACK(mWin,gerrmess);
         trashworks = 0;                                                   //  did not work
      }
   }

   if (! trashworks)
   {
      snprintf(trashdir,199,"%s/%s",getenv("HOME"),ftrash);                //  use fotoxx trash filespec

      trstat.st_mode = 0;
      err = stat(trashdir,&trstat);
      if (! S_ISDIR(trstat.st_mode)) {
         err = mkdir(trashdir,0750);                                       //  v.11.03
         if (err) {
            zmessLogACK(mWin,ZTX("Cannot create trash folder: %s"),wstrerror(errno));
            return;
         }
      }

      snprintf(command,ccc,"cp \"%s\" \"%s\" ",curr_file,trashdir);        //  copy image file to trash
      err = system(command);
      if (err) {
         zmessLogACK(mWin,ZTX("error: %s"),wstrerror(err));
         return;
      }

      err = remove(curr_file);                                             //  remove original file      v.11.03
      if (err) {
         zmessLogACK(mWin,ZTX("error: %s"),wstrerror(errno));
         return;
      }
   }

   delete_search_index(curr_file);                                         //  delete in search index file
   image_gallery(0,"delete",curr_file_posn);                               //  delete in gallery list
   pp = image_gallery(0,"find",curr_file_posn);                            //  open next file (now current position)
   if (! pp) pp = image_gallery(0,"find",curr_file_posn-1);                //  no next, get prev. if poss.        v.11.12
   if (! pp) {
      free_resources(0);                                                   //  leave screen blank                 v.11.12
      mwpaint2();
   }
   else {
      f_open(pp,1);                                                        //  open next/prev file
      zfree(pp);
      image_gallery(0,"paint2",curr_file_posn);                            //  refresh gallery window if active   v.11.07
   }

   return;
}


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

//  rename menu function
//  activate rename dialog, stuff data from current file
//  dialog remains active when new file is opened

char     rename_old[100] = "";
char     rename_new[100] = "";

void m_rename(GtkWidget *, cchar *menu)
{
   int rename_dialog_event(zdialog *zd, cchar *event);
   
   char     *pdir, *pfile, *pext;

   if (menu) zfuncs::F1_help_topic = "rename";

   if (! curr_file) return;
   if (is_syncbusy()) return;                                              //  v.11.11

   if (! zdrename)                                                         //  restart dialog
   {
      zdrename = zdialog_new(ZTX("Rename Image File"),mWin,Bcancel,null);
      zdialog_add_widget(zdrename,"hbox","hb1","dialog",0,"space=10");
      zdialog_add_widget(zdrename,"vbox","vb1","hb1",0,"homog|space=5");
      zdialog_add_widget(zdrename,"vbox","vb2","hb1",0,"homog|expand");

      zdialog_add_widget(zdrename,"button","Bold","vb1",ZTX("old name"));
      zdialog_add_widget(zdrename,"button","Bnew","vb1",ZTX("rename to"));
      zdialog_add_widget(zdrename,"button","Bprev","vb1",ZTX("previous"));

      zdialog_add_widget(zdrename,"hbox","hb21","vb2",0);                  //  [ old name ] [ oldname  ]
      zdialog_add_widget(zdrename,"hbox","hb22","vb2",0);                  //  [ new name ] [ newname  ] [+1]
      zdialog_add_widget(zdrename,"hbox","hb23","vb2",0);                  //  [ previous ] [ prevname ]

      zdialog_add_widget(zdrename,"label","Lold","hb21");
      zdialog_add_widget(zdrename,"entry","Enew","hb22",0,"expand|scc=30");
      zdialog_add_widget(zdrename,"button","B+1","hb22"," +1 ","space=5");
      zdialog_add_widget(zdrename,"label","Lprev","hb23");

      zdialog_help(zdrename,"rename");                                     //  zdialog help topic     v.11.08
      zdialog_run(zdrename,rename_dialog_event);                           //  run dialog
   }

   parsefile(curr_file,&pdir,&pfile,&pext);
   strncpy0(rename_old,pfile,99);
   strncpy0(rename_new,pfile,99);
   zdialog_stuff(zdrename,"Lold",rename_old);                              //  current file name
   zdialog_stuff(zdrename,"Enew",rename_new);                              //  entered file name

   return;
}


//  dialog event and completion callback function

int rename_dialog_event(zdialog *zd, cchar *event)
{
   char           *pp, *pdir, *pfile, *pext, *pnew, *pold;
   int            nseq, digits, ccp, ccn, ccx, err;
   struct stat    statb;
   
   if (zd->zstat) {                                                        //  complete
      zdialog_free(zdrename);                                              //  kill dialog
      return 0;
   }
   
   if (strEqu(event,"Bold"))                                               //  reset to current file name
      zdialog_stuff(zd,"Enew",rename_old);

   if (strEqu(event,"Bprev")) {                                            //  previous name >> new name
      zdialog_fetch(zd,"Lprev",rename_new,99);
      zdialog_stuff(zd,"Enew",rename_new);
   }

   if (strEqu(event,"B+1"))                                                //  increment sequence number
   {
      zdialog_fetch(zd,"Enew",rename_new,94);                              //  get entered filename
      pp = rename_new + strlen(rename_new);
      digits = 0;
      while (pp[-1] >= '0' && pp[-1] <= '9') {
         pp--;                                                             //  look for NNN in filenameNNN
         digits++;
      }
      nseq = 1 + atoi(pp);                                                 //  NNN + 1
      if (nseq > 9999) nseq = 0;
      if (digits < 2) digits = 2;                                          //  keep digit count if enough
      if (nseq > 99 && digits < 3) digits = 3;                             //  use leading zeros
      if (nseq > 999 && digits < 4) digits = 4;
      snprintf(pp,digits+1,"%0*d",digits,nseq);
      zdialog_stuff(zd,"Enew",rename_new);
   }

   if (strEqu(event,"Bnew"))                                               //  [rename to] button
   {
      if (is_syncbusy()) return 0;                                         //  v.11.11
      if (! menulock(1)) return 0;                                         //  check lock but leave unlocked
      menulock(0);

      parsefile(curr_file,&pdir,&pfile,&pext);                             //  existing /directories/file.ext

      zdialog_fetch(zd,"Enew",rename_new,94);                              //  new file name from user

      ccp = strlen(pdir);                                                  //  length of /directories/
      ccn = strlen(rename_new);                                            //  length of file
      if (pext) ccx = strlen(pext);                                        //  length of .ext
      else ccx = 0;

      pnew = zmalloc(ccp + ccn + ccx + 1,"rename");                        //  put it all together
      strncpy(pnew,curr_file,ccp);                                         //   /directories/file.ext
      strcpy(pnew+ccp,rename_new);
      if (ccx) strcpy(pnew+ccp+ccn,pext);
      
      err = stat(pnew,&statb);                                             //  check if new name exists
      if (! err) {
         zmessageACK(mWin,ZTX("The target file already exists"));
         zfree(pnew);
         return 0;
      }
      
      snprintf(command,ccc,"cp -p \"%s\" \"%s\"",curr_file,pnew);          //  copy to new file   -p v.10.3
      err = system(command);
      if (err) {
         zmessageACK(mWin,ZTX("Rename failed: \n %s"),wstrerror(err));
         printf("command: %s \n",command);
         zfree(pnew);
         return 0;
      }

      zdialog_stuff(zd,"Lprev",rename_new);                                //  set previous name in dialog

      load_fileinfo(pnew);                                                 //  update search index file
      update_search_index(pnew);

      pold = strdupz(curr_file,0,"curr_file");                             //  save file name to be deleted
      delete_search_index(pold);                                           //  delete in search index       v.9.7
      err = remove(pold);                                                  //  delete file                  v.11.03

      err = f_open(pnew,1);                                                //  no automatic "next"          v.11.08
      image_gallery(curr_file,"init");                                     //  update gallery               v.11.05
      image_gallery(0,"paint2",curr_file_posn);                            //  refresh gallery window if active

      zfree(pnew);
      zfree(pold);
   }
   
   return 0;
}


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

//  menu function - batch rename files

char     **batchrename_filelist = 0;
int      batchrename_filecount = 0;

void m_batchrename(GtkWidget *, cchar *)                                   //  new v.9.7
{
   int   batchrename_dialog_event(zdialog *zd, cchar *event);
   
   zdialog  *zd;

   if (zdrename) return;                                                   //  interactive rename is active

   if (is_syncbusy()) return;                                              //  v.11.11
   if (mod_keep()) return;                                                 //  unsaved edits
   if (! menulock(1)) return;                                              //  lock menus       v.10.8

   zfuncs::F1_help_topic = "batch_rename";                                 //  v.10.8

   zd = zdialog_new(ZTX("Batch Rename"),mWin,Bproceed,Bcancel,null);
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hb1",Bselectfiles,"space=10");
   zdialog_add_widget(zd,"label","labcount","hb1",ZTX("%d files selected"),"space=10");
   zdialog_add_widget(zd,"hbox","hb2","dialog","space=5");
   zdialog_add_widget(zd,"label","lab2","hb2",ZTX("new base name"),"space=10");
   zdialog_add_widget(zd,"entry","basename","hb2");
   zdialog_add_widget(zd,"hbox","hb3","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","lab31","hb3",ZTX("starting sequence"),"space=10");
   zdialog_add_widget(zd,"entry","sequence","hb3","100","scc=5");
   zdialog_add_widget(zd,"label","lab32","hb3",ZTX("increment"),"space=10");
   zdialog_add_widget(zd,"entry","increment","hb3","01","scc=3");

   batchrename_filelist = 0;
   batchrename_filecount = 0;
   
   zdialog_help(zd,"batch_rename");                                        //  zdialog help topic     v.11.08
   zdialog_run(zd,batchrename_dialog_event);                               //  run dialog
   zdialog_wait(zd);                                                       //  wait for completion
   
   zdialog_free(zd);
   menulock(0);
   return;
}


//  dialog event and completion callback function

int batchrename_dialog_event(zdialog *zd, cchar *event)
{
   char           **flist = batchrename_filelist, countmess[50];
   cchar          *selectmess = ZTX("select files to rename");
   int            ii, err, cc, ccp, ccn, ccx;
   int            sequence, increment, adder;
   char           basename[100], filename[120], *oldfile, *newfile;
   char           *pdir, *pfile, *pext;
   cchar          *errmess = ZTX("base name / sequence / increment not reasonable");
   struct stat    statb;

   if (strEqu(event,"files"))                                              //  select images to rename
   {
      if (flist) {                                                         //  free prior list
         for (ii = 0; flist[ii]; ii++) 
            zfree(flist[ii]);
         zfree(flist);
      }

      flist = zgetfileN(selectmess,"openN",curr_file);                     //  get file list from user
      batchrename_filelist = flist;

      if (flist)                                                           //  count files in list
         for (ii = 0; flist[ii]; ii++);
      else ii = 0;
      batchrename_filecount = ii;

      snprintf(countmess,50,ZTX("%d files selected"),batchrename_filecount);
      zdialog_stuff(zd,"labcount",countmess);
   }
   
   if (! zd->zstat) return 0;                                              //  dialog active

   if (zd->zstat != 1) goto cleanup;                                       //  dialog canceled
   if (! batchrename_filecount) goto cleanup;                              //  no files selected

   zdialog_fetch(zd,"basename",basename,99);
   zdialog_fetch(zd,"sequence",sequence);
   zdialog_fetch(zd,"increment",increment);

   if (strlen(basename) < 1 || sequence < 1 || increment < 1) {
      zd->zstat = 0;                                                       //  keep dialog alive      bugfix v.10.7
      zmessageACK(mWin,errmess);
      return 0;
   }

   write_popup_text("open","Renaming files",500,200,mWin);                 //  status monitor popup window    v.10.3
   
   for (ii = 0; flist[ii]; ii++)
   {
      oldfile = flist[ii];
      parsefile(oldfile,&pdir,&pfile,&pext);
      ccp = strlen(pdir);
      if (pext) ccx = strlen(pext);
      else ccx = 0;

      adder = sequence + ii * increment;
      snprintf(filename,119,"%s%d",basename,adder);                        //  removed "-" between        v.10.7
      ccn = strlen(filename);

      newfile = zmalloc(ccp + ccn + ccx + 1,"rename");                     //  construct /path/filename.ext
      strcpy(newfile,pdir);
      strcpy(newfile+ccp,filename);
      if (ccx) strcpy(newfile+ccp+ccn,pext);

      err = stat(newfile,&statb);
      if (! err) {
         snprintf(command,ccc,"%s %s",ZTX("new file already exists:"),newfile);
         write_popup_text("write",command);
         zfree(newfile);
         break;
      }

      cc = snprintf(command,ccc,"cp -p \"%s\" \"%s\"",oldfile,newfile);    //  copy to new file   -p v.10.3
      if (cc >= maxfcc*2) {
         snprintf(command,ccc,"%s %s",ZTX("filespec too long:"),oldfile);
         write_popup_text("write",command);
         zfree(newfile);
         break;
      }

      write_popup_text("write",command);                                   //  report progress
      zmainloop();

      err = system(command);
      if (err) {
         snprintf(command,ccc,"%s %s",ZTX("Rename failed:"),wstrerror(err));
         write_popup_text("write",command);
         zfree(newfile);
         break;
      }

      load_fileinfo(newfile);                                              //  update search index file
      update_search_index(newfile);
      zfree(newfile);

      err = remove(oldfile);                                               //  delete old file           v.11.03
      delete_search_index(oldfile);                                        //  remove from search index
      zmainloop();
   }

   write_popup_text("write","COMPLETED");
   write_popup_text("close",0);

   image_gallery(curr_file,"init");                                        //  update gallery file list
   image_gallery(0,"paint2",curr_file_posn);                               //  refresh gallery window if active

cleanup:

   if (batchrename_filecount) {
      for (ii = 0; flist[ii]; ii++)
         zfree(flist[ii]);
      zfree(flist);
      batchrename_filecount = 0;                                           //  bugfix v.12.01
   }

   return 0;
}


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

//  print current image file

double print_size[2] = { 29.7, 21.0 };                                     //  default A4 landscape
double print_margins[4] = { 0.5, 0.5, 0.5, 0.5 };                          //  top, bottom, left, right

void m_print(GtkWidget *, cchar *)                                         //  use GTK print   v.9.4
{
   PXM * print_addgrid(PXM *printimage);                                   //  v.11.01

   int      err;
   char     *printfile;
   PXM      *printimage, *pxmtemp;
   
   zfuncs::F1_help_topic = "print";
   
   if (! curr_file) return;                                                //  no image file
   
   printfile = strdupz(get_zuserdir(),20,"printfile");                     //  make temp print file:
   strcat(printfile,"/printfile.tif");                                     //    ~/.fotoxx/printfile.tif    v.11.03
   
   if (Fpxm16) printimage = PXM_convbpc(Fpxm16);
   else printimage = PXM_copy(Fpxm8);
   
   pxmtemp = print_addgrid(printimage);                                    //  add grid lines if wanted     v.11.01
   if (pxmtemp) {
      PXM_free(printimage);
      printimage = pxmtemp;
   }
   
   err = PXBwrite(printimage,printfile);
   PXM_free(printimage);

   if (err) {
      zfree(printfile);                                                    //  v.10.3
      return;
   }
   
   print_image_paper_setup(mWin);                                          //  new v.11.10
   print_image_margins_setup(mWin);
   print_image_file(printfile);
   
   zfree(printfile);
   return;
}


//  add grid lines to print image if wanted

PXM * print_addgrid(PXM *printimage)                                       //  v.11.01
{
   PXM      *temp8;
   uint8    *pixel;
   int      px, py, ww, hh, row;
   int      startx, starty, stepx, stepy;
   
   if (! Fgrid) return 0;                                                  //  grid lines off

   temp8 = f_load(curr_file,8);
   if (! temp8) return 0;
   
   ww = temp8->ww;
   hh = temp8->hh;
   row = ww * 3;

   stepx = gridspace[0];                                                   //  space between grid lines
   stepy = gridspace[1];
   
   stepx = stepx / Mscale;                                                 //  window scale to image scale
   stepy = stepy / Mscale;
   
   if (gridcount[0]) stepx = ww / (1 + gridcount[0]);                      //  if line counts specified,
   if (gridcount[1]) stepy = hh / ( 1 + gridcount[1]);                     //    set spacing accordingly
   
   startx = stepx * gridoffset[0] / 100;                                   //  variable offsets             v.11.11
   if (startx < 0) startx += stepx;

   starty = stepy * gridoffset[1] / 100;
   if (starty < 0) starty += stepy;

   if (gridon[0]) {
      for (px = startx; px < ww-1; px += stepx)
      for (py = 0; py < hh; py++)
      {
         pixel = PXMpix8(temp8,px,py);                                     //  adjoining white and black lines
         pixel[0] = pixel[1] = pixel[2] = 255;
         pixel[3] = pixel[4] = pixel[5] = 0;
      }
   }

   if (gridon[1]) {
      for (py = starty; py < hh-1; py += stepy)
      for (px = 0; px < ww; px++)
      {
         pixel = PXMpix8(temp8,px,py);
         pixel[0] = pixel[1] = pixel[2] = 255;
         pixel[row] = pixel[row+1] = pixel[row+2] = 0;
      }
   }

   return temp8;
}


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

//  normal quit menu function

void m_quit(GtkWidget *, cchar *)
{
   if (mod_keep()) return;                                                 //  unsaved edits
   printf("quit \n");
   Fshutdown++;                                                            //  bugfix  v.10.11

   for (int ii = 0; ii < 100; ii++)                                        //  wait if something running
      if (Ffuncbusy) {                                                     //  v.11.01
         zmainloop();
         zsleep(0.01);
      }
   
   if (Ffuncbusy) printf("busy function killed");                          //  v.11.05
   
   gtk_window_get_position(MWIN,&mwgeom[0],&mwgeom[1]);                    //  get last window position     v.11.07
   gtk_window_get_size(MWIN,&mwgeom[2],&mwgeom[3]);                        //    and size for next session
   save_params();                                                          //  save state for next session
   zdialog_positions("save");                                              //  save dialog positions too    v.11.07
   free_resources();                                                       //  delete temp files
   if (KBzmalloclog) zmalloc_report();                                     //  report memory v.10.8
   fflush(null);                                                           //  flush stdout, stderr      v.11.05
   gtk_main_quit();                                                        //  gone forever
   return;
}


/**************************************************************************
   plugin menu functions
**************************************************************************/

//  process plugin menu selection
//  execute correspinding command using current image file

editfunc    EFplugin;

void  m_run_plugin(GtkWidget *, cchar *menu)                               //  v.11.03
{
   int         ii, jj, err;
   char        *pp = 0, pluginfile[200];
   PXM         *pxmtemp;
   
   zfuncs::F1_help_topic = "plugins";
   
   for (ii = 0; ii < Nplugins; ii++)                                       //  search plugins for menu name
   {
      pp = strstr(plugins[ii]," = ");
      if (! pp) continue;
      *pp = 0;
      jj = strEqu(plugins[ii],menu);
      *pp = ' ';
      if (jj) break;
   }

   if (ii == Nplugins) {
      zmessageACK(mWin,"plugin menu not found %s",menu);
      return;
   }

   pp += 3;
   if (strlen(pp) < 3) {
      zmessageACK(mWin,"no plugin command");
      return;
   }

   strncpy0(command,pp,ccc);                                               //  corresp. command
   strTrim2(command);

   EFplugin.funcname = menu;
   if (! edit_setup(EFplugin)) return;                                     //  setup edit

   snprintf(pluginfile,199,"%s/plugfile.tif",get_zuserdir());              //  /home/user/.fotoxx/plugfile.tif
   
   TIFFwrite(E1pxm16,pluginfile);                                          //  E1 >> plugin_file

   strcat(command," ");                                                    //  construct: command /.../plugin_file &
   strcat(command,pluginfile);
   printf("plugin: %s \n",command);

   err = system(command);                                                  //  execute plugin command
   if (err) {
      zmessageACK(mWin,"plugin command error");
      edit_cancel(EFplugin);
      return;
   }
   
   pxmtemp = TIFFread(pluginfile);                                         //  read command output file
   if (! pxmtemp) {
      zmessageACK(mWin,"plugin failed");
      edit_cancel(EFplugin);
      return;
   }
   
   PXM_free(E3pxm16);                                                      //  plugin_file >> E3
   if (pxmtemp->bpc == 16) E3pxm16 = pxmtemp;
   else {
      E3pxm16 = PXM_convbpc(pxmtemp);
      PXM_free(pxmtemp);
   }

   EFplugin.Fmod = 1;                                                      //  assume image was modified
   edit_done(EFplugin);

   return;
}   


//  edit plugins menu

void  m_edit_plugins(GtkWidget *, cchar *)                                 //  v.11.03
{
   int   edit_plugins_event(zdialog *zd, cchar *event);

   int         ii;
   char        *pp;
   zdialog     *zd;
   
   zfuncs::F1_help_topic = "plugins";
   
   zd = zdialog_new("Edit Plugins",mWin,ZTX("Add"),ZTX("Remove"),Bdone,null);
   zdialog_add_widget(zd,"hbox","hbm","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labm","hbm",ZTX("menu name"),"space=5");
   zdialog_add_widget(zd,"comboE","menu","hbm",0,"space=5");
   zdialog_add_widget(zd,"hbox","hbc","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labc","hbc","command","space=5");
   zdialog_add_widget(zd,"entry","command","hbc",0,"space=5|expand");

   for (ii = 0; ii < Nplugins; ii++)                                       //  stuff combo box with available
   {                                                                       //     plugin menu names
      pp = strstr(plugins[ii]," = ");
      if (! pp) continue;
      *pp = 0;
      zdialog_cb_app(zd,"menu",plugins[ii]);
      *pp = ' ';
   }
   
   zdialog_help(zd,"plugins");                                             //  zdialog help topic     v.11.08
   zdialog_run(zd,edit_plugins_event);
   return;
}


//  dialog event function

int  edit_plugins_event(zdialog *zd, cchar *event)
{
   int      ii, jj, cc, zstat;
   char     menu[40], *pp = 0;
   
   zdialog_fetch(zd,"menu",menu,40);                                       //  selected menu
   zdialog_fetch(zd,"command",command,ccc);
   
   if (strEqu(event,"menu")) 
   {
      for (ii = 0; ii < Nplugins; ii++)
      {
         pp = strstr(plugins[ii]," = ");                                   //  find corresp. plugin record
         if (! pp) continue;
         *pp = 0;
         jj = strEqu(plugins[ii],menu);
         *pp = ' ';
         if (jj) break;
      }
      
      if (ii == Nplugins) return 0;

      pp += 3;
      if (strlen(pp) < 3) return 0;
      strncpy0(command,pp,ccc);
      zdialog_stuff(zd,"command",command);                                 //  stuff corresp. command in dialog
      
      return 0;
   }
   
   zstat = zd->zstat;
   if (! zstat) return 0;
   
   if (zstat == 1)                                                         //  add new plugin
   {
      if (strlen(menu) < 3 || strlen(command) < 3) return 0;
      if (Nplugins == maxplugins) {
         zmessageACK(mWin,"too many plugins");
         return 0;
      }
      ii = Nplugins;                                                       //  add plugin record
      cc = strlen(menu) + strlen(command) + 5;
      plugins[ii] = zmalloc(cc,"plugins");                                 //  format: menu = command
      strcpy(plugins[ii],menu);
      strcat(plugins[ii]," = ");
      strcat(plugins[ii],command);
      Nplugins++;

      zmessageACK(mWin,ZTX("Restart Fotoxx to update plugin menu"));
   }
   
   if (zstat == 2)                                                         //  remove current plugin
   {
      for (ii = 0; ii < Nplugins; ii++)
      {
         pp = strstr(plugins[ii]," = ");                                   //  find corresp. plugin record
         if (! pp) continue;
         *pp = 0;
         jj = strEqu(plugins[ii],menu);
         *pp = ' ';
         if (jj) break;
      }
      
      if (ii == Nplugins) return 0;
      
      Nplugins--;                                                          //  remove plugin record
      for (jj = ii; jj < Nplugins; jj++)
         plugins[jj] = plugins[jj+1];
      
      zmessageACK(mWin,ZTX("Restart Fotoxx to update plugin menu"));
   }
   
   if (zstat == 3) {                                                       //  done
      zdialog_free(zd);
      return 0;
   }
   
   return 0;
}



