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

   Fotoxx      edit photos and manage collections

   Copyright 2007-2020 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.

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

   Fotoxx image edit - Process Menu functions

   m_batch_convert         batch rename, convert, resize, move
   batch_sharp_func        callable sharpen function
   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_burn_DVD              burn images to DVD/Blue-Ray disc
   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 "fotoxx.h"                                                              //  (variables in fotoxx.h are refs)
#include <sys/wait.h>

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


//  Batch file rename, convert, resize, move

namespace batch_convert
{
   int      Fsametype, Fsamesize, maxww, maxhh;
   int      Fsamebpc, newbpc, jpeg_quality;
   int      Fdelete, Fcopymeta, Fupright, Fsharpen, Freplace;
   int      Fplugdate, Fplugseq, Fplugname;
   char     newloc[500], newname[200], newext[8];
   int      baseseq, addseq;
   int      amount, thresh;
   int      Foverlay, ovpos, ovwidth;
   int      Fovconst, ovscrww, ovscrhh;
   char     *ovfile = 0;
};


//  menu function

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

   int  batch_convert_dialog_event(zdialog *zd, cchar *event);
   int  batch_sharp_func(PXM *pxm, int amount, int thresh);

   zdialog     *zd, *zd2;
   int         zstat;
   char        *infile, *outfile, tempfile[300];
   char        *inloc, *inname, *inext;
   char        *outloc, *outname, *outext;
   char        *tempname, seqplug[8], seqnum[8];
   char        plugyyyy[8], plugmm[4], plugdd[4];
   char        **oldfiles, **newfiles;
   char        *pp, *pp1, *ppv[2], text[100];
   int         ii, jj, cc, err, Fjpeg;
   int         outww, outhh, outbpc, Fdelete2;
   int         Noldnew;
   char        orientation;
   float       scale, wscale, hscale;
   PXM         *pxmin, *pxmout;
   STATB       statdat;
   xxrec_t     *xxrec;
   cchar       *exifkey[1] = { exif_orientation_key };
   cchar       *exifdata[1];
   GdkPixbuf   *ovpxb;
   GError      *gerror = 0;
   float       imageR, screenR;
   int         pww, phh, orgx, orgy, nc, rs;
   int         px1, py1, px2, py2;
   uint8       *pix0, *pix1;
   float       *pix2, f1, f2;

   F1_help_topic = "batch convert";

   if (checkpend("all")) return;                                                 //  check nothing pending

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

   Fblock = 1;

