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

   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 - file menu

   m_clone                 start a new parallel instance of Fotoxx
   m_recentfiles           show gallery of recently viewed files
   add_recent_file         add an image file to the list of recent files
   m_newfiles              show gallery of newest image files
   m_open                  open image file menu function
   m_previous              open the previously opened image file
   m_rawtherapee           open a camera RAW file using the RawTherapee program
   f_open                  open and display an image file
   f_open_saved            open the last image file saved
   f_preload               preload image files ahead of need
   m_create                create a new monocolor image
   create_blank_file       callable function to create a new monocolor image
   m_rename                rename current image file or clicked thumbnail
   m_copy_move             copy or move an image file to a new location
   m_copyto_clip           copy clicked file or current file to the clipboard
   m_delete_trash          delete or trash an image file
   m_print                 print an image file
   m_print_calibrated      print an image file with calibrated colors
   m_quit                  menu quit
   quitxx                  callable quit
   m_file_save             save a (modified) image file to disk
   file_new_version        get next avail. file version name
   f_save                  save an image file to disk (replace, new version, new file)
   f_save_as               dialog to save an image file with a designated file name
   m_prev                  open previous file in current gallery
   m_next                  open next file in current gallery
   m_prev_next             open previous or next file in current gallery
   m_help                  help menu
   find_imagefiles         find all image files under a given directory path
   raw_to_tiff             convert a RAW file name to equivalent .tif name
   PXB_load                load an image file into a PXB pixmap structure (8-bit RGB)
   PXM_load                load an image file into a PXM pixmap structure (float RGB)
   TIFF_PXB_load           load a .tif file into a PXB pixmap structure (8-bit RGB)
   TIFF_PXM_load           load a .tif file into a PXM pixmap structure (float RGB)
   PXM_TIFF_save           save a PXM pixmap to a .tif file (8/16-bit RGB)
   PNG_PXB_load            load a .png file into a PXB pixmap structure (8-bit RGB)
   PNG_PXM_load            load a .png file into a PXM pixmap structure (float RGB)
   PXM_PNG_save            save a PXM pixmap to a .png file (8/16-bit RGB)
   ANY_PXB_load            load other image file into a PXB pixmap structure (8-bit RGB)
   ANY_PXM_load            load other image file into a PXM pixmap structure (float RGB)
   PXM_ANY_save            save a PXM pixmap to other file type (8-bit RGB)
   RAW_PXB_load            load a RAW file into a PXB pixmap structure (8-bit RGB)
   RAW_PXM_load            load a RAW file into a PXM pixmap structure (float RGB)


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

#define EX extern                                                                //  disable extern declarations
#include "fotoxx.h"                                                              //  (variables in fotoxx.h are refs)

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


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

void m_clone(GtkWidget *, cchar *)
{
   int      cc, xx, yy, ww, hh;
   char     progexe[300];

   F1_help_topic = "new_window";

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

   cc = readlink("/proc/self/exe",progexe,300);                                  //  get own program path
   if (cc <= 0) {
      zmessageACK(Mwin,"cannot get /proc/self/exe");
      return;
   }
   progexe[cc] = 0;

   snprintf(command,CCC,"%s -c %d %d %d %d -lang %s",progexe,xx,yy,ww,hh,zfuncs::zlang);
   if (curr_file) strncatv(command,CCC," \"",curr_file,"\"",null);

   strcat(command," &");                                                         //  start new instance and pass
   shell_ack(command);                                                           //    my window posn and size
   return;
}


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

//  show recently seen image files (edited or only viewed)
//  optionally update gallery list only but no change to G view

void m_recentfiles(GtkWidget *, cchar *menu)
{
   F1_help_topic = "recent_images";
   navi::gallerytype = RECENT;                                                   //  gallery type = recent files
   gallery(recentfiles_file,"initF");                                            //  generate gallery of recent files
   m_viewmode(0,"G");
   gallery(0,"paint",0);
   return;
}


//  add a new file to the list of recent files, first position

void add_recent_file(cchar *newfile)
{
   FILE     *fidr, *fidw;
   char     *pp, tempfile[200], buff[XFCC];
   int      ii, err;

   strcpy(tempfile,recentfiles_file);
   strcat(tempfile,"_temp");

   fidw = fopen(tempfile,"w");                                                   //  open output temp file
   if (! fidw) {
      zmessageACK(Mwin,"file error: %s",strerror(errno));
      return;
   }

   fprintf(fidw,"%s\n",newfile);                                                 //  add new file as first in list

   fidr = fopen(recentfiles_file,"r");
   if (fidr) {
      for (ii = 0; ii < 100; ii++) {                                             //  read list of recent files <= 100
         pp = fgets_trim(buff,XFCC,fidr,1);
         if (! pp) break;
         if (strmatch(pp,newfile)) continue;                                     //  skip new file if present
         if (*pp == '/') fprintf(fidw,"%s\n",pp);                                //  write to output file
      }
      fclose(fidr);
   }

   fclose(fidw);

   err = rename(tempfile,recentfiles_file);
   if (err) zmessageACK(Mwin,"file error: %s",strerror(errno));

   return;
}


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

//  report the newest or most recently modified image files
//  based on EXIF photo date if available, else file mod date is used

struct NFxrec_t  {                                                               //  index record
   char        date[16];                                                         //  sort date, yyyymmddhhmmss
   char        *file;                                                            //  image file
};


//  menu function

void m_newfiles(GtkWidget *, cchar *menu)                                        //  15.05
{
   int NFxrec_comp(cchar *rec1, cchar *rec2);
   
   cchar  *mess = ZTX("Use EXIF photo date or \n file modification date?");

   F1_help_topic = "newest_images";

   int         ii, cc, err, sort, Nxrec, ftf;
   sxrec_t     sxrec;
   FILE        *fid;
   NFxrec_t    *NFxrec = 0;

   cc = maximages * sizeof(NFxrec_t);                                            //  allocate memory
   NFxrec = (NFxrec_t *) zmalloc(cc);

   Nxrec = 0;
   ftf = 1;
   
   if (menu && strmatch(menu,"EXIF")) sort = 1;
   else if (menu && strmatch(menu,"file")) sort = 2;
   else sort = zdialog_choose(Mwin,mess,"EXIF",ZTX("File"),null);
   
   while (true)
   {
      err = read_sxrec_seq(sxrec,ftf);                                           //  read image index recs.
      if (err) break;
      
      ii = Nxrec;

      if (sort == 1) {                                                           //  15.07
         if (strmatch(sxrec.pdate,"null")) continue;                             //  use EXIF photo date
         strncpy0(NFxrec[ii].date,sxrec.pdate,16);                               //  ignore images without photo date
      }
      else strncpy0(NFxrec[ii].date,sxrec.fdate,16);                             //  else use file mod date

      NFxrec[ii].file = sxrec.file;                                              //  image file

      zfree(sxrec.tags);                                                         //  free unused stuff
      zfree(sxrec.capt);
      zfree(sxrec.comms);
      zfree(sxrec.gtags);

      if (++Nxrec == maximages) {
         zmessageACK(Mwin,"too many image files");
         goto cleanup;
      }
   }

   if (! Nxrec) {
      zmessageACK(Mwin,"no data found");
      return;
   }

   if (Nxrec > 1)                                                                //  sort index recs. by file date
      HeapSort((char *) NFxrec, sizeof(NFxrec_t), Nxrec, NFxrec_comp);

   fid = fopen(searchresults_file,"w");                                          //  open output file
   if (! fid) {
      zmessageACK(Mwin,"file error: %s",strerror(errno));
      goto cleanup;
   }

   for (ii = 0; ii < 1000 && ii < Nxrec; ii++)                                   //  output newest 1000 image files
      fprintf(fid,"%s\n",NFxrec[ii].file);

   fclose(fid);

cleanup:

   for (ii = 0; ii < Nxrec; ii++)                                                //  free memory
      zfree(NFxrec[ii].file);
   zfree(NFxrec);

   free_resources();
   navi::gallerytype = NEWEST;                                                   //  newest files
   gallery(searchresults_file,"initF");                                          //  generate gallery of matching files
   m_viewmode(0,"G");
   gallery(0,"paint",0);
   return;
}


//  Compare 2 NFxrec records by file date
//  return <0 =0 >0  for  rec2 < = > rec1.

int NFxrec_comp(cchar *rec1, cchar *rec2)
{
   char *date1 = ((NFxrec_t *) rec1)->date;
   char *date2 = ((NFxrec_t *) rec2)->date;
   return strcmp(date2,date1);
}


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

//  open file menu function

void m_open(GtkWidget *, cchar *)
{
   F1_help_topic = "open_image_file";
   if (checkpend("busy block mods")) return;                                     //  15.11
   f_open(null);
   return;
}


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

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

void m_previous(GtkWidget *, cchar *menu) 
{
   FILE     *fid;
   char     *file, buff[XFCC];
   int      Nth = 0, err;
   float    gzoom;

   F1_help_topic = "open_previous_file";
   if (checkpend("busy block mods")) return;                                     //  15.11

   m_viewmode(0,"F");                                                            //  bugfix                             16.06
   if (! Cstate) return;
   gzoom = Cstate->fzoom;

   fid = fopen(recentfiles_file,"r");
   if (! fid) return;

   file = fgets_trim(buff,XFCC,fid,1);                                           //  skip over first most recent file

   while (true)
   {
      file = fgets_trim(buff,XFCC,fid,1);                                        //  find next most recent file
      if (! file) break;
      err = f_open(file,Nth,0,0,0);
      if (! err) break;
   }

   fclose(fid);

   Cstate->fzoom = gzoom;
   Fpaint2();
   return;
}


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

//  menu function: open a camera RAW file and edit with the Raw Therapee GUI
//  opens 'clicked_file' if present or 'rawfile' if not

void m_rawtherapee(GtkWidget *, cchar *menu)
{
   char     *pp;
   char     *tiffile;
   int      err;
   STATB    statb;

   cchar *command = "rawtherapee -o \"%s\" -t -Y \"%s\" ";

   F1_help_topic = "open_raw_file";

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

   if (checkpend("busy block mods")) return;                                     //  15.11

   if (rawfile) zfree(rawfile);

   if (clicked_file) {
      rawfile = clicked_file;
      clicked_file = 0;
   }

   if (! rawfile) {
      pp = curr_file;                                                            //  directory for file open dialog
      if (! pp) pp = curr_dirk;
      rawfile = zgetfile(ZTX("Open RAW file (Raw Therapee)"),MWIN,"file",pp);
      if (! rawfile) return;
   }

   if (image_file_type(rawfile) != RAW) {
      zmessageACK(Mwin,ZTX("RAW type not registered in User Settings"));
      zfree(rawfile);
      rawfile = 0;
      return;
   }

   zmainloop();                                                                  //  16.02
   tiffile = raw_to_tiff(rawfile);
   shell_quiet(command,tiffile,rawfile);                                         //  start command, convert to tiff

   zfree(rawfile);
   rawfile = 0;

   err = stat(tiffile,&statb);
   if (err) {
      zfree(tiffile);
      printz("Raw Therapee produced no tif-16 file\n");
      return;
   }

   err = f_open(tiffile,0,0,1);                                                  //  open tiff file
   if (err) {
      zfree(tiffile);
      return;
   }

   update_image_index(tiffile);                                                  //  update index rec.                  15.03
   zfree(tiffile);

   return;
}


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

//  Open a file and initialize Fpxb pixbuf.
//
//  Nth:    if Nth matches file position in current gallery, curr_file_posn
//          is set to Nth, otherwise it is searched and set correctly.
//          (a file can be present multiple times in an album gallery).
//  Fkeep:  edit undo/redo stack is not purged, and current edits are kept
//          after opening the new file (used by file_save()).
//  Fack:   failure will cause a popup ACK dialog.
//  zoom:   keep current zoom level and position, otherwise fit window.
//
//  Following are set: curr_file_type, curr_file_bpc, curr_file_size.
//  Returns: 0 = OK, +N = error.
//  errors: 1  reentry (bug)
//          2  curr. edit function cannot be restarted or canceled
//          3  file not found or user cancel
//          4  unsupported file type or PXB_load() failure

int f_open(cchar *filespec, int Nth, int Fkeep, int Fack, int zoom)
{
   PXB         *tempxb = 0;
   int         err, fposn, retcode = 0;
   FTYPE       ftype;
   static int  Freent = 0;
   char        *pp, *file;
   void        (*menufunc)(GtkWidget *, cchar *);
   STATB       statb;
   
   if (Freent++) {                                                               //  stop re-entry
      printz("f_open() re-entry \n");
      goto ret1;
   }

// if (clicked_file) zfree(clicked_file);                                        //  removed 16.06
// clicked_file = 0;

   if (CEF && ! CEF->Frestart) goto ret2;                                        //  edit function not restartable

   if (CEF) menufunc = CEF->menufunc;                                            //  active edit, save menu function
   else menufunc = 0;                                                            //    for possible edit restart

   if (CEF && CEF->zd) zdialog_send_event(CEF->zd,"cancel");                     //  cancel if possible
   if (CEF) goto ret2;                                                           //  cannot

   sa_unselect();                                                                //  unselect area if any

   file = 0;                                                                     //  15.11

   if (filespec)
      file = zstrdup(filespec);                                                  //  use passed filespec
   else {
      pp = curr_file;                                                            //  use file open dialog
      if (! pp) pp = curr_dirk;
      file = zgetfile(ZTX("Open Image File"),MWIN,"file",pp);
   }

   if (! file) goto ret3;

   err = stat(file,&statb);                                                      //  check file exists
   if (err) {
      if (Fack) zmessage_post(Mwin,4,"%s \n %s",file,strerror(errno));
      zfree(file);
      goto ret3;
   }

   ftype = image_file_type(file);                                                //  must be image or RAW file type
   if (ftype == THUMB) {
      if (Fack) zmessageACK(Mwin,"thumbnail file");
      goto ret4;
   }

   if (ftype != IMAGE && ftype != RAW) {                                         //  must be supported image file type
      if (Fack) zmessageACK(Mwin,ZTX("unknown file type"));                      //    or RAW file
      zfree(file);
      goto ret4;
   }

   Ffuncbusy = 1;                                                                //  may be large or RAW file, slow CPU
   tempxb = PXB_load(file,1);                                                    //  load image as PXB pixbuf
   Ffuncbusy = 0;

   if (! tempxb) {                                                               //  PXB_load() messages user
      zfree(file);
      goto ret4;
   }

   free_resources(Fkeep);                                                        //  free resources for old image file
   
   curr_file = file;                                                             //  new current file

   if (curr_dirk) zfree(curr_dirk);                                              //  set current directory
   curr_dirk = zstrdup(curr_file);                                               //    for new current file
   pp = strrchr(curr_dirk,'/');
   *pp = 0;

   Fpxb = tempxb;                                                                //  pixmap for current image

   strcpy(curr_file_type,f_load_type);                                           //  set curr_file_xxx from f_load_xxx
   curr_file_bpc = f_load_bpc;
   curr_file_size = f_load_size;

   load_filemeta(curr_file);                                                     //  load metadata used by fotoxx       15.03

   fposn = gallery_position(file,Nth);                                           //  file position in gallery list
   if (fposn < 0) {                                                              //  not there
      gallery(curr_file,"init");                                                 //  generate new gallery list
      fposn = gallery_position(curr_file,0);                                     //  position and count in gallery list
   }
   curr_file_posn = fposn;                                                       //  keep track of file position

   URS_reopen_pos = 0;                                                           //  not f_open_saved()

   if (! zoom) {                                                                 //  discard zoom state
      Fzoom = 0;                                                                 //  zoom level = fit window
      zoomx = zoomy = 0;                                                         //  no zoom center
   }

   set_mwin_title();                                                             //  set win title from curr_file info

   add_recent_file(curr_file);                                                   //  most recent file opened

   if (FGWM == 'F') {                                                            //  16.06
      if (zdrename) m_rename(0,0);                                               //  update active rename dialog
      if (zdcopymove) m_copy_move(0,0);                                          //    "  copy/move dialog              16.06
      if (zddeltrash) m_delete_trash(0,0);                                       //    "  delete/trash dialog           16.06
      if (zdexifview) meta_view(0);                                              //    "  EXIF/IPTC data view window
      if (zdeditmeta) m_edit_metadata(0,0);                                      //    "  edit metadata dialog
      if (zdexifedit) m_meta_edit_any(0,0);                                      //    "  edit any metadata dialog
      if (zdupright) m_upright(0,0);                                             //    "  upright dialog                16.06
      if (Fcaptions) m_captions(0,0);                                            //  show caption/comments at top
   }

   if (FGWM == 'G') gallery(curr_file,"paint");                                  //  if gallery view, repaint           16.06

   if (menufunc) menufunc(0,0);                                                  //  restart edit function

   goto ret0; 

   ret4: retcode++;
   ret3: retcode++;
   ret2: retcode++;
   ret1: retcode++;
   ret0:
   Freent = 0;
   return retcode;
}


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

