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

   Fotocx - edit photos and manage collections

   Copyright 2007-2024 Michael Cornelison
   source code URL: https://kornelix.net
   contact: mkornelix@gmail.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. See https://www.gnu.org/licenses

   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.

*********************************************************************************

   Fotocx image edit - Batch Menu functions

   m_batch_convert         batch rename, convert, rescale, move
   m_batch_copy_move       batch copy/move selected files to another folder
   m_batch_upright         find rotated files and upright them
   m_batch_deltrash        delete or trash selected files
   m_batch_RAW             convert RAW files to jpg/png/tif, 8/16 bits
   m_batch_overlay         add overlay image (caption ...) to selected files
   m_export_filelist       select files and generate a file list (text)
   m_export_files          select files and export to a folder
   m_edit_script           edit a script file calling multiple edit functions
   m_edit_script_addfunc   edit_done() hook to insert dialog settings into script
   m_run_script            run script like edit function for current image file
   m_batch_script          run script for batch of selected image files

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

#define EX extern                                                                //  enable extern declarations
#include "fotocx.h"                                                              //  (variables in fotocx.h are refs)
#include <sys/wait.h>

using namespace zfuncs;

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


//  Batch file rename, convert, rescale, move

namespace batch_convert
{
   int      Fsametype, Fupsize, Fdownsize, maxww, maxhh;
   int      Fsamebpc, newbpc, jpeg_quality;
   int      Fdelete, Fcopymeta, Freplace;
   int      Fnewtext;
   int      Fplugdate, Fplugseq, Fplugname;
   ch       text1[100], text2[100];
   ch       newloc[500], newname[200], newext[8];
   int      baseseq, addseq;
   int      amount, thresh;
};


//  menu function

void m_batch_convert(GtkWidget *, ch *)
{
   using namespace batch_convert;

   int  batch_convert_dialog_event(zdialog *zd, ch *event);

   zdialog     *zd, *zdpop;
   int         zstat;
   ch          *infile, *outfile, tempfile[300];
   ch          *inloc, *inname, *inext;
   ch          *outloc, *outname, *outext;
   ch          *tempname, seqplug[8], seqnum[8];
   ch          plugyyyy[8], plugmm[4], plugdd[4];
   ch          **oldfiles, **newfiles;
   ch          *pp, *pp1, text[100];
   int         ii, jj, cc, err, Fjpeg;
   int         outww, outhh, outbpc, Fdelete2;
   int         Noldnew;
   float       scale, wscale, hscale;
   PXM         *pxmin, *pxmout;
   xxrec_t     *xxrec;

   F1_help_topic = "batch convert";

   Plog(1,"m_batch_convert \n");

   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }
   
   if (Fblock("batch convert")) return;

/***
       ______________________________________________________________
      |                    Batch Convert                             |
      |                                                              |
      |  [Select Files]  N files selected                            |
      |                                                              |
      |  Replace Text  [__________________] --> [__________________] |           replace substring in file names
      |  New Name [________________________________________________] |           $yyyy $mm $dd $oldname $ss
      |  Sequence Numbers   base [_____]  adder [____]               |
      |  New Location [__________________________________] [browse]  |
      |                                                              |
      |  (o) tif  (o) png  (o) jpg [90] jpg quality  (o) no change   |
      |  Color Depth: (o) 8-bit  (o) 16-bit  (o) no change           |
      |  Max. Width [____]  Height [____]  [x] downsize  [x] upsize  |
      |  [x] Delete Originals  [x] Copy Metadata                     |
      |                                                              |
      |                                                [Proceed] [X] |
      |______________________________________________________________|

***/

   zd = zdialog_new("Batch Convert",Mwin,"Proceed"," X ",null);

   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf","Select Files","space=5");
   zdialog_add_widget(zd,"label","fcount","hbf","no files selected","space=10");

   zdialog_add_widget(zd,"hbox","hbrep","dialog");
   zdialog_add_widget(zd,"label","labrep1","hbrep","Replace Text","space=5");
   zdialog_add_widget(zd,"zentry","text1","hbrep",0,"expand");
   zdialog_add_widget(zd,"label","arrow","hbrep"," → ","space=5");
   zdialog_add_widget(zd,"zentry","text2","hbrep",0,"expand");
   zdialog_add_ttip(zd,"labrep1","replace substring within file name");

   zdialog_add_widget(zd,"hbox","hbname","dialog");
   zdialog_add_widget(zd,"label","labname","hbname","New Name","space=5");
   zdialog_add_widget(zd,"zentry","newname","hbname",0,"expand");
   zdialog_add_ttip(zd,"labname","plugins: (year month day old-name sequence) $yyyy  $mm  $dd  $oldname  $s");

   zdialog_add_widget(zd,"hbox","hbseq","dialog");
   zdialog_add_widget(zd,"label","labseq","hbseq","Sequence Numbers","space=5");
   zdialog_add_widget(zd,"label","space","hbseq",0,"space=8");
   zdialog_add_widget(zd,"label","labbase","hbseq","base","space=5");
   zdialog_add_widget(zd,"zspin","baseseq","hbseq","0|100000|1|0","size=5");
   zdialog_add_widget(zd,"label","space","hbseq","","space=3");
   zdialog_add_widget(zd,"label","labadder","hbseq","adder","space=5");
   zdialog_add_widget(zd,"zspin","addseq","hbseq","0|100|1|0","size=3");
   zdialog_add_widget(zd,"label","space","hbseq",0,"space=60");                  //  push back oversized entries

   zdialog_add_widget(zd,"hbox","hbloc","dialog");
   zdialog_add_widget(zd,"label","labloc","hbloc","New Location","space=5");
   zdialog_add_widget(zd,"zentry","newloc","hbloc",0,"expand");
   zdialog_add_widget(zd,"button","browse","hbloc","Browse","space=5");

   zdialog_add_widget(zd,"hbox","hbft","dialog");
   zdialog_add_widget(zd,"label","labtyp","hbft","New File Type","space=5");
   zdialog_add_widget(zd,"radio","tif","hbft","tif","space=4");
   zdialog_add_widget(zd,"radio","png","hbft","png","space=4");
   zdialog_add_widget(zd,"radio","jpg","hbft","jpg","space=2");
   zdialog_add_widget(zd,"zspin","jpgqual","hbft","10|100|1|90","size=3");
   zdialog_add_widget(zd,"label","labqual","hbft","jpg quality","space=6");
   zdialog_add_widget(zd,"radio","sametype","hbft","no change","space=6");

   zdialog_add_widget(zd,"hbox","hbcd","dialog");
   zdialog_add_widget(zd,"label","labcd","hbcd","Color Depth:","space=5");
   zdialog_add_widget(zd,"radio","8-bit","hbcd","8-bit","space=4");
   zdialog_add_widget(zd,"radio","16-bit","hbcd","16-bit","space=4");
   zdialog_add_widget(zd,"radio","samebpc","hbcd","no change","space=4");

   zdialog_add_widget(zd,"hbox","hbwh","dialog");
   zdialog_add_widget(zd,"label","labw","hbwh","Max. Width","space=5");
   zdialog_add_widget(zd,"zspin","maxww","hbwh","0|10000|1|1000","size=5");
   zdialog_add_widget(zd,"label","space","hbwh",0,"space=5");
   zdialog_add_widget(zd,"label","labh","hbwh","Height","space=5");
   zdialog_add_widget(zd,"zspin","maxhh","hbwh","0|10000|1|700","size=5");
   zdialog_add_widget(zd,"check","downsize","hbwh","downsize","space=12");
   zdialog_add_widget(zd,"check","upsize","hbwh","upsize","space=12");
   zdialog_add_widget(zd,"label","space","hbwh",0,"space=30");                   //  push back oversized entries
   zdialog_add_ttip(zd,"downsize","reduce to these limits if larger");
   zdialog_add_ttip(zd,"upsize","expand to these limits if smaller");

   zdialog_add_widget(zd,"hbox","hbopts","dialog");
   zdialog_add_widget(zd,"check","delete","hbopts","Delete Originals","space=3");
   zdialog_add_widget(zd,"check","copymeta","hbopts","Copy Metadata","space=5");

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"fcount",text);

   zdialog_stuff(zd,"tif",0);
   zdialog_stuff(zd,"png",0);
   zdialog_stuff(zd,"jpg",0);
   zdialog_stuff(zd,"jpgqual",jpeg_def_quality);
   zdialog_stuff(zd,"sametype",1);                                               //  same file type

   zdialog_stuff(zd,"8-bit",0);
   zdialog_stuff(zd,"16-bit",0);
   zdialog_stuff(zd,"samebpc",1);                                                //  same bits/color

   zdialog_stuff(zd,"downsize",0);                                               //  no size change
   zdialog_stuff(zd,"upsize",0);
   zdialog_stuff(zd,"delete",0);                                                 //  delete originals - no
   zdialog_stuff(zd,"copymeta",1);                                               //  copy metadata - yes

   *newloc = 0;
   oldfiles = newfiles = 0;
   Noldnew = 0;

   zdialog_load_inputs(zd);                                                      //  preload prior user inputs
   zdialog_run(zd,batch_convert_dialog_event,"parent");                          //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);
   if (zstat != 1) goto cleanup;                                                 //  canceled
   if (! SFcount) goto cleanup;

   free_resources();                                                             //  no curr. file

   if (Fdelete) {
      cc = SFcount * sizeof(ch *);                                               //  input files are to be deleted:
      oldfiles = (ch **) zmalloc(cc,"batch convert");                            //    reserve space to hold list of
      newfiles = (ch **) zmalloc(cc,"batch convert");                            //    old/new filespecs to update albums
   }

   zdpop = popup_report_open("Processing files",Mwin,600,300,0,0,0,"X",0);       //  log report

   Funcbusy(+1);                                                                 //  24.10

   for (ii = 0; ii < SFcount; ii++)                                              //  loop selected files
   {
      zmainloop();                                                               //  keep GTK alive

      if (! zdialog_valid(zdpop)) break;                                         //  report canceled

      infile = SelFiles[ii];                                                     //  input file

      popup_report_write2(zdpop,0,"\n");
      popup_report_write2(zdpop,0,"%s \n",infile);                               //  log each input file

      if (image_file_type(infile) != IMAGE) {
         popup_report_write2(zdpop,0,"*** invalid file \n");
         continue;
      }

      parsefile(infile,&inloc,&inname,&inext);                                   //  parse folder, filename, .ext
      if (! inloc || ! inname || ! inext) continue;

      outloc = zstrdup(inloc,"batch convert");                                   //  initial output = input file
      outname = zstrdup(inname,"batch convert");
      outext = zstrdup(inext,"batch convert",4);

      cc = strlen(outloc) - 1;                                                   //  remove trailing '/'
      if (outloc[cc] == '/') outloc[cc] = 0;

      if (*newname) {
         zfree(outname);
         outname = zstrdup(newname,"batch convert");                             //  may contain $-plugins
      }

      if (Fnewtext) {
         tempname = zstrdup(outname,"batch convert",100);
         cc = strlen(outname) + 100;
         repl_1str(outname,tempname,cc,text1,text2);                             //  replace text1 (if present) with text2
         zfree(outname);
         outname = tempname;
      }

      if (Fplugname)                                                             //  insert old file name
      {
         cc = strlen(outname) - 8 + strlen(inname);
         if (cc < 1) cc = 1;
         tempname = zstrdup(outname,"batch convert",cc);
         cc += strlen(outname);
         repl_1str(outname,tempname,cc,"$oldname",inname);                       // ...$oldname...  >>  ...inname...
         zfree(outname);
         outname = tempname;
      }

      if (Fplugdate)                                                             //  insert meta date
      {
         xxrec = get_xxrec(infile);                                              //  get photo date, yyyymmddhhmmdd

         if (strmatch(xxrec->pdate,"null")) {
            popup_report_write2(zdpop,0,"no photo date, skipped \n");
            zfree(outloc);
            zfree(outname);
            zfree(outext);
            continue;
         }

         strncpy0(plugyyyy,xxrec->pdate,5);                                      //  yyyy
         strncpy0(plugmm,xxrec->pdate+4,3);                                      //  mm
         strncpy0(plugdd,xxrec->pdate+6,3);                                      //  dd
         tempname = zstrdup(outname,"batch convert",8);
         cc = strlen(outname) + 8;
         repl_Nstrs(outname,tempname,cc,"$yyyy",plugyyyy,"$mm",plugmm,"$dd",plugdd,null);
         zfree(outname);
         outname = tempname;
      }

      if (Fplugseq)                                                              //  insert sequence number
      {
         pp = strcasestr(outname,"$s");                                          //  find $s... in output name
         if (pp) {
            for (cc = 1; pp[cc] == pp[1] && cc < 6; cc++);                       //  length of "$s...s"  2-6 chars.
            strncpy0(seqplug,pp,cc+1);
            jj = baseseq + ii * addseq;                                          //  new sequence number
            snprintf(seqnum,8,"%0*d",cc-1,jj);                                   //  1-5 chars.
            tempname = zstrdup(outname,"batch convert",8);
            cc = strlen(outname) + 8;
            repl_1str(outname,tempname,cc,seqplug,seqnum);                       //  ...$ssss...  >>  ...1234...
            zfree(outname);
            outname = tempname;
         }
      }

      if (*newloc) {                                                             //  new location was given
         zfree(outloc);
         outloc = zstrdup(newloc,"batch convert");
      }

      if (Fsametype) {                                                           //  new .ext = existing .ext
         if (strcasestr(".jpg .jpeg .png .tif .tiff",inext))
            strcpy(outext,inext);
         else strcpy(outext,".jpg");                                             //  other >> .jpg
      }
      else strcpy(outext,newext);                                                //  new .ext was given

      cc = strlen(outloc) + strlen(outname) + strlen(outext) + 4;                //  construct output file name
      outfile = (ch *) zmalloc(cc,"batch convert");
      snprintf(outfile,cc,"%s/%s%s",outloc,outname,outext);

      Fjpeg = 0;
      if (strmatch(outext,".jpg")) Fjpeg = 1;

      zfree(outloc);
      zfree(outname);
      zfree(outext);

      pxmin = PXM_load(infile,0);                                                //  read input file
      if (! pxmin) {
         popup_report_write2(zdpop,1,"file type not supported: %s \n",inext);
         zfree(outfile);
         continue;
      }

      popup_report_write2(zdpop,0,"%s \n",outfile);                              //  log each output file

      if (*newloc) {
         if (regfile(outfile)) {                                                 //  check if file exists in new location
            popup_report_write2(zdpop,1,"output file exists \n");
            zfree(outfile);
            continue;
         }
      }

      outbpc = f_load_bpc;                                                       //  input file bits/color
      outww = pxmin->ww;                                                         //  input file size
      outhh = pxmin->hh;

      if (! Fsamebpc) outbpc = newbpc;                                           //  new bits/color
      if (Fjpeg) outbpc = 8;                                                     //  if jpeg, force 8 bits/color

      if (Fdownsize)                                                             //  downsize larger images
      {
         wscale = hscale = 1.0;
         if (outww > maxww) wscale = 1.0 * maxww / outww;                        //  compute new size
         if (outhh > maxhh) hscale = 1.0 * maxhh / outhh;
         if (wscale < hscale) scale = wscale;                                    //  scale < 1
         else scale = hscale;
         if (scale < 1.0) {
            outww = outww * scale + 0.5;                                         //  round
            outhh = outhh * scale + 0.5;
            pxmout = PXM_rescale(pxmin,outww,outhh);                             //  rescaled output file
            PXM_free(pxmin);
            pxmin = 0;
         }
      }

      if (Fupsize && pxmin)                                                      //  upsize smaller images
      {
         wscale = hscale = 1.0;
         if (outww < maxww) wscale = 1.0 * maxww / outww;                        //  compute new size
         if (outhh < maxhh) hscale = 1.0 * maxhh / outhh;
         if (wscale < hscale) scale = wscale;                                    //  scale > 1
         else scale = hscale;
         if (scale > 1.0) {
            outww = outww * scale + 0.5;                                         //  round
            outhh = outhh * scale + 0.5;
            pxmout = PXM_rescale(pxmin,outww,outhh);                             //  rescaled output file
            PXM_free(pxmin);
            pxmin = 0;
         }
      }

      if (pxmin) {                                                               //  no size change
         pxmout = pxmin;
         pxmin = 0;
      }

      pp1 = strrchr(outfile,'/');                                                //  get filename.ext
      if (pp1) pp1++;
      else pp1 = outfile;
      snprintf(tempfile,300,"%s/%s",temp_folder,pp1);                            //  temp file for meta copy

      err = PXM_save(pxmout,tempfile,outbpc,jpeg_quality,0);                     //  write output image to temp file
      if (err) {
         popup_report_write2(zdpop,1,"cannot create new file \n");
         zfree(outfile);
         PXM_free(pxmout);
         continue;
      }

      if (Fcopymeta) {                                                           //  copy meta if requested
         err = meta_copy(infile,tempfile,0,0,0);
         if (err) popup_report_write2(zdpop,1,"metadata update error \n");
      }

      err = cp_copy(tempfile,outfile);                                           //  copy tempfile to output file
      if (err) popup_report_write2(zdpop,1,"%s \n",strerror(err));

      remove(tempfile);                                                          //  remove tempfile

      Fdelete2 = 0;                                                              //  figure out if input file can be deleted
      if (Fdelete) Fdelete2 = 1;                                                 //  user says yes
      if (err) Fdelete2 = 0;                                                     //  not if error
      if (strmatch(infile,outfile)) Fdelete2 = 0;                                //  not if overwritten by output

      if (Fdelete2)                                                              //  delete input file
         err = f_remove(infile,"delete");                                        //  file/index/thumb/gallery

      if (! err) {
         load_filemeta(outfile);                                                 //  update image index for output file
         update_image_index(outfile);
      }

      if (Fdelete2) {                                                            //  if input file was deleted,
         oldfiles[Noldnew] = zstrdup(infile,"batch convert");                    //    mark for updating albums
         newfiles[Noldnew] = zstrdup(outfile,"batch convert");
         Noldnew++;
      }

      zfree(outfile);
      PXM_free(pxmout);
   }

   Funcbusy(-1);

   if (! zdialog_valid(zdpop)) {
      Plog(0,"*** report canceled \n");
      goto cleanup;
   }

   popup_report_write2(zdpop,0,"\n *** COMPLETED \n");
   popup_report_bottom(zdpop);