/***
       ______________________________________________________________
      |                    Batch Convert                             |
      |                                                              |
      |  [Select Files]  N files selected                            |
      |                                                              |
      |  New Name [________________________________________________] |
      |  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] no change             |
      |  [x] Delete Originals  [x] Copy Metadata  [x] Upright        |
      |  [x] Sharpen   amount [___]  threshold [___]                 |
      |                                                              |
      |  [x] Overlay Image [open]    Width % [ 23 ]                  |
      |  Overlay Position  [_] [_] [_] [_] [_] [_] [_] [_] [_]       |
      |  [x] make constant width for screen [_____] [_____]          |
      |                                                              |
      |                                           [proceed] [cancel] |
      |______________________________________________________________|

***/

   zd = zdialog_new(E2X("Batch Convert"),Mwin,Bproceed,Bcancel,null);

   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf",Bselectfiles,"space=5");
   zdialog_add_widget(zd,"label","fcount","hbf",Bnofileselected,"space=10");

   zdialog_add_widget(zd,"hbox","hbname","dialog");
   zdialog_add_widget(zd,"label","labname","hbname",E2X("New Name"),"space=5");
   zdialog_add_widget(zd,"zentry","newname","hbname",0,"expand");

   zdialog_add_widget(zd,"hbox","hbseq","dialog");
   zdialog_add_widget(zd,"label","labseq","hbseq",E2X("Sequence Numbers"),"space=5");
   zdialog_add_widget(zd,"label","space","hbseq",0,"space=8");
   zdialog_add_widget(zd,"label","labbase","hbseq",E2X("base"),"space=5");
   zdialog_add_widget(zd,"zentry","baseseq","hbseq",0,"size=5");
   zdialog_add_widget(zd,"label","space","hbseq","","space=3");
   zdialog_add_widget(zd,"label","labadder","hbseq",E2X("adder"),"space=5");
   zdialog_add_widget(zd,"zentry","addseq","hbseq",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",E2X("New Location"),"space=5");
   zdialog_add_widget(zd,"zentry","newloc","hbloc",0,"expand");
   zdialog_add_widget(zd,"button","browse","hbloc",Bbrowse,"space=5");

   zdialog_add_widget(zd,"hbox","hbft","dialog");
   zdialog_add_widget(zd,"label","labtyp","hbft",E2X("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",E2X("no change"),"space=6");

   zdialog_add_widget(zd,"hbox","hbcd","dialog");
   zdialog_add_widget(zd,"label","labcd","hbcd",E2X("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",E2X("no change"),"space=4");

   zdialog_add_widget(zd,"hbox","hbwh","dialog");
   zdialog_add_widget(zd,"label","labw","hbwh",E2X("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",Bheight,"space=5");
   zdialog_add_widget(zd,"zentry","maxhh","hbwh","700","size=5");
   zdialog_add_widget(zd,"check","samesize","hbwh",E2X("no change"),"space=12");
   zdialog_add_widget(zd,"label","space","hbwh",0,"space=30");                   //  push back oversized entries

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

   zdialog_add_widget(zd,"hbox","hbsharp","dialog");
   zdialog_add_widget(zd,"check","sharpen","hbsharp",E2X("Sharpen"),"space=3");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labamount","hbsharp",Bamount,"space=3");
   zdialog_add_widget(zd,"zspin","amount","hbsharp","0|400|1|100");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labthresh","hbsharp",Bthresh,"space=3");
   zdialog_add_widget(zd,"zspin","thresh","hbsharp","0|100|1|20");
   
   zdialog_add_widget(zd,"hsep","ovsep","dialog","","space=3");
   zdialog_add_widget(zd,"hbox","hbov1","dialog");                               //  overlay image
   zdialog_add_widget(zd,"check","ckov","hbov1",E2X("Overlay Image"),"space=3");
   zdialog_add_widget(zd,"button","imgov","hbov1",Bopen,"space=3");
   zdialog_add_widget(zd,"label","space","hbov1",0,"space=5");
   zdialog_add_widget(zd,"label","labww","hbov1",E2X("Width %"),"space=3");
   zdialog_add_widget(zd,"zspin","ovwidth","hbov1","1.0|100.0|0.5|10.0","space=3");
   zdialog_add_widget(zd,"label","space","hbov1",0,"space=5");

   zdialog_add_widget(zd,"hbox","hbov2","dialog");
   zdialog_add_widget(zd,"label","labpos","hbov2",E2X("Position"),"space=3");
   zdialog_add_widget(zd,"imagebutt","ovTL","hbov2","overlayTL.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovTC","hbov2","overlayTC.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovTR","hbov2","overlayTR.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovCL","hbov2","overlayCL.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovCC","hbov2","overlayCC.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovCR","hbov2","overlayCR.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovBL","hbov2","overlayBL.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovBC","hbov2","overlayBC.png","size=20|space=2");
   zdialog_add_widget(zd,"imagebutt","ovBR","hbov2","overlayBR.png","size=20|space=2");
   
   zdialog_add_widget(zd,"hbox","hbov3","dialog");
   zdialog_add_widget(zd,"check","ovconst","hbov3",E2X("Make constant size for screen:"),"space=3");
   zdialog_add_widget(zd,"label","space","hbov3","","space=3");
   zdialog_add_widget(zd,"label","ovlabww","hbov3",Bwidth,"space=3");
   zdialog_add_widget(zd,"zentry","ovscrww","hbov3","","size=5|space=3");
   zdialog_add_widget(zd,"label","space","hbov3","","space=3");
   zdialog_add_widget(zd,"label","ovlabhh","hbov3",Bheight,"space=3");
   zdialog_add_widget(zd,"zentry","ovscrhh","hbov3","","size=5|space=3");
   
   zdialog_add_ttip(zd,"newname",E2X("plugins: (year month day old-name sequence) $yyyy  $mm  $dd  $oldname  $s"));

   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   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,"samesize",1);                                               //  same size
   zdialog_stuff(zd,"delete",0);                                                 //  delete originals - no
   zdialog_stuff(zd,"copymeta",0);                                               //  copy metadata - no
   zdialog_stuff(zd,"upright",1);                                                //  upright rotation - yes
   zdialog_stuff(zd,"sharpen",0);                                                //  sharpen - no
   zdialog_stuff(zd,"amount",100);                                               //  sharpen amount
   zdialog_stuff(zd,"thresh",20);                                                //  sharpen threshold
   zdialog_stuff(zd,"ovconst",0);                                                //  overlay constant size - no
   zdialog_stuff(zd,"ovscrww",zfuncs::monitor_ww);                               //  screen pixel size
   zdialog_stuff(zd,"ovscrhh",zfuncs::monitor_hh);

   *newloc = 0;
   oldfiles = newfiles = 0;
   Noldnew = 0;
   Foverlay = 0;
   ovpos = 0;
   Fovconst = 0;

   zdialog_restore_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 (! GScount) goto cleanup;

   free_resources();                                                             //  no curr. file 

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

   zd2 = popup_report_open("Processing files",Mwin,600,300,0,0,0);               //  status monitor popup window

   for (ii = 0; ii < GScount; ii++)                                              //  loop selected files
   {
      zmainloop();                                                               //  keep GTK alive
      
      infile = GSfiles[ii];                                                      //  input file
      
      popup_report_write(zd2,0,"\n");
      popup_report_write(zd2,0,"%s \n",infile);                                  //  log each input file

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

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

      cc = strlen(outloc) - 1;                                                   //  remove trailing '/'
      if (outloc[cc] == '/') outloc[cc] = 0;
      
      if (*newname) {
         zfree(outname);
         outname = zstrdup(newname);                                             //  may contain $-plugins
      }
      
      if (Fplugname)                                                             //  insert old file name
      {
         cc = strlen(outname) - 8 + strlen(inname);
         if (cc < 1) cc = 1;
         tempname = zstrdup(outname,cc);
         repl_1str(outname,tempname,"$oldname",inname);                          // ...$oldname...  >>  ...inname...
         zfree(outname);
         outname = tempname;
      }
      
      if (Fplugdate)                                                             //  insert EXIF date
      {
         xxrec = get_xxrec(infile);                                              //  get photo date, yyyymmddhhmmdd     19.0

         if (strmatch(xxrec->pdate,"null")) {
            popup_report_write(zd2,0,"no photo date, skipped \n");               //  19.13
            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,8);
         repl_Nstrs(outname,tempname,"$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,8);
         repl_1str(outname,tempname,seqplug,seqnum);                             //  ...$ssss...  >>  ...1234...
         zfree(outname);
         outname = tempname;
      }
      
      if (*newloc) {                                                             //  new location was given
         zfree(outloc);
         outloc = zstrdup(newloc);
      }

      if (Fsametype) {
         if (strcasestr(".jpg .jpeg",inext)) strcpy(outext,".jpg");              //  new .ext from existing .ext
         else if (strcasestr(".png",inext)) strcpy(outext,".png");
         else if (strcasestr(".tif .tiff",inext)) strcpy(outext,".tif");
         else strcpy(outext,".jpg");                                             //  unknown >> .jpg
      }
      else strcpy(outext,newext);                                                //  new .ext was given
      
      cc = strlen(outloc) + strlen(outname) + strlen(outext) + 4;
      outfile = (char *) zmalloc(cc);
      snprintf(outfile,cc,"%s/%s%s",outloc,outname,outext);

      Fjpeg = 0;                                                                 //  bugfix                             19.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_write(zd2,1,E2X("file type not supported: %s \n"),inext);
         zfree(outfile);
         continue;
      }

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

      if (*newloc) {
         err = stat(outfile,&statdat);                                           //  check if file exists in new location
         if (! err) {
            popup_report_write(zd2,1,"%s \n",Bfileexists);
            zfree(outfile);
            continue;
         }
      }

      if (Fupright)                                                              //  upright image if turned
      {
         exif_get(infile,exifkey,ppv,1);                                         //  get EXIF: Orientation
         if (ppv[0]) {
            orientation = *ppv[0];                                               //  single character
            zfree(ppv[0]);
         }
         else orientation = '1';                                                 //  if missing assume unrotated

         pxmout = 0;
         if (orientation == '6')
            pxmout = PXM_rotate(pxmin,+90);                                      //  rotate clockwise 90 deg.
         if (orientation == '8')
            pxmout = PXM_rotate(pxmin,-90);                                      //  counterclockwise 90 deg.
         if (orientation == '3')
            pxmout = PXM_rotate(pxmin,180);                                      //  180 deg.
         if (pxmout) {
            PXM_free(pxmin);                                                     //  input image unrotated
            pxmin = pxmout;
         }
      }

      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        19.0

      if (Fsamesize) pxmout = pxmin;                                             //  same size, output = input
      else {
         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;
         else scale = hscale;
         if (scale > 0.999) pxmout = pxmin;                                      //  no change
         else {
            outww = outww * scale;
            outhh = outhh * scale;
            pxmout = PXM_rescale(pxmin,outww,outhh);                             //  rescaled output file
            PXM_free(pxmin);                                                     //  free memory
         }
      }

      if (Fsharpen)                                                              //  auto sharpen output image
         batch_sharp_func(pxmout,amount,thresh);

      if (Foverlay)                                                              //  add overlay image
      {
         pww = outww * 0.01 * ovwidth;                                           //  overlay width, % image width

         if (Fovconst) {                                                         //  make overlay width constant for
            imageR = 1.0 * outww / outhh;                                        //    images with variable ww/hh
            screenR = 1.0 * ovscrww / ovscrhh;
            if (imageR < screenR) pww = pww * screenR / imageR;                  //  image < screen, increase width
         }
         
         ovpxb = gdk_pixbuf_new_from_file_at_scale(ovfile,pww,-1,1,&gerror);     //  read and scale overlay image
         if (! ovpxb) {
            popup_report_write(zd2,1,"overlay file error: %s \n",ovfile,gerror->message);
            zfree(outfile);
            PXM_free(pxmout);
            gerror = 0;
            continue;
         }
         
         pww = gdk_pixbuf_get_width(ovpxb);                                      //  get actual overlay pixbuf data
         phh = gdk_pixbuf_get_height(ovpxb);
         nc = gdk_pixbuf_get_n_channels(ovpxb);
         rs = gdk_pixbuf_get_rowstride(ovpxb);
         pix0 = gdk_pixbuf_get_pixels(ovpxb);
         
         orgx = orgy = -1;
         if (ovpos == 1) orgx = orgy = 0;                                        //  top left 
         if (ovpos == 2) { orgx = (outww-pww)/2; orgy = 0; }                     //  top center
         if (ovpos == 3) { orgx = outww-pww; orgy = 0; }                         //  top right

         if (ovpos == 4) { orgx = 0; orgy = (outhh-phh)/2; }                     //  center left
         if (ovpos == 5) { orgx = (outww-pww)/2; orgy = (outhh-phh)/2; }         //  center center
         if (ovpos == 6) { orgx = outww-pww; orgy = (outhh-phh)/2; }             //  center right

         if (ovpos == 7) { orgx = 0; orgy = outhh-phh; }                         //  bottom left
         if (ovpos == 8) { orgx = (outww-pww)/2; orgy = outhh-phh; }             //  bottom center
         if (ovpos == 9) { orgx = outww-pww; orgy = outhh-phh; }                 //  bottom right
         
         if (orgx < 0) orgx = 0;
         if (orgy < 0) orgy = 0;
         if (orgx + pww > outww) pww = outww - orgx;
         if (orgy + phh > outhh) phh = outhh - orgy;

         for (py1 = 0; py1 < phh; py1++)                                         //  loop all pixels in overlay image
         for (px1 = 0; px1 < pww; px1++)
         {
            pix1 = pix0 + py1 * rs + px1 * nc;                                   //  copy-from overlay pixel

            px2 = orgx + px1;                                                    //  copy-to image pixel
            py2 = orgy + py1;
            pix2 = PXMpix(pxmout,px2,py2);

            if (nc > 3) f1 = pix1[3] / 256.0;                                    //  visible part
            else f1 = 1;
            f2 = 1.0 - f1;

            pix2[0] = f1 * pix1[0] + f2 * pix2[0];                               //  combine overlay and image
            pix2[1] = f1 * pix1[1] + f2 * pix2[1];
            pix2[2] = f1 * pix1[2] + f2 * pix2[2];
         }
         
         g_object_unref(ovpxb);                                                  //  free overlay pixbuf
         ovpxb = 0;
      }
      
      pp1 = strrchr(outfile,'/');                                                //  get filename.ext
      if (pp1) pp1++;
      else pp1 = outfile;
      snprintf(tempfile,300,"%s/%s",temp_folder,pp1);                            //  temp file for EXIF/IPTC copy

      err = PXM_save(pxmout,tempfile,outbpc,jpeg_quality,0);                     //  write output file to temp file     19.0
      if (err) {
         popup_report_write(zd2,1,"%s \n",E2X("cannot create new file"));
         zfree(outfile);
         PXM_free(pxmout);
         continue;
      }

      if (Fcopymeta)                                                             //  copy EXIF/IPTC if requested
      {
         if (Fupright) {                                                         //  if image possibly uprighted
            exifdata[0] = "";                                                    //    remove exif:orientation
            exif_copy(infile,tempfile,exifkey,exifdata,1);                       //      (set = 1 does not work)
         }
         else exif_copy(infile,tempfile,0,0,0);                                  //  else no EXIF change
      }

      err = copyFile(tempfile,outfile);                                          //  copy tempfile to output file
      if (err) popup_report_write(zd2,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
         remove(infile);
         delete_image_index(infile);                                             //  remove from image index
         delete_thumbfile(infile);                                               //  delete thumbnail, file and cache
      }

      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);                                    //    mark for updating albums
         newfiles[Noldnew] = zstrdup(outfile);
         Noldnew++;
      }

      zfree(outfile);
      PXM_free(pxmout);
   }

   if (Noldnew) {                                                                //  update albums for renamed/moved files
      popup_report_write(zd2,0,"%s \n",E2X("updating albums ..."));
      album_batch_rename(oldfiles,newfiles,Noldnew);
   }

   popup_report_write(zd2,0,"\n *** %s \n",Bcompleted);
   popup_report_bottom(zd2);

cleanup:

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

   Fblock = 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

   return;
}