//  Open a file that was just saved. Used by file_save().
//  The edit undo/redo stack is not purged and current edits are kept.
//  Following are set:
//    curr_file  *_dirk  *_file_posn  *_file_type  *_file_bpc  *_file_size
//  Returns: 0 = OK, +N = error.

int f_open_saved()
{
   int      Nth = -1, fposn;
   
   if (clicked_file) zfree(clicked_file);
   clicked_file = 0;

   if (E0pxm) {                                                                  //  edits were made
      PXB_free(Fpxb);                                                            //  new window image
      Fpxb = PXM_PXB_copy(E0pxm);
   }
   
   if (! f_save_file) return 1;                                                  //  bugfix                             v.15.10

   if (curr_file) zfree(curr_file);
   curr_file = zstrdup(f_save_file);                                             //  new current file

   fposn = gallery_position(curr_file,Nth);                                      //  file position in gallery list
   if (fposn < 0) {                                                              //  not there
      gallery(curr_file,"init");                                                 //  generate new gallery list
      fposn = gallery_position(curr_file,0);                                     //  position and count in gallery list
   }
   curr_file_posn = fposn;                                                       //  keep track of file position

   strcpy(curr_file_type,f_save_type);                                           //  set curr_file_xxx from f_save_xxx
   curr_file_bpc = f_save_bpc;
   curr_file_size = f_save_size;

   URS_reopen_pos = URS_pos;                                                     //  track undo/redo stack position

   zoomx = zoomy = 0;                                                            //  no zoom center
   set_mwin_title();                                                             //  set win title from curr_file info
   gtk_window_present(MWIN);

   if (zdexifview) meta_view(0);                                                 //  update EXIF/IPTC view window
   return 0;
}


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

//  Function to preload image files hopefully ahead of need.
//  Usage: f_preload(next)
//    next = -1 / +1  to read previous / next image file in curr. gallery
//    preload_file will be non-zero if and when preload_pxb is available.

int      preload_lock = 0;

void f_preload(int next)                                                         //  revised 15.01
{
   void * preload_thread(void *);

   char     *file = 0;

   if (! curr_file) return;

   file = gallery_getnext(next,Flastversion);
   if (! file) return;

   if (strmatch(file,curr_file)) {
      zfree(file);
      return;
   }

   while (preload_lock) zsleep(0.01);                                            //  wait if preload thread busy        15.02
   preload_lock = 1;

   start_detached_thread(preload_thread,file);
   return;
}

void * preload_thread(void *arg)
{
   int      fd;
   char     *file = (char *) arg;

   fd = open(file,O_RDONLY);
   if (fd >= 0) {
      posix_fadvise(fd,0,0,POSIX_FADV_WILLNEED);                                 //  preload file in kernel cache
      close(fd);
   }

   zfree(file);
   preload_lock = 0;                                                             //  15.02
   pthread_exit(0);
}


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

//  create a new blank image with desired background color

void m_create(GtkWidget *, cchar *pname)
{
   int  create_dialog_event(zdialog *zd, cchar *event);

   char        color[20], fname[100], fext[8], *filespec;
   int         zstat, err, cc, ww, hh, RGB[3];
   zdialog     *zd;
   cchar       *pp;

   F1_help_topic = "new_blank_image";

   if (checkpend("all")) return;
   Fblock = 1;

//    file name [___________________________] [.jpg|v]
//    width [____]  height [____] (pixels)
//    color [____]

   zd = zdialog_new(ZTX("Create Blank Image"),Mwin,Bdone,Bcancel,null);
   zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labf","hbf",ZTX("file name"),"space=3");
   zdialog_add_widget(zd,"entry","file","hbf",0,"space=3|expand");
   zdialog_add_widget(zd,"combo","ext","hbf",".jpg");
   zdialog_add_widget(zd,"hbox","hbz","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labw","hbz",Bwidth,"space=5");
   zdialog_add_widget(zd,"spin","width","hbz","100|20000|1|1600");
   zdialog_add_widget(zd,"label","space","hbz",0,"space=5");
   zdialog_add_widget(zd,"label","labh","hbz",Bheight,"space=5");
   zdialog_add_widget(zd,"spin","height","hbz","100|16000|1|1000");
   zdialog_add_widget(zd,"label","labp","hbz","(pixels)","space=3");
   zdialog_add_widget(zd,"hbox","hbc","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labc","hbc",Bcolor,"space=5");
   zdialog_add_widget(zd,"colorbutt","color","hbc","200|200|200");

   zdialog_cb_app(zd,"ext",".jpg");
   zdialog_cb_app(zd,"ext",".png");
   zdialog_cb_app(zd,"ext",".tif");

   zdialog_restore_inputs(zd);                                                   //  preload prior user inputs
   zdialog_stuff(zd,"file","");                                                  //  force input of new name

   zdialog_run(zd,create_dialog_event);                                          //  run dialog
   zstat = zdialog_wait(zd);                                                     //  wait for completion

   if (zstat != 1) {                                                             //  cancel
      zdialog_free(zd);
      Fblock = 0;
      return;
   }

   zdialog_fetch(zd,"file",fname,92);                                            //  get new file name
   strTrim2(fname);
   if (*fname <= ' ') {
      zmessageACK(Mwin,ZTX("supply a file name"));                               //  15.03
      zdialog_free(zd);
      Fblock = 0;
      m_create(0,0);                                                             //  retry
      return;
   }

   zdialog_fetch(zd,"ext",fext,8);                                               //  add extension
   strcat(fname,fext);

   cc = strlen(fname);
   filespec = zstrdup(curr_dirk,cc+4);                                           //  make full filespec
   strcat(filespec,"/");
   strcat(filespec,fname);

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

   RGB[0] = RGB[1] = RGB[2] = 255;
   zdialog_fetch(zd,"color",color,19);                                           //  get image color
   pp = strField(color,"|",1);
   if (pp) RGB[0] = atoi(pp);
   pp = strField(color,"|",2);
   if (pp) RGB[1] = atoi(pp);
   pp = strField(color,"|",3);
   if (pp) RGB[2] = atoi(pp);

   zdialog_free(zd);

   err = create_blank_file(filespec,ww,hh,RGB);
   if (! err) f_open(filespec);                                                  //  make it the current file
   Fblock = 0;
   return;
}


//  dialog event and completion function

int create_dialog_event(zdialog *zd, cchar *event)
{
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   return 1;
}


//  function to create a new blank image file
//  file extension must be one of: .jpg .tif .png
//  RGB args are in the range 0 - 255
//  if file exists it is overwritten
//  returns 0 if OK, 1 if file exists, +N if error

int create_blank_file(cchar *file, int ww, int hh, int RGB[3])
{
   cchar          *pp;
   cchar          *fext;
   int            err, cstat, cc;
   PXB            *tempxb;
   GError         *gerror = 0;
   uint8          *pixel;
   STATB          statb;

   err = stat(file,&statb);
   if (! err) {                                                                  //  file already exists
      zmessageACK(Mwin,Bfileexists);
      return 1;
   }

   pp = strrchr(file,'.');                                                       //  get file .ext
   if (! pp || strlen(pp) > 4) return 1;

   if (strmatch(pp,".jpg")) fext = "jpeg";                                       //  validate and set pixbuf arg.
   else if (strmatch(pp,".png")) fext = "png";
   else if (strmatch(pp,".tif")) fext = "tif";
   else return 2;

   tempxb = PXB_make(ww,hh,0);                                                   //  create pixbuf image
   if (! tempxb) {
      cc = ww * hh * 3;
      zpopup_message(0,"malloc(%u KB) failure, OUT OF MEMORY",cc);               //  16.05
      exit(12);
   }

   for (int py = 0; py < hh; py++)                                               //  fill with color
   for (int px = 0; px < ww; px++)
   {
      pixel = PXBpix(tempxb,px,py);
      pixel[0] = RGB[0];
      pixel[1] = RGB[1];
      pixel[2] = RGB[2];
   }

   cstat = gdk_pixbuf_save(tempxb->pixbuf,file,fext,&gerror,null);
   if (! cstat) {
      zmessageACK(Mwin,"error: %s",gerror->message);
      PXB_free(tempxb);
      return 3;
   }

   PXB_free(tempxb);
   return 0;
}


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

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

char     rename_old[200] = "";
char     rename_new[200] = "";
char     rename_prev[200] = "";
char     *rename_file = 0;

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

   char     *pdir, *pfile, *pext;

   F1_help_topic = "rename_file";

   if (rename_file) zfree(rename_file);
   rename_file = 0;

   if (clicked_file) {                                                           //  use clicked file if present
      rename_file = clicked_file;
      clicked_file = 0;
   }
   else if (curr_file)                                                           //  else current file
      rename_file = zstrdup(curr_file);
   else return;

   if (checkpend("all")) return;

/***
       ______________________________________
      |         Rename Image File            |
      |                                      |
      | Old Name  [_______________________]  |
      | New Name  [_______________________]  |
      |           [previous name] [Add 1]    |
      |                                      |
      |                   [apply] [cancel]   |
      |______________________________________|

***/

   if (! zdrename)                                                               //  restart dialog
   {
      zdrename = zdialog_new(ZTX("Rename Image File"),Mwin,Bapply,Bcancel,null);
      zdialog *zd = zdrename;

      zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=3");
      zdialog_add_widget(zd,"vbox","vb1","hb1",0,"homog|space=5");
      zdialog_add_widget(zd,"vbox","vb2","hb1",0,"homog|expand");

      zdialog_add_widget(zd,"label","Lold","vb1",ZTX("Old Name"));
      zdialog_add_widget(zd,"label","Lnew","vb1",ZTX("New Name"));
      zdialog_add_widget(zd,"label","space","vb1");

      zdialog_add_widget(zd,"hbox","hb2","vb2");
      zdialog_add_widget(zd,"label","oldname","hb2");
      zdialog_add_widget(zd,"label","space","hb2",0,"expand");

      zdialog_add_widget(zd,"entry","newname","vb2",0,"size=30");
      zdialog_add_widget(zd,"hbox","hb3","vb2",0,"space=3");
      zdialog_add_widget(zd,"button","Bprev","hb3",ZTX("previous name"));
      zdialog_add_widget(zd,"button","Badd1","hb3",ZTX("Add 1"),"space=8");

      zdialog_run(zd,rename_dialog_event);                                       //  run dialog
   }

   zdialog *zd = zdrename;
   parsefile(rename_file,&pdir,&pfile,&pext);
   strncpy0(rename_old,pfile,199);
   strncpy0(rename_new,pfile,199);
   zdialog_stuff(zd,"oldname",rename_old);                                       //  current file name
   zdialog_stuff(zd,"newname",rename_new);                                       //  entered file name (same)

   gtk_window_present(MWIN);                                                     //  keep focus on main window          15.03
   return;
}


//  dialog event and completion callback function

int rename_dialog_event(zdialog *zd, cchar *event)
{
   char     *pp, *pdir, *pfile, *pext;
   char     *newfile, *nextfile, namever[200];
   int      nseq, digits, ccp, ccn, ccx, err;
   STATB    statb;
   
   if (strmatch(event,"enter")) zd->zstat = 1;                                   //  [apply]
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07

   if (strmatch(event,"Bprev"))                                                  //  previous name >> new name
   {
      if (! *rename_prev) return 1;
      parsefile(rename_prev,&pdir,&pfile,&pext);                                 //  get previous rename name
      pp = strrchr(rename_prev,'.');
      if (pp && strlen(pp) == 4 && pp[1] == 'v'                                  //  look for file version .vNN         16.01
         && pp[2] >= '0' && pp[2] <= '9'                                         //    and remove if found
         && pp[3] >= '0' && pp[3] <= '9') *pp = 0;
      zdialog_stuff(zd,"newname",rename_prev);                                   //  stuff prev rename name into dialog

      parsefile(rename_file,&pdir,&pfile,&pext);                                 //  curr. file to be renamed           16.01
      pp = strrchr(pfile,'.');                                                   //  look for file version .vNN
      if (pp && strlen(pp) == 4 && pp[1] == 'v'
         && pp[2] >= '0' && pp[2] <= '9'
         && pp[3] >= '0' && pp[3] <= '9') 
      {
         *namever = 0;                                                           //  prev rename name + curr file version
         strncatv(namever,200,rename_prev,pp,null);
         zdialog_stuff(zd,"newname",namever);                                    //  stuff into dialog
      }
   }     

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

   if (zd->zstat == 0) return 1;                                                 //  not finished

   if (zd->zstat != 1) {                                                         //  canceled
      if (rename_file) zfree(rename_file);
      rename_file = 0;
      zdialog_free(zd);                                                          //  kill dialog
      zdrename = 0;
      return 1;
   }

   zd->zstat = 0;                                                                //  apply - keep dialog active
   
   if (! rename_file) return 1;

   if (checkpend("all")) return 1;                                               //  15.11

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

   zdialog_fetch(zd,"newname",rename_new,194);                                   //  new file name from user

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

   newfile = (char *) zmalloc(ccp + ccn + ccx + 1);                              //  put it all together
   strncpy(newfile,rename_file,ccp);                                             //   /directories.../newfilename.ext
   strcpy(newfile+ccp,rename_new);
   if (ccx) strcpy(newfile+ccp+ccn,pext);

   err = stat(newfile,&statb);                                                   //  check if new name exists
   if (! err) {
      zmessageACK(Mwin,Bfileexists);
      zfree(newfile);
      return 1;
   }

   if (FGWM == 'F')
      nextfile = gallery(0,"find",curr_file_posn+1);                             //  save next file, before rename      15.03
   else nextfile = 0;

   err = rename(rename_file,newfile);                                            //  rename the file
   if (err) {
      zmessageACK(Mwin,"file error: %s",strerror(errno));
      zdrename = 0;
      zfree(newfile);
      zfree(rename_file);
      rename_file = 0;
      zdialog_free(zd);
      return 1;
   }
   
   load_filemeta(newfile);                                                       //  add new file to image index
   update_image_index(newfile);
   delete_image_index(rename_file);                                              //  delete old file in image index

   image_thumbnail_delete(rename_file);                                          //  remove thumbnails, disk and cache  15.12
   image_thumbnail_delete(newfile);                                              //  (will auto add when referenced)

   strncpy0(rename_prev,rename_new,199);                                         //  save new name to previous name

   add_recent_file(newfile);                                                     //  first in recent files list
   
   if (curr_file && strmatch(rename_file,curr_file))                             //  current file exists no more        15.11.1
      free_resources();

   zfree(rename_file);                                                           //  15.11.1
   rename_file = 0;

   if (navi::gallerytype == GDIR) {                                              //  refresh gallery list               15.11
      gallery(0,"init");
      gallery(0,"paint",-1);
   }
   
   if (FGWM == 'F' && nextfile) {
      f_open(nextfile);                                                          //  will call m_rename()
      zfree(nextfile);
   }

/**                                                                              //  removed for use in gallery         16.06
   else {
      zdialog_free(zd);
      zdrename = 0;
   }
**/
   
   zfree(newfile);                                                               //  15.11.1
   return 1;
}


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

  right-click popup menu functions

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


//  copy or move an image file to a new location

char     *copymove_file = 0;


//  menu function