cleanup:

   if (Noldnew) {
      for (ii = 0; ii < Noldnew; ii++) {
         zfree(oldfiles[ii]);
         zfree(newfiles[ii]);
      }
      zfree(oldfiles);
      zfree(newfiles);
      Noldnew = 0;
   }

   gallery(navi::galleryname,"init",0);                                          //  refresh file list
   gallery(0,"sort",-2);                                                         //  recall sort and position
   gallery(0,"paint",-1);                                                        //  repaint from same position

   Fblock(0);
   return;
}


//  dialog event and completion callback function

int batch_convert_dialog_event(zdialog *zd, ch *event)
{
   using namespace batch_convert;

   ch          *pp, *ploc, badplug[20];
   ch          countmess[80];
   int         ii, cc, yn, err;

   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      zdialog_show(zd,0);                                                        //  hide parent dialog
      select_files(0,1);                                                         //  get list of files to convert          24.20
      zdialog_show(zd,1);

      snprintf(countmess,80,"%d image files selected",SFcount);                  //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

   if (strmatch(event,"browse")) {
      zdialog_fetch(zd,"newloc",newloc,500);
      ploc = zgetfile("Select folder",MWIN,"folder",newloc);                     //  new location browse
      if (! ploc) return 1;
      zdialog_stuff(zd,"newloc",ploc);
      zfree(ploc);
   }

   if (zstrstr("tif png jpg sametype",event)) {
      zdialog_stuff(zd,"tif",0);
      zdialog_stuff(zd,"png",0);
      zdialog_stuff(zd,"jpg",0);
      zdialog_stuff(zd,"sametype",0);
      zdialog_stuff(zd,event,1);
   }

   if (zstrstr("8-bit 16-bit samebpc",event)) {
      zdialog_stuff(zd,"8-bit",0);
      zdialog_stuff(zd,"16-bit",0);
      zdialog_stuff(zd,"samebpc",0);
      zdialog_stuff(zd,event,1);
   }

   zdialog_fetch(zd,"jpg",ii);                                                   //  if jpeg, force 8 bits/color
   if (ii) {
      zdialog_stuff(zd,"16-bit",0);
      zdialog_stuff(zd,"samebpc",0);
      zdialog_stuff(zd,"8-bit",1);
   }

   //  wait for dialog completion via [proceed] button

   if (zd->zstat != 1) return 1;
   zd->zstat = 0;                                                                //  keep active until inputs OK

   zdialog_fetch(zd,"text1",text1,100);                                          //  text within file name
   zdialog_fetch(zd,"text2",text2,100);                                          //  replacement text
   zdialog_fetch(zd,"newname",newname,200);                                      //  new file name
   zdialog_fetch(zd,"baseseq",baseseq);                                          //  base sequence number
   zdialog_fetch(zd,"addseq",addseq);                                            //  sequence number adder
   zdialog_fetch(zd,"newloc",newloc,500);                                        //  new location (folder)
   zdialog_fetch(zd,"maxww",maxww);                                              //  new max width
   zdialog_fetch(zd,"maxhh",maxhh);                                              //  new max height
   zdialog_fetch(zd,"downsize",Fdownsize);                                       //  downsize checkbox
   zdialog_fetch(zd,"upsize",Fupsize);                                           //  upsize checkbox

   zdialog_fetch(zd,"jpgqual",jpeg_quality);                                     //  jpeg quality
   zdialog_fetch(zd,"delete",Fdelete);                                           //  delete originals
   zdialog_fetch(zd,"copymeta",Fcopymeta);                                       //  copy metadata

   zdialog_fetch(zd,"sametype",Fsametype);
   zdialog_fetch(zd,"jpg",ii);
   if (ii) strcpy(newext,".jpg");
   zdialog_fetch(zd,"tif",ii);
   if (ii) strcpy(newext,".tif");
   zdialog_fetch(zd,"png",ii);
   if (ii) strcpy(newext,".png");

   zdialog_fetch(zd,"samebpc",Fsamebpc);
   zdialog_fetch(zd,"8-bit",ii);
   if (ii) newbpc = 8;
   zdialog_fetch(zd,"16-bit",ii);
   if (ii) newbpc = 16;

   if (! SFcount) {
      zmessageACK(Mwin,"no files selected");
      return 1;
   }

   Fnewtext = 0;
   if (*text1) Fnewtext = 1;                                                     //  replace text1 with text2 (may be null)

   Fplugdate = Fplugseq = Fplugname = 0;

   strTrim2(newname);
   if (! blank_null(newname))
   {
      if (Fnewtext) {
         zmessageACK(Mwin,"you cannot use new name and replace text together");
         return 1;
      }

      pp = newname;

      while ((pp = strchr(pp,'$')))
      {
         if (strmatchN(pp,"$yyyy",5)) Fplugdate = 1;                             //  $yyyy
         else if (strmatchN(pp,"$mm",3)) Fplugdate = 1;                          //  $mm
         else if (strmatchN(pp,"$dd",3)) Fplugdate = 1;                          //  $dd
         else if (strmatchN(pp,"$s",2)) Fplugseq = 1;                            //  $s...s   (sequence number cc)
         else if (strmatchN(pp,"$oldname",8)) Fplugname = 1;                     //  $oldname
         else {
            for (cc = 0; pp[cc] > ' ' && cc < 20; cc++);
            strncpy0(badplug,pp,cc);
            zmessageACK(Mwin,"invalid plugin: %s",badplug);
            return 1;
         }
         pp++;
      }
   }

   if (! blank_null(newname) && ! Fplugname && ! Fplugseq) {
      zmessageACK(Mwin,"you must use either $s or $oldname");
      return 1;
   }

   if (Fplugseq && (baseseq < 1 || addseq < 1)) {
      zmessageACK(Mwin,"$s plugin needs base and adder");
      return 1;
   }

   if (! Fplugseq && (baseseq > 0 || addseq > 0)) {
      zmessageACK(Mwin,"base and adder need $s plugin");
      return 1;
   }

   strTrim2(newloc);                                                             //  check location
   if (! blank_null(newloc)) {
      cc = strlen(newloc) - 1;
      if (newloc[cc] == '/') newloc[cc] = 0;                                     //  remove trailing '/'
      err = check_create_dir(newloc);                                            //  create if needed
      if (err) return 1;
   }

   if (! Fdownsize && ! Fupsize) {                                               //  if no downsize or upsize wanted,
      zdialog_stuff(zd,"maxww",0);                                               //    clear max. width and height values
      zdialog_stuff(zd,"maxhh",0);
   }

   else {                                                                        //  if either downsize/upsize wanted,
      if (maxww < 20 || maxhh < 20                                               //    check max. width and height values
          || maxww > wwhh_limit1 || maxhh > wwhh_limit1
          || maxww * maxhh > wwhh_limit2) {
         zmessageACK(Mwin,"max. width or height is not reasonable");
         return 1;
      }
   }

   Freplace = 0;
   if (*newname <= ' ' && *newloc <= ' ' && Fsametype)
      Freplace = 1;

   if (Freplace && Fdelete) {
      zmessageACK(Mwin,"delete originals specified but no new name given");
      return 1;
   }

   /**   Convert NN image files                    CF
           Replace text: xxxx --> yyyy             RT
           Rename to xxxxxxx                       RN
           Convert to .ext  N-bits/color           CX  CXa
           Downsize within NNxNN                   DZ
           Upsize within NNxNN                     UZ  UZa
           Output to /.../...                      OP
           Copy Metadata                           CM
           *** Delete Originals ***                DO
           *** Replace Originals ***               RO
         PROCEED?                                  PR
   **/

   ch    messCF[60], messRN[100], messRT[230], messCX[60], messCXa[60],
         messDZ[60], messDZa[60], messOP[550], messCM[80], messDO[60],
         messRO[60], messPR[40];
   ch    warnmess[1000];

   *messCF = *messRN = *messRT = *messCX = *messCXa = *messDZ = *messDZa
          = *messOP = *messCM = *messDO = *messRO = *messPR = 0;

   snprintf(messCF,60,"Convert %d image files",SFcount);
   if (Fnewtext) snprintf(messRT,230,"\n  Replace text: %s  →  %s",text1,text2);
   if (*newname) snprintf(messRN,100,"\n  %s %s","Rename to",newname);
   if (! Fsametype) snprintf(messCX,60,"\n  %s %s","Convert to",newext);
   if (! Fsamebpc) snprintf(messCXa,60,"\n  %d-bits/color",newbpc);
   if (Fdownsize) snprintf(messDZ,60,"\n  %s %dx%d","Downsize within",maxww,maxhh);
   if (Fupsize) snprintf(messDZa,60,"\n  %s %dx%d","Upsize within",maxww,maxhh);
   if (*newloc) snprintf(messOP,550,"\n  %s  %s","Output to",newloc);
   if (Fcopymeta) { strcat(messCM,"\n  Copy Metadata"); strcat(messCM,"  "); }
   if (Fdelete) snprintf(messDO,60,"\n  %s","*** Delete Originals ***");
   if (Freplace) snprintf(messRO,60,"\n  %s","*** Replace Originals ***");
   snprintf(messPR,40,"\n\n%s","PROCEED?");

   snprintf(warnmess,1000,"%s %s %s %s %s %s %s %s %s %s %s %s",
            messCF, messRT, messRN, messCX, messCXa, messDZ, messDZa, messOP,
            messCM, messDO, messRO, messPR);

   yn = zmessageYN(Mwin,warnmess);
   if (! yn) return 1;

   zd->zstat = 1;                                                                //  [proceed]
   return 1;
}


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