//  dialog event and completion callback function

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

   char        *pp, badplug[20];
   char        countmess[80];
   char        *ploc, *ofile, *oname = 0;
   int         ii, cc, yn, err;
   GdkPixbuf   *ovpxb;
   GError      *gerror = 0;
   
   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      zdialog_show(zd,0);                                                        //  hide parent dialog
      gallery_select();                                                          //  get list of files to convert
      zdialog_show(zd,1);

      snprintf(countmess,80,Bfileselected,GScount);                              //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

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

   if (zstrstr("maxhh maxww",event))                                             //  if max width/height changed,
      zdialog_stuff(zd,"samesize",0);                                            //    reset "no change"

   if (strmatch(event,"samesize")) {                                             //  if "no change" set,
      zdialog_fetch(zd,"samesize",Fsamesize);                                    //    clear max width / height
      if (Fsamesize) {
         zdialog_stuff(zd,"maxww","");
         zdialog_stuff(zd,"maxhh","");
      }
   }
   
   if (zstrstr("tif png jpg sametype",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,"sametype",0);
      zdialog_stuff(zd,event,1);
   }

   if (zstrstr("8-bit 16-bit samebpc",event)) {                                  //  gtk fails to do this correctly
      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);
   }

   if (strmatch(event,"imgov")) {                                                //  [open] overlay image file
      if (ovfile) 
         ofile = gallery_select1(ovfile);
      else
         ofile = gallery_select1(saved_areas_folder);
      if (! ofile) return 1;
      if (image_file_type(ofile) != IMAGE) {                                     //  check it is an image file
         zmessageACK(Mwin,E2X("unknown file type"));
         return 1;
      }
      if (ovfile) zfree(ovfile);
      ovfile = ofile;
   }
   
   if (strmatch(event,"ovTL")) ovpos = 1;                                        //  overlay position = top left
   if (strmatch(event,"ovTC")) ovpos = 2;                                        //  top center
   if (strmatch(event,"ovTR")) ovpos = 3;                                        //  top right
   if (strmatch(event,"ovCL")) ovpos = 4;                                        //  center left
   if (strmatch(event,"ovCC")) ovpos = 5;                                        //  center center
   if (strmatch(event,"ovCR")) ovpos = 6;                                        //  center right
   if (strmatch(event,"ovBL")) ovpos = 7;                                        //  bottom left
   if (strmatch(event,"ovBC")) ovpos = 8;                                        //  bottom center
   if (strmatch(event,"ovBR")) ovpos = 9;                                        //  bottom right
   
   //  wait for dialog completion via [proceed] button

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

   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,"samesize",Fsamesize);                                       //  keep same width/height

   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,"upright",Fupright);                                         //  upright rotation
   zdialog_fetch(zd,"sharpen",Fsharpen);                                         //  auto sharpen
   zdialog_fetch(zd,"amount",amount);                                            //  sharpen amount
   zdialog_fetch(zd,"thresh",thresh);                                            //  sharpen threshold
   zdialog_fetch(zd,"ckov",Foverlay);                                            //  overlay image
   zdialog_fetch(zd,"ovwidth",ovwidth);                                          //  overlay width 1-100%
   zdialog_fetch(zd,"ovconst",Fovconst);                                         //  flag, make overlay size constant
   zdialog_fetch(zd,"ovscrww",ovscrww);                                          //  for this screen width
   zdialog_fetch(zd,"ovscrhh",ovscrhh);                                          //  and this screen height

   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 (! GScount) {
      zmessageACK(Mwin,Bnofileselected);
      return 1;
   }

   strTrim2(newname);
   if (! blank_null(newname)) 
   {
      Fplugdate = Fplugseq = Fplugname = 0;                                      //  validate plugins
      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,E2X("invalid plugin: %s"),badplug);
            return 1;
         }
         pp++;
      }
      
      if (! Fplugname && ! Fplugseq) {
         zmessageACK(Mwin,E2X("you must use either $s or $oldname"));
         return 1;
      }
      
      if (Fplugseq && (baseseq < 1 || addseq < 1)) {
         zmessageACK(Mwin,E2X("$s plugin needs base and adder"));
         return 1;
      }
      
      if (! Fplugseq && (baseseq > 0 || addseq > 0)) {
         zmessageACK(Mwin,E2X("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 (! Fsamesize && (maxww < 20 || maxhh < 20)) {
      zmessageACK(Mwin,E2X("max. size %d x %d is not reasonable"),maxww,maxhh);
      return 1;
   }
   
   if (Foverlay)
   {
      if (! ovfile) {                                                            //  overlay image file
         zmessageACK(Mwin,E2X("specify overlay image file"));
         return 1;
      }

      ovpxb = gdk_pixbuf_new_from_file(ovfile,&gerror);                          //  verify image file
      if (! ovpxb) {
         zmessageACK(Mwin,gerror->message);
         return 1;
      }
      g_object_unref(ovpxb);

      oname = strrchr(ovfile,'/');                                               //  filename.png
      if (! oname) oname = ovfile;
      oname++;

      if (! ovpos) {                                                             //  overlay position
         zmessageACK(Mwin,E2X("specify overlay position"));
         return 1;
      }
   }
   
   Freplace = 0;
   if (*newname <= ' ' && *newloc <= ' ' && Fsametype) 
      Freplace = 1;

   /**   Convert NN image files                    0
           Rename to xxxxxxx                       1
           Convert to .ext  N-bits/color           2
           Resize within NNxNN                     3
           Output to /.../...                      4
           Copy Metadata  Upright  Sharpen         5
           Overlay Image: xxxxxxxxxx               6
           *** Delete Originals ***                7
           *** Replace Originals ***               8
         PROCEED?                                  9
   **/

   char  mess0[60], mess1[100], mess2[60], mess2a[60], mess3[60], mess4[550], 
         mess5[80], mess6[200], mess7[60], mess8[60], mess9[40];
   char  warnmess[1000];

   *mess0 = *mess1 = *mess2 = *mess2a = *mess3 = *mess4 = *mess5 
          = *mess6 = *mess7 = *mess8 = *mess9 = 0;

   snprintf(mess0,60,E2X("Convert %d image files"),GScount);
   if (*newname) snprintf(mess1,100,"\n  %s %s",E2X("Rename to"),newname);
   if (! Fsametype) snprintf(mess2,60,"\n  %s %s",E2X("Convert to"),newext);
   if (! Fsamebpc) snprintf(mess2a,60,"\n  %d-bits/color",newbpc);
   if (! Fsamesize) snprintf(mess3,60,"\n  %s %dx%d",E2X("Resize within"),maxww,maxhh);
   if (*newloc) snprintf(mess4,550,"\n  %s  %s",E2X("Output to"),newloc);
   if (Fcopymeta || Fupright || Fsharpen) strcat(mess5,"\n  ");
   if (Fcopymeta) { strcat(mess5,E2X("Copy Metadata")); strcat(mess5,"  "); }
   if (Fupright) { strcat(mess5,E2X("Upright")); strcat(mess5,"  "); }
   if (Fsharpen) { strcat(mess5,E2X("Sharpen")); strcat(mess5,"  "); }
   if (Foverlay) { snprintf(mess6,200,"\n  %s: %s",E2X("Overlay Image"),oname); }
   if (Fdelete) snprintf(mess7,60,"\n  %s",E2X("*** Delete Originals ***"));
   if (Freplace) snprintf(mess8,60,"\n  %s",E2X("*** Replace Originals ***"));
   snprintf(mess9,40,"\n\n%s",E2X("PROCEED?"));

   snprintf(warnmess,1000,"%s %s %s %s %s %s %s %s %s %s %s",
            mess0,mess1,mess2,mess2a,mess3,mess4,mess5,mess6,mess7,mess8,mess9);

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

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


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

//  callable sharpen function for batch_convert and batch_RAW_convert
//  amount: 0 to 400    strength of applied algorithm
//  thresh: 0 to 100    contrast level below which sharpen is diminished

namespace batch_sharp_names
{
   PXM      *pxm1, *pxm2;
   int      BSamount, BSthresh;
}


int batch_sharp_func(PXM *pxm, int amount, int thresh)                           //  14.06
{
   using namespace batch_sharp_names;

   void * batch_sharp_wthread(void *arg);

   pxm2 = pxm;                                                                   //  output
   pxm1 = PXM_copy(pxm2);                                                        //  input

   BSamount = amount;
   BSthresh = thresh;

   do_wthreads(batch_sharp_wthread,NWT);                                         //  worker threads

   PXM_free(pxm1);
   return 1;
}


void * batch_sharp_wthread(void *arg)                                            //  worker thread function
{
   using namespace batch_sharp_names;

   float       *pix1, *pix2;
   int         px, py;
   float       amount, thresh;
   float       b1, b1x, b1y, b2x, b2y, b2, bf, f1, f2;
   float       red1, green1, blue1, red2, green2, blue2;

   int         index = *((int *) arg);

   amount = 1 + 0.01 * BSamount;                                                 //  1.0 - 5.0
   thresh = BSthresh;                                                            //  0 - 100

   for (py = index + 1; py < pxm1->hh; py += NWT)                                //  loop all image pixels
   for (px = 1; px < pxm1->ww; px++)
   {
      pix1 = PXMpix(pxm1,px,py);                                                 //  input pixel
      pix2 = PXMpix(pxm2,px,py);                                                 //  output pixel

      b1 = pixbright(pix1);                                                      //  pixel brightness, 0 - 256
      if (b1 == 0) continue;                                                     //  black, don't change
      b1x = b1 - pixbright(pix1-3);                                              //  horiz. brightness gradient
      b1y = b1 - pixbright(pix1-3 * pxm1->ww);                                   //  vertical
      f1 = fabsf(b1x + b1y);

      if (f1 < thresh)                                                           //  moderate brightness change for
         f1 = f1 / thresh;                                                       //    pixels below threshold gradient
      else  f1 = 1.0;
      f2 = 1.0 - f1;

      b1x = b1x * amount;                                                        //  amplified gradient
      b1y = b1y * amount;

      b2x = pixbright(pix1-3) + b1x;                                             //  + prior pixel brightness
      b2y = pixbright(pix1-3 * pxm2->ww) + b1y;                                  //  = new brightness
      b2 = 0.5 * (b2x + b2y);

      b2 = f1 * b2 + f2 * b1;                                                    //  possibly moderated

      bf = b2 / b1;                                                              //  ratio of brightness change
      if (bf < 0) bf = 0;
      if (bf > 4) bf = 4;

      red1 = pix1[0];                                                            //  input RGB
      green1 = pix1[1];
      blue1 = pix1[2];

      red2 = bf * red1;                                                          //  output RGB
      if (red2 > 255.9) red2 = 255.9;
      green2 = bf * green1;
      if (green2 > 255.9) green2 = 255.9;
      blue2 = bf * blue1;
      if (blue2 > 255.9) blue2 = 255.9;

      pix2[0] = red2;
      pix2[1] = green2;
      pix2[2] = blue2;
   }

   pthread_exit(0);
}


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

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

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

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

   zdialog        *zd, *zd2;
   int            zstat;
   char           *infile, *tempfile;
   char           *pp1, *pp2, *ppv[1], text[100];
   int            ii, err, bpc;
   char           orientation;
   PXM            *pxmin, *pxmout;
   cchar          *exifkey[1] = { exif_orientation_key };
   cchar          *exifdata[1];

   F1_help_topic = "batch upright";

   if (checkpend("all")) return;                                                 //  check nothing pending

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

   Fblock = 1;

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

***/

   zd = zdialog_new(E2X("Batch Upright"),Mwin,Bproceed,Bcancel,null);

   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf",Bselectfiles,"space=5");
   zdialog_add_widget(zd,"label","fcount","hbf",Bnofileselected,"space=10");
   zdialog_add_widget(zd,"hbox","hbaf","dialog");
   zdialog_add_widget(zd,"check","allfiles","hbaf",E2X("Survey all files"),"space=5");

   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   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

   zd2 = popup_report_open("Processing files",Mwin,500,200,0,0,0);               //  status monitor popup window
   
   if (bup_allfiles)                                                             //  "survey all files" selected
   {
      bup_filelist = (char **) zmalloc(Nxxrec * sizeof(char *));
      for (ii = 0; ii < Nxxrec; ii++)
         bup_filelist[ii] = zstrdup(xxrec_tab[ii]->file);
      bup_filecount = Nxxrec;
   }

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

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

      exif_get(infile,exifkey,ppv,1);                                            //  get EXIF: Orientation
      if (! ppv[0]) continue;
      orientation = *ppv[0];
      if (orientation == '1') continue;                                          //  not rotated

      pxmin = PXM_load(infile,0);                                                //  read input file
      if (! pxmin) {
         popup_report_write(zd2,1,"%s \n",E2X("file cannot be read"));
         continue;
      }

      pxmout = 0;
      if (orientation == '6')
         pxmout = PXM_rotate(pxmin,+90);                                         //  rotate clockwise 90 deg.
      if (orientation == '8')
         pxmout = PXM_rotate(pxmin,-90);                                         //  counterclockwise
      if (orientation == '3')
         pxmout = PXM_rotate(pxmin,180);                                         //  180 deg.

      PXM_free(pxmin);
      if (! pxmout) continue;                                                    //  not rotated

      tempfile = zstrdup(infile,12);                                             //  temp file needed for EXIF/IPTC copy
      pp1 = strrchr(infile,'.');
      pp2 = strrchr(tempfile,'.');
      strcpy(pp2,"-temp");
      strcpy(pp2+5,pp1);

      bpc = f_load_bpc;                                                          //  input file bits/color

      if (strmatch(f_load_type,"tif"))
         err = PXM_TIFF_save(pxmout,tempfile,bpc);
      else if (strmatch(f_load_type,"png"))
         err = PXM_PNG_save(pxmout,tempfile,bpc);
      else err = PXM_JPG_save(pxmout,tempfile,jpeg_def_quality);

      PXM_free(pxmout);

      if (err) {
         popup_report_write(zd2,1," upright failed \n");
         zfree(tempfile);
         continue;
      }

      exifdata[0] = "";                                                          //  remove exif:orientation
      exif_copy(infile,tempfile,exifkey,exifdata,1);                             //  (set = 1 does not work)

      err = copyFile(tempfile,infile);                                           //  copy temp file to input file 
      if (err) popup_report_write(zd2,1,"%s \n",strerror(err));

      remove(tempfile);                                                          //  remove tempfile
      zfree(tempfile);

      if (! err) {
         load_filemeta(infile);                                                  //  update image index
         update_image_index(infile);
         popup_report_write(zd2,0,"  uprighted \n");
      }

      gallery(0,"paint",-1);                                                     //  repaint gallery                    19.0
   }

   popup_report_write(zd2,0,"\n *** %s \n",Bcompleted);
   popup_report_bottom(zd2);

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;
   }

   Fblock = 0;
   return;
}