void m_copy_move(GtkWidget *, cchar *)                                           //  combine copy and move              16.06
{
   int copymove_dialog_event(zdialog *zd, cchar *event);

   cchar    *title = ZTX("Copy or Move Image File");

   F1_help_topic = "copy_move";

   if (copymove_file) zfree(copymove_file);
   copymove_file = 0;

   if (clicked_file) {                                                           //  use clicked file if present
      copymove_file = clicked_file;
      clicked_file = 0;
   }
   else if (curr_file)                                                           //  else use current file
      copymove_file = zstrdup(curr_file);
   else return;

   if (checkpend("all")) return;

/***
          _______________________________________________
         |          Copy or Move Image File              |
         |                                               |
         | Image File: [_____________________]           |
         | New Location: [___________________] [browse]  |
         |                                               |
         | [x] copy (duplicate file)                     |
         | [x] move (remove original)                    |
         |                                               |
         |                             [apply] [cancel]  |
         |_______________________________________________|
***/

   if (! zdcopymove)
   {
      zdcopymove = zdialog_new(title,Mwin,Bapply,Bcancel,null);
      zdialog *zd = zdcopymove;

      zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=3");
      zdialog_add_widget(zd,"label","labf","hb1",ZTX("Image File:"),"space=5");
      zdialog_add_widget(zd,"label","file","hb1");
      zdialog_add_widget(zd,"label","space","hb1",0,"expand");

      zdialog_add_widget(zd,"hbox","hb2","dialog");
      zdialog_add_widget(zd,"label","labl","hb2",ZTX("New Location:"),"space=5");
      zdialog_add_widget(zd,"entry","newloc","hb2",0,"expand");
      zdialog_add_widget(zd,"button","browse","hb2",Bbrowse,"space=5");
      
      zdialog_add_widget(zd,"hbox","hb3","dialog",0,"space=3");
      zdialog_add_widget(zd,"check","copy","hb3",ZTX("copy (duplicate file)"),"space=5");
      zdialog_add_widget(zd,"hbox","hb4","dialog");
      zdialog_add_widget(zd,"check","move","hb4",ZTX("move (remove original)"),"space=5");
      
      zdialog_stuff(zd,"copy",0);                                                //  both options off
      zdialog_stuff(zd,"move",0);

      zdialog_resize(zd,500,0);
      zdialog_run(zd,copymove_dialog_event);                                     //  run dialog
   }

   zdialog *zd = zdcopymove;

   char *pp = strrchr(copymove_file,'/');
   if (pp) pp++;
   else pp = copymove_file;
   zdialog_stuff(zd,"file",pp);

   if (copymove_loc) zdialog_stuff(zd,"newloc",copymove_loc);

   gtk_window_present(MWIN);                                                     //  keep focus on main window          15.03
   return;
}


//  dialog event and completion callback function

int copymove_dialog_event(zdialog *zd, cchar *event)
{
   int      Fcopy, Fmove;
   char     *newloc, *pfile, *newfile = 0, *nextfile = 0;
   int      cc, err, Nth;
   STATB    statb;
   
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07

   if (strmatch(event,"browse")) {                                               //  browse for new location
      if (! copymove_loc) {         
         copymove_loc = (char *) zmalloc(XFCC);                                  //  15.08
         zdialog_fetch(zd,"newloc",copymove_loc,XFCC);
      }
      newloc = zgetfile(ZTX("Select directory"),MWIN,"folder",copymove_loc);
      if (! newloc) return 1;
      zdialog_stuff(zd,"newloc",newloc);
      if (copymove_loc) zfree(copymove_loc);
      copymove_loc = newloc;
      return 1;
   }
   
   if (strmatch(event,"copy")) {                                                 //  get copy or move option            16.06
      zdialog_fetch(zd,"copy",Fcopy);
      Fmove = 1 - Fcopy;
      zdialog_stuff(zd,"move",Fmove);
   }

   if (strmatch(event,"move")) {
      zdialog_fetch(zd,"move",Fmove);
      Fcopy = 1 - Fmove;
      zdialog_stuff(zd,"copy",Fcopy);
   }
   
   if (zd->zstat == 0) return 1;                                                 //  wait for dialog finished

   if (zd->zstat != 1) {                                                         //  cancel or [x]
      zdialog_free(zd);                                                          //  kill dialog
      zdcopymove = 0;
      return 1;
   }

   zd->zstat = 0;                                                                //  apply - keep dialog active

   if (! copymove_file) return 1;

   if (checkpend("all")) return 1;                                               //  15.11

   if (copymove_loc) zfree(copymove_loc);                                        //  get new location from dialog
   copymove_loc = (char *) zmalloc(XFCC);
   zdialog_fetch(zd,"newloc",copymove_loc,XFCC);

   stat(copymove_loc,&statb);                                                    //  check for valid directory
   if (! S_ISDIR(statb.st_mode)) {
      zmessageACK(Mwin,ZTX("new location is not a directory"));
      return 1;
   }

   pfile = strrchr(copymove_file,'/');                                           //  isolate source file filename.ext
   if (pfile) pfile++;
   else pfile = copymove_file;
   cc = strlen(copymove_loc) + strlen(pfile) + 2;                                //  new file = /new/location/filename.ext
   newfile = (char *) zmalloc(cc);
   strcpy(newfile,copymove_loc);
   strcat(newfile,"/");
   strcat(newfile,pfile);

   err = stat(newfile,&statb);                                                   //  check if new file exists
   if (! err) {
      zmessageACK(Mwin,Bfileexists);
      zfree(newfile);
      return 1;
   }

   err = shell_ack("cp -p \"%s\" \"%s\"",copymove_file,newfile);                 //  copy source file to new file
   if (err) {
      zfree(newfile);
      zdialog_free(zd);
      zdcopymove = 0;
      return 1;
   }

   load_filemeta(newfile);                                                       //  update image index for new file
   update_image_index(newfile);                                                  //  (memory metadata now invalid)

   if (FGWM == 'F') {                                                            //  if F-view, get next file
      Nth = curr_file_posn + 1;
      nextfile = gallery(0,"find",Nth);
   }

   zdialog_fetch(zd,"move",Fmove);
   if (Fmove) {                                                                  //  move - delete source file
      err = remove(copymove_file);
      if (err) {
         zmessageACK(Mwin,ZTX("delete failed: \n %s"),strerror(errno));
         zfree(newfile);
         zdialog_free(zd);
         zdcopymove = 0;
         return 1;
      }

      delete_image_index(copymove_file);                                         //  delete in image index
      image_thumbnail_delete(copymove_file);                                     //  delete thumbnail file and cache    15.12
      if (curr_file && strmatch(curr_file,copymove_file))                        //  current file gone                  16.06
         free_resources();
   }

   if (FGWM == 'F') {                                                            //  F-view                             15.03
      if (nextfile) f_open(nextfile);                                            //  open next file
      else {                                                                     //  end of gallery
         if (Fmove) free_resources();                                            //  no curr. file
         zdialog_free(zd);                                                       //  kill dialog
         zdcopymove = 0;
      }
   }
   
   if (FGWM == 'G' && Fmove)                                                     //  G-view and file is gone            16.06
      zdialog_stuff(zd,"file","");

   if (navi::gallerytype == GDIR) {                                              //  refresh gallery                    15.03
      gallery(0,"init");
      gallery(0,"paint",-1);
   }

   if (nextfile) zfree(nextfile);
   if (newfile) zfree(newfile);

   return 1;
}


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

//  function to copy an image file to the clipboard
//  used in file view and thumbnail right-click popup menu
//  uses and frees clicked_file if present, else uses current file

void m_copyto_clip(GtkWidget *, cchar *)
{
   int file_copytoclipboard(char *file);
   
   if (clicked_file) {
      file_copytoclipboard(clicked_file);
      zfree(clicked_file);
      clicked_file = 0;
   }
   else if (curr_file)
      file_copytoclipboard(curr_file);

   return;
}


//  copy an image file to the clipboard (as pixbuf)
//  any prior clipboard image is replaced
//  supports copy/paste to other apps (not used in fotoxx)
//  returns 1 if OK, else 0

int file_copytoclipboard(char *file)
{
   GtkClipboard   *clipboard;
   PIXBUF         *pixbuf;
   GError         *gerror = 0;

   clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
   if (! clipboard) return 0;
   gtk_clipboard_clear(clipboard);

   pixbuf = gdk_pixbuf_new_from_file(file,&gerror);
   if (! pixbuf) return 0;

   gtk_clipboard_set_image(clipboard,pixbuf);
   g_object_unref(pixbuf);
   
   return 1;
}


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

//  Delete or Trash an image file.
//  Use the Linux standard trash function.
//  If not available, revert to Desktop folder.

char  *delete_trash_file = 0;

void m_delete_trash(GtkWidget *, cchar *)                                        //  combined delete/trash function     16.06
{
   int delete_trash_dialog_event(zdialog *zd, cchar *event);

   cchar  *title = ZTX("Delete or Trash Image File");

   F1_help_topic = "delete_trash";

   if (delete_trash_file) zfree(delete_trash_file);
   delete_trash_file = 0;
   
   if (FGWM == 'F' && curr_file)                                                 //  use current file if file view
      delete_trash_file = zstrdup(curr_file);

   else if (FGWM == 'G' && clicked_file) {                                       //  use clicked file if gallery view
      delete_trash_file = clicked_file;
      clicked_file = 0;
   }

   if (checkpend("all")) return;

/***
          ______________________________________
         |      Delete or Trash Image File      |
         |                                      |
         | Image File: [______________________] |
         |                                      |
         |            [delete] [trash] [cancel] |
         |______________________________________|

***/

   if (! zddeltrash)                                                             //  start dialog if not already
   {
      zddeltrash = zdialog_new(title,Mwin,Bdelete,Btrash,Bcancel,null);
      zdialog *zd = zddeltrash;

      zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=3");
      zdialog_add_widget(zd,"label","labf","hb1",ZTX("Image File:"),"space=3");
      zdialog_add_widget(zd,"label","file","hb1",0,"space=3");

      zdialog_resize(zd,300,0);
      zdialog_run(zd,delete_trash_dialog_event);                                 //  run dialog
   }
   
   if (delete_trash_file) 
   {
      char *pp = strrchr(delete_trash_file,'/');
      if (pp) pp++;
      else pp = delete_trash_file;
      zdialog_stuff(zddeltrash,"file",pp);
   }
   else zdialog_stuff(zddeltrash,"file","");

   gtk_window_present(MWIN);                                                     //  keep focus on main window
   return;
}


//  dialog event and completion callback function

int delete_trash_dialog_event(zdialog *zd, cchar *event)
{
   int         err = 0, yn = 0, Nth, gstat, ftype;
   char        *file;
   GError      *gerror = 0;
   GFile       *gfile = 0;
   STATB       statb;

   cchar    *trashmess = ZTX("GTK g_file_trash() function failed");

   if (strmatch(event,"escape")) goto KILL;                                      //  escape = cancel

   if (zd->zstat == 0) return 1;                                                 //  wait for dialog finished
   if (zd->zstat == 1) goto PREP;                                                //  [delete] button
   if (zd->zstat == 2) goto PREP;                                                //  [trash] button
   goto KILL;                                                                    //  [cancel] or [x]

PREP:

   if (! delete_trash_file) goto KILL;                                           //  no file to delete or trash

   err = stat(delete_trash_file,&statb);                                         //  check file exists
   if (err) {
      zmessageACK(Mwin,strerror(errno));
      goto KILL;
   }

   ftype = image_file_type(delete_trash_file);
   if (ftype != IMAGE && ftype != RAW) {                                         //  must be image or RAW file
      zmessageACK(Mwin,ZTX("not a known image file"));
      goto KILL;
   }

   if (! (statb.st_mode & S_IWUSR)) {                                            //  check permission
      if (zd->zstat == 1)
         yn = zmessageYN(Mwin,ZTX("Delete read-only file?"));
      if (zd->zstat == 2)
         yn = zmessageYN(Mwin,ZTX("Trash read-only file?"));
      if (! yn) goto NEXT;                                                       //  keep file
      statb.st_mode |= S_IWUSR;                                                  //  make writable if needed
      err = chmod(delete_trash_file,statb.st_mode);
      if (err) {
         zmessageACK(Mwin,strerror(errno));
         goto KILL;
      }
   }
   
   if (checkpend("all")) return 1;                                               //  check for conflicts

   if (zd->zstat == 1) goto DELETE;                                              //  [delete] button
   if (zd->zstat == 2) goto TRASH;                                               //  [trash] button
   
DELETE:

   err = remove(delete_trash_file);                                              //  delete file
   if (! err) goto BOTH;
   zmessageACK(Mwin,ZTX("delete failed: \n %s"),strerror(errno));
   goto KILL;

TRASH:

   gfile = g_file_new_for_path(delete_trash_file);
   gstat = g_file_trash(gfile,0,&gerror);                                        //  move file to trash
   g_object_unref(gfile);
   if (gstat) goto BOTH;
   zmessageACK(Mwin,trashmess);
   if (gerror) printz("%s\n",gerror->message);
   goto KILL;

BOTH:

   delete_image_index(delete_trash_file);                                        //  delete file in image index
   image_thumbnail_delete(delete_trash_file);                                    //  delete thumbnail file and cache

   Nth = gallery_position(delete_trash_file,0);                                  //  find in gallery list
   if (Nth >= 0) gallery(0,"delete",Nth);                                        //  delete from gallery list

   if (curr_file && strmatch(delete_trash_file,curr_file)) {                     //  current file was removed
      last_file_posn = curr_file_posn;                                           //  remember for prev/next use 
      free_resources();
   }

   if (FGWM == 'F') {                                                            //  F window
      file = gallery(0,"find",curr_file_posn);                                   //  new current file
      if (! file)
         poptext_window(ZTX("no more images"),MWIN,200,200,0,3);
      else {
         f_open(file);
         zfree(file);
      }

      zd->zstat = 0;                                                             //  keep dialog active
      gtk_window_present(MWIN);                                                  //  keep focus on main window
      return 1;
   }

NEXT:

   if (FGWM == 'G') {                                                            //  gallery view
      gallery(0,"paint");                                                        //  refresh for removed file
      zdialog_stuff(zd,"file","");                                               //  erase file in dialog
   }

   if (FGWM == 'F') {                                                            //  file view mode
      m_next(0,0);                                                               //  open next image
      gtk_window_present(MWIN);                                                  //  keep focus on main window 
   }

   zd->zstat = 0;                                                                //  keep dialog active
   return 1;

KILL:

   if (delete_trash_file) zfree(delete_trash_file);                              //  free memory
   delete_trash_file = 0;
   zdialog_free(zd);                                                             //  kill dialog
   zddeltrash = 0;
   return 1;
}


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

//  print image file

void m_print(GtkWidget *, cchar *)                                               //  use GTK print
{
   int print_addgrid(PXB *Ppxb);

   int      pstat;
   char     *printfile = 0;
   PXB      *Ppxb = 0;
   GError   *gerror = 0;

   F1_help_topic = "print_image";

   if (clicked_file) Ppxb = PXB_load(clicked_file);                              //  clicked thumbnail
   else if (E0pxm) Ppxb = PXM_PXB_copy(E0pxm);                                   //  current edited file
   else if (curr_file) Ppxb = PXB_load(curr_file);                               //  current file
   clicked_file = 0;
   if (! Ppxb) return;

   print_addgrid(Ppxb);                                                          //  add grid lines if wanted

   printfile = zstrdup(tempdir,20);                                              //  make temp print file:
   strcat(printfile,"/printfile.bmp");                                           //    /.../fotoxx-nnnnnn/printfile.bmp

   pstat = gdk_pixbuf_save(Ppxb->pixbuf,printfile,"bmp",&gerror,null);           //  bmp much faster than png
   if (! pstat) {
      zmessageACK(Mwin,"error: %s",gerror->message);
      PXB_free(Ppxb);
      zfree(printfile);
      return;
   }

   print_image_file(Mwin,printfile);

   PXB_free(Ppxb);
   zfree(printfile);
   return;
}


//  add grid lines to print image if wanted

int print_addgrid(PXB *Ppxb)
{
   uint8    *pix;
   int      px, py, ww, hh;
   int      startx, starty, stepx, stepy;
   int      G = currgrid;

   if (! gridsettings[G][GON]) return 0;                                         //  grid lines off

   ww = Ppxb->ww;
   hh = Ppxb->hh;

   stepx = gridsettings[G][GXS];                                                 //  space between grid lines
   stepy = gridsettings[G][GYS];

   stepx = stepx / Mscale;                                                       //  window scale to image scale
   stepy = stepy / Mscale;

   if (gridsettings[G][GXC])                                                     //  if line counts specified,
      stepx = ww / (1 + gridsettings[G][GXC]);                                   //    set spacing accordingly
   if (gridsettings[G][GYC])
      stepy = hh / ( 1 + gridsettings[G][GYC]);

   startx = stepx * gridsettings[G][GXF] / 100;                                  //  variable offsets
   if (startx < 0) startx += stepx;

   starty = stepy * gridsettings[G][GYF] / 100;
   if (starty < 0) starty += stepy;

   if (gridsettings[G][GX]) {                                                    //  x-grid enabled
      for (px = startx; px < ww-1; px += stepx)
      for (py = 0; py < hh; py++)
      {
         pix = PXBpix(Ppxb,px,py);
         pix[0] = pix[1] = pix[2] = 255;
         pix[3] = pix[4] = pix[5] = 0;
      }
   }

   if (gridsettings[G][GY]) {                                                    //  y-grid enabled
      for (py = starty; py < hh-1; py += stepy)
      for (px = 0; px < ww; px++)
      {
         pix = PXBpix(Ppxb,px,py);
         pix[0] = pix[1] = pix[2] = 255;
         pix = PXBpix(Ppxb,px,py+1);
         pix[0] = pix[1] = pix[2] = 0;
      }
   }

   return 1;
}