//  Move selected files to a new location (Fotocx indexed)

namespace batch_copy_move_names
{
   ch       newloc[500];                                                         //  destination folder
   int      Fdelete;                                                             //  delete originals
}


//  menu function

void m_batch_copy_move(GtkWidget *, ch *)
{
   using namespace batch_copy_move_names;

   int  batch_copy_move_dialog_event(zdialog *zd, ch *event);

   zdialog     *zd, *zdpop;
   int         ii, cc, err;
   int         zstat;
   ch          *pp, text[100];
   ch          *infile, *outfile;
   ch          **oldfiles, **newfiles;
   int         Noldnew;

   F1_help_topic = "batch copy/move";

   Plog(1,"m_batch_copy_move \n");
   
   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }

   if (Fblock("batch copy/move")) return;

/***
       ___________________________________________________________
      |                    Batch Copy/Move                        |
      |                                                           |
      |  [Select Files]  N files selected                         |
      |                                                           |
      |  New Location [_______________________________] [browse]  |
      |                                                           |
      |  [x] Delete Originals                                     |
      |                                                           |
      |                                             [Proceed] [X] |
      |___________________________________________________________|

***/

   zd = zdialog_new("Batch Copy/Move",Mwin,"Proceed"," X ",null);

   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf","Select Files","space=5");
   zdialog_add_widget(zd,"label","fcount","hbf","no files selected","space=10");

   zdialog_add_widget(zd,"hbox","hbloc","dialog");
   zdialog_add_widget(zd,"label","labloc","hbloc","New Location","space=5");
   zdialog_add_widget(zd,"zentry","newloc","hbloc",0,"expand");
   zdialog_add_widget(zd,"button","browse","hbloc","Browse","space=5");

   zdialog_add_widget(zd,"hbox","hbdel","dialog");
   zdialog_add_widget(zd,"check","delete","hbdel","Delete Originals","space=3");

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"fcount",text);

   zdialog_stuff(zd,"delete",0);                                                 //  delete originals - no

   *newloc = 0;                                                                  //  no new location

   oldfiles = newfiles = 0;                                                      //  list of old/new files
   Noldnew = 0;                                                                  //    for album updates

   zdialog_load_inputs(zd);                                                      //  preload prior user inputs
   zdialog_run(zd,batch_copy_move_dialog_event,"parent");                        //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);
   if (zstat != 1) goto cleanup;                                                 //  canceled
   if (! SFcount) goto cleanup;                                                  //  no files selected

   free_resources();                                                             //  no curr. file

   if (Fdelete) {
      cc = SFcount * sizeof(ch *);                                               //  input files are to be deleted:
      oldfiles = (ch **) zmalloc(cc,"batch copy/move");                          //    reserve space to hold list of
      newfiles = (ch **) zmalloc(cc,"batch copy/move");                          //    old/new filespecs to update albums
   }

   cc = strlen(newloc);                                                          //  output location
   if (newloc[cc-1] == '/') newloc[cc-1] = 0;                                    //  remove trailing '/'

   zdpop = popup_report_open("Processing files",Mwin,600,300,0,0,0,"X",0);       //  log report

   Funcbusy(+1);                                                                 //  24.10

   for (ii = 0; ii < SFcount; ii++)                                              //  loop selected files
   {
      zmainloop();                                                               //  keep GTK alive

      if (! zdialog_valid(zdpop)) break;                                         //  report canceled

      infile = SelFiles[ii];                                                     //  input file

      popup_report_write2(zdpop,0,"\n");
      popup_report_write2(zdpop,0,"%s \n",infile);                               //  log each input file

      if (image_file_type(infile) != IMAGE) {
         popup_report_write2(zdpop,0,"*** invalid file \n");
         continue;
      }

      pp = strrchr(infile,'/');                                                  //  /filename.ext
      cc = strlen(newloc) + strlen(pp) + 2;
      outfile = (ch *) zmalloc(cc,"batch copy/move");
      strcpy(outfile,newloc);                                                    //  /newloc/.../filename.ext
      strcat(outfile,pp);

      popup_report_write2(zdpop,0,"%s \n",outfile);                              //  log each output file

      if (regfile(outfile)) {                                                    //  check if file exists in new location
         popup_report_write2(zdpop,1,"%s \n","output file exists");
         zfree(outfile);
         continue;
      }

      err = cp_copy(infile,outfile);                                             //  copy infile to outfile
      if (err) {
         popup_report_write2(zdpop,1,"%s \n",strerror(err));                     //  error, do nothing
         zfree(outfile);
         continue;
      }

      load_filemeta(outfile);                                                    //  update image index for output file
      update_image_index(outfile);

      if (Fdelete)                                                               //  delete input file
      {
         err = f_remove(infile,"delete");                                        //  file/index/thumb/gallery

         oldfiles[Noldnew] = zstrdup(infile,"batch copy/move");                  //  mark for updating albums
         newfiles[Noldnew] = zstrdup(outfile,"batch copy/move");
         Noldnew++;
      }

      zfree(outfile);
   }

   Funcbusy(-1);

   if (! zdialog_valid(zdpop)) {
      Plog(0,"*** report canceled \n");
      goto cleanup;
   }

   if (Noldnew) {                                                                //  update albums for renamed/moved files
      popup_report_write2(zdpop,0,"%s \n","updating albums ...");
      album_purge_replace("ALL",Noldnew,oldfiles,newfiles);
   }

   popup_report_write2(zdpop,0,"\n *** %s \n","COMPLETED");
   popup_report_bottom(zdpop);

cleanup:

   if (Noldnew) {
      for (ii = 0; ii < Noldnew; ii++) {
         zfree(oldfiles[ii]);
         zfree(newfiles[ii]);
      }
      if (oldfiles) zfree(oldfiles);
      if (newfiles) zfree(newfiles);
      Noldnew = 0;
   }

   gallery(navi::galleryname,"init",0);                                          //  refresh file list
   gallery(0,"sort",-2);                                                         //  recall sort and position
   gallery(0,"paint",-1);                                                        //  repaint from same position

   Fblock(0);
   return;
}


//  dialog event and completion callback function

int batch_copy_move_dialog_event(zdialog *zd, ch *event)
{
   using namespace batch_copy_move_names;

   ch          countmess[80];
   ch          *ploc;
   int         cc, yn, err;

   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      zdialog_show(zd,0);                                                        //  hide parent dialog
      select_files(0,1);                                                         //  get list of files to copy             24.20
      zdialog_show(zd,1);

      snprintf(countmess,80,"%d image files selected",SFcount);                  //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

   if (strmatch(event,"browse")) {
      zdialog_fetch(zd,"newloc",newloc,500);
      ploc = zgetfile("Select folder",MWIN,"folder",newloc);                     //  new location browse
      if (! ploc) return 1;
      zdialog_stuff(zd,"newloc",ploc);
      zfree(ploc);
   }

   if (zd->zstat != 1) return 1;                                                 //  not [proceed]

   zd->zstat = 0;                                                                //  keep active until inputs OK

   if (! SFcount) {
      zmessageACK(Mwin,"no files selected");
      return 1;
   }

   zdialog_fetch(zd,"newloc",newloc,500);
   zdialog_fetch(zd,"delete",Fdelete);                                           //  delete originals

   strTrim2(newloc);                                                             //  check location
   cc = strlen(newloc) - 1;
   if (newloc[cc] == '/') newloc[cc] = 0;                                        //  remove trailing '/'
   err = check_create_dir(newloc);                                               //  create if needed
   if (err) return 1;

   /**
         Move NN image files                       0
         New Location: /.../...                    1
         *** Delete Originals ***                  2
         PROCEED?                                  3
   **/

   ch    mess0[60], mess1[600], mess2[60], mess3[40];
   ch    warnmess[800];

   *mess0 = *mess1 = *mess2 = *mess3 = 0;

   snprintf(mess0,60,"Move %d image files",SFcount);
   snprintf(mess1,600,"\n New Location: %s",newloc);
   if (Fdelete) snprintf(mess2,60,"\n *** Delete Originals ***");
   snprintf(mess3,40,"\n PROCEED?");

   snprintf(warnmess,800,"%s %s %s %s",mess0,mess1,mess2,mess3);

   yn = zmessageYN(Mwin,warnmess);
   if (! yn) return 1;

   zd->zstat = 1;                                                                //  [proceed]
   return 1;
}


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

//  Batch upright image files.
//  Look for files rotated 90˚ (according to metadata) and upright them.

ch     **bup_filelist = 0;
int      bup_filecount = 0;
int      bup_allfiles;

void m_batch_upright(GtkWidget *, ch *)
{
   int  batch_upright_dialog_event(zdialog *zd, ch *event);

   zdialog        *zd, *zdpop;
   int            zstat;
   ch             *infile, *newfile, *delfile;
   ch             *pp, *ppv[1], text[100];
   int            ii, cc, err, yn;
   int            angle, mirror;
   ch             *metakey[1] = { meta_orientation_key };
   ch             orient;

   F1_help_topic = "batch upright";

   Plog(1,"m_batch_upright \n");

   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }
   
   if (Fblock("batch upright")) return;