//  dialog event and completion callback function

int batch_upright_dialog_event(zdialog *zd, cchar *event)
{
   char         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
      gallery_select();                                                          //  get new list
      zdialog_show(zd,1);

      snprintf(countmess,80,Bfileselected,GScount);                              //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
      zdialog_stuff(zd,"allfiles",0);

      if (! GScount) return 1;

      bup_filelist = (char **) zmalloc(GScount * sizeof(char *));                //  copy selected files
      for (ii = 0; ii < GScount; ii++)
         bup_filelist[ii] = GSfiles[ii];
      bup_filecount = GScount;
      GScount = 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,Bnofileselected);
      zd->zstat = 0;                                                             //  keep dialog active
   }

   if (bup_allfiles && bup_filecount) {
      zmessageACK(Mwin,E2X("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 *, cchar *) 
{
   int  batch_deltrash_dialog_event(zdialog *zd, cchar *event);

   zdialog     *zd, *zd2;
   int         zstat, ii, err, gstat;
   char        *file, text[100];
   GError      *gerror = 0;
   GFile       *gfile = 0;
   STATB       statb;

   F1_help_topic = "batch delete/trash";
   if (checkpend("all")) return;                                                 //  check nothing pending
   Fblock = 1;

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

***/

   zd = zdialog_new(E2X("Batch Delete/Trash"),Mwin,Bproceed,Bcancel,null);
   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf",Bselectfiles,"space=5");
   zdialog_add_widget(zd,"label","fcount","hbf",Bnofileselected,"space=10");
   zdialog_add_widget(zd,"hbox","hbdt","dialog");
   zdialog_add_widget(zd,"label","labdel","hbdt",E2X("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",E2X("trash"),"space=5");
   zdialog_add_widget(zd,"radio","trash","hbdt",0);
   
   bdt_option = 2;

   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   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 cleanup;                                                 //  canceled
   if (! GScount) goto cleanup;

   free_resources();                                                             //  no curr. file

   zd2 = popup_report_open("Processing files",Mwin,500,200,0,0,0);               //  status monitor popup window

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

      file = GSfiles[ii];                                                        //  log each file
      popup_report_write(zd2,0,"%s \n",file);
   
      err = stat(file,&statb);                                                   //  file exists?
      if (err || ! S_ISREG(statb.st_mode)) {
         popup_report_write(zd2,1,"file not found \n");
         continue;
      }

      if (bdt_option == 1) {                                                     //  delete file
         err = remove(file);
         if (err) {
            popup_report_write(zd2,1,"%s \n",strerror(err));
            continue;
         }
      }
      
      if (bdt_option == 2) {                                                     //  move file to trash
         gfile = g_file_new_for_path(file);
         gstat = g_file_trash(gfile,0,&gerror);                                  //  move file to trash
         g_object_unref(gfile);
         if (! gstat) {
            popup_report_write(zd2,1,"%s \n",gerror->message);
            continue;
         }
      }

      delete_image_index(file);                                                  //  delete file in image index
      delete_thumbfile(file);                                                    //  delete thumbnail, file and cache
   }

   popup_report_write(zd2,0,"\n *** %s \n",Bcompleted);
   popup_report_bottom(zd2);
   
   popup_report_write(zd2,0,E2X("Purging deleted files from albums \n"));        //  purge deleted files from albums    20.0
   album_purge_replace_all();                                                    //  replace with new version if poss.

   popup_report_write(zd2,0,"\n *** %s \n",Bcompleted);
   popup_report_bottom(zd2);

cleanup:

   Fblock = 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

   return;
}


//  dialog event and completion callback function

int batch_deltrash_dialog_event(zdialog *zd, cchar *event)
{
   char         countmess[80];
   int          ii;
   
   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      zdialog_show(zd,0);                                                        //  hide parent dialog
      gallery_select();
      zdialog_show(zd,1);

      snprintf(countmess,80,Bfileselected,GScount);                              //  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 (! GScount) {                                                              //  nothing selected
      zmessageACK(Mwin,Bnofileselected);
      zd->zstat = 0;                                                             //  keep dialog active
   }

   return 1;
}


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

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

namespace batch_raw
{
   char     location[400], biasfile[400];
   cchar    *filetype = 0;
   int      bpc, jpeg_quality;
   float    resize;
   int      Fsharpen, Fdead, Fbias;
   int      amount, thresh;
};


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

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

   zdialog     *zd, *zd2;
   cchar       *title = E2X("Batch Convert RAW Files");
   char        *rawfile, *infile, *outfile, *pp;
   char        text[100];
   int         zstat, ii, err;
   FTYPE       ftype;
   int         cc, ww2, hh2;
   PXM         *pxm1, *pxm2;

   F1_help_topic = "batch raw";
   if (checkpend("all")) return;                                                 //  check nothing pending
   Fblock = 1;

   m_viewmode(0,"G");                                                            //  gallery view                       20.0

/***
       ________________________________________________________
      |              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                     |
      | Resize  (o) 1.0  (o) 3/4  (o) 2/3  (o) 1/2  (o) 1/3    |
      | [x] Sharpen   amount [___]   threshold [___]           |
      | [x] Fix dead pixels  [load] dead pixel map file        |                 //  20.0
      | [x] Fix pixel bias   [load] pixel bias map file        |                 //  20.0
      |                                                        |
      |                                    [proceed] [cancel]  |
      |________________________________________________________|

***/

   zd = zdialog_new(title,Mwin,Bproceed,Bcancel,null);
   
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=2");
   zdialog_add_widget(zd,"button","files","hb1",Bselectfiles,"space=5");
   zdialog_add_widget(zd,"label","fcount","hb1",Bnofileselected,"space=10");

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

   zdialog_add_widget(zd,"hbox","hbft","dialog");
   zdialog_add_widget(zd,"label","labtyp","hbft",E2X("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",E2X("jpg quality"),"space=6");

   zdialog_add_widget(zd,"hbox","hbcd","dialog");
   zdialog_add_widget(zd,"label","labcd","hbcd",E2X("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",E2X("Resize"),"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");

   zdialog_add_widget(zd,"hbox","hbsharp","dialog",0,"space=2");
   zdialog_add_widget(zd,"check","sharpen","hbsharp",E2X("Sharpen"),"space=3");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labamount","hbsharp",E2X("amount"),"space=3");
   zdialog_add_widget(zd,"zspin","amount","hbsharp","0|400|1|100");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labthresh","hbsharp",E2X("threshold"),"space=3");
   zdialog_add_widget(zd,"zspin","thresh","hbsharp","0|100|1|20");

   zdialog_add_widget(zd,"hbox","hbdead","dialog",0,"space=2");
   zdialog_add_widget(zd,"check","fixdead","hbdead",E2X("Fix dead pixels"),"space=3");
   zdialog_add_widget(zd,"label","space","hbdead",0,"space=10");
   zdialog_add_widget(zd,"button","loadead","hbdead",Bload,"space=5");
   zdialog_add_widget(zd,"label","labloadead","hbdead",E2X("dead pixel map file"));

   zdialog_add_widget(zd,"hbox","hbbias","dialog",0,"space=2");
   zdialog_add_widget(zd,"check","fixbias","hbbias",E2X("Fix pixel bias"),"space=3");
   zdialog_add_widget(zd,"label","space","hbbias",0,"space=10");
   zdialog_add_widget(zd,"button","loadbias","hbbias",Bload,"space=5");
   zdialog_add_widget(zd,"label","labloadbias","hbbias",E2X("pixel bias map file"));

   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   zdialog_stuff(zd,"fcount",text);

   *location = 0;
   Fdead = Fbias = 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_restore_inputs(zd);                                                   //  get prior inputs if any
   zdialog_resize(zd,500,0);
   
   zdialog_stuff(zd,"fixdead",0);                                                //  turn off initially
   zdialog_stuff(zd,"fixbias",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 (! GScount) goto cleanup;
   
   m_viewmode(0,"F");

   zd2 = popup_report_open("Converting RAW files",Mwin,500,200,0,0,0);           //  status monitor popup window

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

      rawfile = GSfiles[ii];                                                     //  filename.raw
      popup_report_write(zd2,0,"%s \n",rawfile);                                 //  write to log window

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

      pxm1 = RAW_PXM_load(rawfile,Fautobright,Fmatchthumb);                      //  20.0
      if (! pxm1) continue;
      
      if (Fdead) {                                                               //  fix dead pixels                    20.0
         err = dead_pixels_fix(pxm1);
         if (err) popup_report_write(zd2,1," fix dead pixels not done \n");
      }

      if (Fbias)                                                                 //  apply pixel bias corrections       20.0
      {
         err = pixel_bias_fix(pxm1);
         if (err) popup_report_write(zd2,1," fix pixel bias not done \n");
      }

      zmainloop();

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

         if (! pxm1) {
            popup_report_write(zd2,1," resize failed \n");
            continue;
         }
      }
      
      if (Fsharpen)                                                              //  sharpen if wanted
         batch_sharp_func(pxm1,amount,thresh);

      outfile = zstrdup(rawfile,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_write(zd2,1," file type conversion failed \n");
         zfree(outfile);
         continue;
      }

      exif_copy(rawfile,outfile,0,0,0);                                          //  copy metadata from RAW file

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

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

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

   popup_report_write(zd2,0,"\n *** %s \n",Bcompleted);
   popup_report_bottom(zd2);

cleanup:                                                                         //  clean up and return

   Fblock = 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

   return;
}


//  dialog event and completion callback function

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

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

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

      snprintf(countmess,80,Bfileselected,GScount);                              //  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(E2X("Select folder"),MWIN,"folder",location);
      if (! ploc) return 1;
      zdialog_stuff(zd,"location",ploc);
      zfree(ploc);
   }
   
   if (strmatch(event,"fixdead"))                                                //  fix dead pixels checkbox           20.0
   {
      zdialog_fetch(zd,"fixdead",ii);
      if (ii) event = "loadead";
   }

   if (strmatch(event,"loadead"))                                                //  load dead pixel map file           20.0
   {
      err = dead_pixels_map_load();
      if (! err) {
         Fdead = 1;
         zdialog_stuff(zd,"fixdead",1);
      }
      else {
         Fdead = 0;
         zdialog_stuff(zd,"fixdead",0);
      }
   }

   if (strmatch(event,"fixbias"))                                                //  fix pixel bias checkbox            20.0
   {
      zdialog_fetch(zd,"fixbias",ii);
      if (ii) event = "loadbias";
   }

   if (strmatch(event,"loadbias"))                                               //  load pixel bias map file           20.0
   {
      zdialog_show(zd,0);
      err = pixel_bias_map_load();
      zdialog_show(zd,1);

      if (! err) {
         Fbias = 1;
         zdialog_stuff(zd,"fixbias",1);
      }
      else {
         Fbias = 0;
         zdialog_stuff(zd,"fixbias",0);
      }
   }

   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 (! GScount) {                                                              //  no RAW files selected
      zmessageACK(Mwin,Bnofileselected);
      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";                                                            //  bugfix  19.2
   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;                                                                      //  bugfix  19.2   
   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
   
   resize = 1.0;
   zdialog_fetch(zd,"1.0",ii);                                                   //  resize option
   if (ii) resize = 1.0;
   zdialog_fetch(zd,"3/4",ii);
   if (ii) resize = 0.75;
   zdialog_fetch(zd,"2/3",ii);
   if (ii) resize = 0.666667;
   zdialog_fetch(zd,"1/2",ii);
   if (ii) resize = 0.50;
   zdialog_fetch(zd,"1/3",ii);
   if (ii) resize = 0.333333;

   zdialog_fetch(zd,"sharpen",Fsharpen);                                         //  sharpen option
   zdialog_fetch(zd,"amount",amount);
   zdialog_fetch(zd,"thresh",thresh);

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


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

//  burn images to DVD/BlueRay optical disc

namespace burn_images
{
   zdialog  *zd;
   char     mydvd[60];
};


//  menu function

void m_burn_DVD(GtkWidget *, cchar *)                                            //  overhauled
{
   using namespace burn_images;

   int   burn_dialog_event(zdialog *zd, cchar *event);

   FILE     *fid;
   char     *pp, text[100];
   int      ii, Fdvd, Ndvd, zstat;
   cchar    *growisofs = "growisofs -Z %s -iso-level 4 -r -path-list %s 2>&1";   //  growisofs command
   char     burnlist[200], buffer[200];
   char     dvddev[20][4], dvddesc[40][4], dvddevdesc[60][4];                    //  up to 4 DVD/BR devices

   F1_help_topic = "burn DVD/BRD";

   if (checkpend("all")) return;                                                 //  check nothing pending

   if (! Fgrowisofs) {
      zmessageACK(Mwin,E2X("growisofs not installed"));
      return;
   }

   Fdvd = Ndvd = 0;
   *mydvd = 0;

   fid = popen("lshw -class disk","r");                                          //  find all DVD/BR devices
   if (! fid) {
      zmessageACK(Mwin,strerror(errno));
      return;
   }

   while (true)
   {
      pp = fgets_trim(buffer,200,fid,1);
      if (! pp) break;

      if (strstr(buffer,"*-")) {                                                 //  start of some device
         if (strstr(buffer,"*-cdrom")) Fdvd = 1;                                 //  start of DVD/BD device
         else Fdvd = 0;
         continue;
      }

      if (! Fdvd) continue;                                                      //  ignore recs for other devices

      if (strstr(buffer,"description:")) {
         pp = strstr(buffer,"description:");                                     //  save DVD/BD description
         pp += 12;
         strncpy0(dvddesc[Ndvd],pp,40);
         continue;
      }

      if (strstr(buffer,"/dev/")) {
         pp = strstr(buffer,"/dev/");                                            //  have /dev/sr0 or similar format
         if (pp[7] < '0' || pp[7] > '9') continue;
         pp[8] = 0;
         strcpy(dvddev[Ndvd],pp);                                                //  save DVD/BD device
         Ndvd++;
         if (Ndvd == 4) break;
         continue;
      }
   }
   
   pclose(fid);

   if (Ndvd < 1) {
      zmessageACK(Mwin,E2X("no DVD/BlueRay device found"));
      return;
   }

   for (ii = 0; ii < Ndvd; ii++)                                                 //  combine devices and descriptions
   {                                                                             //    for use in GUI chooser list
      strcpy(dvddevdesc[ii],dvddev[ii]);
      strcat(dvddevdesc[ii],"  ");
      strcat(dvddevdesc[ii],dvddesc[ii]);
   }

/***
       ______________________________________  
      |    Burn Images to DVD/BlueRay        |
      |                                      |
      |  [Select Files]  NNN files selected  |
      |  [Select device] [_____________|v]   |
      |                                      |
      |                     [Start] [Cancel] |
      |______________________________________|

***/
   
   zd = zdialog_new(E2X("Burn Images to DVD/BlueRay"),Mwin,Bstart,Bcancel,null);
   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","files","hbf",Bselectfiles,"space=5");
   zdialog_add_widget(zd,"label","fcount","hbf",Bnofileselected,"space=10");
   zdialog_add_widget(zd,"hbox","hbdvd","dialog",0);
   zdialog_add_widget(zd,"label","labdvd","hbdvd",E2X("Select device"),"space=5");
   zdialog_add_widget(zd,"combo","dvd","hbdvd",0,"expand");

   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   zdialog_stuff(zd,"fcount",text);

   for (int ii = 0; ii < Ndvd; ii++)                                             //  put available drives into list
      zdialog_cb_app(zd,"dvd",dvddevdesc[ii]);
   
   zdialog_resize(zd,300,0);
   zdialog_run(zd,burn_dialog_event,"parent");                                   //  run dialog, wait for response
   zstat = zdialog_wait(zd);
   zdialog_free(zd);
   if (zstat != 1) return;
   
   if (! GScount) return;                                                        //  no files selected

   snprintf(burnlist,200,"%s/burnlist",get_zhomedir());                          //  write files to burnlist file
   fid = fopen(burnlist,"w");
   if (! fid) {
      zmessageACK(Mwin,strerror(errno));
      return;
   }

   for (ii = 0; ii < GScount; ii++)
      fprintf(fid,"%s\n",GSfiles[ii]);

   fclose(fid);

   if (! *mydvd) return;                                                         //  no device selected

   pp = strstr(mydvd," ");                                                       //  remove description
   if (pp) *pp = 0;

   snprintf(command,200,growisofs,mydvd,burnlist);                               //  start burn process
   popup_command(command,600,400,Mwin);

   return;
}


//  dialog event and completion function

int burn_dialog_event(zdialog *zd, cchar *event)
{
   using namespace burn_images;

   char     countmess[80];

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

      snprintf(countmess,80,Bfileselected,GScount);                              //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }
   
   if (strmatch(event,"dvd"))
      zdialog_fetch(zd,"dvd",mydvd,60);                                          //  get user device choice

   return 1;
}


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

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

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


//  menu function

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

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

   FILE     *fid;
   int      ii, zstat;
   cchar    *title = E2X("Create a file of selected image files");
   char     text[100];

   F1_help_topic = "export file list";

   if (checkpend("all")) return;                                                 //  check nothing pending

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

***/
   
   zd = zdialog_new(title,Mwin,Bproceed,Bcancel,null);

   zdialog_add_widget(zd,"hbox","hbif","dialog",0,"space=3");
   zdialog_add_widget(zd,"button","infiles","hbif",Bselectfiles,"space=3");
   zdialog_add_widget(zd,"label","Nfiles","hbif",Bnofileselected,"space=10");

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

   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   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);
      return;
   }

   zdialog_fetch(zd,"outfile",outfile,300);                                      //  get output file from dialog
   
   if (! GScount) {
      zmessageACK(Mwin,E2X("no input files selected"));
      zd->zstat = 0;
      goto retry;                                                                //  no input files
   }
   
   if (! *outfile) {
      zmessageACK(Mwin,E2X("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 < GScount; ii++)                                              //  write input file names to output
      fprintf(fid,"%s\n",GSfiles[ii]);

   fclose(fid);

   zmessageACK(Mwin,Bcompleted);
   return;
}


//  dialog event and completion function

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

   char     *file;
   char     countmess[80];

   if (strmatch(event,"infiles"))                                                //  select image files
   {
      gallery_select();                                                          //  get list of files to convert
      snprintf(countmess,80,Bfileselected,GScount);                              //  update dialog
      zdialog_stuff(zd,"Nfiles",countmess);
   }
   
   if (strmatch(event,"browse"))
   {
      file = zgetfile(E2X("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 
{
   char     tolocation[500];
   int      Fsamesize;
   int      maxww, maxhh;
   int      Fmeta;
};


//  menu function

void m_export_files(GtkWidget*, cchar *menu)                                     //  overhauled
{
   using namespace export_files_names;
   
   int export_files_dialog_event(zdialog *zd, cchar *event);

   zdialog     *zd, *zd2;
   int         ii, cc, err, ww, hh, zstat;
   PXM         *pxmin, *pxmout;
   char        *infile, *outfile, *pp, text[100];
   float       scale, wscale, hscale;
   #define     NK 10
   cchar       *keys[NK] = { exif_date_key, iptc_keywords_key, exif_copyright_key,
                             exif_comment_key, exif_usercomment_key, iptc_caption_key, 
                             exif_city_key, exif_country_key, exif_lati_key, exif_longi_key };
   char        *kdata[NK];

   F1_help_topic = "export files";

   if (checkpend("all")) return;                                                 //  check nothing pending

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

***/
   
   zd = zdialog_new(E2X("Export Files"),Mwin,Bproceed,Bcancel,null);
   zdialog_add_widget(zd,"hbox","hbf","dialog");
   zdialog_add_widget(zd,"button","files","hbf",Bselectfiles,"space=5");
   zdialog_add_widget(zd,"label","fcount","hbf",Bnofileselected,"space=10");
   zdialog_add_widget(zd,"hbox","hbloc","dialog");
   zdialog_add_widget(zd,"label","labloc","hbloc",E2X("To Location"),"space=5");
   zdialog_add_widget(zd,"zentry","toloc","hbloc",0,"expand");
   zdialog_add_widget(zd,"button","browse","hbloc",Bbrowse,"space=5");
   zdialog_add_widget(zd,"hbox","hbwh","dialog");
   zdialog_add_widget(zd,"label","labw","hbwh",E2X("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",Bheight,"space=5");
   zdialog_add_widget(zd,"zentry","maxhh","hbwh","700","size=5");
   zdialog_add_widget(zd,"check","samesize","hbwh",E2X("no change"),"space=12");
   zdialog_add_widget(zd,"hbox","hbmeta","dialog");
   zdialog_add_widget(zd,"check","meta","hbmeta",E2X("export metadata"));

   zdialog_restore_inputs(zd);                                                   //  preload prior location

   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   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) return;
   
   zd2 = popup_report_open("exporting files",Mwin,500,200,0,0,0);
   
   for (ii = 0; ii < GScount; ii++)                                              //  loop selected files
   {
      infile = GSfiles[ii];                                                      //  input filespec
      popup_report_write(zd2,0,"%s \n",infile);

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

      pxmin = PXM_load(infile,0);                                                //  read input file
      if (! pxmin) {
         popup_report_write(zd2,1," %s \n",E2X("file type not supported"));
         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_write(zd2,1," %s \n",E2X("cannot create new file"));

      PXM_free(pxmout);

      if (Fmeta) {                                                               //  optional copy metadata             20.0
         err = exif_get(infile,keys,kdata,NK);
         exif_put(outfile,keys,(cchar **) kdata,NK);
      }

      zfree(outfile);
   }

   popup_report_write(zd2,0,"\n *** %s \n",Bcompleted);
   popup_report_bottom(zd2); 

   return;
}


//  dialog event and completion callback function

int export_files_dialog_event(zdialog *zd, cchar *event)
{
   using namespace export_files_names;
   
   int      cc, err;
   char     countmess[80];
   char     *ploc;
   STATB    stbuff;

   if (strmatch(event,"files"))                                                  //  select files to export
   {
      gallery_select();                                                          //  get list of files to convert
      snprintf(countmess,80,Bfileselected,GScount);                              //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

   if (strmatch(event,"browse")) {
      zdialog_fetch(zd,"toloc",tolocation,500);
      ploc = zgetfile(E2X("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;                                                 //  [cancel] 

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

   zdialog_fetch(zd,"toloc",tolocation,500);                                     //  get output location
   err = stat(tolocation,&stbuff);
   if (err || ! S_ISDIR(stbuff.st_mode)) {
      zmessageACK(Mwin,E2X("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 resize options
   zdialog_fetch(zd,"maxww",maxww);
   zdialog_fetch(zd,"maxhh",maxhh);
   zdialog_fetch(zd,"meta",Fmeta);                                               //  metadata option                    20.0

   return 1;
}


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

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

    fotoxx.h:  char     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 *, cchar *menu)
{
   int  edit_script_dialog_event(zdialog *zd, cchar *event);

   zdialog  *zd;

   F1_help_topic = "script files";

   if (checkpend("all")) return;                                                 //  check nothing pending

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

***/

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

   zdialog_add_widget(zd,"hbox","hb1","dialog");
   zdialog_add_widget(zd,"button","start","hb1",Bstart,"space=5");
   zdialog_add_widget(zd,"label","labstart","hb1",E2X("begin making a script file"));
   zdialog_add_widget(zd,"hbox","hb2","dialog");
   zdialog_add_widget(zd,"button","close","hb2",Bclose,"space=5");
   zdialog_add_widget(zd,"label","labclose","hb2",E2X("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, cchar *event)
{
   int  edit_script_start(void);
   int  edit_script_close(void);

   if (zd->zstat) {                                                              //  cancel
      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()
{
   char     *pp;

   if (checkpend("all")) return 0;

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

   pp = zgetfile(E2X("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,E2X("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 = func_save_widgets(CEF->zd,CEF->sd,CEF->funcname,script_fid);         //  write widget settings
      if (err) {
         zmessageACK(Mwin,E2X("script file error"));
         return;
      }
   }

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


//  complete and save a script file under construction

int edit_script_close()
{
   if (! Fscriptbuild) {
      zmessageACK(Mwin,E2X("no script file was started"));
      return 0;
   }
   
   fprintf(script_fid,"menu: end");
   fclose(script_fid);
   script_fid = 0;
   Fscriptbuild = 0;
   zmessageACK(Mwin,E2X("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 *, cchar *scriptname);

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

   char        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,E2X("no script files found"));
      return;
   }
   
   scriptmenu = create_popmenu();                                                //  create popup menu for script names
   add_popmenu_item(scriptmenu,Bcancel,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
   else zmessageACK(Mwin,E2X("no script files found"));

   return;
}


//  execute a script file using the current image file

void run_script(GtkWidget *, cchar *scriptname)
{
   char     buff[200];
   char     *pp, menuname[40];
   int      ii, err;
   
   if (! curr_file) return;

   if (script_fid) printz("*** run_script(): script_fid not 0 \n");
   
   snprintf(scriptfile,200,"%s/%s",scripts_folder,scriptname);                   //  script file from script name

   printz("start script: %s \n",scriptfile);
   script_fid = fopen(scriptfile,"r");                                           //  open script file
   if (! script_fid) {
      zmessageACK(Mwin,E2X("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
   {
      Fscriptrun = 1;                                                            //  script file active                 20.0

      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,E2X("unknown edit function: %s"),menuname);
         goto badfile;
      }
      
      printz("start edit: %s for file: %s \n",menuname,curr_file);               //  19.0
      
      menutab[ii].func(0,menutab[ii].arg);                                       //  call the menu function
      zmainsleep(0.1);

      if (CEF && CEF->zd) {
         err = func_load_widgets(CEF->zd,CEF->sd,CEF->funcname,script_fid);      //  read and load dialog settings
         if (err) {
            zmessageACK(Mwin,E2X("load widgets failed: %s"),menuname);
            goto badfile;
         }
         zdialog_send_event(CEF->zd,"apply");                                    //  finish edit
         wait_thread_idle();                                                     //  insure complete             bugfix 19.0
         zdialog_send_event(CEF->zd,"done");
         printz("finish edit \n");
      }
   }

   fclose(script_fid);                                                           //  close script file
   script_fid = 0;
   Fscriptrun = 0;                                                               //  script file not active             20.0
   return;

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


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

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

   if (! GScount) {                                                              //  preselected file list
      zmessageACK(Mwin,Bnofileselected);
      return;
   }
   
   for (ii = 0; ii < GScount; ii++)                                              //  loop image files to process
   {
      imagefile = GSfiles[ii];
      err = f_open(imagefile,0,0,1,0);                                           //  open, current image file
      if (err) {
         zmessageACK(Mwin,E2X("open failure: %s \n %s"),imagefile,strerror(errno));
         return;
      }
      
      run_script(0,scriptname);                                                  //  run script for image file
      
      newfilevers = file_new_version(curr_file);                                 //  get next avail. file version name
      if (! newfilevers) return;                                                 //  (includes error message)

      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);
   }

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


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

void m_run_script(GtkWidget *, cchar *menu) 
{
   F1_help_topic = "script files";
   select_script(run_script);
   return;   
}


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

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

   zdialog  *zd;
   char     text[100];

   F1_help_topic = "script files";

   if (checkpend("all")) return;                                                 //  check nothing pending
   
/***
       _________________________________________
      |         Batch Script                    |
      |                                         |
      |  [Select Files]  NN files selected      |
      |  [Select Script]  script file to run    |
      |                                         |
      |                               [cancel]  |
      |_________________________________________|

***/

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

   zdialog_add_widget(zd,"hbox","hb1","dialog");
   zdialog_add_widget(zd,"button","select-files","hb1",Bselectfiles,"space=5");
   zdialog_add_widget(zd,"label","fcount","hb1",Bnofileselected);

   zdialog_add_widget(zd,"hbox","hb2","dialog");
   zdialog_add_widget(zd,"button","select-script","hb2",E2X("Select Script"),"space=5");
   zdialog_add_widget(zd,"label","labscript","hb2",E2X("script file to run"));
   
   snprintf(text,100,Bfileselected,GScount);                                     //  show selected files count          20.0
   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, cchar *event)
{
   char        countmess[80];

   F1_help_topic = "script files";

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

   if (strmatch(event,"select-files"))
   {   
      gallery_select();                                                          //  get new file list
      
      if (GScount) {
         snprintf(countmess,80,Bfileselected,GScount);                           //  update dialog file count
         zdialog_stuff(zd,"fcount",countmess);
      }
      else zdialog_stuff(zd,"fcount",Bnofileselected);
   }
   
   if (strmatch(event,"select-script")) {                                        //  select and run a script file
      if (! GScount) zmessageACK(Mwin,Bnofileselected);
      else {
         select_script(batch_script);
         zdialog_free(zd);
      }
   }

   return 1;
}