//  print calibrated image
//  menu function calling print_calibrated() in f.tools.cc

void m_print_calibrated(GtkWidget *, cchar *)
{
   F1_help_topic = "print_image";
   print_calibrated();
   return;
}


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

//  normal quit menu function

void m_quit(GtkWidget *, cchar *)
{
   int      busy;

   printz("quit \n");
   Fshutdown++;

   for (int ii = 0; ii < 20; ii++) {                                             //  wait up to 2 secs. if something running
      busy = checkpend("all quiet");
      if (! busy) break;
      zmainloop();
      zsleep(0.1);
   }

   if (busy) printz("busy function killed \n");

   quitxx();                                                                     //  does not return
   return;
}

//  used also for main window delete and destroy events
//  does not return

void quitxx()
{
   Fshutdown++;
   gtk_window_get_position(MWIN,&mwgeom[0],&mwgeom[1]);                          //  get last window position
   gtk_window_get_size(MWIN,&mwgeom[2],&mwgeom[3]);                              //    and size for next session
   save_params();                                                                //  save state for next session
   zdialog_inputs("save");                                                       //  save dialog inputs
   zdialog_positions("save");                                                    //  save dialog positions
   free_resources();                                                             //  delete temp files
   fflush(null);                                                                 //  flush stdout, stderr
   exif_server(0,0,0);                                                           //  kill exif_server process
   shell_quiet("rm -R -f %s",tempdir);                                           //  delete temp files
   gtk_main_quit();                                                              //  gone forever
   exit(1);                                                                      //  just in case
}


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

//  save (modified) image file to disk

void m_file_save(GtkWidget *, cchar *menu)
{
   int  file_save_dialog_event(zdialog *zd, cchar *event);

   cchar          *pp;
   zdialog        *zd;

   F1_help_topic = "file_save";
   if (! curr_file) {
      if (zdfilesave) zdialog_destroy(zdfilesave);
      zdfilesave = 0;
      return;
   }

   if (strmatch(curr_file_type,"other"))                                         //  if unsupported type, use jpg
      strcpy(curr_file_type,"jpg");

/***
          _______________________________________________
         |                                               |
         |        Save Image File                        |
         |                                               |
         |   filename.jpg                                |
         |                                               |
         |  [new version] save as new file version       |
         |  [new file ...] save as new file name or type |
         |  [replace file] replace file (OVERWRITE)      |
         |                                               |
         |                                    [cancel]   |
         |_______________________________________________|

***/

   if (! zdfilesave)
   {
      zd = zdialog_new(ZTX("Save Image File"),Mwin,Bcancel,null);
      zdfilesave = zd;

      zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=3");
      zdialog_add_widget(zd,"label","filename","hbf",0,"space=10");

      zdialog_add_widget(zd,"hbox","hb0","dialog");
      zdialog_add_widget(zd,"vbox","vb1","hb0",0,"space=3|homog");
      zdialog_add_widget(zd,"vbox","vb2","hb0",0,"space=3|homog");

      zdialog_add_widget(zd,"button","newvers","vb1",ZTX("new version"));
      zdialog_add_widget(zd,"button","newfile","vb1",ZTX("new file ..."));
      zdialog_add_widget(zd,"button","replace","vb1",ZTX("replace file"));

      zdialog_add_widget(zd,"hbox","hb1","vb2");
      zdialog_add_widget(zd,"hbox","hb2","vb2");
      zdialog_add_widget(zd,"hbox","hb3","vb2");

      zdialog_add_widget(zd,"label","labvers","hb1",ZTX("save as new file version"));
      zdialog_add_widget(zd,"label","labfile","hb2",ZTX("save as new file name or type"));
      zdialog_add_widget(zd,"icon","warning","hb3","warning.png","size=30");
      zdialog_add_widget(zd,"label","labrepl","hb3",ZTX("replace old file (OVERWRITE)"),"space=3");
   }

   zd = zdfilesave;

   pp = strrchr(curr_file,'/');
   if (pp) zdialog_stuff(zd,"filename",pp+1);

   zdialog_run(zd,file_save_dialog_event,"mouse");
   return;
}


//  dialog event and completion function

int file_save_dialog_event(zdialog *zd, cchar *event)
{
   char     *newfilename;
   char     newfiletype[8];                                                      //  16.03
   int      err;
   char     event2[12];

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

   if (zd->zstat) {                                                              //  cancel
      zdialog_free(zd);                                                          //  kill dialog
      zdfilesave = 0;
      return 1;
   }

   if (! strstr("newvers newfile replace",event)) return 1;                      //  ignore other events

   strncpy0(event2,event,12);                                                    //  preserve event

   zdialog_free(zd);                                                             //  kill dialog
   zdfilesave = 0;

   if (strstr("newvers replace",event2)) {
      if (strmatch(curr_file_type,"RAW")) {
         zmessageACK(Mwin,ZTX("cannot save as RAW type"));
         return 1;
      }
   }
   
   if (checkpend("busy block")) return 1;                                        //  15.11
   
   if (strmatch(event2,"newvers"))   
   {
      strncpy0(newfiletype,curr_file_type,6);
      newfilename = file_new_version(curr_file);                                 //  get next avail. file version name
      if (! newfilename) return 1;
      err = f_save(newfilename,newfiletype,curr_file_bpc);                       //  save file
      if (! err) f_open_saved();                                                 //  open saved file with edit hist
      zfree(newfilename);
   }

   if (strmatch(event2,"replace"))
   {
      err = f_save(curr_file,curr_file_type,curr_file_bpc);                      //  save file with curr. edits applied
      if (err) return 1;
      curr_file_size = f_save_size;
   }

   if (strmatch(event2,"newfile"))
      err = f_save_as();                                                         //  save-as file chooser dialog

   return 1;
}


//  menu entry for KB shortcut Save File Version

void m_file_save_version(GtkWidget *, cchar *menu)
{
   char     *newfilename;
   char     newfiletype[8];                                                      //  16.03
   int      err;

   strncpy0(newfiletype,curr_file_type,6);
   newfilename = file_new_version(curr_file);                                    //  get next avail. file version name
   if (! newfilename) return;
   err = f_save(newfilename,newfiletype,curr_file_bpc);                          //  save file
   if (! err) f_open_saved();                                                    //  open saved file with edit hist
   zfree(newfilename);
   return;
}


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

//  Get the next available version name "filename.vNN" for given file.
//  Returns null if bad file name or 99 versions already exist.
//  Returned file name is subject for zfree().

char * file_new_version(char *file)                                              //  overhauled  16.03
{
   char     *findcom = 0, *findbuff = 0;
   char     *pp1, *pp2, pext[8];
   int      vers, hivers, cc, nb;
   FILE     *fid;

   pp1 = strrchr(file,'/');                                                      //  find file .ext
   if (! pp1) return 0;
   pp1 = strrchr(pp1,'.');
   if (! pp1) return 0;  
   strncpy0(pext,pp1,6);                                                         //  save file .ext

   if (pp1[-4] == '.' && pp1[-3] == 'v' &&                                       //  look for .vNN.ext
       pp1[-2] >= '0' && pp1[-2] <= '9' &&
       pp1[-1] >= '0' && pp1[-1] <= '9') pp1 -= 4;                               //  pp1 -->  .ext or .vNN.ext

   cc = strlen(file);
   findcom = (char *) zmalloc(cc+60);                                            //  construct: find /.../filename.*
   strcpy(findcom,"find ");
   nb = repl_1str(file,findcom+5," ","\\ ");                                     //  escape embedded blanks with "\ "
   pp2 = findcom + 5 + nb + (pp1 - file);                                        //  pp2 -->  .ext or .vNN.ext
   strcpy(pp2,".*");                                                             //  pp2 -->  .* 

   fid = popen(findcom,"r");                                                     //  find matching files
   if (! fid) goto ret0;
   findbuff = (char *) zmalloc(XFCC);
   pp2 = findbuff + (pp1 - file);                                                //  pp2 -->  .*  buffer position

   hivers = 0;
   while ((fgets(findbuff,XFCC,fid))) {                                          //  find all matching files
      if (pp2[0] != '.' || pp2[1] != 'v' ||                                      //  ignore if not ".vNN.ext"
          pp2[2] < '0'  || pp2[2] > '9'  ||
          pp2[3] < '0'  || pp2[3] > '9'  ||
          pp2[4] != '.')  continue;
      vers = 10 * (pp2[2] - '0') + (pp2[3] - '0');                               //  convert ".vNN" to integer NN
      if (vers > hivers) hivers = vers;
   }
   pclose(fid);

   if (hivers == 99) {
      zmessageACK(Mwin,ZTX("too many file versions: 99"));
      goto ret0;
   }

   vers = hivers + 1;
   strcpy(findcom,file);                                                         //  construct: /.../filename.vNN.ext
   pp2 = findcom + (pp1 - file);
   strcpy(pp2,".v");
   pp2[2] = vers/10 + '0';
   pp2[3] = vers - 10 * (vers/10) + '0';
   strcpy(pp2+4,pext);
   zfree(findbuff);
   return findcom;

ret0:
   if (findcom) zfree(findcom);
   if (findbuff) zfree(findbuff);
   return 0;
}


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

//  save current image to specified disk file (same or new).
//  set f_save_file, f_save_type, f_save_bpc, f_save_size.
//  update image index file.
//  returns 0 if OK, else +N.
//  If Fack is true, failure will cause a popup ACK dialog.

int f_save(char *outfile, cchar *outype, int outbpc, int Fack)                   //  use tiff/png/pixbuf libraries
{
   cchar    *exifkey[2] = { exif_orientation_key, exif_editlog_key };
   cchar    *exifdata[2];
   char     *ppv[1], *tempfile, *pext, *outfile2, *thumbfile;
   char     edithist[exif_maxcc];                                                //  edit history trail
   int      nkeys, err, cc1, cc2, ii, ii0;
   int      Fmod, Fcopy, Ftransp, Fnewfile;
   int      px, py;
   void     (*menufunc)(GtkWidget *, cchar *);
   STATB    statbuf;
   
   cchar    *warnalpha = ZTX("Transparency map will be lost.\n"
                             "save to PNG file to retain.");

   if (! curr_file) return 1;

   if (strmatch(outype,"RAW")) {                                                 //  disallow saving as RAW type
      zmessageACK(Mwin,ZTX("cannot save as RAW type"));
      return 1;
   }

   Fmod = 0;
   menufunc = 0;

   if (CEF) {                                                                    //  edit function active
      if (CEF->Fmods) Fmod = 1;                                                  //  active edits pending
      menufunc = CEF->menufunc;                                                  //  save menu function for restart
      if (CEF->zd) zdialog_send_event(CEF->zd,"done");                           //  tell it to finish
      if (CEF) return 1;                                                         //  failed (HDR etc.)
   }

   if (URS_pos > 0 && URS_saved[URS_pos] == 0) Fmod = 1;                         //  completed edits not saved
   
   if (strmatch(outfile,curr_file)) Fnewfile = 0;                                //  replace current file               16.04
   else Fnewfile = 1;                                                            //  new file (name/.ext/location)

   outfile2 = zstrdup(outfile,6);                                                //  file to be output                  16.04

   if (Fnewfile) {                                                               //  if new file, force .ext
      pext = strrchr(outfile2,'/');                                              //    to one of: .jpg .png .tif
      if (pext) pext = strrchr(pext,'.');
      if (! pext) pext = outfile2 + strlen(outfile2);
      pext[0] = '.';
      strcpy(pext+1,outype);
   }

   if (! Fnewfile && ! Fmod) {                                                   //  no new file, no edit mods bugfix   16.04.1
      Fcopy = 1;                                                                 //  direct copy intput file to output ?
      if (E0pxm) Fcopy = 0;                                                      //  no, non-edit change (upright)      15.03.1
      if (curr_file_bpc != outbpc) Fcopy = 0;                                    //  no, BPC change
      if (jpeg_1x_quality < jpeg_def_quality) Fcopy = 0;                         //  no, higher compression wanted      15.03
      if (Fcopy) {
         if (! Fnewfile) goto updateindex;                                       //  save to same file, skip copy
         err = shell_quiet("cp \"%s\" \"%s\"",curr_file,outfile2);               //  copy unchanged file to output
         if (err) return 1;
         goto updateindex;
      }
   }

   Ffuncbusy = 1;                                                                //  may be large file, slow CPU

   if (! E0pxm) {
      paintlock(1);                                                              //  block window updates               16.02
      E0pxm = PXM_load(curr_file,1);                                             //  no prior edits, load image file
      paintlock(0);                                                              //  unblock window updates             16.02
      if (! E0pxm) {
         zfree(outfile2);
         Ffuncbusy = 0;                                                          //  PXM_load() diagnoses error
         return 1;
      }
   }

   tempfile = zstrdup(outfile2,20);                                              //  temp file in same directory
   strcat(tempfile,"-temp.");
   strcat(tempfile,outype);

   if (strmatch(outype,"tif"))                                                   //  save as tif file (bpc = 8 or 16)
      err = PXM_TIFF_save(E0pxm,tempfile,outbpc);

   else if (strmatch(outype,"png"))                                              //  save as png file (bpc = 8 or 16)
      err = PXM_PNG_save(E0pxm,tempfile,outbpc);

   else                                                                          //  save as .jpg
   {
      Ftransp = 0;
      if (E0pxm->nc > 3) {                                                       //  check for transparency
         for (py = 2; py < E0pxm->hh-2; py += 2)                                 //  ignore extreme edges               16.03
            for (px = 2; px < E0pxm->ww-2; px += 2)
               if (PXMpix(E0pxm,px,py)[3] < 254) {
                  Ftransp = 1;
                  goto breakout;
               }
      }
   breakout:
      if (Ftransp) {
         ii = zdialog_choose(Mwin,warnalpha,ZTX("save anyway"),Bcancel,null);    //  16.01
         if (ii != 1) {
            remove(tempfile);
            zfree(tempfile);
            zfree(outfile2);
            Ffuncbusy = 0;
            return 0;
         }
      }
      err = PXM_ANY_save(E0pxm,tempfile);                                        //  (bpc = 8 always)
      outbpc = 8;
   }

   if (err) {
      if (*file_errmess)                                                         //  pass error to user
         if (Fack) zmessageACK(Mwin,file_errmess);
      remove(tempfile);                                                          //  failure, clean up
      zfree(tempfile);
      zfree(outfile2);
      Ffuncbusy = 0;
      return 1;
   }

   exif_get(curr_file,&exifkey[1],ppv,1);                                        //  get prior edit history in EXIF
   if (ppv[0]) {
      strncpy0(edithist,ppv[0],exif_maxcc-2);
      zfree(ppv[0]);
      cc1 = strlen(edithist);                                                    //  edits made before this file was opened
   }
   else cc1 = 0;                                                                 //  none

   if ((CEF && CEF->Fmods) || URS_pos > 0)                                       //  active or completed edits
   {
      strcpy(edithist+cc1," ");                                                  //  update edit history
      strcpy(edithist+cc1+1,"Fotoxx:");                                          //  append " Fotoxx:"
      cc1 += 8;

      if (URS_reopen_pos > 0) ii0 = URS_reopen_pos + 1;                          //  if last file saved was kept open,
      else ii0 = 1;                                                              //    these edits are already in history

      for (ii = ii0; ii <= URS_pos; ii++)                                        //  append list of edits from undo/redo stack
      {                                                                          //  (omit index 0 = initial image)
         cc2 = strlen(URS_funcs[ii]);
         strcpy(edithist+cc1,URS_funcs[ii]);
         strcpy(edithist+cc1+cc2,"|");
         cc1 += cc2 + 1;
      }

      if (CEF && CEF->Fmods) {                                                   //  save during active edit function
         cc2 = strlen(CEF->funcname);                                            //  add curr. edit to history list
         strcpy(edithist+cc1,CEF->funcname);
         strcpy(edithist+cc1+cc2,"|");
         cc1 += cc2 + 1;
      }
   }

   exifdata[0] = "";                                                             //  remove EXIF orientation 
   nkeys = 1;                                                                    //  (assume saved file is fixed)

   if (cc1) {                                                                    //  prior and/or curr. edit hist
      exifdata[1] = edithist;                                                    //    will be added to EXIF
      nkeys = 2;
   }

   err = exif_copy(curr_file,tempfile,exifkey,exifdata,nkeys);                   //  copy all EXIF/IPTC data to
   if (err && Fack)                                                              //    temp file with above revisions
      zmessageACK(Mwin,ZTX("Unable to copy EXIF/IPTC data"));

   err = rename(tempfile,outfile2);                                              //  rename temp file to output file
   if (err) {
      if (Fack) zmessageACK(Mwin,wstrerror(err));
      remove(tempfile);                                                          //  delete temp file
      zfree(tempfile);
      zfree(outfile2);
      Ffuncbusy = 0;
      return 2;                                                                  //  could not save
   }

   zfree(tempfile);                                                              //  free memory

   for (ii = 0; ii <= URS_pos; ii++)                                             //  mark all prior edits as saved
      URS_saved[ii] = 1;

   thumbfile = image_thumbfile(outfile2);                                        //  update thumbnail file
   if (thumbfile) zfree(thumbfile);

updateindex:

   load_filemeta(outfile2);                                                      //  load output file metadata          15.02
   update_image_index(outfile2);                                                 //  add output file to image index     15.08

   if (Fnewfile) load_filemeta(curr_file);                                       //  restore metadata for curr. file    15.03

   if (samedirk(outfile2,navi::galleryname)) {                                   //  if saving into current gallery     16.02
      gallery(curr_file,"init");                                                 //  update curr. gallery list
      set_mwin_title();                                                          //  update posn, count in title
   }

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

   if (f_save_file) zfree(f_save_file);                                          //  actual file saved
   f_save_file = outfile2;

   strcpy(f_save_type,outype);                                                   //  update f_save_xxx data
   f_save_bpc = outbpc;
   err = stat(outfile2,&statbuf);
   f_save_size = statbuf.st_size;

   Ffuncbusy = 0;

   if (menufunc) menufunc(0,0);                                                  //  restart edit function
   return 0;
}