/***
       ___________________________________
      |       Batch Upright               |
      |                                   |
      |  [Select Files]  N files selected |
      |  [x] Survey all files             |
      |                                   |
      |                     [Proceed] [X] |
      |___________________________________|

***/

   zd = zdialog_new("Batch Upright",Mwin,"Proceed"," X ",null);

   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf","Select Files","space=5");
   zdialog_add_widget(zd,"label","fcount","hbf","no files selected","space=10");
   zdialog_add_widget(zd,"hbox","hbaf","dialog");
   zdialog_add_widget(zd,"check","allfiles","hbaf","Survey all files","space=5");

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"fcount",text);

   bup_filelist = 0;
   bup_filecount = 0;

   zdialog_run(zd,batch_upright_dialog_event,"parent");                          //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);
   if (zstat != 1) goto cleanup;                                                 //  canceled
   if (! bup_allfiles && ! bup_filecount) goto cleanup;                          //  nothing selected

   free_resources();                                                             //  no curr. file

   if (bup_allfiles) {                                                           //  "survey all files" selected
      cc = Nxxrec * sizeof(ch *);
      bup_filelist = (ch **) zmalloc(cc,"batch upright");
      for (ii = 0; ii < Nxxrec; ii++)
         bup_filelist[ii] = zstrdup(xxrec_tab[ii]->file,"batch upright");
      bup_filecount = Nxxrec;
   }

   for (ii = 0; ii < bup_filecount; ii++) {                                      //  warn about file type changes
      pp = strrchr(bup_filelist[ii],'.');
      if (pp && strcasestr(".jp2 .heic .avif .webp",pp)) break;
   }
   if (ii < bup_filecount) {
      yn = zmessageYN(Mwin,"files types: .jp2 .heic .avif .webp \n"
                           "will become: .jpg    CONTINUE?");
      if (! yn) goto cleanup;
   }

   zdpop = popup_report_open("Processing files",Mwin,500,200,0,0,0,"X",0);       //  log report

   Funcbusy(+1);                                                                 //  24.10

   for (ii = 0; ii < bup_filecount; ii++)                                        //  loop selected files
   {
      zmainloop();                                                               //  keep GTK alive

      if (! zdialog_valid(zdpop)) break;                                         //  report canceled

      infile = bup_filelist[ii];                                                 //  input file
      popup_report_write2(zdpop,0,"%s \n",infile);                               //  log each output file

      if (image_file_type(infile) != IMAGE) {
         popup_report_write2(zdpop,0,"*** invalid file \n");
         continue;
      }

      meta_get1(infile,(ch **) metakey,ppv,1);                                   //  get metadata Orientation
      if (! ppv[0]) continue;
      orient = *ppv[0];
      zfree(ppv[0]);
      if (orient < '2' || orient > '8') continue;                                //  not rotated

      angle = mirror = 0;

      if (orient == '2') { angle = 0; mirror = 1; }                              //  horizontal mirror
      if (orient == '3') { angle = 180; mirror = 0; }                            //  rotate 180
      if (orient == '4') { angle = 0; mirror = 2; }                              //  vertical mirror
      if (orient == '5') { angle = 90; mirror = 1; }                             //  rotate 90 + horizontal mirror
      if (orient == '6') { angle = 90; mirror = 0; }                             //  rotate 90
      if (orient == '7') { angle = 270; mirror = 1; }                            //  rotate 270 + horizontal mirror
      if (orient == '8') { angle = 270; mirror = 0; }                            //  rotate 270

      err = f_open(infile);
      if (err) {
         popup_report_write2(zdpop,0,"*** cannot open input file");
         continue;
      }

      E0pxm = PXM_load(curr_file,0);                                             //  load poss. 16-bit image
      if (! E0pxm) {
         popup_report_write2(zdpop,0,"*** cannot read input file");
         continue;
      }

      E3pxm = PXM_upright(E0pxm,angle,mirror);                                   //  do rotate/mirror

      PXM_free(E0pxm);
      E0pxm = E3pxm;
      E3pxm = 0;

      if (strcasestr("jp2 heic avif webp",curr_file_type)) {                     //  save these types as .jpg
         newfile = zstrdup(curr_file,"batch_upright",16);
         delfile = zstrdup(curr_file,"batch_upright");
         pp = strrchr(newfile,'.');
         strcpy(pp,"-upright.jpg");
         Fupright = 1;                                                           //  mark uprighted (for metadata update)
         f_save(newfile,"jpg",8,0,1);                                            //  make .jpg duplicate
         f_open(newfile);                                                        //  show uprighted file
         remove(delfile);
         zfree(newfile);
         zfree(delfile);
      }
      else {
         Fupright = 1;                                                           //  mark uprighted (for metadata update)
         f_save(curr_file,curr_file_type,curr_file_bpc,0,1);                     //  replace file
         f_open(curr_file);
      }
   }

   Funcbusy(-1);

   if (! zdialog_valid(zdpop)) {
      Plog(0,"*** report canceled \n");
      goto cleanup;
   }

   popup_report_write2(zdpop,0,"\n *** COMPLETED \n");
   popup_report_bottom(zdpop);

cleanup:

   if (bup_filecount) {                                                          //  free memory
      for (ii = 0; ii < bup_filecount; ii++)
         zfree(bup_filelist[ii]);
      zfree(bup_filelist);
      bup_filelist = 0;
      bup_filecount = 0;
   }

   gallery(curr_file,"init",0);

   Fblock(0);
   return;
}


//  dialog event and completion callback function

int batch_upright_dialog_event(zdialog *zd, ch *event)
{
   ch        countmess[80];
   int       ii;

   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      if (bup_filecount) {
         for (ii = 0; ii < bup_filecount; ii++)                                  //  free prior list
            zfree(bup_filelist[ii]);
         zfree(bup_filelist);
         bup_filelist = 0;
         bup_filecount = 0;
      }

      zdialog_show(zd,0);                                                        //  hide parent dialog
      select_files(0,1);                                                         //  get new list                          24.20
      zdialog_show(zd,1);

      snprintf(countmess,80,"%d image files selected",SFcount);                  //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
      zdialog_stuff(zd,"allfiles",0);

      if (! SFcount) return 1;

      bup_filelist = (ch **) zmalloc(SFcount * sizeof(ch *),"batch upright");    //  copy selected files
      for (ii = 0; ii < SFcount; ii++)
         bup_filelist[ii] = SelFiles[ii];
      bup_filecount = SFcount;
      SFcount = 0;
   }

   if (zd->zstat != 1) return 1;                                                 //  wait for [proceed]

   zdialog_fetch(zd,"allfiles",bup_allfiles);                                    //  get "survey all" option

   if (! bup_allfiles && ! bup_filecount) {                                      //  nothing selected
      zmessageACK(Mwin,"no files selected");
      zd->zstat = 0;                                                             //  keep dialog active
   }

   if (bup_allfiles && bup_filecount) {
      zmessageACK(Mwin,"cannot select both options");
      zd->zstat = 0;
   }

   return 1;
}


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

//  Batch delete or trash image files.

int      bdt_option;                                                             //  1/2 = delete/trash

void m_batch_deltrash(GtkWidget *, ch *)
{
   int  batch_deltrash_dialog_event(zdialog *zd, ch *event);

   zdialog     *zd, *zdpop;
   int         zstat, ii, nn, err;
   ch          *file, text[100];
   ch          **delfiles = 0;
   int         cc, Ndel = 0;

   F1_help_topic = "batch delete/trash";

   Plog(1,"m_batch_deltrash \n");

   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }
   
   if (Fblock("batch delete/trash")) return;

/***
       ___________________________________
      |       Batch Delete/Trash          |
      |                                   |
      |  [Select Files]  N files selected |
      |  (o) delete    (o) trash          |
      |                                   |
      |                     [Proceed] [X] |
      |___________________________________|

***/

   zd = zdialog_new("Batch Delete/Trash",Mwin,"Proceed"," X ",null);
   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf","Select Files","space=5");
   zdialog_add_widget(zd,"label","fcount","hbf","no files selected","space=10");
   zdialog_add_widget(zd,"hbox","hbdt","dialog");
   zdialog_add_widget(zd,"label","labdel","hbdt","delete","space=5");
   zdialog_add_widget(zd,"radio","delete","hbdt",0);
   zdialog_add_widget(zd,"label","space","hbdt",0,"space=10");
   zdialog_add_widget(zd,"label","labtrash","hbdt","trash","space=5");
   zdialog_add_widget(zd,"radio","trash","hbdt",0);

   bdt_option = 2;

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"fcount",text);

   zdialog_stuff(zd,"delete",0);
   zdialog_stuff(zd,"trash",1);

   zdialog_run(zd,batch_deltrash_dialog_event,"parent");                         //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion

   zdialog_fetch(zd,"delete",bdt_option);                                        //  get delete/trash option
   if (! bdt_option) bdt_option = 2;

   zdialog_free(zd);
   if (zstat != 1) goto finish;                                                  //  canceled
   if (! SFcount) goto finish;

   free_resources();                                                             //  no curr. file

   cc = SFcount * sizeof(ch *);                                                  //  files to purge from albums
   delfiles = (ch **) zmalloc(cc,"batch deltrash");
   Ndel = 0;

   zdpop = popup_report_open("Processing files",Mwin,500,200,0,0,0,"X",0);       //  log report

   Funcbusy(+1);                                                                 //  24.10

   for (ii = 0; ii < SFcount; ii++)                                              //  loop selected files
   {
      zmainloop();                                                               //  keep GTK alive

      if (! zdialog_valid(zdpop)) break;                                         //  report canceled

      file = SelFiles[ii];                                                       //  log each file
      popup_report_write2(zdpop,0,"%s \n",file);

      if (! regfile(file)) {                                                     //  file exists?
         popup_report_write2(zdpop,1,"file not found \n");
         continue;
      }

      err = 0;
      if (bdt_option == 1)                                                       //  delete file
         err = f_remove(file,"delete");                                          //  file/index/thumb/gallery
      if (bdt_option == 2)                                                       //  move file to trash
         err = f_remove(file,"trash");
      if (err) {
         popup_report_write2(zdpop,1,"move to trash failed \n");                 //  gnome trash failed
         nn = zdialog_choose(Mwin,"parent","continue?","Yes","Quit",0);
         if (nn == 2) {                                                          //  quit
            zdpop->zstat = 1;
            break;
         }
      }

      delfiles[Ndel] = zstrdup(file,"batch deltrash");                           //  add to deleted files list
      Ndel++;
   }

   Funcbusy(-1);

   if (! zdialog_valid(zdpop)) {
      Plog(0,"*** report canceled \n");
      goto finish;
   }

   if (Ndel) {
      popup_report_write2(zdpop,0,"Purging deleted files from albums \n");       //  purge deleted files from albums
      album_purge_replace("ALL",Ndel,delfiles,0);
   }

   popup_report_write2(zdpop,0,"\n *** %s \n","COMPLETED");
   popup_report_bottom(zdpop);

finish:

   for (ii = 0; ii < Ndel; ii++)                                                 //  free memory
      zfree(delfiles[ii]);
   if (delfiles) zfree(delfiles);

   gallery(navi::galleryname,"init",0);                                          //  refresh file list
   gallery(0,"sort",-2);                                                         //  recall sort and position
   gallery(0,"paint",-1);                                                        //  repaint from same position

   Fblock(0);
   return;
}


//  dialog event and completion callback function

int batch_deltrash_dialog_event(zdialog *zd, ch *event)
{
   ch           countmess[80];
   int          ii;

   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      zdialog_show(zd,0);                                                        //  hide parent dialog
      select_files(0,1);                                                         //  get files                             24.20
      zdialog_show(zd,1);

      snprintf(countmess,80,"%d image files selected",SFcount);                  //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

   if (strmatch(event,"delete")) {                                               //  delete radio button
      zdialog_fetch(zd,"delete",ii);
      if (ii) bdt_option = 1;
      zdialog_stuff(zd,"trash",0);
   }

   if (strmatch(event,"trash")) {                                                //  trash radio button
      zdialog_fetch(zd,"trash",ii);
      if (ii) bdt_option = 2;
      zdialog_stuff(zd,"delete",0);
   }

   if (zd->zstat != 1) return 1;                                                 //  wait for [proceed]

   if (! SFcount) {                                                              //  nothing selected
      zmessageACK(Mwin,"no files selected");
      zd->zstat = 0;                                                             //  keep dialog active
   }

   return 1;
}


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

//  convert multiple RAW files to tiff, jpeg, or png

namespace batch_raw
{
   ch       location[400], biasfile[400];
   ch       *filetype = 0;
   int      bpc, jpeg_quality;
   float    rescale;
   int      amount, thresh;
};


