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

   Fotoxx      edit photos and manage collections

   Copyright 2007-2016 Michael Cornelison
   Source URL: http://kornelix.net
   Contact: kornelix@posteo.de

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

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

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

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

   Fotoxx image edit - Batch 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 using libraw                        //  16.07
   m_batch_rawtherapee     convert RAW files using Raw Therapee
   m_scriptfiles           script files for batch editing 
   m_burn                  burn images to DVD/Blue-Ray disc
   m_duplicates            find all duplicated images
   m_imagelist             output a file listing 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
{
   char     **filelist = 0;
   int      Fsametype, Fsamesize, maxww, maxhh;
   int      Fdelete, Fcopymeta, Fupright, Fsharpen, Freplace;
   char     newloc[500], newname[200], newext[8];
   int      filecount, 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;
   int         zstat;
   char        *infile, *outfile;
   char        tempfile[300];
   char        *inloc, *inname, *inext;
   char        *outloc, *outname, *outext;
   char        *tempname;
   char        **oldfiles, **newfiles;
   char        seqnum[20];
   char        *pp1, *pp2, *ppv[2];
   int         ii, jj, cc, err;
   int         ww, hh, bpc, delinput;
   int         Noldnew;
   char        orientation;
   float       scale, wscale, hscale;
   PXM         *pxmin, *pxmout;
   STATB       statdat;
   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
   Fblock = 1;

/***
       ______________________________________________________________
      |                    Batch Convert                             |
      |                                                              |
      |  [Select Files]  N files selected                            |
      |                                                              |
      |  New Name [________________________________________________] |
      |  Sequence Numbers   base [_____]  adder [____]               |
      |  New Location [__________________________________] [browse]  |
      |  New File Type  (o) JPG  (o) PNG  (o) TIF  (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 |-+]               |           //  16.01
      |  Overlay Position  [_] [_] [_] [_] [_] [_] [_] [_] [_]       |
      |  [x] make constant width for screen [_____] [_____]          |           //  16.02
      |                                                              |
      |                                           [proceed] [cancel] |
      |______________________________________________________________|

***/

   zd = zdialog_new(ZTX("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",ZTX("New Name"),"space=5");
   zdialog_add_widget(zd,"entry","newname","hbname",0,"expand|size=30");
   zdialog_add_widget(zd,"label","space","hbname",0,"space=60");                 //  push back oversized entry

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

   zdialog_add_widget(zd,"hbox","hbtyp","dialog");
   zdialog_add_widget(zd,"label","labtyp","hbtyp",ZTX("New File Type"),"space=5");
   zdialog_add_widget(zd,"radio","jpg","hbtyp","JPG","space=6");
   zdialog_add_widget(zd,"radio","png","hbtyp","PNG","space=6");
   zdialog_add_widget(zd,"radio","tif","hbtyp","TIF","space=6");
   zdialog_add_widget(zd,"radio","sametype","hbtyp",ZTX("no change"),"space=6");

   zdialog_add_widget(zd,"hbox","hbwh","dialog");
   zdialog_add_widget(zd,"label","labw","hbwh",ZTX("max. Width"),"space=5");
   zdialog_add_widget(zd,"entry","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,"entry","maxhh","hbwh","700","size=5");
   zdialog_add_widget(zd,"check","samesize","hbwh",ZTX("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",ZTX("Delete Originals"),"space=3");
   zdialog_add_widget(zd,"check","copymeta","hbopts",ZTX("Copy Metadata"),"space=5");
   zdialog_add_widget(zd,"check","upright","hbopts",ZTX("Upright"),"space=5");

   zdialog_add_widget(zd,"hbox","hbsharp","dialog");
   zdialog_add_widget(zd,"check","sharpen","hbsharp",ZTX("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,"spin","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,"spin","thresh","hbsharp","0|100|1|20");
   
   zdialog_add_widget(zd,"hsep","ovsep","dialog","","space=3");
   zdialog_add_widget(zd,"hbox","hbov1","dialog");                               //  overlay image   16.01
   zdialog_add_widget(zd,"check","ckov","hbov1",ZTX("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",ZTX("Width %"),"space=3");
   zdialog_add_widget(zd,"spin","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",ZTX("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");                               //  16.02
   zdialog_add_widget(zd,"check","ovconst","hbov3",ZTX("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,"entry","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,"entry","ovscrhh","hbov3","","size=5|space=3");

   zdialog_stuff(zd,"sametype",1);                                               //  same file type
   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);
   zdialog_stuff(zd,"thresh",20);
   zdialog_stuff(zd,"ovconst",0);                                                //  overlay constant size - no
   zdialog_stuff(zd,"ovscrww",screenww);                                         //  screen pixel size
   zdialog_stuff(zd,"ovscrhh",screenhh);

   filelist = 0;
   filecount = 0;
   *newloc = 0;
   oldfiles = newfiles = 0;
   Noldnew = 0;
   Foverlay = 0;                                                                 //  16.01
   ovpos = 0;
   Fovconst = 0;                                                                 //  16.02

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

   free_resources();                                                             //  no curr. file                      15.03

   m_viewmode(0,"F");
   gallery_monitor("stop");                                                      //  stop tracking gallery updates

   if (Fdelete) {
      cc = filecount * 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
   }

   write_popup_text("open","Processing files",500,200,Mwin);                     //  status monitor popup window

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

      infile = filelist[ii];                                                     //  input file

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

      outloc = zstrdup(inloc);                                                   //  initial output = input file
      outname = zstrdup(inname);
      outext = (char *) zmalloc(8);

      cc = strlen(outloc) - 1;                                                   //  remove trailing '/'
      if (outloc[cc] == '/') outloc[cc] = 0;
      
      if (*newname) {
         zfree(outname);
         outname = zstrdup(newname);
      }
      
      if (*newname && strstr(newname,"[oldname]"))                               //  15.11
      {
         zfree(outname);
         outname = (char *) zmalloc(strlen(newname) + strlen(inname));           //  .....[oldname].....
         pp1 = strstr(newname,"[oldname]");                                      //       |        |
         pp2 = pp1 + 9;                                                          //       pp1      pp2
         cc = pp1 - newname;
         strncpy(outname,newname,cc);
         strcpy(outname+cc,inname);
         cc += strlen(inname);
         strcpy(outname+cc,pp2);
      }

      if (strchr(outname,'#'))                                                   //  sequence number needed             15.11
      {
         pp1 = strchr(outname,'#');                                              //  .....#####.....
         pp2 = pp1 + 1;                                                          //       |    |
         while (*pp2 == '#') pp2++;                                              //       pp1  pp2
         cc = pp2 - pp1;
         jj = baseseq + ii * addseq;                                             //  new sequence number nnnnnn
         snprintf(seqnum,20,"%0*d",cc,jj);
         cc = strlen(seqnum);
         tempname = (char *) zmalloc(strlen(outname) + cc + 2);
         cc = pp1 - outname;
         strncpy(tempname,outname,cc);
         strcpy(tempname + cc, seqnum);
         strcat(tempname + cc, pp2);
         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);

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

      write_popup_text("write",outfile);                                         //  log each output file

      if (*newloc) {
         err = stat(outfile,&statdat);                                           //  check if file exists in new location
         if (! err) {
            write_popup_text("write",Bfileexists);
            zfree(outfile);
            continue;
         }
      }

      pxmin = PXM_load(infile,0);                                                //  read input file
      if (! pxmin) {
         write_popup_text("write",ZTX("file type not supported"));
         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.                           15.03
         if (pxmout) {
            PXM_free(pxmin);                                                     //  input image unrotated
            pxmin = pxmout;
         }
      }

      bpc = f_load_bpc;                                                          //  input file bits/color
      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
         }
      }

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

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

         if (Fovconst) {                                                         //  make overlay width constant for    16.02
            imageR = 1.0 * ww / hh;                                              //    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) {
            zmessageACK(Mwin,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 = (ww-pww)/2; orgy = 0; }                        //  top center
         if (ovpos == 3) { orgx = ww-pww; orgy = 0; }                            //  top right

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

         if (ovpos == 7) { orgx = 0; orgy = hh-phh; }                            //  bottom left
         if (ovpos == 8) { orgx = (ww-pww)/2; orgy = hh-phh; }                   //  bottom center
         if (ovpos == 9) { orgx = ww-pww; orgy = hh-phh; }                       //  bottom right
         
         if (orgx < 0) orgx = 0;
         if (orgy < 0) orgy = 0;
         if (orgx + pww > ww) pww = ww - orgx;
         if (orgy + phh > hh) phh = hh - 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",tempdir,pp1);                                //  temp file for EXIF/IPTC copy       16.01

      pp1 = strrchr(outfile,'.');
      if (strmatch(".tif",pp1))                                                  //  write output file to temp file
         err = PXM_TIFF_save(pxmout,tempfile,bpc);
      else if (strmatch(".png",pp1))
         err = PXM_PNG_save(pxmout,tempfile,bpc);
      else
         err = PXM_ANY_save(pxmout,tempfile);

      if (err) {
         write_popup_text("write",ZTX("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 = shell_ack("cp -p \"%s\" \"%s\" ",tempfile,outfile);                  //  copy tempfile to output file
      if (err) write_popup_text("write",wstrerror(err));
      remove(tempfile);                                                          //  remove tempfile

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

      if (delinput) {                                                            //  delete input file
         remove(infile);
         delete_image_index(infile);                                             //  remove from image index
         image_thumbnail_delete(infile);                                         //  delete thumbnail, file and cache   15.12
      }

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

      if (delinput) {                                                            //  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
      write_popup_text("write",ZTX("updating albums ..."));
      conv_albums(oldfiles,newfiles,Noldnew);
   }

   write_popup_text("write",Bcompleted);

   if (navi::gallerytype == GDIR)                                                //  update gallery file list           15.03
      gallery(0,"init");

   gallery_monitor("start");                                                     //  resume tracking gallery updates

cleanup:

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

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

   m_viewmode(0,"G");                                                            //  gallery view                       16.01
   
   Fblock = 0;
   return;
}


//  dialog event and completion callback function

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

   char        countmess[60];
   char        *ploc, *ofile, *oname = 0;
   int         ii, cc, yn, err;
   GdkPixbuf   *ovpxb;
   GError      *gerror = 0;
   
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   
   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      if (filelist) {                                                            //  free prior list
         for (ii = 0; ii < filecount; ii++)
            zfree(filelist[ii]);
         zfree(filelist);
         filelist = 0;
         filecount = 0;
      }

      zdialog_show(zd,0);                                                        //  hide parent dialog
      filelist = gallery_getfiles();                                             //  get list of files to convert
      zdialog_show(zd,1);

      ii = 0;
      if (filelist)                                                              //  count files selected
         for (ii = 0; filelist[ii]; ii++);
      filecount = ii;

      snprintf(countmess,60,Bfileselected,ii);                                   //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

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

   if (strstr("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 (strmatch(event,"imgov")) {                                                //  [open] overlay image file          16.01
      if (ovfile) 
         ofile = zgetfile("image file",MWIN,"file",ovfile,0);
      else
         ofile = zgetfile("image file",MWIN,"file",saved_areas_dirk,0);
      if (! ofile) return 1;
      if (ovfile) zfree(ovfile);
      ovfile = ofile;
   }
   
   if (strmatch(event,"ovTL")) ovpos = 1;                                        //  overlay position = top left        16.01
   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
   
   if (zd->zstat != 1) return 1;                                                 //  wait for [proceed]
   zd->zstat = 0;                                                                //  dialog active until inputs OK

   zdialog_fetch(zd,"newname",newname,100);                                      //  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 (directory)
   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,"sametype",Fsametype);                                       //  keep same file type
   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                      16.01
   zdialog_fetch(zd,"ovwidth",ovwidth);                                          //  overlay width 1-100%
   zdialog_fetch(zd,"ovconst",Fovconst);                                         //  flag, make overlay size constant   16.02
   zdialog_fetch(zd,"ovscrww",ovscrww);                                          //  for this screen width
   zdialog_fetch(zd,"ovscrhh",ovscrhh);                                          //  and this screen height

   if (! filecount) {
      zmessageACK(Mwin,Bnofileselected);
      return 1;
   }

   strTrim2(newname);                                                            //  check new name, baseseq, addseq    15.11
   if (! blank_null(newname)) {
      err = 0;
      if (strlen(newname) < 2) err++;                                            //  disallow tiny new name
      if (! strstr(newname,"[oldname]") && ! strchr(newname,'#')) err++;         //  must use old name or sequence number
      if (strchr(newname,'#')) {                                                 //  if '#' present, 
         if (baseseq < 1 || addseq < 1) err++; }                                 //    baseseq and addseq present
      else if (baseseq || addseq) err++;                                         //  if no '#' then no sequence numbers
      if (err) {
         zmessageACK(Mwin,ZTX("new name/base/adder unreasonable"                 //  15.11
                              "\n e.g. newname ###   100  10"
                              "\n or  ... [oldname] ..."));
         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 (! Fsametype) {                                                            //  file type change
      strcpy(newext,".jpg");                                                     //  get new .ext
      zdialog_fetch(zd,"png",ii);
      if (ii) strcpy(newext,".png");
      zdialog_fetch(zd,"tif",ii);
      if (ii) strcpy(newext,".tif");
   }

   if (! Fsamesize && (maxww < 20 || maxhh < 20)) {
      zmessageACK(Mwin,ZTX("max. size %d x %d is not reasonable"),maxww,maxhh);
      return 1;
   }
   
   if (Foverlay)                                                                 //  16.01
   {
      if (! ovfile) {                                                            //  overlay image file
         zmessageACK(Mwin,ZTX("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,ZTX("specify overlay position"));
         return 1;
      }
   }
   
   Freplace = 0;                                                                 //  16.01
   if (*newname <= ' ' && *newloc <= ' ' && Fsametype) 
      Freplace = 1;

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

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

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

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

   snprintf(warnmess,1000,"%s %s %s %s %s %s %s %s %s %s",
            mess0,mess1,mess2,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;

   for (int ii = 0; ii < NWT; ii++)                                              //  start worker threads
      start_wthread(batch_sharp_wthread,&Nval[ii]);
   wait_wthreads();                                                              //  wait for completion

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

   exit_wthread();
   return 0;                                                                     //  not executed, avoid gcc warning
}


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

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

char     **bup_filelist;
int      bup_filecount;
int      bup_allfiles;

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

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

   F1_help_topic = "batch_upright";

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

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

***/

   zd = zdialog_new(ZTX("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",ZTX("Survey all files"),"space=5");

   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                      15.03
   m_viewmode(0,"F");
   gallery_monitor("stop");                                                      //  stop tracking gallery updates

   write_popup_text("open","Processing files",500,200,Mwin);                     //  status monitor popup window
   
   if (bup_allfiles)                                                             //  "survey all files" selected        15.07
   {
      bup_filelist = (char **) zmalloc(maximages * sizeof(char *));
      bup_filecount = 0;

      for (ii = 0; ii < Ntopdirks; ii++)
      {
         err = find_imagefiles(topdirks[ii],allfiles,NAF,0);
         if (err) {
            write_popup_text("write","find_imagefiles() failure");
            goto cleanup;
         }
         
         if (bup_filecount + NAF >= maximages) {
            write_popup_text("write","exceed max images: %d",maximages);
            goto cleanup;
         }

         for (jj = 0; jj < NAF; jj++)
            bup_filelist[bup_filecount+jj] = allfiles[jj];
         bup_filecount += NAF;
      }
   }

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

      infile = bup_filelist[ii];                                                 //  input file
      write_popup_text("write",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) {
         write_popup_text("write",ZTX("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.                           15.03

      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_ANY_save(pxmout,tempfile);

      PXM_free(pxmout);

      if (err) {
         write_popup_text("write","*** upright failed");
         zfree(tempfile);
         continue;
      }

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

      err = shell_ack("cp -p \"%s\" \"%s\" ",tempfile,infile);                   //  copy temp file to input file
      if (err) write_popup_text("write",wstrerror(err));
      remove(tempfile);                                                          //  remove tempfile
      zfree(tempfile);

      if (! err) {
         load_filemeta(infile);                                                  //  update image index
         update_image_index(infile);
         write_popup_text("write","  uprighted");
      }
   }

   write_popup_text("write",Bcompleted);

   if (navi::gallerytype == GDIR)                                                //  refresh gallery                    15.03
      gallery(0,"init");

   gallery_monitor("start");                                                     //  resume tracking gallery updates

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[50];
   int          ii;

   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   
   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      if (bup_filelist) {                                                        //  free prior list
         for (ii = 0; ii < bup_filecount; ii++)
            zfree(bup_filelist[ii]);
         zfree(bup_filelist);
         bup_filelist = 0;
         bup_filecount = 0;
      }

      zdialog_show(zd,0);                                                        //  hide parent dialog
      bup_filelist = gallery_getfiles();                                         //  get list of files to convert
      zdialog_show(zd,1);

      ii = 0;
      if (bup_filelist)                                                          //  count files selected
         for (ii = 0; bup_filelist[ii]; ii++);
      bup_filecount = ii;

      snprintf(countmess,50,Bfileselected,ii);                                   //  update dialog
      zdialog_stuff(zd,"fcount",countmess);
      zdialog_stuff(zd,"allfiles",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,ZTX("cannot select both options"));
      zd->zstat = 0;
   }

   return 1;
}


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

//  Batch delete or trash image files.

char     **bdt_filelist;
int      bdt_filecount;
int      bdt_option;                                                             //  1/2 = delete/trash

void m_batch_deltrash(GtkWidget *, cchar *)                                      //  15.07
{
   int  batch_deltrash_dialog_event(zdialog *zd, cchar *event);

   zdialog     *zd;
   int         zstat, ii, err, gstat;
   char        *file;
   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(ZTX("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",ZTX("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",ZTX("trash"),"space=5");
   zdialog_add_widget(zd,"radio","trash","hbdt",0);
   
   bdt_filelist = 0;
   bdt_filecount = 0;
   bdt_option = 2;

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

   free_resources();                                                             //  no curr. file

   m_viewmode(0,"F");
   gallery_monitor("stop");                                                      //  stop tracking gallery updates

   write_popup_text("open","Processing files",500,200,Mwin);                     //  status monitor popup window

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

      file = bdt_filelist[ii];                                                   //  log each file
      write_popup_text("write",file);
   
      err = stat(file,&statb);                                                   //  file exists?
      if (err || ! S_ISREG(statb.st_mode)) {
         write_popup_text("write","file not found");
         continue;
      }

      if (bdt_option == 1) {                                                     //  delete file
         err = remove(file);
         if (err) {
            write_popup_text("write",wstrerror(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) {
            write_popup_text("write",gerror->message);
            continue;
         }
      }

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

   write_popup_text("write",Bcompleted);

cleanup:

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

   if (navi::gallerytype == GDIR)                                                //  refresh gallery                    15.03
      gallery(0,"init");

   gallery_monitor("start");                                                     //  resume tracking gallery updates

   Fblock = 0;
   return;
}


//  dialog event and completion callback function

int batch_deltrash_dialog_event(zdialog *zd, cchar *event)
{
   char         countmess[50];
   int          ii;
   
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   
   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      if (bdt_filelist) {                                                        //  free prior list
         for (ii = 0; ii < bdt_filecount; ii++)
            zfree(bdt_filelist[ii]);
         zfree(bdt_filelist);
         bdt_filelist = 0;
         bdt_filecount = 0;
      }

      zdialog_show(zd,0);                                                        //  hide parent dialog
      bdt_filelist = gallery_getfiles();                                         //  get list of files to convert
      zdialog_show(zd,1);

      ii = 0;
      if (bdt_filelist)                                                          //  count files selected
         for (ii = 0; bdt_filelist[ii]; ii++);
      bdt_filecount = ii;

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

   return 1;
}


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

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

namespace batch_raw
{
   char     location[400];
   char     **filelist;
   int      filecount;
   cchar    *filetype;
   int      bpc;
   float    resize;
   int      Fsharpen;
   int      amount, thresh;
};


void  m_batch_raw(GtkWidget *, cchar *menu)                                      //  16.07
{
   using namespace batch_raw;

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

   zdialog     *zd;
   cchar       *title = ZTX("Batch Convert RAW Files");
   char        *infile, *outfile, *pp;
   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;

/***
       _____________________________________________________________________________
      | [x] [-] [_]             Batch Convert RAW Files                             |
      |                                                                             |
      | [Select Files]  N files selected                                            |
      | output location [________________________________________________] [Browse] |
      | output file type  (o) JPEG  (o) PNG-8  (o) PNG-16  (o) TIFF-8  (o) TIFF-16  |
      | resize  (o) 1.0  (o) 3/4  (o) 2/3  (o) 1/2  (o) 1/3                         |
      | [x] Sharpen   amount [___]   threshold [___]                                |
      |                                                         [proceed] [cancel]  |  dcraw parameters removed         16.07
      |_____________________________________________________________________________|

***/

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

   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=3");
   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");
   zdialog_add_widget(zd,"label","labout","hbout",ZTX("output location"),"space=5");
   zdialog_add_widget(zd,"entry","location","hbout",0,"space=5|expand");
   zdialog_add_widget(zd,"button","browse","hbout",Bbrowse,"space=5");

   zdialog_add_widget(zd,"hbox","hb2","dialog");
   zdialog_add_widget(zd,"label","labtype","hb2",ZTX("output file type"),"space=5");
   zdialog_add_widget(zd,"radio","JPEG","hb2","JPEG","space=5");
   zdialog_add_widget(zd,"radio","PNG-8","hb2","PNG-8","space=5");
   zdialog_add_widget(zd,"radio","PNG-16","hb2","PNG-16","space=5");
   zdialog_add_widget(zd,"radio","TIFF-8","hb2","TIFF-8","space=5");
   zdialog_add_widget(zd,"radio","TIFF-16","hb2","TIFF-16","space=5");

   zdialog_add_widget(zd,"hbox","hbsize","dialog");
   zdialog_add_widget(zd,"label","labsize","hbsize",ZTX("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");
   zdialog_add_widget(zd,"check","sharpen","hbsharp",ZTX("Sharpen"),"space=3");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labamount","hbsharp",ZTX("amount"),"space=3");
   zdialog_add_widget(zd,"spin","amount","hbsharp","0|400|1|100");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labthresh","hbsharp",ZTX("threshold"),"space=3");
   zdialog_add_widget(zd,"spin","thresh","hbsharp","0|100|1|20");

   filelist = 0;                                                                 //  set no files selected
   filecount = 0;
   *location = 0;

   zdialog_stuff(zd,"JPEG",1);                                                   //  compensate GTK bug
   batch_raw_dialog_event(zd,"defaults");                                        //  set defaults
   zdialog_restore_inputs(zd);                                                   //  get prior inputs if any

   zdialog_run(zd,batch_raw_dialog_event,"save");                                //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);
   if (zstat != 1) goto cleanup;
   if (! filecount) goto cleanup;

   m_viewmode(0,"F");
   gallery_monitor("stop");                                                      //  stop tracking gallery updates

   write_popup_text("open","Converting RAW files",500,200,Mwin);                 //  status monitor popup window

   rawfile = 0;

   for (ii = 0; ii < filecount; ii++)                                            //  loop all RAW files
   {
      zmainloop();                                                               //  keep GTK alive                     16.04

      rawfile = filelist[ii];                                                    //  filename.raw

      write_popup_text("write",rawfile);                                         //  write input raw file to log window

      ftype = image_file_type(rawfile);
      if (ftype != RAW) {
         write_popup_text("write"," *** unknown RAW file type");
         continue;
      }
      
      pxm1 = RAW_PXM_load(rawfile);                                              //  make PXM image from RAW file       16.07
      if (! pxm1) {
         write_popup_text("write"," *** raw conversion failed");                 //  conversion failed
         continue;
      }

      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) {
            write_popup_text("write"," *** resize failed");
            continue;
         }
      }

      if (Fsharpen)                                                              //  sharpen if wanted
         batch_sharp_func(pxm1,amount,thresh);

      outfile = zstrdup(rawfile);                                                //  construct output file name         16.07

      pp = strrchr(outfile,'.');                                                 //  make *.tif or *.jpg or *.png       16.02
      if (pp) strcpy(pp+1,filetype);

      if (strmatch(filetype,"tif"))                                              //  output .tif file, 8/16 bits        16.02
         err = PXM_TIFF_save(pxm1,outfile,bpc);

      else if (strmatch(filetype,"png"))                                         //  output .png file, 8/16 bits
         err = PXM_PNG_save(pxm1,outfile,bpc);

      else err = PXM_ANY_save(pxm1,outfile);                                     //  output .jpg file, 8 bits

      if (err) {
         write_popup_text("write"," *** file type conversion failed");
         zfree(outfile);
         continue;
      }

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

      if (*location && ! samedirk(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 = shell_ack("cp -p \"%s\" \"%s\" ",infile,outfile);                 //  copy to new location
         if (err) write_popup_text("write",wstrerror(err));
         remove(infile);                                                         //  remove tempfile
         zfree(infile);
      }

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

      write_popup_text("write",outfile);                                         //  write output file to log window
      zfree(outfile);
   }

   write_popup_text("write",Bcompleted);

   gallery(curr_dirk,"init");                                                    //  update gallery file list           15.03
   gallery_monitor("start");                                                     //  resume tracking gallery updates

cleanup:                                                                         //  clean up and return

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

   rawfile = 0;
   Fblock = 0;
   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[50], *ploc;

   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   
   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      if (filecount) {                                                           //  free prior list
         for (ii = 0; ii < filecount; ii++)
            zfree(filelist[ii]);
         zfree(filelist);
         filelist = 0;
         filecount = 0;
      }

      zdialog_show(zd,0);                                                        //  hide parent dialog
      filelist = gallery_getfiles();                                             //  get list of files to convert
      zdialog_show(zd,1);

      ii = 0;
      if (filelist)                                                              //  count files in list
         for (ii = 0; filelist[ii]; ii++);
      filecount = ii;

      snprintf(countmess,50,Bfileselected,ii);                                   //  stuff count into dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

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

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

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

   if (! filecount) {
      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;
   }

   zdialog_fetch(zd,"JPEG",ii);                                                  //  file type, color depth
   if (ii) { filetype = "jpg"; bpc = 8; }
   zdialog_fetch(zd,"PNG-8",ii);
   if (ii) { filetype = "png"; bpc = 8; }
   zdialog_fetch(zd,"PNG-16",ii);
   if (ii) { filetype = "png"; bpc = 16; }
   zdialog_fetch(zd,"TIFF-8",ii);
   if (ii) { filetype = "tif"; bpc = 8; }
   zdialog_fetch(zd,"TIFF-16",ii);
   if (ii) { filetype = "tif"; bpc = 16; }                                       //  use .tif for both 8/18 bpc         16.02

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


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

//  convert multiple RAW files to tiff, jpeg, or png using Raw Therapee

namespace batch_rawtherapee
{
   char     location[400];
   char     **filelist;
   int      filecount;
   char     rawtherapee_command[100] = "";
   cchar    *filetype;
   int      bpc;
   float    resize;
   int      Fsharpen;
   int      amount, thresh;
};


void  m_batch_rawtherapee(GtkWidget *, cchar *menu)
{
   using namespace batch_rawtherapee;

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

   zdialog     *zd;
   cchar       *title = ZTX("Batch Convert RAW Files (Raw Therapee)");
   char        *tiffile, *infile, *outfile, *pp;
   int         zstat, ii, err;
   FTYPE       ftype;
   int         cc, ww2, hh2;
   PXM         *pxm1, *pxm2;

   F1_help_topic = "batch_raw";

   if (! Frawtherapee) {
      zmessageACK(Mwin,ZTX("Raw Therapee not installed"));
      return;
   }

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

/***
       _____________________________________________________________________________
      | [x] [-] [_]       Batch Convert RAW Files (Raw Therapee)                    |
      |                                                                             |
      | [Select Files]  N files selected                                            |
      | output location [_________________________________________________]         |
      | output file type  (o) JPEG  (o) PNG-8  (o) PNG-16  (o) TIFF-8  (o) TIFF-16  |
      | resize  (o) 1.0  (o) 3/4  (o) 2/3  (o) 1/2  (o) 1/3                         |
      | [x] Sharpen   amount [___]   threshold [___]                                |
      |                                                         [proceed] [cancel]  |
      |_____________________________________________________________________________|

***/

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

   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=3");
   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");
   zdialog_add_widget(zd,"label","labout","hbout",ZTX("output location"),"space=5");
   zdialog_add_widget(zd,"entry","location","hbout",0,"space=5|expand");
   zdialog_add_widget(zd,"button","browse","hbout",Bbrowse,"space=5");

   zdialog_add_widget(zd,"hbox","hb2","dialog");
   zdialog_add_widget(zd,"label","labtype","hb2",ZTX("output file type"),"space=5");
   zdialog_add_widget(zd,"radio","JPEG","hb2","JPEG","space=5");
   zdialog_add_widget(zd,"radio","PNG-8","hb2","PNG-8","space=5");
   zdialog_add_widget(zd,"radio","PNG-16","hb2","PNG-16","space=5");
   zdialog_add_widget(zd,"radio","TIFF-8","hb2","TIFF-8","space=5");
   zdialog_add_widget(zd,"radio","TIFF-16","hb2","TIFF-16","space=5");

   zdialog_add_widget(zd,"hbox","hbsize","dialog");
   zdialog_add_widget(zd,"label","labsize","hbsize",ZTX("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");
   zdialog_add_widget(zd,"check","sharpen","hbsharp",ZTX("Sharpen"),"space=3");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labamount","hbsharp",ZTX("amount"),"space=3");
   zdialog_add_widget(zd,"spin","amount","hbsharp","0|400|1|100");
   zdialog_add_widget(zd,"label","space","hbsharp",0,"space=8");
   zdialog_add_widget(zd,"label","labthresh","hbsharp",ZTX("threshold"),"space=3");
   zdialog_add_widget(zd,"spin","thresh","hbsharp","0|100|1|20");

   filelist = 0;                                                                 //  set no files selected
   filecount = 0;
   *location = 0;

   zdialog_stuff(zd,"JPEG",1);                                                   //  compensate GTK bug
   zdialog_restore_inputs(zd);                                                   //  get prior inputs if any

   zdialog_run(zd,batch_rawtherapee_dialog_event,"save");                        //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion
   zdialog_free(zd);
   if (zstat != 1) goto cleanup;
   if (! filecount) goto cleanup;

   m_viewmode(0,"F");
   gallery_monitor("stop");                                                      //  stop tracking gallery updates

   write_popup_text("open","Converting RAW files",500,200,Mwin);                 //  status monitor popup window

   rawfile = 0;

   for (ii = 0; ii < filecount; ii++)                                            //  loop all RAW files
   {
      zmainloop();                                                               //  keep GTK alive                     16.04

      rawfile = filelist[ii];                                                    //  filename.raw

      write_popup_text("write",rawfile);                                         //  write input raw file to log window

      ftype = image_file_type(rawfile);
      if (ftype != RAW) {
         write_popup_text("write"," *** unknown RAW file type");
         continue;
      }

      snprintf(rawtherapee_command,100,"rawtherapee -t -Y -c ");

      err = shell_ack("%s \"%s\" ",rawtherapee_command,rawfile);                 //  convert filename.raw to filename.tif
      if (err) {
         write_popup_text("write"," *** raw conversion failed");                 //  conversion failed
         write_popup_text("write",wstrerror(err));
         continue;
      }

      tiffile = raw_to_tiff(rawfile);                                            //  rawfilename.tif

      pxm1 = PXM_load(tiffile,0);                                                //  load tiff-16 output file
      if (! pxm1) {
         strcat(tiffile,"f");                                                    //  look for both .tif and .tiff
         pxm1 = PXM_load(tiffile,0);                                             //  load tiff-16 output file
      }

      if (! pxm1) {
         write_popup_text("write"," *** no Raw Therapee .tif output");
         zfree(tiffile);
         continue;
      }

      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) {
            write_popup_text("write"," *** resize failed");
            zfree(tiffile);
            continue;
         }
      }

      if (Fsharpen)                                                              //  sharpen if wanted
         batch_sharp_func(pxm1,amount,thresh);

      outfile = zstrdup(tiffile);                                                //  have filename.tif, 16 bits/color

      pp = strrchr(outfile,'.');                                                 //  make *.tif or *.jpg or *.png
      if (pp) strcpy(pp+1,filetype);

      if (strmatch(filetype,"tif"))                                              //  output .tif file, 8/16 bits        16.02
         err = PXM_TIFF_save(pxm1,outfile,bpc);

      else if (strmatch(filetype,"png"))                                         //  output .png file, 8/16 bits
         err = PXM_PNG_save(pxm1,outfile,bpc);

      else err = PXM_ANY_save(pxm1,outfile);                                     //  output .jpg file, 8 bits

      if (err) {
         write_popup_text("write"," *** file type conversion failed");
         zfree(tiffile);
         zfree(outfile);
         continue;
      }

      if (! strmatch(outfile,tiffile))                                           //  if conversion was made,
         remove(tiffile);                                                        //    delete tiff-16 file

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

      if (*location && ! samedirk(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 = shell_ack("cp -p \"%s\" \"%s\" ",infile,outfile);                 //  copy to new location
         if (err) write_popup_text("write",wstrerror(err));
         remove(infile);                                                         //  remove tempfile
         zfree(infile);
      }

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

      write_popup_text("write",outfile);                                         //  write output file to log window
      zfree(outfile);
      zfree(tiffile);
   }

   write_popup_text("write",Bcompleted);

   gallery(curr_dirk,"init");                                                    //  update gallery file list           15.03
   gallery_monitor("start");                                                     //  resume tracking gallery updates

cleanup:                                                                         //  clean up and return

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

   rawfile = 0;
   Fblock = 0;
   return;
}


//  dialog event and completion callback function

int batch_rawtherapee_dialog_event(zdialog *zd, cchar *event)
{
   using namespace batch_rawtherapee;

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

   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   
   if (strmatch(event,"files"))                                                  //  select images to convert
   {
      if (filecount) {                                                           //  free prior list
         for (ii = 0; ii < filecount; ii++)
            zfree(filelist[ii]);
         zfree(filelist);
         filelist = 0;
         filecount = 0;
      }

      zdialog_show(zd,0);                                                        //  hide parent dialog
      filelist = gallery_getfiles();                                             //  get list of files to convert
      zdialog_show(zd,1);

      ii = 0;
      if (filelist)                                                              //  count files in list
         for (ii = 0; filelist[ii]; ii++);
      filecount = ii;

      snprintf(countmess,50,Bfileselected,ii);                                   //  stuff count into dialog
      zdialog_stuff(zd,"fcount",countmess);
   }

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

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

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

   if (! filecount) {
      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;
   }

   zdialog_fetch(zd,"JPEG",ii);                                                  //  file type, color depth
   if (ii) { filetype = "jpg"; bpc = 8; }
   zdialog_fetch(zd,"PNG-8",ii);
   if (ii) { filetype = "png"; bpc = 8; }
   zdialog_fetch(zd,"PNG-16",ii);
   if (ii) { filetype = "png"; bpc = 16; }
   zdialog_fetch(zd,"TIFF-8",ii);
   if (ii) { filetype = "tif"; bpc = 8; }
   zdialog_fetch(zd,"TIFF-16",ii);
   if (ii) { filetype = "tif"; bpc = 16; }                                       //  use .tif for both 8/16 bpc         16.02

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


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

//  script files: 
//    build a script file with one or more predefined edit functions
//    execute the script file for selected image files

namespace script
{
   int  dialog_event(zdialog *zd, cchar *event);
   int  script_start();
   int  script_close();
   int  script_run();
   
   char     *scriptfile = 0;                                                     //  script filespec
   FILE     *fid = 0;                                                            //  script file FID
}


//  menu function   

void m_scriptfiles(GtkWidget *, cchar *menu)                                     //  15.10
{
   using namespace script;

   F1_help_topic = "script_files";
   
/***
       _________________________________________
      |         Script Files                    |
      |                                         |
      |  [start]  begin making a script file    |
      |  [close]  finish making a script file   |
      |  [run]    execute a script file         |  
      |                                         |
      |                               [cancel]  |
      |_________________________________________|

***/

   zdialog *zd = zdialog_new("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",ZTX("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",ZTX("finish making a script file"));
   zdialog_add_widget(zd,"hbox","hb3","dialog");
   zdialog_add_widget(zd,"button","run","hb3",Brun,"space=5");
   zdialog_add_widget(zd,"label","labrun","hb3",ZTX("execute a script file"));

   zdialog_run(zd,dialog_event);
   return;
}


//  dialog event and completion function

int script::dialog_event(zdialog *zd, cchar *event)
{
   using namespace script;

   F1_help_topic = "script_files";

   if (strmatch(event,"escape")) zd->zstat = 1;                                  //  escape = cancel

   if (zd->zstat) {                                                              //  cancel
      zdialog_free(zd);
      if (fid) fclose(fid);
      fid = 0;
      Fscriptbuild = 0;
      return 1;
   }
   
   if (strmatch(event,"start")) {
      script_start();
      zdialog_free(zd);
   }

   if (strmatch(event,"close"))
      script_close();

   if (strmatch(event,"run")) {
      zdialog_free(zd);
      script_run();
   }

   return 1;
}


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

int script::script_start()
{
   using namespace script;
   
   char     *pp;
   
   if (checkpend("all")) return 0;

   if (Fscriptbuild) {
      zmessageACK(Mwin,ZTX("script already started"));
      return 0;
   }
   
   pp = zgetfile(ZTX("open new script file"),MWIN,"save",edit_scripts_dirk,1);
   if (! pp) return 0;
   if (scriptfile) zfree(scriptfile);
   scriptfile = pp;
   
   fid = fopen(scriptfile,"w");
   if (! fid) {
      zmessageACK(Mwin,strerror(errno));
      return 0;
   }
   
   pp = strrchr(scriptfile,'/');
   fprintf(fid,"script file: %s\n",pp+1);

   Fscriptbuild = 1;
   return 1;
}


//  this function is called from edit_done() when Fscriptbuild is active

int addscript(editfunc *CEF)
{
   using namespace script;
   
   int      err;
   
   fprintf(fid,"menu: %s\n",CEF->menuname);                                      //  write "menu: menu name"

   if (CEF->zd) {
      err = edit_save_widgets(CEF,fid);                                          //  write widget settings
      if (err) {
         zmessageACK(Mwin,ZTX("script file error"));
         return 0;
      }
   }

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


//  complete a script file under construction
//  and save the script file with assigned name

int script::script_close()
{
   using namespace script;
   
   if (! Fscriptbuild) {
      zmessageACK(Mwin,ZTX("no script file was started"));
      return 0;
   }
   
   fprintf(fid,"menu: end");
   fclose(fid);
   fid = 0;
   Fscriptbuild = 0;
   zmessageACK(Mwin,ZTX("script file closed"));
   return 1;
}


//  execute a script file
//  returns 0 if OK, +N if error

int script::script_run()
{
   using namespace script;
   
   char     **filelist = 0, *imagefile, *pp;
   char     scriptname[100], buff[100], menuname[40];
   char     *newfilevers;
   int      ii, jj, err;
   int      retstat = 1;                                                         //  failure
   
   if (Fscriptbuild) {
      zmessageACK(Mwin,ZTX("script file is not closed"));
      return 1;
   }
   
   zmessageACK(Mwin,ZTX("select script file to run"));

   pp = zgetfile(ZTX("open script file"),MWIN,"file",edit_scripts_dirk,1);
   if (! pp) goto cleanup;
   if (scriptfile) zfree(scriptfile);
   scriptfile = pp;

   pp = strrchr(scriptfile,'/');                                                 //  get file name 
   if (! pp || strlen(pp) > 99) {
      zmessageACK(Mwin,ZTX("unknown script file"));
      goto cleanup;
   }
   strncpy0(scriptname,pp+1,100);                                                //  save script name

   fid = fopen(scriptfile,"r");                                                  //  open script file
   if (! fid) {
      zmessageACK(Mwin,ZTX("script file: %s \n %s"),scriptfile,strerror(errno));
      goto cleanup;
   }

   pp = fgets_trim(buff,100,fid,1);                                              //  read "script file: scriptname"
   if (! pp) goto badfile;
   if (! strmatchN("script file: ",pp,13)) goto badfile;
   if (! strmatch(pp+13,scriptname)) goto badfile;

   fclose(fid);

   zmessageACK(Mwin,ZTX("select image files to be processed"));
   
   filelist = gallery_getfiles();                                                //  get list of files to edit
   if (! filelist) goto cleanup;

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

   for (ii = 0; filelist[ii]; ii++)                                              //  loop image files to process
   {
      imagefile = filelist[ii];
      err = f_open(imagefile,0,0,1,0);
      if (err) {
         zmessageACK(Mwin,ZTX("open failure: %s \n %s"),imagefile,strerror(errno));
         goto cleanup;
      }
      
      fid = fopen(scriptfile,"r");                                               //  open script file
      if (! fid) goto cleanup;
      pp = fgets_trim(buff,100,fid,1);                                           //  discard "script file: scriptname"
      if (! pp) goto badfile;
      
      while (true)                                                               //  process script file
      {
         pp = fgets_trim(buff,100,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 (jj = 0; menutab[jj].menu; jj++)                                    //  convert menu name to menu function
            if (strmatch(menuname,menutab[jj].menu)) break;
         
         if (! menutab[jj].menu) {
            zmessageACK(Mwin,ZTX("unknown edit function: %s"),menuname);
            goto cleanup;
         }
         
         menutab[jj].func(0,menutab[jj].arg);                                    //  call the menu function

         if (CEF && CEF->zd) {
            err = edit_load_widgets(CEF,fid);                                    //  read and load dialog settings
            if (err) {
               zmessageACK(Mwin,ZTX("load widgets failed: %s"),menuname);
               goto cleanup;
            }
            zdialog_send_event(CEF->zd,"apply");                                 //  finish edit
            zdialog_send_event(CEF->zd,"done");
         }
      }

      newfilevers = file_new_version(curr_file);                                 //  get next avail. file version name
      if (! newfilevers) goto cleanup;
      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);                    //  save file
      zfree(newfilevers);

      err = f_open_saved();                                                      //  open saved file version
      if (err) {
         zmessageACK(Mwin,ZTX("open failure: %s \n %s"),imagefile,strerror(errno));
         goto cleanup;
      }
   }

   retstat = 0;                                                                  //  success
   goto cleanup;

badfile:

   zmessageACK(Mwin,ZTX("script file format error: %s"),scriptname);

cleanup:

   if (fid) fclose(fid);
   fid = 0;
   if (filelist) {
      for (ii = 0; filelist[ii]; ii++)                                           //  free memory
         zfree(filelist[ii]);
      zfree(filelist);
   }

   return retstat;
}


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

//  burn images to DVD/BlueRay optical disc

namespace burn_images
{
   zdialog  *zd;
   char     **filelist = 0;
   int      filecount = 0;
   char     mydvd[60];
};


//  menu function

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

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

   FILE     *fid;
   char     *pp;
   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

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

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

   Fdvd = Ndvd = 0;
   filelist = 0;
   filecount = 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,ZTX("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(ZTX("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",ZTX("Select device"),"space=5");
   zdialog_add_widget(zd,"combo","dvd","hbdvd",0,"expand");

   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 (! filecount) return;                                                      //  no files selected

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

   for (ii = 0; ii < filecount; ii++)
   {
      fprintf(fid,"%s\n",filelist[ii]);
      zfree(filelist[ii]);
   }

   zfree(filelist);
   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;

   int      ii;
   char     countmess[60];

   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel

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

      zdialog_show(zd,0);                                                        //  hide parent dialog
      filelist = gallery_getfiles();                                             //  get list of files to convert
      zdialog_show(zd,1);

      ii = 0;
      if (filelist)                                                              //  count files selected
         for (ii = 0; filelist[ii]; ii++);
      filecount = ii;

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

   return 1;
}


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

//  find all duplicated files and create corresponding gallery of duplicates

void m_duplicates(GtkWidget *, const char *)
{
   int  duplicates_dialog_event(zdialog *zd, cchar *event);

   PIXBUF      *pxb;
   GError      *gerror;
   FILE        *fid = 0;
   int         Nfiles = 0, Ndups = 0;                                            //  image file and duplicate counts
   char        **files = 0;                                                      //  image file list
   char        **thumbs = 0;                                                     //  64x64 thumbnail images
   int         *tcc = 0;                                                         //  thumbnail actual width x height
   uint8       *pix;
   zdialog     *zd;
   int         thumbsize, pixdiff, pixcount;
   char        text[100], gfile[100], *pp;
   int         zstat, ii, jj, kk, cc, err, ww, hh, ndup;
   int         m1, m2, sum, diff;

   F1_help_topic = "find_duplicates";

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

/***
       _________________________________________
      |         Find Duplicate Images           |
      |                                         |
      |  thumbnail size [ 64 |-+]               |
      |  pixel difference [ 2 |-+]              |
      |  pixel count [ 2 |-+]                   |
      |  Images: nnnnnn   Duplicates: nnn       |
      |  /topdirk/subdirk1/subdirk2/...         |
      |  imagefile.jpg                          |
      |                      [proceed] [cancel] |
      |_________________________________________|

***/

   zd = zdialog_new(ZTX("Find Duplicate Images"),Mwin,Bproceed,Bcancel,null);
   zdialog_add_widget(zd,"hbox","hbthumb","dialog");
   zdialog_add_widget(zd,"label","labthumb","hbthumb",ZTX("Thumbnail size"));
   zdialog_add_widget(zd,"spin","thumbsize","hbthumb","32|256|32|64","space=3");
   zdialog_add_widget(zd,"hbox","hbdiff","dialog");
   zdialog_add_widget(zd,"label","labdiff","hbdiff",ZTX("pixel difference"));
   zdialog_add_widget(zd,"spin","pixdiff","hbdiff","1|20|1|2","space=3");
   zdialog_add_widget(zd,"hbox","hbsum","dialog");
   zdialog_add_widget(zd,"label","labsum","hbsum",ZTX("pixel count"));
   zdialog_add_widget(zd,"spin","pixcount","hbsum","1|999|1|10","space=3");
   zdialog_add_widget(zd,"hbox","hbcount","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labcount1","hbcount",ZTX("Images:"));
   zdialog_add_widget(zd,"label","labcount2","hbcount",ZTX("searching ..."));
   zdialog_add_widget(zd,"label","space","hbcount",0,"space=5");
   zdialog_add_widget(zd,"label","labdup1","hbcount",ZTX("Duplicates:"));
   zdialog_add_widget(zd,"label","labdup2","hbcount","0");
   zdialog_add_widget(zd,"hbox","hbdirk","dialog",0,"space=3");
   zdialog_add_widget(zd,"label","labdirk","hbdirk"," ","space=5");
   zdialog_add_widget(zd,"hbox","hbfile","dialog");
   zdialog_add_widget(zd,"label","labfile","hbfile"," ","space=5");

   zdialog_restore_inputs(zd);
   zdialog_resize(zd,300,0);
   zdialog_run(zd,duplicates_dialog_event);

   err = find_imagefiles(thumbdirk,files,Nfiles,1);                              //  get all thumbnail files
   if (err) {
      zmessageACK(Mwin,strerror(errno));
      goto cleanup;
   }

   if (Nfiles < 10) {
      zmessageACK(Mwin,ZTX("only %d image thumbnails found"),Nfiles);
      goto cleanup;
   }

   snprintf(text,100,"%d",Nfiles);                                               //  stuff thumbnail count in dialog
   zdialog_stuff(zd,"labcount2",text);

   zstat = zdialog_wait(zd);                                                     //  wait for user inputs
   if (zstat != 1) goto cleanup;

   zdialog_fetch(zd,"thumbsize",thumbsize);                                      //  thumbnail size to use
   zdialog_fetch(zd,"pixdiff",pixdiff);                                          //  pixel difference threshold
   zdialog_fetch(zd,"pixcount",pixcount);                                        //  pixel count threshold

   cc = Nfiles * sizeof(uint8 *);
   thumbs = (char **) zmalloc(cc);
   memset(thumbs,0,cc);

   cc = Nfiles * sizeof(int);
   tcc = (int *) zmalloc(cc);

   Ffuncbusy = 1;

   for (ii = 0; ii < Nfiles; ii++)                                               //  screen out thumbnails not          15.02
   {                                                                             //    within top image directories
      pp = thumb2imagefile(files[ii]);
      if (! pp) {
         zfree(files[ii]);
         files[ii] = 0;
         continue;
      }

      for (kk = 0; kk < Ntopdirks; kk++)
      {
         cc = strlen(topdirks[kk]);
         if (pp[cc] != '/') continue;
         if (strmatchN(pp,topdirks[kk],cc)) break;
      }

      zfree(pp);
      if (kk < Ntopdirks) continue;
      zfree(files[ii]);
      files[ii] = 0;
   }

   for (ii = 0; ii < Nfiles; ii++)                                               //  get thumbnails in memory
   {
      if (! files[ii]) continue;
      if (Fkillfunc) goto cleanup;

      kk = thumbsize;
      gerror = 0;
      pxb = gdk_pixbuf_new_from_file_at_size(files[ii],kk,kk,&gerror);
      if (! pxb) {
         printz("file: %s \n %s",files[ii],gerror->message);
         zfree(files[ii]);
         files[ii] = 0;
         continue;
      }

      ww = gdk_pixbuf_get_width(pxb);                                            //  actual thumbnail dimensions
      hh = gdk_pixbuf_get_height(pxb);                                           //  ( <= 64x64)
      pix = gdk_pixbuf_get_pixels(pxb);

      cc = ww * hh * 3;
      thumbs[ii] = (char *) zmalloc(cc);
      memcpy(thumbs[ii],pix,cc);
      tcc[ii] = cc;
      g_object_unref(pxb);
      zmainloop(10);                                                             //  keep GTK alive
   }

   for (ii = 0; ii < Nfiles; ii++)                                               //  replace thumbnail filespecs
   {                                                                             //    with corresp. image filespecs
      if (! files[ii]) continue;
      pp = thumb2imagefile(files[ii]);
      zfree(files[ii]);
      files[ii] = pp;
   }

   snprintf(gfile,100,"%s/search_results",tempdir);                              //  open file for gallery output
   fid = fopen(gfile,"w");
   if (! fid) goto filerror;

   Fbusy_goal = Nfiles;                                                          //  set up progress monitor
   Fbusy_done = 0;

   for (ii = 0; ii < Nfiles; ii++)                                               //  loop all thumbnails
   {
      if (Fkillfunc) goto cleanup;
      Fbusy_done++;

      if (! files[ii]) continue;
      pp = strrchr(files[ii],'/');
      if (! pp) continue;
      *pp = 0;
      zdialog_stuff(zd,"labdirk",files[ii]);                                     //  update directory and file name
      zdialog_stuff(zd,"labfile",pp+1);                                          //    in dialog
      *pp = '/';
      zmainloop(10);                                                             //  keep GTK alive
      zsleep(0.001);                                                             //  deliberate slowdown

      for (jj = ndup = 0; jj < Nfiles; jj++)                                     //  loop all thumbnails
      {
         if (jj == ii) continue;
         if (! files[jj]) continue;
         if (tcc[ii] != tcc[jj]) continue;                                       //  look for matching thumbnails

         for (kk = sum = 0; kk < tcc[ii]; kk++)
         {
            m1 = thumbs[ii][kk];
            m2 = thumbs[jj][kk];
            diff = abs(m1 - m2);
            if (diff < pixdiff) continue;
            if (++sum > pixcount) break;
         }

         if (kk < tcc[ii]) continue;                                             //  no match

         gerror = 0;                                                             //  thumbnails match
         pxb = gdk_pixbuf_new_from_file_at_size(files[jj],64,64,&gerror);        //  check image is readable
         if (pxb) g_object_unref(pxb);
         if (! pxb || gerror) {                                                  //  no, ignore matching
            zfree(files[jj]);                                                    //    "broken file" thumbnails
            files[jj] = 0;
            continue;
         }

         if (ndup == 0) fprintf(fid,"%s\n",files[ii]);                           //  first duplicate, output file name
         fprintf(fid,"%s\n",files[jj]);                                          //  output duplicate image file name
         zfree(files[jj]);
         files[jj] = 0;
         ndup++;

         Ndups++;
         snprintf(text,100,"%d",Ndups);                                          //  update duplicates found in dialog
         zdialog_stuff(zd,"labdup2",text);
      }
   }

   fclose(fid);
   fid = 0;

   free_resources();
   navi::gallerytype = SEARCH;                                                   //  generate gallery of duplicate images
   gallery(gfile,"initF");
   m_viewmode(0,"G");
   gallery(0,"paint",0);                                                         //  position at top

cleanup:
   zdialog_free(zd);
   if (fid) fclose(fid);
   for (ii = 0; ii < Nfiles; ii++)
      if (files[ii]) zfree(files[ii]);
   if (thumbs)
      for (ii = 0; ii < Nfiles; ii++)
         if (thumbs[ii]) zfree(thumbs[ii]);
   if (files) zfree(files);
   if (thumbs) zfree(thumbs);
   if (tcc) zfree(tcc);
   fid = 0;
   Fbusy_goal = 0;
   Ffuncbusy = 0;
   Fkillfunc = 0;
   Fblock = 0;
   return;

filerror:
   zmessageACK(Mwin,"file error: %s",strerror(errno));
   goto cleanup;
}


//  dialog event and completion function

int  duplicates_dialog_event(zdialog *zd, cchar *event)
{
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   if (! zd->zstat) return 1;                                                    //  wait for user input
   if (zd->zstat != 1) Fkillfunc = 1;                                            //  [cancel] or [x]
   return 1;                                                                     //  [proceed]
}


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

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

namespace imagelist_images
{
   zdialog  *zd;
   char     countmess[60];
   char     **infiles = 0;
   int      Nfiles = 0;
   char     outfile[300];
};


//  menu function

void  m_imagelist(GtkWidget *, cchar *)                                          //  16.02
{
   using namespace imagelist_images;

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

   FILE     *fid;
   int      ii, zstat;
   cchar    *title = ZTX("Create a file of selected image files");

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

/***
       ____________________________________________  
      |    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",ZTX("Output File"),"space=3");
   zdialog_add_widget(zd,"entry","outfile","hbof",0,"size=30|space=5");
   zdialog_add_widget(zd,"button","browse","hbof",Bbrowse,"space=5");

   if (Nfiles) {   
      snprintf(countmess,60,Bfileselected,Nfiles);
      zdialog_stuff(zd,"Nfiles",countmess);
   }

   if (*outfile) zdialog_stuff(zd,"outfile",outfile);
   
   zdialog_run(zd,imagelist_dialog_event,"parent");                              //  run dialog, wait for response
   zstat = zdialog_wait(zd);
   if (zstat != 1) return;

   zdialog_fetch(zd,"outfile",outfile,300);                                      //  get output file from dialog
   zdialog_free(zd);
   
   if (! Nfiles) {
      zmessageACK(Mwin,ZTX("no input files selected"));
      m_imagelist(0,0);                                                          //  no input files
   }
   
   if (! *outfile) {
      zmessageACK(Mwin,ZTX("no output file selected"));
      m_imagelist(0,0);                                                          //  no output file
   }

   fid = fopen(outfile,"w");                                                     //  open output file
   if (! fid) {
      zmessageACK(Mwin,strerror(errno));                                         //  error
      return;
   }

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

   fclose(fid);
   zmessageACK(Mwin,Bcompleted);

   return;
}


//  dialog event and completion function

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

   int      ii;
   char     *file;

   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel

   if (strmatch(event,"infiles"))                                                //  select image files
   {
      infiles = gallery_getfiles(infiles);                                       //  get list of files to convert

      ii = 0;
      if (infiles)                                                               //  count files selected
         for (ii = 0; infiles[ii]; ii++);
      Nfiles = ii;

      snprintf(countmess,60,Bfileselected,ii);                                   //  update dialog
      zdialog_stuff(zd,"Nfiles",countmess);
   }
   
   if (strmatch(event,"browse"))
   {
      file = zgetfile(ZTX("Output File"),MWIN,"save",outfile,0);
      if (file) zdialog_stuff(zd,"outfile",file);
      else zdialog_stuff(zd,"outfile","");
   }
   
   return 1;
}