//  save (modified) image to new file name or type
//  confirm if overwrite of existing file
//  returns 0 if OK, 1 if cancel or error

GtkWidget      *saveas_fchooser;

int f_save_as()
{
   int  f_save_as_dialog_event(zdialog *zd, cchar *event);

   zdialog        *zd;
   static char    *save_dirk = 0;
   cchar          *type;
   char           *newfile, *fname;                                              //  16.03
   char           *outfile = 0, *outfile2 = 0, *pp, *pext;
   int            ii, zstat, err, yn;
   int            bpc, mkcurr = 0;
   STATB          statbuf;

   zthreadcrash();

/***
       _____________________________________________________________________________
      |   Save as New File Name or Type                                             |
      |   ________________________________________________________________________  |
      |  |                                                                        | |
      |  |       file chooser dialog                                              | |
      |  |                                                                        | |
      |  |                                                                        | |
      |  |                                                                        | |
      |  |                                                                        | |
      |  |                                                                        | |
      |  |________________________________________________________________________| |
      |                                                                             |
      |  (o) tif-8  (o) tif-16  (o) png-8  (o) png-16  (o) jpg  jpg quality [90|-+] |
      |  [x] make current (new file becomes current file)                           |
      |                                                                             |
      |                                                            [save] [cancel]  |
      |_____________________________________________________________________________|

***/

   zd = zdialog_new(ZTX("save as new file name or type"),Mwin,Bsave,Bcancel,null);

   zdialog_add_widget(zd,"hbox","hbfc","dialog",0,"expand");
   saveas_fchooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_SAVE);
   gtk_container_add(GTK_CONTAINER(zdialog_widget(zd,"hbfc")),saveas_fchooser);

   zdialog_add_widget(zd,"vbox","space","dialog",0,"space=3");
   zdialog_add_widget(zd,"hbox","hbft","dialog");
   zdialog_add_widget(zd,"radio","tif-8","hbft","tif-8","space=4");
   zdialog_add_widget(zd,"radio","tif-16","hbft","tif-16","space=4");
   zdialog_add_widget(zd,"radio","png-8","hbft","png-8","space=4");
   zdialog_add_widget(zd,"radio","png-16","hbft","png-16","space=4");
   zdialog_add_widget(zd,"radio","jpg","hbft","jpg","space=4");
   zdialog_add_widget(zd,"label","space","hbft",0,"space=4");
   zdialog_add_widget(zd,"label","labqual","hbft","jpg quality");
   zdialog_add_widget(zd,"spin","jpgqual","hbft","10|100|1|90","space=3");       //  15.11
   zdialog_add_widget(zd,"hbox","hbmc","dialog");
   zdialog_add_widget(zd,"check","mkcurr","hbmc",0,"space=3");
   zdialog_add_widget(zd,"label","labmc","hbmc",ZTX("make current"),"space=3");
   zdialog_add_widget(zd,"label","labmc2","hbmc",ZTX("(new file becomes current file)"),"space=5");

   zdialog_labelfont(zd,"labmc","sans bold",ZTX("make current"));                //  15.11

   if (! save_dirk) {                                                            //  default from curr. file
      save_dirk = zstrdup(curr_file);
      pp = strrchr(save_dirk,'/');
      if (! pp) zappcrash("curr_file missing /");                                //  16.04
      *pp = 0;
   }

   gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(saveas_fchooser),save_dirk);
   newfile = file_new_version(curr_file);                                        //  suggest next version               16.03
   if (! newfile) newfile = zstrdup(curr_file);
   fname = strrchr(newfile,'/') + 1;
   gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(saveas_fchooser),fname);
   if (newfile) zfree(newfile);

   zdialog_stuff(zd,"tif-8",0);                                                  //  turn off all radio buttons
   zdialog_stuff(zd,"tif-16",0);                                                 //  GTK bug: programatic selection of one
   zdialog_stuff(zd,"png-8",0);                                                  //    does not deselect the alternatives
   zdialog_stuff(zd,"png-16",0);                                                 //      (this works for GUI selection only)
   zdialog_stuff(zd,"jpg",0);

   zdialog_stuff(zd,"jpgqual",jpeg_def_quality);                                 //  default jpeg quality, user setting

   if (strmatch(curr_file_type,"tif")) {                                         //  if curr. file type is tif,
      if (curr_file_bpc == 16)                                                   //  select tif-8 or tif-16 button
         zdialog_stuff(zd,"tif-16",1);
      else zdialog_stuff(zd,"tif-8",1);
   }

   else if (strmatch(curr_file_type,"png")) {                                    //  same for png
      if (curr_file_bpc == 16)
         zdialog_stuff(zd,"png-16",1);
      else zdialog_stuff(zd,"png-8",1);
   }

   else zdialog_stuff(zd,"jpg",1);                                               //  else default jpg

   zdialog_stuff(zd,"mkcurr",0);                                                 //  deselect "make current"

   zdialog_resize(zd,600,500);
   zdialog_run(zd,f_save_as_dialog_event);

zdialog_wait:

   zstat = zdialog_wait(zd);
   if (zstat != 1) {                                                             //  user cancel
      zdialog_free(zd);
      return 1;
   }

   outfile2 = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(saveas_fchooser));
   if (! outfile2) {
      zd->zstat = 0;
      goto zdialog_wait;
   }

   if (outfile) zfree(outfile);
   outfile = zstrdup(outfile2,12);                                               //  add space for possible .vNN and .ext
   g_free(outfile2);

   zfree(save_dirk);                                                             //  remember save dirk for next time
   save_dirk = zstrdup(outfile);
   pp = strrchr(save_dirk,'/');
   *(pp+1) = 0;                                                                  //  keep trailing '/'

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

   zdialog_fetch(zd,"tif-8",ii);                                                 //  get selected radio button
   if (ii) type = "tif";

   zdialog_fetch(zd,"tif-16",ii);
   if (ii) {
      type = "tif";
      bpc = 16;
   }

   zdialog_fetch(zd,"png-8",ii);
   if (ii) type = "png";

   zdialog_fetch(zd,"png-16",ii);
   if (ii) {
      type = "png";
      bpc = 16;
   }

   if (strmatch(type,"jpg")) {                                                   //  set non-default jpeg save quality
      zdialog_fetch(zd,"jpgqual",ii);                                            //  will be used only one time
      jpeg_1x_quality = ii;
   }

   pext = strrchr(outfile,'/');                                                  //  locate file .ext
   if (pext) pext = strrchr(pext,'.');

   if (pext) {                                                                   //  validate .ext OK for type
      if (strmatch(type,"jpg") && ! strcasestr(".jpg .jpeg",pext)) *pext = 0;
      if (strmatch(type,"tif") && ! strcasestr(".tif .tiff",pext)) *pext = 0;
      if (strmatch(type,"png") && ! strcasestr(".png",pext)) *pext = 0;
   }

   if (! pext || ! *pext) {
      pext = outfile + strlen(outfile);                                          //  wrong or missing, add new .ext
      *pext = '.';                                                               //  NOT replace .JPG with .jpg etc.
      strcpy(pext+1,type);
   }

   zdialog_fetch(zd,"mkcurr",mkcurr);                                            //  get make current option

   err = stat(outfile,&statbuf);                                                 //  check if file exists
   if (! err) {
      yn = zmessageYN(Mwin,ZTX("Overwrite file? \n %s"),outfile);                //  confirm overwrite
      if (! yn) {
         zd->zstat = 0;
         goto zdialog_wait;
      }
   }

   zdialog_free(zd);                                                             //  zdialog_free(zd);

   err = f_save(outfile,type,bpc);                                               //  save the file
   if (err) {
      zfree(outfile);
      return 1;
   }

   if (samedirk(outfile,navi::galleryname)) {                                    //  if saving into current gallery
      gallery(outfile,"init");                                                   //    refresh gallery list
      curr_file_posn = gallery_position(curr_file,curr_file_posn);               //    update curr. file position
      set_mwin_title();                                                          //    update window title (file count)
   }

   if (mkcurr) f_open_saved();                                                   //  open saved file with edit hist

   zfree(outfile);
   return 0;
}


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

int f_save_as_dialog_event(zdialog *zd, cchar *event)
{
   int      ii;
   cchar    *filetypes[5] = { "tif-8", "tif-16", "png-8", "png-16", "jpg" };
   char     ext[4];
   char     *filespec;
   char     *filename, *pp;

   if (strmatch(event,"enter")) zd->zstat = 1;
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07
   
   if (zd->zstat) return 1;                                                      //  [done] or [cancel]

   if (strmatch(event,"jpgqual")) {                                              //  if jpg quality edited, set jpg .ext
      zdialog_stuff(zd,"jpg",1);
      event = "jpg";
   }

   for (ii = 0; ii < 5; ii++)                                                    //  detect file type selection
      if (strmatch(event,filetypes[ii])) break;
   if (ii == 5) return 1;

   zdialog_stuff(zd,"tif-8",0);                                                  //  turn off unselected types first
   zdialog_stuff(zd,"tif-16",0);                                                 //  (see GTK bug note above)
   zdialog_stuff(zd,"png-8",0);
   zdialog_stuff(zd,"png-16",0);
   zdialog_stuff(zd,"jpg",0);
   zdialog_stuff(zd,event,1);                                                    //  lastly, turn on selected type

   strncpy0(ext,event,4);                                                        //  "tif-16" >> "tif" etc.

   filespec = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(saveas_fchooser));
   if (! filespec) return 1;
   filename = strrchr(filespec,'/');                                             //  revise file .ext in chooser dialog
   if (! filename) return 1;
   filename = zstrdup(filename+1,6);
   pp = strrchr(filename,'.');
   if (! pp || strlen(pp) > 5) pp = filename + strlen(filename);
   *pp = '.';
   strcpy(pp+1,ext);
   gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(saveas_fchooser),filename);
   zfree(filename);
   g_free(filespec);

   return 1;
}


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

//  open previous or next file in current gallery list
//  index is -1 or +1

void x_prev_next(int index)                                                      //  overhauled                         16.07
{
   using namespace navi;

   char            *pp, *newfile = 0, *newgallery = 0;
   int             err, Nth = 0;
   static int      xbusy = 0;
   static int      jumpgallery = 0;
   static zdialog  *zd = 0;

   cchar    *endmess1 = ZTX("Start of gallery, preceding gallery: %s");
   cchar    *endmess2 = ZTX("End of gallery, following gallery: %s");
   
   F1_help_topic = "prev_next";
   
   if (zd && zdialog_valid(zd)) {
      if (strmatch(zd->widget[0].data,"post"))  {
         zdialog_free(zd);                                                       //  clear prior popup message
         zd = 0;
      }
   }
   
   if (checkpend("busy block mods")) return;
   if (xbusy) return;                                                            //  avoid "function busy"
   xbusy = 1;

   newfile = gallery_getnext(index,Flastversion);                                //  get prev/next file (last version)
   if (newfile) {
      Nth = curr_file_posn + index;                                              //  albums can have repeat files
      err = f_open(newfile,Nth,0,1,0);                                           //  open image or RAW file
      if (! err) f_preload(index);                                               //  preload next image
      goto returnx;
   }

   if (jumpgallery) {                                                            //  jump to prev/next gallery
      jumpgallery = 0;
      newgallery = prev_next_gallery(index);
      if (! newgallery) goto returnx;
      gallery(newgallery,"init");                                                //  load gallery
      if (Nfiles - Ndirs == 0) {
         m_viewmode(0,"G");
         gallery(0,"paint");
         goto returnx;
      }
      if (index == +1) Nth = Ndirs;                                              //  get first or last image file
      if (index == -1) Nth = Nfiles - 1;
      newfile = gallery(0,"find",Nth);
      err = f_open(newfile,Nth,0,1,0);                                           //  open image or RAW file
      if (! err) f_preload(index);                                               //  preload next image
      goto returnx;
   }
   
   newgallery = prev_next_gallery(index);                                        //  check for prev/next gallery
   if (newgallery) {
      pp = strrchr(newgallery,'/');
      if (pp) pp++;
      if (index == -1) zd = zmessage_post(Mwin,3,endmess1,pp,0);                 //  prepare jump to prev/next gallery
      if (index == +1) zd = zmessage_post(Mwin,3,endmess2,pp,0);                 //    if user tries prev/next again
      jumpgallery = 1;
   }
   else {
      if (index == -1) zd = zmessage_post(Mwin,3,endmess1,"(null)",0);           //  no prev/next gallery
      if (index == +1) zd = zmessage_post(Mwin,3,endmess2,"(null)",0);
   }

returnx:
   if (newfile) zfree(newfile);
   if (newgallery) zfree(newgallery);
   Fpaint2();
   xbusy = 0;
   return;
}


void m_prev(GtkWidget *, cchar *menu)
{
   F1_help_topic = "open_image_file";
   x_prev_next(-1);                                                              //  search from curr_file -1 to first file
   return;
}


void m_next(GtkWidget *, cchar *menu)
{
   F1_help_topic = "open_image_file";
   x_prev_next(+1);                                                              //  search from curr_file +1 to last file
   return;
}


void m_prev_next(GtkWidget *, cchar *menu)                                       //  prev/next if left/right mouse click on icon
{
   int button = zfuncs::vmenuclickbutton;
   if (button == 1) m_prev(0,0);
   else m_next(0,0);
   return;
}


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

//  help menu function

void m_help(GtkWidget *, cchar *menu)
{
   if (strmatch(menu,ZTX("Quick Start")))
      showz_html(quickstart_file);

   if (strmatch(menu,ZTX("User Guide")))
      showz_userguide();

   if (strmatch(menu,ZTX("User Guide Changes")))
      showz_userguide("recent_changes");

   if (strmatch(menu,ZTX("README")))
      showz_textfile("doc","README");

   if (strmatch(menu,ZTX("Edit Functions Summary")))
      showz_textfile("data","edit-menus");

   if (strmatch(menu,ZTX("Change Log")))
      showz_textfile("doc","changelog");

   if (strmatch(menu,ZTX("Log File")))
      showz_logfile();

   if (strmatch(menu,ZTX("Translations")))
      showz_userguide("translations");

   if (strmatch(menu,ZTX("Home Page")))
      showz_html(Fhomepage);

   if (strmatch(menu,ZTX("About")))
      zmessageACK(Mwin," %s   " __DATE__ "\n %s\n %s\n %s\n %s\n %s\n",
         Frelease,Flicense,Fhomepage,Fcontact,Fsoftware,Ftranslators);           //  16.07

   if (strmatch(menu,ZTX("Help")))                                               //  menu button
      showz_userguide(F1_help_topic);                                            //  show topic if there, or page 1

   return;
}


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

   Find all image files within given path.

      dirk        directory path to search
      NF          count of filespecs returned
      flist       list of filespecs returned
      Fthumbs     include thumbnail files if Fthumbs = 1
      Finit       initialize flag (omit, default = 1)
                  (0 is used for internal recursive calls)

   Hidden directories and files (dotfiles) are suppressed.
   Returns 0 if OK, +N if error (errno is set).
   flist and flist[*] are subjects for zfree().

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