void m_batch_RAW(GtkWidget *, ch *menu)
{
   using namespace batch_raw;

   int  batch_raw_dialog_event(zdialog *zd, ch *event);

   zdialog     *zd, *zdpop;
   ch          *title = "Batch Convert RAW Files";
   ch          *rawfile, *tempfile, *outfile, *pp;
   ch          text[100];
   int         zstat, ii, err;
   FTYPE       ftype;
   int         cc, ww2, hh2;
   PXM         *pxm1, *pxm2;

   F1_help_topic = "batch raw";

   Plog(1,"m_batch_RAW \n");

   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }

   if (Fblock("batch RAW")) return;
   
/***
       ________________________________________________________
      |              Batch Convert RAW Files                   |
      |                                                        |
      | [Select Files]  N image files selected                 |
      | output location [___________________________] [Browse] |
      | File Type: (o) tif  (o) png  (o) jpg [90] jpg quality  |
      | Color Depth: (o) 8-bit  (o) 16-bit                     |
      | Rescale  (o) 1.0  (o) 3/4  (o) 2/3  (o) 1/2  (o) 1/3   |
      |                                                        |
      |                                          [Proceed] [X] |
      |________________________________________________________|

***/

   zd = zdialog_new(title,Mwin,"Proceed"," X ",null);

   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=2");
   zdialog_add_widget(zd,"button","files","hb1","Select Files","space=5");
   zdialog_add_widget(zd,"label","fcount","hb1","no files selected","space=10");

   zdialog_add_widget(zd,"hbox","hbout","dialog",0,"space=2");
   zdialog_add_widget(zd,"label","labout","hbout","output location","space=5");
   zdialog_add_widget(zd,"zentry","location","hbout",0,"space=5|expand");
   zdialog_add_widget(zd,"button","browselocation","hbout","Browse","space=5");

   zdialog_add_widget(zd,"hbox","hbft","dialog");
   zdialog_add_widget(zd,"label","labtyp","hbft","File Type","space=5");
   zdialog_add_widget(zd,"radio","tif","hbft","tif","space=4");
   zdialog_add_widget(zd,"radio","png","hbft","png","space=4");
   zdialog_add_widget(zd,"radio","jpg","hbft","jpg","space=2");
   zdialog_add_widget(zd,"zspin","jpgqual","hbft","10|100|1|90","size=3");
   zdialog_add_widget(zd,"label","labqual","hbft","jpg quality","space=6");

   zdialog_add_widget(zd,"hbox","hbcd","dialog");
   zdialog_add_widget(zd,"label","labcd","hbcd","Color Depth:","space=5");
   zdialog_add_widget(zd,"radio","8-bit","hbcd","8-bit","space=4");
   zdialog_add_widget(zd,"radio","16-bit","hbcd","16-bit","space=4");

   zdialog_add_widget(zd,"hbox","hbsize","dialog",0,"space=2");
   zdialog_add_widget(zd,"label","labsize","hbsize","Rescale","space=5");
   zdialog_add_widget(zd,"label","space","hbsize",0,"space=5");
   zdialog_add_widget(zd,"radio","1.0","hbsize","1.0","space=5");
   zdialog_add_widget(zd,"radio","3/4","hbsize","3/4","space=5");
   zdialog_add_widget(zd,"radio","2/3","hbsize","2/3","space=5");
   zdialog_add_widget(zd,"radio","1/2","hbsize","1/2","space=5");
   zdialog_add_widget(zd,"radio","1/3","hbsize","1/3","space=5");

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"fcount",text);

   *location = 0;

   zdialog_stuff(zd,"tif",0);
   zdialog_stuff(zd,"png",0);
   zdialog_stuff(zd,"jpg",0);
   zdialog_stuff(zd,"jpgqual",jpeg_def_quality);
   zdialog_stuff(zd,"8-bit",0);
   zdialog_stuff(zd,"16-bit",0);

   zdialog_load_inputs(zd);                                                      //  get prior inputs if any
   zdialog_resize(zd,500,0);

   zdialog_run(zd,batch_raw_dialog_event,"parent");                              //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);

   if (zstat != 1) goto cleanup;
   if (! SFcount) goto cleanup;
   
   free_resources();                                                             //  no current file                       24.30

   viewmode("F");

   zdpop = popup_report_open("Converting RAW files",Mwin,500,200,0,0,0,"X",0);   //  log report

   Funcbusy(+1);                                                                 //  24.10

   for (ii = 0; ii < SFcount; ii++)                                              //  loop all RAW files
   {
      zmainloop();

      if (! zdialog_valid(zdpop)) break;                                         //  report canceled

      rawfile = SelFiles[ii];                                                    //  filename.raw
      popup_report_write2(zdpop,0,"%s \n",rawfile);                              //  write to log window

      ftype = image_file_type(rawfile);
      if (ftype != RAW) {
         popup_report_write2(zdpop,1," unknown RAW file type \n");
         continue;
      }

      pxm1 = RAW_PXM_load(rawfile,Fraw_match_embed);
      if (! pxm1) continue;

      zmainloop();

      if (rescale < 1.0)                                                         //  rescale down if wanted
      {
         ww2 = rescale * pxm1->ww;
         hh2 = rescale * pxm1->hh;
         pxm2 = PXM_rescale(pxm1,ww2,hh2);
         PXM_free(pxm1);
         pxm1 = pxm2;

         if (! pxm1) {
            popup_report_write2(zdpop,1," rescale failed \n");
            continue;
         }
      }

      outfile = zstrdup(rawfile,"batch raw",5);                                  //  output file name = RAW file name

      pp = strrchr(outfile,'.');                                                 //  rename:  *.tif  *.jpg  *.png
      if (pp) strcpy(pp,filetype);

      err = PXM_save(pxm1,outfile,bpc,jpeg_quality,1);
      PXM_free(pxm1);

      if (err) {
         popup_report_write2(zdpop,1," file type conversion failed \n");
         zfree(outfile);
         continue;
      }

      err = meta_copy(rawfile,outfile,0,0,0);                                    //  copy metadata from RAW file
      if (err) popup_report_write2(zdpop,1," metadata update error \n");

      if (*location && ! samefolder(location,outfile)) {
         tempfile = zstrdup(outfile,"batch raw");                                //  copy to new location
         zfree(outfile);
         pp = strrchr(tempfile,'/');                                             //  /raw-location/filename.ext
         cc = strlen(pp);                                                        //               |
         outfile = zstrdup(location,"batch raw",cc+1);                           //               pp
         strcat(outfile,pp);                                                     //  /new-location/filename.ext
         err = cp_copy(tempfile,outfile);                                        //  copy to new location
         if (err) popup_report_write2(zdpop,1," %s \n",strerror(err));
         remove(tempfile);                                                       //  remove tempfile
         zfree(tempfile);
      }

      f_open(outfile,0,0,0);                                                     //  open converted file
      update_image_index(outfile);

      popup_report_write2(zdpop,0,"%s \n",outfile);                              //  write output file to log window
      zfree(outfile);
   }

   Funcbusy(-1);

   if (! zdialog_valid(zdpop)) {
      Plog(0,"*** report canceled \n");
      goto cleanup;
   }

   popup_report_write2(zdpop,0,"\n *** COMPLETED \n");
   popup_report_bottom(zdpop);

cleanup:                                                                         //  clean up and return

   gallery(navi::galleryname,"init",0);                                          //  refresh file list
   gallery(0,"sort",-2);                                                         //  recall sort and position
   gallery(0,"paint",-1);                                                        //  repaint from same position

   Fblock(0);
   return;
}


//  dialog event and completion callback function

int batch_raw_dialog_event(zdialog *zd, ch *event)
{
   using namespace batch_raw;

   int      ii, err, cc;
   ch       countmess[80], *ploc;

   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      zdialog_show(zd,0);                                                        //  hide parent dialog
      select_files(0,1);                                                         //  get list of files to convert          24.20
      zdialog_show(zd,1);

      snprintf(countmess,80,"%d image files selected",SFcount);                  //  stuff count into dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

   if (strmatch(event,"browselocation")) {                                       //  new location browse
      zdialog_fetch(zd,"location",location,400);
      if (*location <= ' ' && topfolders[0])
         strncpy0(location,topfolders[0],400);
      ploc = zgetfile("Select folder",MWIN,"folder",location);
      if (! ploc) return 1;
      zdialog_stuff(zd,"location",ploc);
      zfree(ploc);
   }

   if (zstrstr("tif png jpg",event)) {                                           //  gtk fails to do this correctly
      zdialog_stuff(zd,"tif",0);
      zdialog_stuff(zd,"png",0);
      zdialog_stuff(zd,"jpg",0);
      zdialog_stuff(zd,event,1);
   }

   if (zstrstr("8-bit 16-bit",event)) {                                          //  gtk fails to do this correctly
      zdialog_stuff(zd,"8-bit",0);
      zdialog_stuff(zd,"16-bit",0);
      zdialog_stuff(zd,event,1);
   }

   zdialog_fetch(zd,"jpg",ii);                                                   //  if jpeg, force 8 bits/color
   if (ii) {
      zdialog_stuff(zd,"16-bit",0);
      zdialog_stuff(zd,"8-bit",1);
   }

   //  wait for dialog completion via [proceed] button

   if (zd->zstat != 1) return 0;
   zd->zstat = 0;                                                                //  keep dialog active until inputs OK

   if (! SFcount) {                                                              //  no RAW files selected
      zmessageACK(Mwin,"no files selected");
      return 1;
   }

   zdialog_fetch(zd,"location",location,400);                                    //  output location
   strTrim2(location);
   if (! blank_null(location)) {
      cc = strlen(location) - 1;
      if (location[cc] == '/') location[cc] = 0;                                 //  remove trailing '/'
      err = check_create_dir(location);                                          //  create if needed
      if (err) return 1;
   }

   filetype = ".tif";
   zdialog_fetch(zd,"jpg",ii);
   if (ii) filetype = ".jpg";
   zdialog_fetch(zd,"tif",ii);
   if (ii) filetype = ".tif";
   zdialog_fetch(zd,"png",ii);
   if (ii) filetype = ".png";

   bpc = 8;
   zdialog_fetch(zd,"8-bit",ii);
   if (ii) bpc = 8;
   zdialog_fetch(zd,"16-bit",ii);
   if (ii) bpc = 16;

   zdialog_fetch(zd,"jpgqual",jpeg_quality);                                     //  jpeg quality

   rescale = 1.0;
   zdialog_fetch(zd,"1.0",ii);                                                   //  rescale option
   if (ii) rescale = 1.0;
   zdialog_fetch(zd,"3/4",ii);
   if (ii) rescale = 0.75;
   zdialog_fetch(zd,"2/3",ii);
   if (ii) rescale = 0.666667;
   zdialog_fetch(zd,"1/2",ii);
   if (ii) rescale = 0.50;
   zdialog_fetch(zd,"1/3",ii);
   if (ii) rescale = 0.333333;

   zdialog_fetch(zd,"amount",amount);
   zdialog_fetch(zd,"thresh",thresh);

   zd->zstat = 1;                                                                //  dialog complete
   return 1;
}


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

//  Add a small image file over selected host files (for author, copyright, etc.).
//  Added image position is designated % from host image top and left.
//  Added image width is designated % of host image width.
//  Added image can have transparency.

namespace batch_overlay
{
   ch       *ovfile = 0;                           //  overlay image file
   PXB      *ovfilepxb = 0;                        //  overlay file PXB
   int      pcttop, pctleft;                       //  overlay position, % from top, % from left, 1-99
   int      pctwidth;                              //  overlay width, % host image width, 5-95
   int      Fwinadj;                               //  flag, adjust overlay width for window size
   int      winww, winhh;                          //  window size (current or user input)
   int      Frepl, Fvers;                          //  flags, replace host files or make new versions
};


//  menu function

void m_batch_overlay(GtkWidget *, ch *)
{
   using namespace batch_overlay;

   int  batch_overlay_dialog_event(zdialog *zd, ch *event);

   zdialog     *zd, *zdpop;
   int         zstat;
   ch          *infile = 0, *outfile = 0;
   ch          text[100], *pp;
   int         ii, err;
   PXM         *infilepxm = 0;
   PXB         *ovpxb = 0;
   int         ovww, ovhh;
   int         ovorgx, ovorgy;
   int         px1, py1, px2, py2;
   uint8       *pix1;
   float       *pix2;
   float       f1, f2;
   float       winAR, infileAR;                                                  //  window and host image ww/hh ratios

   F1_help_topic = "batch overlay";

   Plog(1,"m_batch_overlay \n");

   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }

   if (Fblock("batch overlay")) return;
   
/***
          __________________________________________________
         |               Batch Overlay Image                |
         |                                                  |
         |  [Select host image files]  no files selected    |           button: hostselect
         |  [Select overlay file]  no file selected         |           button: ovselect
         |  - - - - - - - - - - - - - - - - - - - - - - - - |
         |  Overlay position in host image:                 |
         |      % from top: [__]   % from left: [__]        |           zspin: pcttop  pctleft
         |  - - - - - - - - - - - - - - - - - - - - - - - - |
         |  Overlay width, % host image width: [__]         |           zspin: pctwidth
         |  [x] Make width constant for window size:        |           check: Fwinadj
         |      width [____]  height [____]   [_] refresh]  |           zspin: winww, winhh  zbutton: refresh
         |  - - - - - - - - - - - - - - - - - - - - - - - - |
         |  [x] Replace host files  [x] Make new versions   |           check: Frepl  check: Fvers
         |                                                  |
         |                                    [Proceed] [X] |
         |__________________________________________________|

***/

   zd = zdialog_new("Batch Overlay",Mwin,"Proceed"," X ",null);

   zdialog_add_widget(zd,"hbox","hbhf","dialog",0,"space=2");
   zdialog_add_widget(zd,"button","hostselect","hbhf","Select host image files","space=5");
   zdialog_add_widget(zd,"label","hostcount","hbhf","no files selected","space=10");

   zdialog_add_widget(zd,"hbox","hbovf","dialog",0,"space=2");
   zdialog_add_widget(zd,"button","ovselect","hbovf","Select overlay file","space=5");
   zdialog_add_widget(zd,"label","ovfile","hbovf","no file selected","space=10");

   zdialog_add_widget(zd,"hsep","hsep1","dialog",0,"space=5");

   zdialog_add_widget(zd,"hbox","hbpos1","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labpos1","hbpos1","Overlay position in host image:","space=5");
   zdialog_add_widget(zd,"hbox","hbpos2","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","space","hbpos2","","space=10");
   zdialog_add_widget(zd,"label","labtop","hbpos2","% from top:","space=2");
   zdialog_add_widget(zd,"zspin","pcttop","hbpos2","1|99|1|99","space=2");
   zdialog_add_widget(zd,"label","space","hbpos2","","space=10");
   zdialog_add_widget(zd,"label","lableft","hbpos2","% from left:","space=2");
   zdialog_add_widget(zd,"zspin","pctleft","hbpos2","1|99|1|99","space=2");

   zdialog_add_widget(zd,"hsep","hsep2","dialog",0,"space=5");

   zdialog_add_widget(zd,"hbox","hbwidth1","dialog");
   zdialog_add_widget(zd,"label","labwidth1","hbwidth1","Overlay width, % host image width:","space=5");
   zdialog_add_widget(zd,"zspin","pctwidth","hbwidth1","5|95|1|20","space=2");
   zdialog_add_widget(zd,"hbox","hbwidth2","dialog");
   zdialog_add_widget(zd,"check","Fwinadj","hbwidth2","Make width constant for window size","space=6");
   zdialog_add_widget(zd,"hbox","hbwidth3","dialog");
   zdialog_add_widget(zd,"label","space","hbwidth3","","space=10");
   zdialog_add_widget(zd,"label","labwinww","hbwidth3","width","space=2");
   zdialog_add_widget(zd,"zspin","winww","hbwidth3","100|9999|1|2000","space=2");
   zdialog_add_widget(zd,"label","space","hbwidth3","","space=10");
   zdialog_add_widget(zd,"label","labwinhh","hbwidth3","height","space=2");
   zdialog_add_widget(zd,"zspin","winhh","hbwidth3","100|9999|1|1000","space=2");
   zdialog_add_widget(zd,"zbutton","refresh","hbwidth3","refresh","space=15");
   zdialog_add_ttip(zd,"refresh","set current window size");

   zdialog_add_widget(zd,"hsep","hsep3","dialog",0,"space=5");

   zdialog_add_widget(zd,"hbox","hbrepl","dialog",0,"space=1");
   zdialog_add_widget(zd,"check","Frepl","hbrepl","Replace host files","space=6");
   zdialog_add_widget(zd,"check","Fvers","hbrepl","Make new versions","space=6");

   if (SFcount) {
      snprintf(text,100,"%d image files selected",SFcount);                      //  show selected files count
      zdialog_stuff(zd,"hostcount",text);
   }

   if (ovfile) {                                                                 //  show overlay file if known
      pp = strrchr(ovfile,'/');
      zdialog_stuff(zd,"ovfile",pp+1);
   }

   pctwidth = 20;                                                                //  match dialog defaults
   pcttop = 99;
   pctleft = 99;

   Fwinadj = 0;
   gtk_window_get_size(MWIN,&winww,&winhh);
   zdialog_stuff(zd,"winww",winww);                                              //  default window = current size
   zdialog_stuff(zd,"winhh",winhh);

   Frepl = 0;
   Fvers = 1;                                                                    //  default new version
   zdialog_stuff(zd,"Frepl",Frepl);
   zdialog_stuff(zd,"Fvers",Fvers);

   if (ovfile && ! ovfilepxb)
      ovfilepxb = PXB_load(ovfile,1);

   zdialog_run(zd,batch_overlay_dialog_event,"parent");                          //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);

   if (zstat != 1) {                                                             //  canceled
      Plog(0,"canceled \n");
      Fblock(0);
      return;
   }

   free_resources();                                                             //  no current file                       24.30

   zdpop = popup_report_open("Processing files",Mwin,600,300,0,0,0,"X",0);       //  log report

   Funcbusy(+1);                                                                 //  24.10

   for (ii = 0; ii < SFcount; ii++)                                              //  loop selected files
   {
      zmainloop();                                                               //  keep GTK alive

      if (outfile) zfree(outfile);
      outfile = 0;

      if (infilepxm) PXM_free(infilepxm);
      infilepxm = 0;

      if (! zdialog_valid(zdpop)) break;                                         //  report canceled

      infile = SelFiles[ii];                                                     //  input file

      if (image_file_type(infile) != IMAGE) {
         popup_report_write2(zdpop,0,"*** invalid file \n");
         continue;
      }

      popup_report_write2(zdpop,0,"\n");
      popup_report_write2(zdpop,0,"%s \n",infile);                               //  log each input file

      outfile = f_realpath(infile);                                              //  outfile = infile
      if (! outfile) {
         popup_report_write2(zdpop,0,"*** cannot get real path of input file \n");
         continue;
      }

      if (Fvers) {
         pp = file_new_version(outfile);                                         //  outfile is new version
         zfree(outfile);
         outfile = pp;
         if (! outfile) {
            popup_report_write2(zdpop,0,"*** cannot make new version of input file \n");
            continue;
         }
      }

      infilepxm = PXM_load(infile,0);                                            //  load input host file
      if (! infilepxm) {
         popup_report_write2(zdpop,0,"*** cannot load input file \n");
         continue;
      }

      ovww = 0.01 * pctwidth * infilepxm->ww + 0.5;                              //  overlay width, % host image width

      if (Fwinadj) {                                                             //  increase overlay width if left/right
         infileAR = 1.0 * infilepxm->ww / infilepxm->hh;                         //    margins needed for 'tall' image
         winAR = 1.0 * winww / winhh;
         if (winAR > infileAR) ovww = ovww * winAR / infileAR;
      }

      if (ovww < 20) ovww = 20;                                                  //  sanity limit

      ovhh = 1.0 * ovfilepxb->hh * ovww / ovfilepxb->ww + 0.5;                   //  overlay image height

      if (ovpxb) PXB_free(ovpxb);
      ovpxb = PXB_rescale(ovfilepxb,ovww,ovhh);                                  //  rescale overlay image

      ovorgx = 0.01 * pctleft * (infilepxm->ww - ovww);                          //  overlay posn from host image left edge
      ovorgy = 0.01 * pcttop * (infilepxm->hh - ovhh);                           //  overlay posn from host image top edge

      for (py1 = 0; py1 < ovhh; py1++)                                           //  loop overlay image pixels
      for (px1 = 0; px1 < ovww; px1++)
      {
         px2 = ovorgx + px1;                                                     //  corresp. host image pixel
         py2 = ovorgy + py1;

         pix1 = PXBpix(ovpxb,px1,py1);                                           //  overlay image pixel
         pix2 = PXMpix(infilepxm,px2,py2);                                       //  host image pixel

         if (ovpxb->nc == 4) f1 = pix1[3] / 256.0;                               //  use transparency if present
         else f1 = 1.0;
         f2 = 1.0 - f1;

         pix2[0] = f1 * pix1[0] + f2 * pix2[0];                                  //  copy pixel
         pix2[1] = f1 * pix1[1] + f2 * pix2[1];
         pix2[2] = f1 * pix1[2] + f2 * pix2[2];
      }

      err = PXM_save(infilepxm,outfile,f_load_bpc,90,0);
      if (err) {
         popup_report_write2(zdpop,0,"*** cannot save output file \n");
         continue;
      }

      load_filemeta(outfile);                                                    //  update image index for output file
      update_image_index(outfile);

      popup_report_write2(zdpop,0,"*** completed \n");
   }

   Funcbusy(-1);

   if (! zdialog_valid(zdpop)) {
      Plog(0,"*** report canceled \n");
      goto cleanup;
   }

   popup_report_write2(zdpop,0,"\n *** %s \n","COMPLETED");
   popup_report_bottom(zdpop);

cleanup:

   if (outfile) zfree(outfile);
   outfile = 0;

   if (infilepxm) PXM_free(infilepxm);
   infilepxm = 0;

   if (ovfilepxb) PXB_free(ovfilepxb);
   ovfilepxb = 0;

   if (ovpxb) PXB_free(ovpxb);
   ovpxb = 0;

   gallery(navi::galleryname,"init",0);                                          //  refresh file list
   gallery(0,"sort",-2);                                                         //  recall sort and position
   gallery(0,"paint",-1);                                                        //  repaint from same position

   Fblock(0);
   return;
}


//  dialog event and completion callback function

int batch_overlay_dialog_event(zdialog *zd, ch *event)
{
   using namespace batch_overlay;

   ch       countmess[80];
   ch       *pp, *ofile;

   if (zd->zstat)                                                                //  dialog completed
   {
      if (zd->zstat != 1) return 1;                                              //  canceled

      if (! SFcount) {                                                           //  selected host files count
         zmessageACK(Mwin,"no host files selected");
         zd->zstat = 0;
         return 1;
      }

      if (! ovfilepxb) {                                                         //  selected overlay file
         zmessageACK(Mwin,"no overlay file selected");
         zd->zstat = 0;
         return 1;
      }

      return 1;                                                                  //  finished
   }

   if (strmatch(event,"hostselect")) {
      zdialog_show(zd,0);                                                        //  hide parent dialog
      select_files(0,1);                                                         //  get list of files to convert          24.20
      zdialog_show(zd,1);
      snprintf(countmess,80,"%d image files selected",SFcount);                  //  update dialog
      zdialog_stuff(zd,"hostcount",countmess);
   }

   if (strmatch(event,"ovselect"))                                               //  select overlay image file
   {
      zdialog_show(zd,0);                                                        //  hide main dialog
      if (ovfile) ofile = select_files1(ovfile);
      else ofile = select_files1(saved_areas_folder);
      zdialog_show(zd,1);
      gallery(0,"paint",-1);                                                     //  repaint from same position

      if (! ofile) return 1;

      if (image_file_type(ofile) != IMAGE) {
         zmessageACK(Mwin,"not an image file");
         return 1;
      }

      if (ovfilepxb) PXB_free(ovfilepxb);                                        //  create overlay PXB pixmap
      ovfilepxb = PXB_load(ofile,1);
      if (! ovfilepxb) return 1;

      if (ovfile) zfree(ovfile);                                                 //  selected file --> dialog
      ovfile = ofile;
      pp = strrchr(ovfile,'/');
      zdialog_stuff(zd,"ovfile",pp+1);
   }

   if (strmatch(event,"pctwidth"))                                               //  overlay width % value
      zdialog_fetch(zd,"pctwidth",pctwidth);

   if (strmatch(event,"pcttop"))                                                 //  overlay position from top
      zdialog_fetch(zd,"pcttop",pcttop);

   if (strmatch(event,"pctleft"))                                                //  overlay position from left
      zdialog_fetch(zd,"pctleft",pctleft);

   if (strmatch(event,"Fwinadj"))                                                //  adjust overlay width for window
      zdialog_fetch(zd,"Fwinadj",Fwinadj);

   if (strmatch(event,"winww"))                                                  //  window width
      zdialog_fetch(zd,"winww",winww);

   if (strmatch(event,"winhh"))                                                  //  window height
      zdialog_fetch(zd,"winhh",winhh);

   if (strmatch(event,"refresh")) {                                              //  refresh current window size
      gtk_window_get_size(MWIN,&winww,&winhh);
      zdialog_stuff(zd,"winww",winww);                                           //  target window = current size
      zdialog_stuff(zd,"winhh",winhh);
   }

   if (strmatch(event,"Frepl")) {                                                //  replace host files
      zdialog_fetch(zd,"Frepl",Frepl);
      Fvers = 1 - Frepl;
      zdialog_stuff(zd,"Fvers",Fvers);
   }

   if (strmatch(event,"Fvers")) {                                                //  make new versions
      zdialog_fetch(zd,"Fvers",Fvers);
      Frepl = 1 - Fvers;
      zdialog_stuff(zd,"Frepl",Frepl);
   }

   return 1;
}


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