char  **fif_filelist;                  //  list of filespecs returned
int   fif_max;                         //  filelist slots allocated
int   fif_count;                       //  filelist slots filled

int find_imagefiles(cchar *dirk, char **&flist, int &NF, int Fthumbs, int Finit)
{
   int      flags = GLOB_NOSORT;
   int      err1, err2, cc;
   FTYPE    ftype;
   char     *file, *mdirk, **templist;

   glob_t   globdata;
   STATB    statdat;

   if (Finit) fif_max = fif_count = 0;                                           //  initial call

   globdata.gl_pathc = 0;                                                        //  glob() setup
   globdata.gl_offs = 0;
   globdata.gl_pathc = 0;
   NF = 0;
   flist = 0;

   mdirk = zstrdup(dirk,4);                                                      //  append /* to input directory
   strcat(mdirk,"/*");

   err1 = glob(mdirk,flags,null,&globdata);                                      //  find all files in directory

   if (err1) {
      if (err1 == GLOB_NOMATCH) err1 = 0;
      else if (err1 == GLOB_ABORTED) err1 = 1;
      else if (err1 == GLOB_NOSPACE) err1 = 2;
      else err1 = 3;
      goto fif_return;
   }

   for (uint ii = 0; ii < globdata.gl_pathc; ii++)                               //  loop found files
   {
      file = globdata.gl_pathv[ii];
      err1 = stat(file,&statdat);
      if (err1) continue;

      if (S_ISDIR(statdat.st_mode)) {                                            //  directory, call recursively
         err1 = find_imagefiles(file,flist,NF,Fthumbs,0);
         if (err1) goto fif_return;
         continue;
      }

      ftype = image_file_type(file);                                             //  check file type is image,
      if (ftype != IMAGE && ftype != RAW && ftype != THUMB) continue;            //    RAW, or thumbnail file
      if (! Fthumbs && ftype == THUMB) continue;                                 //  exclude thumbnail files

      if (fif_count == fif_max) {                                                //  output list is full
         if (fif_max == 0) {
            fif_max = 1000;                                                      //  initial space, 1000 files
            cc = fif_max * sizeof(char *);
            fif_filelist = (char **) zmalloc(cc);
         }
         else {
            templist = fif_filelist;                                             //  expand by 2x each time needed
            cc = fif_max * sizeof(char *);
            fif_filelist = (char **) zmalloc(cc+cc);
            memcpy(fif_filelist,templist,cc);
            memset(fif_filelist+fif_max,0,cc);
            zfree(templist);
            fif_max *= 2;
         }
      }

      fif_filelist[fif_count] = zstrdup(file);                                   //  add file to output list
      fif_count += 1;
   }

   err1 = 0;

fif_return:

   err2 = errno;                                                                 //  preserve Linux errno

   globfree(&globdata);                                                          //  free memory
   zfree(mdirk);

   if (Finit) {
      NF = fif_count;
      if (NF) flist = fif_filelist;
   }

   errno = err2;                                                                 //  return err1 and preserve errno
   return err1;
}


/********************************************************************************
   Image file load and save functions
   PXM pixmap or PXB pixbuf <--> file on disk
*********************************************************************************/

//  get the equivalent .tif file name for a given RAW file
//  returned file name is subject to zfree()

char * raw_to_tiff(cchar *rawfile)
{
   char     *ptiff, *pext;

   ptiff = zstrdup(rawfile,8);
   pext = strrchr(ptiff,'.');
   if (! pext) pext = ptiff + strlen(ptiff);
   strcpy(pext,".tif");
   return ptiff;
}


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

//  Load an image file into a PXB pixmap (pixbuf 8 bit color).
//  Also sets the following file scope variables:
//    f_load_type = "jpg" "tif" "png" "bmp" "ico" "other"
//    f_load_bpc = disk file bits per color = 1/8/16
//    f_load_size = disk file size
//  If Fack is true, failure will cause a popup ACK dialog
//  Do not call from a thread with Fack true.

PXB * PXB_load(cchar *file, int Fack)
{
   int      err;
   FTYPE    ftype;
   cchar    *pext;
   PXB      *pxb;
   STATB    statbuf;

   err = stat(file,&statbuf);
   if (err) {
      if (Fack) zmessageACK(Mwin,ZTX("file not found: %s"),file);
      goto f_load_fail;
   }

   ftype = image_file_type(file);
   if (ftype != IMAGE && ftype != RAW) {
      if (Fack) zmessageACK(Mwin,ZTX("file type not supported: %s"),file);
      goto f_load_fail;
   }

   f_load_size = statbuf.st_size;                                                //  set f_load_size

   pext = strrchr(file,'/');
   if (! pext) pext = file;
   pext = strrchr(pext,'.');
   if (! pext) pext = "";

   if (ftype == IMAGE) {
      if (strcasestr(".jpg .jpeg",pext)) strcpy(f_load_type,"jpg");              //  f_load_type = jpg/tif/png/...
      else if (strcasestr(".tif .tiff",pext)) strcpy(f_load_type,"tif");
      else if (strcasestr(".png",pext)) strcpy(f_load_type,"png");
      else if (strcasestr(".bmp",pext)) strcpy(f_load_type,"bmp");
      else if (strcasestr(".ico",pext)) strcpy(f_load_type,"ico");
      else strcpy(f_load_type,"other");
   }

   if (ftype == RAW) strcpy(f_load_type,"RAW");                                  //  f_load_type = RAW                  15.08

   if (strmatch(f_load_type,"tif"))                                              //  use loader for file type
      pxb = TIFF_PXB_load(file);                                                 //  f_load_bpc = file bpc

   else if (strmatch(f_load_type,"png"))
      pxb = PNG_PXB_load(file);

   else if (strmatch(f_load_type,"RAW"))                                         //  RAW file
      pxb = RAW_PXB_load(file);

   else pxb = ANY_PXB_load(file);

   if (pxb) return pxb;

   if (Fack && *file_errmess)                                                    //  pass error to user
      zmessageACK(Mwin,file_errmess);

f_load_fail:
   *f_load_type = f_load_size = f_load_bpc = 0;
   return 0;
}


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

//  Load an image file into a PXM pixmap (float color).
//  Also sets the following file scope variables:
//    f_load_type = "jpg" "tif" "png" "bmp" "ico" "other"
//    f_load_bpc = disk file bits per color = 1/8/16
//    f_load_size = disk file size
//  If Fack is true, failure will cause a popup ACK dialog
//  Do not call from a thread with Fack true.

PXM * PXM_load(cchar *file, int Fack)
{
   int      err;
   FTYPE    ftype;
   cchar    *pext;
   PXM      *pxm;
   STATB    statbuf;
   
   err = stat(file,&statbuf);
   if (err) {
      if (Fack) zmessageACK(Mwin,ZTX("file not found: %s"),file);
      goto f_load_fail;
   }

   ftype = image_file_type(file);
   if (ftype != IMAGE && ftype != RAW) {
      if (Fack) zmessageACK(Mwin,ZTX("file type not supported: %s"),file);
      goto f_load_fail;
   }

   f_load_size = statbuf.st_size;                                                //  set f_load_size

   pext = strrchr(file,'/');
   if (! pext) pext = file;
   pext = strrchr(pext,'.');
   if (! pext) pext = "";

   if (strcasestr(".jpg .jpeg",pext)) strcpy(f_load_type,"jpg");                 //  set f_load_type
   else if (strcasestr(".tif .tiff",pext)) strcpy(f_load_type,"tif");
   else if (strcasestr(".png",pext)) strcpy(f_load_type,"png");
   else if (strcasestr(".bmp",pext)) strcpy(f_load_type,"bmp");
   else if (strcasestr(".ico",pext)) strcpy(f_load_type,"ico");
   else strcpy(f_load_type,"other");
   
   if (strmatch(f_load_type,"tif"))                                              //  use loader for file type
      pxm = TIFF_PXM_load(file);                                                 //  f_load_bpc = file bpc

   else if (strmatch(f_load_type,"png"))
      pxm = PNG_PXM_load(file);

   else if (ftype == RAW)                                                        //  RAW file
      pxm = RAW_PXM_load(file);

   else pxm = ANY_PXM_load(file);

   if (pxm) return pxm;

   if (Fack && *file_errmess)                                                    //  pass error to user
      zmessageACK(Mwin,file_errmess);

f_load_fail:
   *f_load_type = f_load_size = f_load_bpc = 0;
   return 0;
}


/********************************************************************************
   TIFF file read and write functions
*********************************************************************************/

//  Intercept TIFF warning messages (many)

void tiffwarninghandler(cchar *module, cchar *format, va_list ap)
{
   return;                                                                       //  stop flood of crap
   char  message[200];
   vsnprintf(message,199,format,ap);
   printz("TIFF warning: %s %s \n",module,message);
   return;
}


//  Load PXB from TIFF file using TIFF library.
//  Native TIFF file bits/pixel >> f_load_bpc

PXB * TIFF_PXB_load(cchar *file)
{
   static int  ftf = 1;
   TIFF        *tiff;
   PXB         *pxb;
   char        *tiffbuff;
   uint8       *tiff8;
   uint16      *tiff16;
   uint8       *pix;
   uint16      bpc, nc, ac, fmt;
   int         ww, hh, rps, stb, nst;                                            //  int not uint
   int         tiffstat, row, col, strip;
   int         row1, row2, cc;

   if (ftf) {
      TIFFSetWarningHandler(tiffwarninghandler);                                 //  intercept TIFF warning messages
      ftf = 0;
   }

   *file_errmess = 0;

   tiff = TIFFOpen(file,"r");
   if (! tiff) {
      snprintf(file_errmess,999,"TIFF file read error: %s",file);
      printz("%s\n",file_errmess);
      return 0;
   }

   TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &ww);                                  //  width
   TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &hh);                                 //  height
   TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bpc);                              //  bits per color, 1/8/16
   TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &nc);                             //  no. channels (colors), 1/2/3/4
   TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rps);                               //  rows per strip
   TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &fmt);                               //  data format
   stb = TIFFStripSize(tiff);                                                    //  strip size
   nst = TIFFNumberOfStrips(tiff);                                               //  number of strips

   if (ww > wwhh_limit1 || hh > wwhh_limit1 || ww * hh > wwhh_limit2) {          //  16.03
      zmessageACK(Mwin,"image too big, %dx%d",ww,hh);
      TIFFClose(tiff);
      return 0;
   }

   if (bpc > 8 && bpc != 16) {                                                   //  check for supported bits/color
      printz("TIFF bits=%d not supported: %s \n",bpc,file);
      TIFFClose(tiff);
      return ANY_PXB_load(file);                                                 //  fallback to pixbuf loader
   }
   
   if (nc == 2 || nc == 4) ac = 1;                                               //  15.08
   else ac = 0;

   if (bpc <= 8)                                                                 //  use universal TIFF reader
   {                                                                             //    if bits/color <= 8
      tiffbuff = (char *) zmalloc(ww*hh*4);
      tiffstat = TIFFReadRGBAImage(tiff, ww, hh, (uint *) tiffbuff, 0);
      TIFFClose(tiff);

      if (tiffstat != 1) {
         snprintf(file_errmess,999,"TIFF file read error: %s",file);
         printz("%s\n",file_errmess);
         zfree(tiffbuff);
         return 0;
      }

      pxb = PXB_make(ww,hh,ac);                                                  //  create PXB

      tiff8 = (uint8 *) tiffbuff;

      for (row = 0; row < hh; row++)
      {
         pix = pxb->pixels + (hh-1 - row) * pxb->rs;                             //  TIFF image is inverted

         for (col = 0; col < ww; col++)
         {
            memcpy(pix,tiff8,(3+ac));                                            //  0 - 255  >> 0 - 255
            pix += 3 + ac;
            tiff8 += 4;
         }
      }

      zfree(tiffbuff);

      f_load_bpc = 8;                                                            //  bugfix                             15.09.2
      return pxb;
   }

   pxb = PXB_make(ww,hh,ac);                                                     //  TIFF file has 16 bits/color

   stb += 1000000;                                                               //  reduce risk of crash
   tiffbuff = (char *) zmalloc(stb);                                             //  read encoded strips

   for (strip = 0; strip < nst; strip++)
   {
      cc = TIFFReadEncodedStrip(tiff,strip,tiffbuff,stb);
      if (cc < 0) {
         snprintf(file_errmess,999,"TIFF file read error: %s",file);
         printz("%s\n",file_errmess);
         TIFFClose(tiff);
         zfree(tiffbuff);
         PXB_free(pxb);
         return 0;
      }

      if (cc == 0) break;

      tiff16 = (uint16 *) tiffbuff;

      row1 = strip * rps;                                                        //  rows in strip
      row2 = row1 + cc / (ww * nc * 2);
      if (row2 > hh) row2 = hh;                                                  //  rps unreliable if nst = 1

      for (row = row1; row < row2; row++)
      {
         pix = pxb->pixels + row * pxb->rs;

         for (col = 0; col < ww; col++)
         {
            pix[0] = tiff16[0] / 256;                                            //  0 - 65535  >>  0 - 255
            pix[1] = tiff16[1] / 256;
            pix[2] = tiff16[2] / 256;
            if (ac) pix[3] = tiff16[3] / 256;
            pix += 3 + ac;
            tiff16 += nc;
         }
      }
   }

   TIFFClose(tiff);
   zfree(tiffbuff);

   f_load_bpc = bpc;                                                             //  file bpc 1/8/16
   return pxb;
}


//  Load PXM from TIFF file using TIFF library.
//  Native TIFF file bits/pixel >> f_load_bpc