//  Select files and output a file containing the selected file names.

namespace imagelist_images
{
   zdialog  *zd;
   ch       outfile[300];
};


//  menu function

void m_export_filelist(GtkWidget *, ch *)
{
   using namespace imagelist_images;

   int   export_filelist_dialog_event(zdialog *zd, ch *event);

   FILE     *fid;
   int      ii, zstat;
   ch       *title = "Create a file of selected image files";
   ch       text[100];

   F1_help_topic = "export file list";

   Plog(1,"m_export_filelist \n");

   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }

   if (Fblock("export file list")) return;

/***
       ____________________________________________
      |    Create a file of selected image files   |
      |                                            |
      |  [Select Files]  NNN files selected        |
      |  Output File [__________________] [Browse] |
      |                                            |
      |                              [Proceed] [X] |
      |____________________________________________|

***/

   zd = zdialog_new(title,Mwin,"Proceed"," X ",null);

   zdialog_add_widget(zd,"hbox","hbif","dialog",0,"space=3");
   zdialog_add_widget(zd,"button","infiles","hbif","Select Files","space=3");
   zdialog_add_widget(zd,"label","Nfiles","hbif","no files selected","space=10");

   zdialog_add_widget(zd,"hbox","hbof","dialog",0,"space=3");
   zdialog_add_widget(zd,"label","labof","hbof","Output File","space=3");
   zdialog_add_widget(zd,"zentry","outfile","hbof",0,"size=30|space=5");
   zdialog_add_widget(zd,"button","browse","hbof","Browse","space=5");

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"Nfiles",text);

   if (*outfile) zdialog_stuff(zd,"outfile",outfile);

   zdialog_run(zd,export_filelist_dialog_event,"parent");                        //  run dialog, wait for response

retry:
   zstat = zdialog_wait(zd);
   if (zstat != 1) {
      zdialog_free(zd);
      Fblock(0);
      return;
   }

   free_resources();                                                             //  no current file                       24.30

   zdialog_fetch(zd,"outfile",outfile,300);                                      //  get output file from dialog

   if (! SFcount) {
      zmessageACK(Mwin,"no input files selected");
      zd->zstat = 0;
      goto retry;                                                                //  no input files
   }

   if (! *outfile) {
      zmessageACK(Mwin,"no output file selected");
      zd->zstat = 0;
      goto retry;                                                                //  no input files
   }

   fid = fopen(outfile,"w");                                                     //  open output file
   if (! fid) {
      zmessageACK(Mwin,strerror(errno));                                         //  error
      zd->zstat = 0;
      goto retry;                                                                //  no input files
   }

   zdialog_free(zd);

   for (ii = 0; ii < SFcount; ii++)                                              //  write input file names to output
      fprintf(fid,"%s\n",SelFiles[ii]);

   fclose(fid);

   zmessageACK(Mwin,"COMPLETED");
   
   Fblock(0);
   return;
}


//  dialog event and completion function

int export_filelist_dialog_event(zdialog *zd, ch *event)
{
   using namespace imagelist_images;

   ch       *file;
   ch       countmess[80];

   if (strmatch(event,"infiles"))                                                //  select image files
   {
      select_files(0,1);                                                         //  get list of files to export           24.20
      snprintf(countmess,80,"%d image files selected",SFcount);                  //  update dialog
      zdialog_stuff(zd,"Nfiles",countmess);
   }

   if (strmatch(event,"browse"))
   {
      file = zgetfile("Output File",MWIN,"save",outfile,0);
      if (file) zdialog_stuff(zd,"outfile",file);
      else zdialog_stuff(zd,"outfile","");
   }

   return 1;
}


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

//  Export selected image files to another folder with optional downsizing.
//  Only the metadata relevant for web photo services is copied.

namespace export_files_names
{
   ch       tolocation[500];
   int      Fsamesize;
   int      maxww, maxhh;
   int      Fmeta;
};


//  menu function

void m_export_files(GtkWidget*, ch *menu)
{
   using namespace export_files_names;

   int export_files_dialog_event(zdialog *zd, ch *event);

   zdialog     *zd, *zdpop;
   int         ii, cc, err, ww, hh, zstat;
   PXM         *pxmin, *pxmout;
   ch          *infile, *outfile, *pp, text[100];
   float       scale, wscale, hscale;
   #define     NK 9
   ch          *keys[NK] = { meta_date_key, meta_tags_key, meta_copyright_key,
                             meta_description_key, meta_title_key,
                             meta_location_key, meta_country_key,
                             meta_lati_key, meta_longi_key };
   ch          *kdata[NK];

   F1_help_topic = "export files";

   Plog(1,"m_export_files \n");

   if (Findexvalid == 0) {
      zmessageACK(Mwin,"image index disabled");                                  //  no image index
      return;
   }

   if (Fblock("export files")) return;

/***
       __________________________________________________
      |                Export Files                      |
      |                                                  |
      |  [Select Files]  N files selected                |
      |  To Location [________________________] [Browse] |
      |  Max. Width [____]  Height [____]  [x] no change |
      |  [x] export metadata                             |
      |                                                  |
      |                                    [Proceed] [X] |
      |__________________________________________________|

***/

   zd = zdialog_new("Export Files",Mwin,"Proceed"," X ",null);
   zdialog_add_widget(zd,"hbox","hbf","dialog");
   zdialog_add_widget(zd,"button","files","hbf","Select Files","space=5");
   zdialog_add_widget(zd,"label","fcount","hbf","no files selected","space=10");
   zdialog_add_widget(zd,"hbox","hbloc","dialog");
   zdialog_add_widget(zd,"label","labloc","hbloc","To Location","space=5");
   zdialog_add_widget(zd,"zentry","toloc","hbloc",0,"expand");
   zdialog_add_widget(zd,"button","browse","hbloc","Browse","space=5");
   zdialog_add_widget(zd,"hbox","hbwh","dialog");
   zdialog_add_widget(zd,"label","labw","hbwh","max. Width","space=5");
   zdialog_add_widget(zd,"zentry","maxww","hbwh","1000","size=5");
   zdialog_add_widget(zd,"label","space","hbwh",0,"space=5");
   zdialog_add_widget(zd,"label","labh","hbwh","Height","space=5");
   zdialog_add_widget(zd,"zentry","maxhh","hbwh","700","size=5");
   zdialog_add_widget(zd,"check","samesize","hbwh","no change","space=12");
   zdialog_add_widget(zd,"hbox","hbmeta","dialog");
   zdialog_add_widget(zd,"check","meta","hbmeta","export metadata");

   zdialog_load_inputs(zd);                                                      //  preload prior location

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"fcount",text);

   zdialog_resize(zd,400,0);
   zdialog_run(zd,export_files_dialog_event,"parent");                           //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);

   if (zstat != 1) {
      Fblock(0);
      return;
   }

   free_resources();                                                             //  no current file                       24.30

   Funcbusy(+1);

   zdpop = popup_report_open("exporting files",Mwin,600,400,0,0,0,"X",0);        //  log report

   Funcbusy(+1);                                                                 //  24.10

   for (ii = 0; ii < SFcount; ii++)                                              //  loop selected files
   {
      zmainloop();                                                               //  keep GTK awake

      if (! zdialog_valid(zdpop)) break;                                         //  canceled

      infile = SelFiles[ii];                                                     //  input filespec
      popup_report_write2(zdpop,0,"%s \n",infile);

      pp = strrchr(infile,'/');                                                  //  construct output filespec
      if (! pp) continue;
      cc = strlen(pp) + 8;
      outfile = zstrdup(tolocation,"export files",cc);
      strcat(outfile,pp);
      pp = strrchr(outfile,'.');                                                 //  outfile is type .jpg
      if (! pp) continue;
      strcpy(pp,".jpg");

      pxmin = PXM_load(infile,0);                                                //  read input file
      if (! pxmin) {
         popup_report_write2(zdpop,1," *** file type not supported \n");
         zfree(outfile);
         continue;
      }

      ww = pxmin->ww;                                                            //  input file size
      hh = pxmin->hh;

      if (Fsamesize) pxmout = pxmin;                                             //  same size, output = input
      else {
         wscale = hscale = 1.0;
         if (ww > maxww) wscale = 1.0 * maxww / ww;                              //  compute new size
         if (hh > maxhh) hscale = 1.0 * maxhh / hh;
         if (wscale < hscale) scale = wscale;
         else scale = hscale;
         if (scale > 0.999) pxmout = pxmin;                                      //  no change
         else {
            ww = ww * scale;
            hh = hh * scale;
            pxmout = PXM_rescale(pxmin,ww,hh);                                   //  rescaled output file
            PXM_free(pxmin);                                                     //  free memory
         }
      }

      err = PXM_JPG_save(pxmout,outfile,jpeg_def_quality);                       //  write output file
      if (err) popup_report_write2(zdpop,1," *** cannot create new file \n");

      if (! err && Fmeta) {                                                      //  optional copy metadata
         err = meta_get1(infile,(ch **) keys,kdata,NK);
         if (! err) {
            err = meta_put(outfile,(ch **) keys,kdata,NK);
            if (err) popup_report_write2(zdpop,1," *** metadata update errpr \n");
         }
      }

      PXM_free(pxmout);
      zfree(outfile);
   }

   Funcbusy(-1);

   if (! zdialog_valid(zdpop)) {
      Plog(0,"*** report canceled \n");
      goto cleanup;
   }

   popup_report_write2(zdpop,0,"\n *** COMPLETED \n");
   popup_report_bottom(zdpop);

cleanup:
   Funcbusy(-1);
   Fblock(0);
   return;
}


//  dialog event and completion callback function

int export_files_dialog_event(zdialog *zd, ch *event)
{
   using namespace export_files_names;

   int      cc;
   ch       countmess[80];
   ch       *ploc;

   if (strmatch(event,"files"))                                                  //  select files to export
   {
      select_files(0,1);                                                         //  get list of files to export           24.20
      snprintf(countmess,80,"%d image files selected",SFcount);                  //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

   if (strmatch(event,"browse")) {
      zdialog_fetch(zd,"toloc",tolocation,500);
      ploc = zgetfile("Select folder",MWIN,"folder",tolocation);                 //  new location browse
      if (! ploc) return 1;
      zdialog_stuff(zd,"toloc",ploc);
      zfree(ploc);
   }

   if (! zd->zstat) return 1;                                                    //  wait for dialog completion
   if (zd->zstat != 1) return 1;                                                 //  canceled

   if (! SFcount) {                                                              //  [proceed]
      zmessageACK(Mwin,"no files selected");                                     //  no files selected
      zd->zstat = 0;                                                             //  keep dialog active
      return 1;
   }

   zdialog_fetch(zd,"toloc",tolocation,500);                                     //  get output location
   if (! dirfile(tolocation)) {
      zmessageACK(Mwin,"location is not a folder");
      zd->zstat = 0;
   }

   cc = strlen(tolocation) - 1;                                                  //  remove trailing '/' if present
   if (tolocation[cc] == '/') tolocation[cc] = 0;

   zdialog_fetch(zd,"samesize",Fsamesize);                                       //  get rescale options
   zdialog_fetch(zd,"maxww",maxww);
   zdialog_fetch(zd,"maxhh",maxhh);
   zdialog_fetch(zd,"meta",Fmeta);                                               //  metadata option

   return 1;
}


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

    Build a script file with one or more predefined edit functions
     that can be executed like a single edit function.

    fotocx.h:  ch       scriptfile[200];               //  current script file
               FILE     *script_fid;                   //  script file FID
               int      Fscriptbuild;                  //  flag, script build in progress

***/


//  menu function

void m_edit_script(GtkWidget *, ch *menu)
{
   int  edit_script_dialog_event(zdialog *zd, ch *event);

   zdialog  *zd;

   F1_help_topic = "script files";

   Plog(1,"m_edit_script \n");

/***
       _________________________________________
      |         Script Files                    |
      |                                         |
      |  [start]  begin making a script file    |
      |  [close]  finish making a script file   |
      |                                         |
      |                                     [X] |
      |_________________________________________|

***/

   zd = zdialog_new("Script Files",Mwin," X ",null);

   zdialog_add_widget(zd,"hbox","hb1","dialog");
   zdialog_add_widget(zd,"button","start","hb1","Start","space=5");
   zdialog_add_widget(zd,"label","labstart","hb1","begin making a script file");
   zdialog_add_widget(zd,"hbox","hb2","dialog");
   zdialog_add_widget(zd,"button","close","hb2","Close","space=5");
   zdialog_add_widget(zd,"label","labclose","hb2","finish making a script file");

   zdialog_run(zd,edit_script_dialog_event,"parent");
   return;
}


//  dialog event and completion function

int edit_script_dialog_event(zdialog *zd, ch *event)
{
   int  edit_script_start(void);
   int  edit_script_close(void);

   if (zd->zstat) {                                                              //  canceled
      zdialog_free(zd);
      if (script_fid) fclose(script_fid);
      script_fid = 0;
      Fscriptbuild = 0;
      return 1;
   }

   if (strmatch(event,"start"))                                                  //  start building script
      edit_script_start();

   if (strmatch(event,"close")) {                                                //  script if finished
      edit_script_close();
      zdialog_free(zd);
   }

   return 1;
}


//  start building a script file
//  if Fscriptbuild is active, edit_done() saves edit settings in script file

int edit_script_start()
{
   ch       *pp;

   if (Fscriptbuild) {
      zmessageACK(Mwin,"script already started");
      return 0;
   }

   pp = zgetfile("start a new script file",MWIN,"save",scripts_folder,1);
   if (! pp) return 0;

   strncpy0(scriptfile,pp,200);                                                  //  script file name from user
   zfree(pp);

   pp = strrchr(scriptfile,'/');                                                 //  script name = file base name
   if (! pp || strlen(pp) > 99) {
      zmessageACK(Mwin,"script file name too big");
      return 0;
   }

   script_fid = fopen(scriptfile,"w");                                           //  open script file
   if (! script_fid) {
      zmessageACK(Mwin,strerror(errno));
      return 0;
   }

   fprintf(script_fid,"script name: %s\n",pp+1);                                 //  write header record
   Fscriptbuild = 1;

   zmessageACK(Mwin,"perform edits to be included in the script file");
   return 1;
}


//  this function is called from edit_done() when Fscriptbuild is active
//  save all widget settings in the script file data

void edit_script_addfunc(editfunc *CEF)
{
   int      err;

   fprintf(script_fid,"menu: %s\n",CEF->menuname);                               //  write "menu: menu name"

   if (CEF->zd) {
      err = zdialog_save_widgets(CEF->zd,CEF->sd,CEF->menuname,script_fid);      //  write widget settings
      if (err) {
         zmessageACK(Mwin,"script file error");
         return;
      }
   }

   zmessageACK(Mwin,"%s added to script",CEF->menuname);
   return;
}


//  complete and save a script file under construction

int edit_script_close()
{
   if (! Fscriptbuild) {
      zmessageACK(Mwin,"no script file was started");
      return 0;
   }

   fprintf(script_fid,"menu: end");
   fclose(script_fid);
   script_fid = 0;
   Fscriptbuild = 0;
   zmessageACK(Mwin,"script file closed");
   return 1;
}


//  present a popup menu for user to select a script file
//  run the given function using the selected script file
//  called by  m_run_script()  and  m_batch_script()
//
//  'runfunc' arg: void runfunc(GtkWidget *, ch *scriptname);

void select_script(cbFunc *runfunc)
{
   static ch         **scriptnames = 0;
   static GtkWidget  *scriptmenu = 0;
   static int        ns = 0;

   ch          buff[200], *pp;
   int         ii, kk;
   FILE        *fid;

   if (scriptnames)
   {
      for (ii = 0; ii < ns; ii++)                                                //  free prior memory
         zfree(scriptnames[ii]);
      zfree(scriptnames);
      scriptnames = 0;
   }

   if (scriptmenu) gtk_widget_destroy(scriptmenu);
   scriptmenu = 0;

   ns = zreaddir(scripts_folder,scriptnames);                                    //  get all script names
   if (ns < 1) {                                                                 //    from script file folder
      zmessageACK(Mwin,"no script files found");
      return;
   }

   scriptmenu = create_popmenu();                                                //  create popup menu for script names
   add_popmenu_item(scriptmenu,"Cancel",0,0,0);

   for (ii = kk = 0; ii < ns; ii++)
   {
      snprintf(scriptfile,200,"%s/%s",scripts_folder,scriptnames[ii]);           //  read script file 1st record
      fid = fopen(scriptfile,"r");
      if (! fid) continue;
      pp = fgets_trim(buff,200,fid,1);                                           //  read "script name: scriptname"
      if (pp && strmatch(pp+13,scriptnames[ii])) {                               //    must match file name
         add_popmenu_item(scriptmenu,scriptnames[ii],runfunc,0,0);               //  add to popup menu
         kk++;
      }
      fclose(fid);
   }

   if (kk) popup_menu(0,scriptmenu);                                             //  present menu to select and run
   else zmessageACK(Mwin,"no script files found");

   return;
}


//  execute a script file using the current image file

void run_script(GtkWidget *, ch *scriptname)
{
   ch       buff[200];
   ch       *pp, menuname[40];
   int      ii, err;

   if (! curr_file) return;

   if (script_fid) Plog(0,"*** run_script(): script_fid not 0 \n");

   Funcbusy(+1);                                                                 //  enable user kill

   snprintf(scriptfile,200,"%s/%s",scripts_folder,scriptname);                   //  script file from script name

   Plog(1,"start script: %s \n",scriptfile);
   script_fid = fopen(scriptfile,"r");                                           //  open script file
   if (! script_fid) {
      zmessageACK(Mwin,"script error: %s \n %s",scriptfile,strerror(errno));
      goto badfile;
   }

   pp = fgets_trim(buff,200,script_fid,1);                                       //  read "script name: scriptname"
   if (! pp) goto badfile;
   if (! strmatch(pp+13,scriptname)) goto badfile;                               //  must match script name

   while (true)                                                                  //  process script file
   {
      if (Fescape) break;                                                        //  user kill

      Fscriptrun = 1;                                                            //  script file active

      pp = fgets_trim(buff,200,script_fid,1);                                    //  read "menu: menuname"
      if (! pp) goto badfile;
      if (! strmatchN(pp,"menu: ",6)) goto badfile;
      strncpy0(menuname,pp+6,40);
      if (strmatch(menuname,"end")) break;                                       //  end of script

      for (ii = 0; menutab[ii].menu; ii++)                                       //  convert menu name to menu function
         if (strmatch(menuname,menutab[ii].menu)) break;

      if (! menutab[ii].menu) {
         zmessageACK(Mwin,"unknown edit function: %s",menuname);
         goto badfile;
      }

      Plog(1,"start edit: %s for file: %s \n",menuname,curr_file);

      menutab[ii].func(0,menutab[ii].arg);                                       //  call the menu function
      zmainsleep(0.1);

      if (CEF && CEF->zd) {
         err = zdialog_load_widgets(CEF->zd,CEF->sd,CEF->menuname,script_fid);   //  read and load dialog settings
         if (err) {
            zmessageACK(Mwin,"load widgets failed: %s",menuname);
            goto badfile;
         }
         zdialog_send_event(CEF->zd,"apply");                                    //  finish edit
         if (CEF && CEF->threadfunc) thread_wait();                              //  insure complete
         if (CEF && CEF->zd) zdialog_send_event(CEF->zd,"done");
         Plog(1,"finish edit \n");
      }
   }

   fclose(script_fid);                                                           //  close script file
   script_fid = 0;
   Fscriptrun = 0;                                                               //  script file not active
   Fescape = 0;
   Funcbusy(-1);
   return;

badfile:
   zmessageACK(Mwin,"script file format error: %s",scriptname);
   if (script_fid) fclose(script_fid);
   script_fid = 0;
   Fscriptrun = 0;
   Fescape = 0;
   Funcbusy(-1);
   return;
}


//  execute a script file using pre-selected image files

void batch_script(GtkWidget *, ch *scriptname)
{
   ch       *imagefile;
   ch       *newfilevers;
   int      ii, err;

   if (! SFcount) {                                                              //  preselected file list
      zmessageACK(Mwin,"no files selected");
      return;
   }

   Funcbusy(+1);                                                                 //  enable user kill

   for (ii = 0; ii < SFcount; ii++)                                              //  loop image files to process
   {
      if (Fescape) break;

      imagefile = SelFiles[ii];
      err = f_open(imagefile);                                                   //  open, current image file
      if (err) {
         zmessageACK(Mwin,"open failure: %s \n %s",imagefile,strerror(errno));
         break;
      }

      run_script(0,scriptname);                                                  //  run script for image file

      newfilevers = file_new_version(curr_file);                                 //  get next avail. file version name
      if (! newfilevers) break;

      if (strmatch(curr_file_type,"RAW")) {                                      //  if RAW, substitute tif-16
         strcpy(curr_file_type,"tif");
         curr_file_bpc = 16;
      }
      err = f_save(newfilevers,curr_file_type,curr_file_bpc,0,1);                //  save file
      zfree(newfilevers);
   }

   Fescape = 0;
   Funcbusy(-1);

   zmessage_post(Mwin,"20/20",3,"script complete");
   return;
}


//  menu function
//  select and run a script file using the current image file

void m_run_script(GtkWidget *, ch *menu)
{
   F1_help_topic = "script files";
   Plog(1,"m_run_script \n");
   select_script(run_script);
   return;
}


//  menu function
//  select and run a script file using multiple selected files

void m_batch_script(GtkWidget *, ch *menu)
{
   int  batch_script_dialog_event(zdialog *zd, ch *event);

   zdialog  *zd;
   ch       text[100];

   F1_help_topic = "script files";

   Plog(1,"m_batch_script \n");

/***
       _________________________________________
      |         Batch Script                    |
      |                                         |
      |  [Select Files]  NN files selected      |
      |  [Select Script]  script file to run    |
      |                                         |
      |                                     [X] |
      |_________________________________________|

***/

   zd = zdialog_new("Batch Script",Mwin," X ",null);

   zdialog_add_widget(zd,"hbox","hb1","dialog");
   zdialog_add_widget(zd,"button","select-files","hb1","Select Files","space=5");
   zdialog_add_widget(zd,"label","fcount","hb1","no files selected");

   zdialog_add_widget(zd,"hbox","hb2","dialog");
   zdialog_add_widget(zd,"button","select-script","hb2","Select Script","space=5");
   zdialog_add_widget(zd,"label","labscript","hb2","script file to run");

   snprintf(text,100,"%d image files selected",SFcount);                         //  show selected files count
   zdialog_stuff(zd,"fcount",text);

   zdialog_run(zd,batch_script_dialog_event,"parent");
   return;
}


//  dialog event and completion function

int batch_script_dialog_event(zdialog *zd, ch *event)
{
   ch       countmess[80];

   F1_help_topic = "script files";

   if (zd->zstat) {                                                              //  canceled
      zdialog_free(zd);
      return 1;
   }

   if (strmatch(event,"select-files"))
   {
      select_files(0,1);                                                         //  get new file list                     24.20

      if (SFcount) {
         snprintf(countmess,80,"%d image files selected",SFcount);               //  update dialog file count
         zdialog_stuff(zd,"fcount",countmess);
      }
      else zdialog_stuff(zd,"fcount","no files selected");
   }

   if (strmatch(event,"select-script")) {                                        //  select and run a script file
      if (! SFcount) zmessageACK(Mwin,"no files selected");
      else {
         select_script(batch_script);
         zdialog_free(zd);
      }
   }

   return 1;
}