PXM * TIFF_PXM_load(cchar *file)
{
   static int  ftf = 1;
   TIFF        *tiff;
   PXM         *pxm;
   char        *tiffbuff;
   uint8       *tiff8;
   uint16      *tiff16;
   float       *pix;
   uint16      bpc, nc, ac, fmt;
   int         ww, hh, rps, stb, nst;                                            //  int not uint
   int         tiffstat, row, col, strip, cc;
   
   if (ftf) {
      TIFFSetWarningHandler(tiffwarninghandler);                                 //  intercept TIFF warning messages
      ftf = 0;
   }

   *file_errmess = 0;

   tiff = TIFFOpen(file,"r");
   if (! tiff) {
      snprintf(file_errmess,999,"TIFF file read error: %s",file);
      printz("%s\n",file_errmess);
      return 0;
   }

   TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &ww);                                  //  width
   TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &hh);                                 //  height
   TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bpc);                              //  bits per color, 1/8/16
   TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &nc);                             //  no. channels (colors), 1/2/3/4
   TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rps);                               //  rows per strip
   TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &fmt);                               //  data format
   stb = TIFFStripSize(tiff);                                                    //  strip size
   nst = TIFFNumberOfStrips(tiff);                                               //  number of strips

   if (bpc > 8 && bpc != 16) {                                                   //  check for supported bits/color
      printz("TIFF bits=%d not supported: %s \n",bpc,file);
      TIFFClose(tiff);
      return ANY_PXM_load(file);                                                 //  fallback to pixbuf loader
   }

   if (nc == 2 || nc == 4) ac = 1;                                               //  alpha channel                      15.08
   else ac = 0;

   pxm = PXM_make(ww,hh,3+ac);                                                   //  create PXM
   if (! pxm) {                                                                  //  16.03
      TIFFClose(tiff);
      return 0;
   }

   if (bpc <= 8)                                                                 //  use universal TIFF reader
   {
      tiffbuff = (char *) zmalloc(ww*hh*4);
      tiffstat = TIFFReadRGBAImage(tiff, ww, hh, (uint *) tiffbuff, 0);          //  with alpha channel
      TIFFClose(tiff);

      if (tiffstat != 1) {
         snprintf(file_errmess,999,"TIFF file read error: %s",file);
         printz("%s\n",file_errmess);
         zfree(tiffbuff);
         return 0;
      }

      tiff8 = (uint8 *) tiffbuff;

      for (row = 0; row < hh; row++)
      {
         pix = pxm->pixels + (hh-1 - row) * ww * (3+ac);                         //  TIFF image is inverted

         for (col = 0; col < ww; col++)
         {
            pix[0] = tiff8[0];                                                   //  0 - 255  >>  0.0 - 255.0
            pix[1] = tiff8[1];
            pix[2] = tiff8[2];
            pix += 3+ac;
            tiff8 += 4;
         }
      }

      zfree(tiffbuff);
      return pxm;
   }
                                                                                 //  TIFF file has 16 bits/color
   stb += 1000000;                                                               //  reduce risk of crash
   tiffbuff = (char *) zmalloc(stb);                                             //  read encoded strips

   for (strip = 0; strip < nst; strip++)
   {
      cc = TIFFReadEncodedStrip(tiff,strip,tiffbuff,stb);
      if (cc < 0) {
         snprintf(file_errmess,999,"TIFF file read error: %s",file);
         printz("%s\n",file_errmess);
         TIFFClose(tiff);
         zfree(tiffbuff);
         PXM_free(pxm);
         return 0;
      }

      if (cc == 0) break;

      tiff16 = (uint16 *) tiffbuff;
      row = strip * rps;
      pix = pxm->pixels + row * ww * (3+ac);

      while (cc >= 6)
      {
         pix[0] = tiff16[0] / 256.0;                                             //  0 - 65535  >>  0.0 - 255.996
         pix[1] = tiff16[1] / 256.0;
         pix[2] = tiff16[2] / 256.0;
         if (ac) pix[3] = tiff16[3] / 256.0;
         pix += 3+ac;
         tiff16 += nc;
         cc -= nc * 2;
      }
   }

   TIFFClose(tiff);
   zfree(tiffbuff);

   f_load_bpc = bpc;                                                             //  file bpc 1/8/16
   return pxm;
}


//  Write PXM to TIFF file using TIFF library.
//  TIFF file bpc is 8 or 16.
//  Returns 0 if OK, +N if error.

int PXM_TIFF_save(PXM *pxm, cchar *file, int bpc)
{
   static int  ftf = 1;
   TIFF        *tiff;
   uint8       *tiff8;
   uint16      *tiff16;
   float       *pix;
   char        *tiffbuff;
   int         tiffstat = 0;
   int         ww, hh, row, col, rowcc;                                          //  int not uint
   int         nc, ac, pm = 2, pc = 1;
   int         comp = COMPRESSION_NONE;                                          //  LZW ineffective                    15.12

   if (ftf) {
      TIFFSetWarningHandler(tiffwarninghandler);                                 //  intercept TIFF warning messages
      ftf = 0;
   }

   *file_errmess = 0;

   tiff = TIFFOpen(file,"w");
   if (! tiff) {
      snprintf(file_errmess,999,"TIFF file write error: %s",file);
      printz("%s\n",file_errmess);
      return 0;
   }

   ww = pxm->ww;
   hh = pxm->hh;
   nc = pxm->nc;                                                                 //  15.08
   ac = nc - 3;

   TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, ww);
   TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, bpc);
   TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, nc);
   TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, pm);                                  //  RGB
   TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, pc);
   TIFFSetField(tiff, TIFFTAG_COMPRESSION, comp);

   rowcc = TIFFScanlineSize(tiff);
   tiffbuff = (char*) zmalloc(rowcc);

   for (row = 0; row < hh; row++)
   {
      if (bpc == 8)                                                              //  write 8-bit TIFF
      {
         tiff8 = (uint8 *) tiffbuff;
         pix = pxm->pixels + row * ww * nc;

         for (col = 0; col < ww; col++)
         {
            tiff8[0] = pix[0];                                                   //  0.0 - 255.996  >>  0 - 255
            tiff8[1] = pix[1];
            tiff8[2] = pix[2];
            if (ac) tiff8[3] = pix[3];
            pix += nc;
            tiff8 += nc;
         }
      }

      if (bpc == 16)                                                             //  write 16-bit TIFF
      {
         tiff16 = (uint16 *) tiffbuff;
         pix = pxm->pixels + row * ww * nc;

         for (col = 0; col < ww; col++)
         {
            tiff16[0] = 256.0 * pix[0];                                          //  0.0 - 255.996  >>  0.0 >> 65535
            tiff16[1] = 256.0 * pix[1];
            tiff16[2] = 256.0 * pix[2];
            if (ac) tiff16[3] = 256.0 * pix[3];
            pix += nc;
            tiff16 += nc;
         }
      }

      tiffstat = TIFFWriteScanline(tiff,tiffbuff,row,0);
      if (tiffstat != 1) break;
   }

   TIFFClose(tiff);
   zfree(tiffbuff);

   if (tiffstat == 1) return 0;
   snprintf(file_errmess,999,"TIFF file write error: %s",file);
   printz("%s\n",file_errmess);
   return 2;
}


/********************************************************************************
   PNG file read and write functions
*********************************************************************************/

//  Load PXB from PNG file using PNG library.
//  Native PNG file bits/pixel >> f_load_bpc

PXB * PNG_PXB_load(cchar *file)
{
   png_structp    pngstruct = 0;
   png_infop      pnginfo = 0;
   FILE           *fid;
   int            ww, hh, bpc, nc, ac, colortype;
   uchar          **pngrows;
   uint8          *png8;
   uint16         *png16;
   PXB            *pxb;
   uint8          *pix;
   int            row, col;

   *file_errmess = 0;

   fid = fopen(file,"r");                                                        //  open file
   if (! fid) goto erret;

   pngstruct = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);              //  create png structs
   if (! pngstruct) goto erret;
   pnginfo = png_create_info_struct(pngstruct);
   if (! pnginfo) goto erret;
   if (setjmp(png_jmpbuf(pngstruct))) goto erret;                                //  setup error handling

   png_init_io(pngstruct,fid);                                                   //  read png file
   png_read_png(pngstruct,pnginfo,PNG_TRANSFORM_SWAP_ENDIAN,0);
   fclose(fid);

   ww = png_get_image_width(pngstruct,pnginfo);                                  //  width
   hh = png_get_image_height(pngstruct,pnginfo);                                 //  height
   bpc = png_get_bit_depth(pngstruct,pnginfo);                                   //  bits per color (channel)
   nc = png_get_channels(pngstruct,pnginfo);                                     //  no. channels
   colortype = png_get_color_type(pngstruct,pnginfo);                            //  color type
   pngrows = png_get_rows(pngstruct,pnginfo);                                    //  array of png row pointers

   if (colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_RGB)
      ac = 0;
   else if (colortype == PNG_COLOR_TYPE_GRAY_ALPHA || colortype == PNG_COLOR_TYPE_RGB_ALPHA)
      ac = 1;
   else {
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return ANY_PXB_load(file);                                                 //  fallback to pixbuf loader
   }

   if (ww > wwhh_limit1 || hh > wwhh_limit1 || ww * hh > wwhh_limit2) {          //  16.03
      zmessageACK(Mwin,"image too big, %dx%d",ww,hh);
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return 0;
   }

   if (bpc != 8 && bpc != 16) {                                                  //  not 8 or 16 bits per channel
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return ANY_PXB_load(file);                                                 //  use standard pixbuf loader
   }
   
   if (nc > 4) {                                                                 //  not gray (+alpha) or RGB (+alpha)
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return ANY_PXB_load(file);                                                 //  use standard pixbuf loader
   }
   
   pxb = PXB_make(ww,hh,ac);                                                     //  create PXB                         16.03
   if (! pxb) {
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return 0;
   }
   
   if (bpc == 8)
   {
      for (row = 0; row < hh; row++)
      {
         png8 = (uint8 *) pngrows[row];
         pix = pxb->pixels + row * pxb->rs;

         for (col = 0; col < ww; col++)
         {
            if (nc < 3) {                                                        //  gray or gray + alpha
               pix[0] = pix[1] = pix[2] = png8[0];
               if (ac) pix[3] = png8[1];                                         //  alpha channel                      15.08
            }
            else memcpy(pix,png8,3+ac);                                          //  RGB or RGB + alpha
            png8 += nc;
            pix += 3+ac;
         }
      }
   }
   
   if (bpc == 16)
   {
      for (row = 0; row < hh; row++)
      {
         png16 = (uint16 *) pngrows[row];
         pix = pxb->pixels + row * pxb->rs;

         for (col = 0; col < ww; col++)
         {
            if (nc < 3) {                                                        //  gray or gray + alpha
               pix[0] = pix[1] = pix[2] = png16[0] >> 8;
               if (ac) pix[3] = png16[1] >> 8;                                   //  alpha channel                      15.08
            }
            else {                                                               //  RGB or RGB + alpha
               pix[0] = png16[0] >> 8;                                           //  16 bits to 8 bits
               pix[1] = png16[1] >> 8;
               pix[2] = png16[2] >> 8;
               if (ac) pix[3] = png16[3] >> 8;                                   //  alpha channel                      15.08
            }
            png16 += nc;
            pix += 3+ac;
         }
      }
   }

   png_destroy_read_struct(&pngstruct,&pnginfo,0);                               //  release memory

   f_load_bpc = bpc;                                                             //  file bpc 16
   return pxb;

erret:
   if (fid) fclose(fid);
   snprintf(file_errmess,999,"PNG file read error: %s",file);
   printz("%s\n",file_errmess);
   return 0;
}


//  Load PXM from PNG file using PNG library.
//  Native PNG file bits/pixel >> f_load_bpc

PXM * PNG_PXM_load(cchar *file)
{
   png_structp    pngstruct = 0;
   png_infop      pnginfo = 0;
   FILE           *fid;
   int            ww, hh, bpc, nc, ac, colortype;
   uchar          **pngrows;
   uint8          *png8;
   uint16         *png16;
   PXM            *pxm;
   float          *pix, f256 = 1.0 / 256.0;
   int            row, col;

   *file_errmess = 0;

   fid = fopen(file,"r");                                                        //  open file
   if (! fid) goto erret;

   pngstruct = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);              //  create png structs
   if (! pngstruct) goto erret;
   pnginfo = png_create_info_struct(pngstruct);
   if (! pnginfo) goto erret;
   if (setjmp(png_jmpbuf(pngstruct))) goto erret;                                //  setup error handling

   png_init_io(pngstruct,fid);                                                   //  read png file
   png_read_png(pngstruct,pnginfo,PNG_TRANSFORM_SWAP_ENDIAN,0);
   fclose(fid);

   ww = png_get_image_width(pngstruct,pnginfo);                                  //  width
   hh = png_get_image_height(pngstruct,pnginfo);                                 //  height
   bpc = png_get_bit_depth(pngstruct,pnginfo);                                   //  bits per color (channel)
   nc = png_get_channels(pngstruct,pnginfo);                                     //  no. channels
   colortype = png_get_color_type(pngstruct,pnginfo);                            //  color type
   pngrows = png_get_rows(pngstruct,pnginfo);                                    //  array of png row pointers

   if (colortype != PNG_COLOR_TYPE_GRAY &&
       colortype != PNG_COLOR_TYPE_RGB &&
       colortype != PNG_COLOR_TYPE_GRAY_ALPHA && 
       colortype != PNG_COLOR_TYPE_RGB_ALPHA) {
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return ANY_PXM_load(file);                                                 //  fallback to pixbuf loader
   }

   if (bpc != 8 && bpc != 16) {
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return ANY_PXM_load(file);                                                 //  use standard pixbuf loader
   }

   if (nc > 4) {                                                                 //  not gray (+alpha) or RGB (+alpha)
      png_destroy_read_struct(&pngstruct,&pnginfo,0);
      return ANY_PXM_load(file);                                                 //  use standard pixbuf loader
   }
   
   if (nc == 2 || nc == 4) ac = 1;                                               //  gray + alpha or RGB + alpha
   else ac = 0;

   pxm = PXM_make(ww,hh,3+ac);                                                   //  create PXM 
   if (! pxm) {
      png_destroy_read_struct(&pngstruct,&pnginfo,0);                            //  16.01
      return 0;                                                          
   }

   if (bpc == 8)
   {
      for (row = 0; row < hh; row++)
      {
         png8 = (uint8 *) pngrows[row];
         pix = pxm->pixels + row * ww * (3 + ac);

         for (col = 0; col < ww; col++)
         {
            if (nc < 3) {                                                        //  gray or gray + alpha
               pix[0] = pix[1] = pix[2] = png8[0];
               if (ac) pix[3] = png8[1];
            }
            else {                                                               //  RGB or RGB + alpha
               pix[0] = png8[0];                                                 //  0-255 >> 0.0 - 255.0
               pix[1] = png8[1];
               pix[2] = png8[2];
               if (ac) pix[3] = png8[3];
            }
            png8 += nc;
            pix += 3 + ac;
         }
      }
   }

   if (bpc == 16)
   {
      for (row = 0; row < hh; row++)
      {
         png16 = (uint16 *) pngrows[row];
         pix = pxm->pixels + row * ww * (3 + ac);

         for (col = 0; col < ww; col++)
         {
            if (nc < 3) {                                                        //  gray or gray + alpha
               pix[0] = pix[1] = pix[2] = f256 * png16[0];
               if (ac) pix[3] = f256 * png16[1];
            }
            else {                                                               //  RGB or RGB + alpha
               pix[0] = f256 * png16[0];                                         //  0-65535 >> 0-255.996
               pix[1] = f256 * png16[1];
               pix[2] = f256 * png16[2];
               if (ac) pix[3] = f256 * png16[3];
            }
            png16 += nc;
            pix += 3 + ac;
         }
      }
   }

   png_destroy_read_struct(&pngstruct,&pnginfo,0);                               //  release memory

   f_load_bpc = bpc;                                                             //  file bpc 1/8/16
   return pxm;

erret:
   if (fid) fclose(fid);
   snprintf(file_errmess,999,"PNG file read error: %s",file);
   printz("%s\n",file_errmess);
   return 0;
}


//  Write PXM to PNG file using PNG library.
//  File bpc is 8 or 16.
//  returns 0 if OK, +N if error.

int PXM_PNG_save(PXM *pxm, cchar *file, int bpc)
{
   png_structp    pngstruct;
   png_infop      pnginfo;
   FILE           *fid;
   uchar          **pngrows;
   uint8          *png8;
   uint16         *png16;
   float          *pix;
   int            ww, hh, nc, ac, cc, row, col;

   *file_errmess = 0;

   if (bpc != 8 && bpc != 16) {                                                  //  15.09
      snprintf(file_errmess,999,"PNG file BPC not 8/16: %s",file);
      printz("%s\n",file_errmess);
      return 2;
   }

   ww = pxm->ww;
   hh = pxm->hh;
   nc = pxm->nc;                                                                 //  15.08
   ac = nc - 3;
   
   fid = fopen(file,"w");                                                        //  open output file
   if (! fid) goto erret;

   pngstruct = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);             //  create png structs
   if (! pngstruct) goto erret;
   pnginfo = png_create_info_struct(pngstruct);
   if (! pnginfo) goto erret;
   if (setjmp(png_jmpbuf(pngstruct))) goto erret;                                //  setup error handling

   png_init_io(pngstruct,fid);                                                   //  initz. for writing file

   png_set_compression_level(pngstruct,1);                                       //  15.09

   if (ac) 
      png_set_IHDR(pngstruct,pnginfo,ww,hh,bpc,PNG_COLOR_TYPE_RGB_ALPHA,0,0,0);
   else
      png_set_IHDR(pngstruct,pnginfo,ww,hh,bpc,PNG_COLOR_TYPE_RGB,0,0,0);

   pngrows = (uchar **) zmalloc(hh * sizeof(char *));                            //  allocate rows of RGB data
   if (bpc == 8) cc = ww * nc;
   else cc = ww * nc * 2;
   for (row = 0; row < hh; row++)
      pngrows[row] = (uchar *) zmalloc(cc);

   png_set_rows(pngstruct,pnginfo,pngrows);                                      //  array of png row pointers

   if (bpc == 8)                                                                 //  8 bit RGB
   {
      for (row = 0; row < hh; row++)
      {
         png8 = (uint8 *) pngrows[row];
         pix = pxm->pixels + row * ww * nc;

         for (col = 0; col < ww; col++)
         {
            png8[0] = pix[0];                                                    //  0.0 - 255.996 >> 0-255
            png8[1] = pix[1];
            png8[2] = pix[2];
            if (ac) png8[3] = pix[3];
            png8 += nc;
            pix += nc;
         }
      }
   }

   else if (bpc == 16)                                                           //  16 bit RGB
   {
      for (row = 0; row < hh; row++)
      {
         png16 = (uint16 *) pngrows[row];
         pix = pxm->pixels + row * ww * nc;

         for (col = 0; col < ww; col++)
         {
            png16[0] = 256.0 * pix[0];                                           //  0.0 - 255.996 >> 0 - 65535
            png16[1] = 256.0 * pix[1];
            png16[2] = 256.0 * pix[2];
            if (ac) png16[3] = 256.0 * pix[3];
            png16 += nc;
            pix += nc;
         }
      }
   }

   png_write_png(pngstruct,pnginfo,PNG_TRANSFORM_SWAP_ENDIAN,0);                 //  write the file
   fclose(fid);

   png_destroy_write_struct(&pngstruct,&pnginfo);                                //  release memory

   for (row = 0; row < hh; row++)
      zfree(pngrows[row]);
   zfree(pngrows);

   return 0;

erret:
   if (fid) fclose(fid);
   snprintf(file_errmess,999,"PNG file write error: %s",file);
   printz("%s\n",file_errmess);
   return 2;
}


/********************************************************************************
   JPEG and other file types read and write functions
*********************************************************************************/

//  Load PXB from JPEG and other file types using the pixbuf library.
//  bpc = 8.

PXB * ANY_PXB_load(cchar *file)
{
   GError         *gerror = 0;
   PXB            *pxb;
   PIXBUF         *pixbuf;
   int            ww, hh, px, py, nc, ac, rs;
   uint8          *pixels1, *pix1;
   uint8          *pixels2, *pix2;

   zthreadcrash();

   *file_errmess = 0;

   pixbuf = gdk_pixbuf_new_from_file(file,&gerror);
   if (! pixbuf) {
      if (gerror) printz("%s\n",gerror->message);
      snprintf(file_errmess,999,"(pixbuf) file read error: %s",file);
      printz("%s\n",file_errmess);
      return 0;
   }

   ww = gdk_pixbuf_get_width(pixbuf);
   hh = gdk_pixbuf_get_height(pixbuf);
   nc = gdk_pixbuf_get_n_channels(pixbuf);                                       //  15.08
   ac = gdk_pixbuf_get_has_alpha(pixbuf);
   rs = gdk_pixbuf_get_rowstride(pixbuf);
   pixels1 = gdk_pixbuf_get_pixels(pixbuf);

   if (ww > wwhh_limit1 || hh > wwhh_limit1 || ww * hh > wwhh_limit2) {          //  16.03
      zmessageACK(Mwin,"image too big, %dx%d",ww,hh);
      g_object_unref(pixbuf);
      return 0;
   }

   pxb = PXB_make(ww,hh,ac);                                                     //  16.03
   if (! pxb) {
      g_object_unref(pixbuf);
      return 0;
   }

   pixels2 = pxb->pixels;

   for (py = 0; py < hh; py++)
   {
      pix1 = pixels1 + rs * py;
      pix2 = pixels2 + py * pxb->rs;

      if (nc >= 3)
      {
         for (px = 0; px < ww; px++)
         {
            memcpy(pix2,pix1,3);
            if (ac) pix2[3] = pix1[3];
            pix1 += nc;
            pix2 += 3 + ac;
         }
      }

      else
      {
         for (px = 0; px < ww; px++)
         {
            pix2[0] = pix2[1] = pix2[2] = pix1[0];
            if (ac) pix2[3] = pix1[1];
            pix1 += nc;
            pix2 += 3 + ac;
         }
      }
   }

   f_load_bpc = 8;                                                               //  file bits per color
   g_object_unref(pixbuf);

   return pxb;
}


//  Load PXM from JPEG and other file types using the pixbuf library.
//  bpc = 8.

PXM * ANY_PXM_load(cchar *file)
{
   GError         *gerror = 0;
   PXM            *pxm;
   PIXBUF         *pixbuf;
   int            ww, hh, px, py, nc, ac, rowst;
   uint8          *pixels1, *pix1;
   float          *pixels2, *pix2;
   static int     ftf = 1;
   static float   ftab[256];

   zthreadcrash();

   if (ftf) {                                                                    //  table lookup for speed
      ftf = 0;
      for (int ii = 0; ii < 256; ii++)
         ftab[ii] = ii;
   }

   *file_errmess = 0;

   pixbuf = gdk_pixbuf_new_from_file(file,&gerror);
   if (! pixbuf) {
      if (gerror) printz("%s\n",gerror->message);
      snprintf(file_errmess,999,"(pixbuf) file read error: %s",file);
      printz("%s\n",file_errmess);
      return 0;
   }

   ww = gdk_pixbuf_get_width(pixbuf);
   hh = gdk_pixbuf_get_height(pixbuf);
   nc = gdk_pixbuf_get_n_channels(pixbuf);                                       //  15.08
   ac = gdk_pixbuf_get_has_alpha(pixbuf);
   rowst = gdk_pixbuf_get_rowstride(pixbuf);
   pixels1 = gdk_pixbuf_get_pixels(pixbuf);

   pxm = PXM_make(ww,hh,3+ac);
   if (! pxm) {                                                                  //  16.01
      g_object_unref(pixbuf);
      return 0;
   }
      
   pixels2 = pxm->pixels;

   for (py = 0; py < hh; py++)
   {
      pix1 = pixels1 + rowst * py;
      pix2 = pixels2 + py * ww * (3+ac);

      if (nc >= 3)
      {
         for (px = 0; px < ww; px++)
         {
            pix2[0] = ftab[pix1[0]];                                             //  0-255  >>  0.0-255.0
            pix2[1] = ftab[pix1[1]];
            pix2[2] = ftab[pix1[2]];
            if (ac) pix2[3] = ftab[pix1[3]]; 
            pix1 += nc;
            pix2 += 3+ac;
         }
      }

      else
      {
         for (px = 0; px < ww; px++)
         {
            pix2[0] = pix2[1] = pix2[2] = ftab[pix1[0]];
            if (ac) pix2[3] = ftab[pix1[1]];
            pix1 += nc;
            pix2 += 3+ac;
         }
      }
   }

   f_load_bpc = 8;                                                               //  file bits per color
   g_object_unref(pixbuf);

   return pxm;
}


//  Write PXM to JPEG and other file types using the pixbuf library.
//  bpc = 8.
//  returns 0 if OK, +N if error.

int PXM_ANY_save(PXM *pxm, cchar *file)
{
   int         ww, hh, nc, ac, px, py, rowst;
   float       *pixels1, *pix1;
   char        txqual[8];
   uint8       *pixels2, *pix2;
   PIXBUF      *pixbuf;
   cchar       *pext;
   GError      *gerror = 0;
   int         pxbstat;

   zthreadcrash();

   *file_errmess = 0;

   ww = pxm->ww;
   hh = pxm->hh;
   nc = pxm->nc;                                                                 //  15.08
   ac = nc - 3;

   pixbuf = gdk_pixbuf_new(GDKRGB,ac,8,ww,hh);
   if (! pixbuf) zappcrash("pixbuf allocation failure");

   pixels1 = pxm->pixels;
   pixels2 = gdk_pixbuf_get_pixels(pixbuf);
   rowst = gdk_pixbuf_get_rowstride(pixbuf);

   for (py = 0; py < hh; py++)
   {
      pix1 = pixels1 + py * ww * nc;
      pix2 = pixels2 + rowst * py;

      for (px = 0; px < ww; px++)
      {
         pix2[0] = pix1[0];                                                      //  0.0 - 255.996  >>  0 - 255
         pix2[1] = pix1[1];
         pix2[2] = pix1[2];
         if (ac) pix2[3] = pix1[3];
         pix1 += nc;
         pix2 += nc;
      }
   }

   pext = strrchr(file,'/');
   if (! pext) pext = file;
   pext = strrchr(pext,'.');
   if (! pext) pext = "";

   if (strstr(".png .PNG",pext))                                                 //  compression level                  15.09
      pxbstat = gdk_pixbuf_save(pixbuf,file,"png",&gerror,"compression","1",null);

   else {
      snprintf(txqual,8,"%d",jpeg_1x_quality);                                   //  in case user deviates from default
      pxbstat = gdk_pixbuf_save(pixbuf,file,"jpeg",&gerror,"quality",txqual,null);
      jpeg_1x_quality = jpeg_def_quality;                                        //  reset to default after use
   }

   g_object_unref(pixbuf);
   if (pxbstat) return 0;

   if (gerror) printz("%s\n",gerror->message);
   snprintf(file_errmess,999,"(pixbuf) file write error: %s",file);
   printz("%s\n",file_errmess);
   return 2;
}


/********************************************************************************
   RAW file read functions (there are no write functions)
*********************************************************************************/

//  RAW file to PXB pixmap (pixbuf, 8-bit color)

PXB * RAW_PXB_load(cchar *rawfile)                                               //  use libraw                         16.07
{
   libraw_data_t  *librawdata = 0;
   libraw_processed_image_t   *RGBimage = 0;

   PXB      *pxb = 0;
   int      err, ww, hh, nc, rs, bpc, row, col;
   uint8    *rgbpixels, *pix1;
   uint16   *rgbpixelsx, *pix1x;
   uint8    *pxbpixels, *pix2;

   librawdata = libraw_init(0);

   err = libraw_open_file(librawdata,rawfile);
   if (err) goto libraw_err1;

   err = libraw_unpack(librawdata);
   if (err) goto libraw_err1;

   librawdata->params.use_camera_wb = 1;                                         //  camera white balance
   librawdata->params.output_color = 1;                                          //  sRGB
   librawdata->params.output_bps = 8;
   librawdata->params.output_tiff = 0;
   librawdata->params.user_qual = 0;                                             //  bilinear interpolation

   err = libraw_dcraw_process(librawdata);
   if (err) goto libraw_err1;
   
   RGBimage = libraw_dcraw_make_mem_image(librawdata,&err);
   if (! RGBimage) goto libraw_err2;
   if (RGBimage->type != LIBRAW_IMAGE_BITMAP) goto libraw_err2;

   ww = RGBimage->width;
   hh = RGBimage->height;
   nc = RGBimage->colors;                                                        //  1 or 3 color channels
   bpc = RGBimage->bits;                                                         //  8 or 16 bits/color
   rgbpixels = RGBimage->data;
   rgbpixelsx = (uint16 *) rgbpixels;
   
   if (nc != 1 && nc != 3) goto libraw_err3;
   if (bpc != 8 && bpc != 16) goto libraw_err3;

   pxb = PXB_make(ww,hh,0);
   rs = pxb->rs;
   pxbpixels = pxb->pixels;
   
   if (nc == 3 && bpc == 8)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1 = rgbpixels + row * ww * 3 + col * 3;
         pix2 = pxbpixels + row * rs + col * 3;
         pix2[0] = pix1[0];
         pix2[1] = pix1[1];
         pix2[2] = pix1[2];
      }
   }

   else if (nc == 1 && bpc == 8)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1 = rgbpixels + row * ww + col;
         pix2 = pxbpixels + row * rs + col * 3;
         memset(pix2,pix1[0],3);
      }
   }

   else if (nc == 3 && bpc == 16)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1x = rgbpixelsx + row * ww * 3 + col * 3;
         pix2 = pxbpixels + row * rs + col * 3;
         pix2[0] = pix1x[0] >> 8;
         pix2[1] = pix1x[1] >> 8;
         pix2[2] = pix1x[2] >> 8;
      }
   }

   else if (nc == 1 && bpc == 16)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1x = rgbpixelsx + row * ww + col;
         pix2 = pxbpixels + row * rs + col * 3;
         pix2[0] = pix2[1] = pix2[2] = pix1x[0] >> 8;
      }
   }
   
   libraw_dcraw_clear_mem(RGBimage);
   libraw_close(librawdata);
   f_load_bpc = bpc;
   return pxb;

libraw_err1:
   zmessageACK(Mwin,"libraw error %d",err);
   goto cleanup;

libraw_err2:
   zmessageACK(Mwin,"libraw failure");
   goto cleanup;

libraw_err3:
   zmessageACK(Mwin,"image is not 8 or 16 bits and 1 or 3 colors");
   goto cleanup;

cleanup:
   if (pxb) PXB_free(pxb);
   if (RGBimage) libraw_dcraw_clear_mem(RGBimage);
   if (librawdata) libraw_close(librawdata);
   return 0;
}


//  RAW file to PXM pixmap (float color)

PXM * RAW_PXM_load(cchar *rawfile)                                               //  use libraw                         16.07
{
   libraw_data_t  *librawdata = 0;
   libraw_processed_image_t   *RGBimage = 0;

   PXM      *pxm = 0;
   int      err, ww, hh, nc, bpc, row, col;
   uint8    *rgbpixels, *pix1;
   uint16   *rgbpixelsx, *pix1x;
   float    *pxmpixels, *pix2;

   librawdata = libraw_init(0);

   err = libraw_open_file(librawdata,rawfile);
   if (err) goto libraw_err1;

   err = libraw_unpack(librawdata);
   if (err) goto libraw_err1;

   librawdata->params.use_camera_wb = 1;                                         //  camera white balance
   librawdata->params.output_color = 1;                                          //  sRGB
   librawdata->params.output_bps = 16;
   librawdata->params.output_tiff = 0;
   librawdata->params.user_qual = 0;                                             //  bilinear interpolation

   err = libraw_dcraw_process(librawdata);
   if (err) goto libraw_err1;
   
   RGBimage = libraw_dcraw_make_mem_image(librawdata,&err);
   if (! RGBimage) goto libraw_err2;
   if (RGBimage->type != LIBRAW_IMAGE_BITMAP) goto libraw_err2;

   ww = RGBimage->width;
   hh = RGBimage->height;
   nc = RGBimage->colors;                                                        //  1 or 3 color channels
   bpc = RGBimage->bits;                                                         //  8 or 16 bits/color
   rgbpixels = RGBimage->data;
   rgbpixelsx = (uint16 *) rgbpixels;
   
   if (nc != 1 && nc != 3) goto libraw_err3;
   if (bpc != 8 && bpc != 16) goto libraw_err3;

   pxm = PXM_make(ww,hh,3);
   pxmpixels = pxm->pixels;
   
   if (nc == 3 && bpc == 8)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1 = rgbpixels + row * ww * 3 + col * 3;
         pix2 = pxmpixels + row * ww * 3 + col * 3;
         pix2[0] = pix1[0];
         pix2[1] = pix1[1];
         pix2[2] = pix1[2];
      }
   }

   else if (nc == 1 && bpc == 8)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1 = rgbpixels + row * ww + col;
         pix2 = pxmpixels + row * ww * 3 + col * 3;
         pix2[0] = pix2[1] = pix2[2] = pix1[0];
      }
   }

   else if (nc == 3 && bpc == 16)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1x = rgbpixelsx + row * ww * 3 + col * 3;
         pix2 = pxmpixels + row * ww * 3 + col * 3;
         pix2[0] = 0.0039 * pix1x[0];                                            //  0 - 65535  >>  0.0 - 255.6
         pix2[1] = 0.0039 * pix1x[1];
         pix2[2] = 0.0039 * pix1x[2];
      }
   }

   else if (nc == 1 && bpc == 16)
   {
      for (row = 0; row < hh; row++)
      for (col = 0; col < ww; col++)
      {
         pix1x = rgbpixelsx + row * ww + col;
         pix2 = pxmpixels + row * ww * 3 + col * 3;
         pix2[0] = pix2[1] = pix2[2] = 0.0039 * pix1x[0];
      }
   }
   
   libraw_dcraw_clear_mem(RGBimage);
   libraw_close(librawdata);
   f_load_bpc = bpc;
   return pxm;

libraw_err1:
   zmessageACK(Mwin,"libraw error %d",err);
   goto cleanup;

libraw_err2:
   zmessageACK(Mwin,"libraw failure");
   goto cleanup;

libraw_err3:
   zmessageACK(Mwin,"image is not 8 or 16 bits and 1 or 3 colors");
   goto cleanup;

cleanup:
   if (pxm) PXM_free(pxm);
   if (RGBimage) libraw_dcraw_clear_mem(RGBimage);
   if (librawdata) libraw_close(librawdata);
   return 0;
}



