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

   Fotocx - edit photos and manage collections

   Copyright 2007-2026 Michael Cornelison
   source code URL: https://kornelix.net
   contact: mkornelix@gmail.com

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

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

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

   Fotocx image edit - developer functions

   m_zmalloc_report        report memory allocated by tag name
   m_zmalloc_growth        report memory increases by tag name
   m_mouse_events          show mouse events popup text
   m_zappcrash_test        zappcrash test
   m_editfunc_template     template to develop new edit function

   m_capture_GUI           capture translatable texts from source code files --> gui_en
   m_slice_GUI             slice gui_en into pieces for AI translator: gui_en_nn (nn = 01, 02 ...) 
   AI translation          gui_en_nn --> AI-translate --> gui_xx_nn, for language code xx
                           combine gui_xx_nn pieces into single gui_xx
   m_transtab_GUI          build translation table from gui_en and gui_xx --> new gui_xx
   m_audit_GUI             audit gui_xx for matching pairs abd format codes
   
   m_slice_UG              slice ug_en into pieces for AI translator: ug_en_nn (nn = 01, 02 ...) 
   AI translation          ug_en_nn --> AI-translate --> ug_xx_nn, for language code xx
                           combine ug_xx_nn pieces into single ug_xx
   m_sync_UG               sync ug_xx translations with gui_xx translations
   m_audit_UG              check ug_xx, all topic references are present
   UG_extra_topics         UG topics not present in other source programs

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

#define EX extern                                                                      //  enable extern declarations
#include "fotocx.h"

using namespace zfuncs;

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

//  popup report of zmalloc() memory allocation per tag

void m_zmalloc_report(GtkWidget *, ch *)                                               //  25.1
{
   zmalloc_report(Mwin);
   return;
}


//  popup report of zmalloc() memory allocation per tag
//  report only tags showing increased allocation from prior max.

void m_zmalloc_growth(GtkWidget *, ch *)                                               //  25.1
{
   zmalloc_growth(Mwin);
   return;
}


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

//  toggle: show mouse events with popup text next to mouse pointer

void m_mouse_events(GtkWidget *, ch *)
{
   Fmousevents = 1 - Fmousevents;
   return;
}


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

//  zappcrash test - make a segment fault
//  test with: $ fotocx -d -m "zappcrash test"

void m_zappcrash_test(GtkWidget *, ch *)
{
   int   *nulladdr = 0;

   printf("*** zappcrash test \n");
   zfuncs::zappcrash_context1 = "zappcrash test";
   zfuncs::zappcrash_context2 = "zappcrash test";
   printf("zappcrash test, ref. null address: %d \n",*nulladdr);
   return;
}


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

//  minimal edit function to use as a template for coding new functions

namespace template_names
{
   editfunc    EFtemplate;
   float       userinput;                                                              //  user input value, -1 ... +1
}


//  menu function

void m_editfunc_template(GtkWidget *, ch *menu)
{
   using namespace template_names;

   int    template_dialog_event(zdialog* zd, ch  *event);
   void   template_curvedit(int spc);
   void * template_thread(void *);

   GtkWidget   *drawwin_scale;                                                         //  drawing window for brightness scale

   printf("m_template \n");

   EFtemplate.menuname = "Template";
   EFtemplate.menufunc = m_editfunc_template;
   EFtemplate.FprevReq = 1;                                                            //  use preview
   EFtemplate.Farea = 2;                                                               //  select area usable
   EFtemplate.Fpaintedits = 1;                                                         //  use with paint edits OK
   EFtemplate.Fscript = 1;                                                             //  scripting supported
   EFtemplate.threadfunc = template_thread;

   if (! edit_setup(EFtemplate)) return;                                               //  setup edit

/***
       _______________________________________________
      |                   Template                    |
      |  ___________________________________________  |
      | |                                           | |
      | |             curve edit area               | |
      | |                                           | |
      | | ----------------------------------------- | |                                //  editable function curve
      | |                                           | |
      | |                                           | |
      | |___________________________________________| |
      | |___________________________________________| |                                //  black to white brightness scale
      |                                               |
      | input  =================[]==================  |                                //  user input value
      |                                               |
      |                              [Reset] [OK] [X] |
      |_______________________________________________|

***/

   zdialog *zd = zdialog_new(TX("template"),Mwin,TX("Reset"),"OK","X",null);
   EFtemplate.zd = zd;

   zdialog_add_widget(zd,"frame","frameH","dialog",0,"expand");                        //  edit-curve
   zdialog_add_widget(zd,"frame","frameB","dialog");                                   //  brightness scale

   zdialog_add_widget(zd,"hbox","hbsat","dialog",0,"space=5|expand");
   zdialog_add_widget(zd,"label","labsat","hbsat",TX("input"));
   zdialog_add_widget(zd,"hscale","userinput","hbsat","-1.0|1.0|0.01|0.0","expand");

   GtkWidget *frameH = zdialog_gtkwidget(zd,"frameH");                                 //  setup edit curves
   spldat *sd = splcurve_init(frameH,template_curvedit);
   EFtemplate.sd = sd;

   sd->Nscale = 1;                                                                     //  horizontal line, neutral value
   sd->xscale[0][0] = 0.00;
   sd->yscale[0][0] = 0.50;
   sd->xscale[1][0] = 1.00;
   sd->yscale[1][0] = 0.50;

   sd->nap[0] = 3;                                                                     //  initial curve is neutral
   sd->vert[0] = 0;
   sd->apx[0][0] = 0.01;                                                               //  horizontal line
   sd->apy[0][0] = 0.50;
   sd->apx[0][1] = 0.50;
   sd->apy[0][1] = 0.50;                                                               //  curve 0 = overall brightness
   sd->apx[0][2] = 0.99;
   sd->apy[0][2] = 0.50;
   splcurve_generate(sd,0);
   sd->mod[0] = 0;                                                                     //  mark curve unmodified

   sd->Nspc = 1;                                                                       //  1 curve
   sd->fact[0] = 1;                                                                    //  curve 0 active

   GtkWidget *frameB = zdialog_gtkwidget(zd,"frameB");                                 //  setup drawing area
   drawwin_scale = gtk_drawing_area_new();
   gtk_container_add(GTK_CONTAINER(frameB),drawwin_scale);
   gtk_widget_set_size_request(drawwin_scale,100,12);
   G_SIGNAL(drawwin_scale,"draw",brightness_scale,0);

   userinput = 0;                                                                      //  neutral value

   zdialog_resize(zd,350,300);
   zdialog_run(zd,template_dialog_event,"save");                                       //  run dialog - parallel

   return;
}


//  dialog event and completion callback function

int template_dialog_event(zdialog *zd, ch  *event)
{
   using namespace template_names;

   spldat      *sd = EFtemplate.sd;
   float       input0, dinput, dy;
   float       Fapply = 0;
   int         ii;

   if (strmatch(event,"done")) zd->zstat = 2;                                          //  apply and quit
   if (strmatch(event,"cancel")) zd->zstat = 3;                                        //  cancel
   if (strmatch(event,"apply")) Fapply = 1;                                            //  from script
   if (strmatch(event,"paint")) Fapply = 1;                                            //  mouse paint

   if (strmatch(event,"fullsize")) {                                                   //  from select area
      edit_fullsize();                                                                 //  get full size image
      thread_signal();
      thread_wait();                                                                   //  required for paint edits
      return 1;
   }

   if (zd->zstat == 1)                                                                 //  [reset]
   {
      zd->zstat = 0;                                                                   //  keep dialog active

      zdialog_stuff(zd,"userinput",0);                                                 //  neutral value
      userinput = 0;

      sd->nap[0] = 3;                                                                  //  curve is neutral
      sd->vert[0] = 0;
      sd->apx[0][0] = 0.01;
      sd->apy[0][0] = 0.50;
      sd->apx[0][1] = 0.50;
      sd->apy[0][1] = 0.50;
      sd->apx[0][2] = 0.99;
      sd->apy[0][2] = 0.50;
      splcurve_generate(sd,0);
      sd->mod[0] = 0;                                                                  //  mark curve unmodified

      gtk_widget_queue_draw(sd->drawarea);                                             //  redraw curves

      edit_reset();
      return 1;
   }

   if (zd->zstat == 2)                                                                 //  [OK]
   {
      freeMouse();                                                                     //  free mouse if curve edit active

      if (CEF->Fmods) {
         edit_fullsize();                                                              //  get full size image
         thread_signal();                                                              //  apply changes
         edit_done(0);                                                                 //  complete edit
      }

      else edit_cancel(0);                                                             //  no change
      return 1;
   }

   if (zd->zstat < 0 || zd->zstat > 2) {                                               //  cancel
      edit_cancel(0);
      return 1;
   }

   if (strmatch(event,"userinput"))                                                    //  input slider -1 ... +1
   {
      input0 = userinput;                                                              //  old value
      zdialog_fetch(zd,"userinput",userinput);                                         //  new value
      dinput = userinput - input0;                                                     //  change, + -

      for (ii = 0; ii < sd->nap[0]; ii++)                                              //  update curve 0 nodes from input value
      {
         dy = sd->apy[0][ii] + 0.5 * dinput;
         if (dy < 0) dy = 0;
         if (dy > 1) dy = 1;
         sd->apy[0][ii] = dy;
      }

      splcurve_generate(sd,0);                                                         //  regenerate curve 0
      gtk_widget_queue_draw(sd->drawarea);                                             //  draw curve 0
      Fapply = 1;
   }

   if (Fapply) thread_signal();                                                        //  update the image

   return 1;
}


//  this function is called when the curve is edited

void template_curvedit(int spc)
{
   using namespace template_names;
   thread_signal();
   return;
}


//  thread function

void * template_thread(void *arg)
{
   using namespace template_names;

   void * template_wthread(void *);

   if (Fpaintedits && ! Mdrag) return 0;                                               //  if mouse paint, must be drag

   get_edit_pixels_init(NSMP,0);                                                       //  initz. pixel loop

   do_wthreads(template_wthread,NSMP);

   CEF->Fmods++;                                                                       //  image3 modified
   CEF->Fsaved = 0;

   if (! Fpaintedits) Fpaint2();                                                       //  update window
   return 0;
}


void * template_wthread(void *arg)                                                     //  worker thread function
{
   using namespace template_names;

   int         index = *((int *) arg);
   int         ii, Fend, px, py;
   float       *pix1, *pix3;
   float       R1, G1, B1, R3, G3, B3, R9, G9, B9;
   float       pixbrite, userinput;
   float       coeff = 1000.0 / 256.0;
   float       blend;
   spldat      *sd = EFtemplate.sd;

   while (true)                                                                        //  loop all edit pixels
   {
      Fend = get_edit_pixels(index,px,py,blend);
      if (Fend) break;
      if (blend == 0) continue;

      pix1 = PXMpix(E1pxm,px,py);                                                      //  input pixel
      pix3 = PXMpix(E3pxm,px,py);                                                      //  output pixel

      R1 = R9 = pix1[0];                                                               //  input RGB values
      G1 = G9 = pix1[1];
      B1 = B9 = pix1[2];

      R3 = pix3[0];                                                                    //  current output RGB
      G3 = pix3[1];
      B3 = pix3[2];

      if (sd->mod[0])                                                                  //  curve was modified
      {
         pixbrite = 0.333 * (R3 + G3 + B3);                                            //  pixel brightness, 0 to 255.9
         ii = coeff * pixbrite;                                                        //  curve index 0-999
         if (ii < 0) ii = 0;
         if (ii > 999) ii = 999;
         userinput = 2.0 * sd->yval[0][ii] - 1.0;                                      //  -1 ... 0 ... +1

         R9 = R9 + userinput * (R9 - pixbrite);                                        //  new output RGB
         G9 = G9 + userinput * (G9 - pixbrite);
         B9 = B9 + userinput * (B9 - pixbrite);
      }

      RGBfix(R9,G9,B9);                                                                //  clamp values within 0-255

      if (Fpaintedits)                                                                 //  gradual edit within mouse circle
      {
         if (blend > 0)
         {
            R3 = blend * R9 + (1-blend) * R3;                                          //  increase edit
            G3 = blend * G9 + (1-blend) * G3;
            B3 = blend * B9 + (1-blend) * B3;
         }

         else if (blend < 0)
         {
            R3 = -blend * R1 + (1+blend) * R3;                                         //  decrease edit
            G3 = -blend * G1 + (1+blend) * G3;
            B3 = -blend * B1 + (1+blend) * B3;
         }
      }

      else                                                                             //  full edit for image or select area
      {
         R3 = blend * R9 + (1-blend) * R1;                                             //  output = blend of input and output
         G3 = blend * G9 + (1-blend) * G1;                                             //  (select area edge blending)
         B3 = blend * B9 + (1-blend) * B1;
      }

      pix3[0] = R3;                                                                    //  set output pixel
      pix3[1] = G3;
      pix3[2] = B3;
   }

   return 0;
}


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

//  limits for following translation functions

#define KBlimit   28                                                                   //  AI translation slice size, KB
#define guixcc    200                                                                  //  max. GUI text cc
#define ugtpxcc   200                                                                  //  max. UG topic cc
#define ugxcc     5000                                                                 //  max. UG text cc

ch  sourcefolder[] = "/home2/mico/programs/            ";                              //  fotocx source folder, parent


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

   TRANSLATE SUSPEND         Omit following code from source code translation scan.
                             Contains tokens: "TX("  that must be ignored.

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

//  Scan source code for translatable texts, marked with TX("english text").
//  Sort and eliminate duplicates. Output file: <fotocx home>/translations/gui_en
//  Format: "NNNN english text" where NNNN is a sequence number.
//  The english text can contain printf format codes: %s %d %f \n

void m_capture_GUI(GtkWidget *, ch *)
{
   FILE     *fid, *fid1, *fid2, *fid3;
   ch       command[300], buff500[500], buff300[300];
   ch       text999[1000];
   ch       *sourcefile;
   ch       gui_en[200];                                                               //  output file: .../translations/gui_en
   ch       *pp, *pp1, *pp2;
   int      Tnum, cc, nn, ii, jj;
   int      ndups, nuniq;
   zlist_t  *zlist1 = 0;
   
   pp = strrchr(sourcefolder,'/');                                                     //  fotocx source folder, parent
   strcpy(pp+1,Frelease);                                                              //  add my release NN.N

   snprintf(gui_en,200,"%s/gui_en",translations_folder);                               //  output file name

   snprintf(command,300,"find \"%s\" -maxdepth 1 -name \"*.cc\" ",sourcefolder);       //  find source files, *.cc
   printf("find command: %s \n",command);

   fid1 = popen(command,"r");
   if (! fid1) {
      zmessageACK(Mwin,"'find' command failure: %s",sourcefolder);
      return;
   }

   fid2 = fopen(gui_en,"w");                                                           //  open gui_en file for write
   if (! fid2) {                                                                       //  wrong folder?
      zmessageACK(Mwin,"cannot open %s output file",gui_en);
      return;
   }

   Tnum = 0;                                                                           //  count translatable texts found

   while (true)                                                                        //  loop all source files
   {
      sourcefile = fgets_trim(buff300,300,fid1);                                       //  next source file
      if (! sourcefile) {                                                              //  no more
         pclose(fid1);                                                                 //  close sourcefolder read
         fclose(fid2);                                                                 //  close output file
         break;
      }

      printf("processing file %s \n",sourcefile);
      
      fid3 = fopen(sourcefile,"r");                                                    //  open source file
      if (! fid3) {
         pclose(fid1);                                                                 //  close sourcefolder read
         fclose(fid2);                                                                 //  close output file
         zmessageACK(Mwin,"cannot read source file: %s",sourcefile);
         return;
      }      

      while (true)                                                                     //  loop all source file lines
      {
         pp = fgets(buff500,500,fid3);                                                 //  read source file line
         if (! pp) {
            fclose(fid3);                                                              //  EOF
            break;
         }
         
         if (strstr(pp,"TRANSLATE SUSPEND")) {                                         //  skip this code block with 
            while (true) {                                                             //    misleading TX(" tokens
               pp = fgets(buff500,500,fid3);
               if (strstr(pp,"TRANSLATE " "RESUME")) break;                            //  segmented to prevent detection
            }                                                                          //    by this function
            continue;
         }
         
         pp1 = strstr(pp,"TX(\"");                                                     //  look for text start TX("
         if (! pp1) continue;

      startscan:
         Tnum++;
         pp1 += 4;                                                                     //  skip TX(", start text
         pp2 = pp1;                                                                    //  initial end of text
         
         while (true)                                                                  //  scan text
         {
            if (*pp2 == 0) break;                                                      //  invalid, no text end found
            else if (*pp2 == '\\') pp2 += 2;                                           //  skip possible \"
            else if (strmatchN(pp2,"TX(",3)) break;                                    //  invalid, no text end found
            else if (*pp2 == '"' && *(pp2+1) == ')') {                                 //  found: ")  text end
               cc = pp2 - pp1;
               pp1[cc] = 0;
               fprintf(fid2,"%s\n",pp1);                                               //  output rest of text + \n
               pp = pp2 + 2;
               pp1 = strstr(pp,"TX(\"");                                               //  continue scan of same line
               if (pp1) goto startscan;                                                //  found TX("
               break;                                                                  //  EOL
            }
            else if (*pp2 == '"') {                                                    //  found: "  continuation line follows
               *pp2 = 0;
               fprintf(fid2,"%s",pp1);                                                 //  output text
               pp = fgets(buff500,500,fid3);                                           //  read next line
               if (! pp) break;
               while (*pp == ' ') pp++;                                                //  skip leading blanks
               if (*pp != '"') break;                                                  //  must start with "
               pp1 = pp2 = pp + 1;                                                     //  continue scan
            }
            else pp2++;
         }
      }
   }

   if (! Tnum) {
      zmessageACK(Mwin,"no translatable texts found");
      goto cleanup1;
   }

//  sort text strings in gui_en and eliminate duplicates

   zlist1 = zlist_from_file(gui_en);                                                   //  load zlist1 from file gui_en
   if (! zlist1) {                                                                     //  gui_en file missing
      zmessageACK(Mwin,"%s file is missing",gui_en);
      goto cleanup1;
   }

   nn = zlist_count(zlist1);
   if (! nn) {                                                                         //  gui_en file empty
      zmessageACK(Mwin,"%s file is empty",gui_en);
      goto cleanup1;
   }

   if (nn > 1) zlist_sort(zlist1);                                                     //  sort zlist alphabetically
   
   for (ii = 1, jj = 0; ii < nn; ii++) {                                               //  scan from text[1] to end
      strncpy0(text999,zlist_get(zlist1,ii),999);
      if (strmatch(text999,zlist_get(zlist1,jj))) continue;                            //  skip duplicate
      zlist_put(zlist1,text999,++jj);                                                  //  output non-duplicate
   }
               
   ndups = nn - jj - 1;                                                                //  duplicates removed
   nuniq = jj + 1;                                                                     //  remaining = new count
   zmessageACK(Mwin,"translatable texts found: %d \n"
                    "duplicates removed: %d \n"
                    "unique translatable texts: %d",Tnum,ndups,nuniq);

//  output gui_en file, with sequence numbers

   fid = fopen(gui_en,"w");
   if (! fid) {                                                                        //  cannot write file
      zmessageACK(Mwin,"cannot open %s output file",gui_en);
      goto cleanup1;
   }

   for (ii = 0; ii < nuniq; ii++) {
      fprintf(fid,"%04d ",ii);                                                         //  4 digit sequence number + blank
      fprintf(fid,"%s\n",zlist_get(zlist1,ii));                                        //    + text string
   }
   
   fclose(fid);

cleanup1:
   if (zlist1) zlist_free(zlist1);
   return;
}


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

//  Slice gui_en into pieces suitable for AI translation.
//  output: gui_en_01, gui_en_02 ...

void m_slice_GUI(GtkWidget *, ch *)                                                    //  26.5
{
   ch       infile[200];                                                               //  gui_en input file
   ch       outfile[200];
   ch       record[guixcc];                                                            //  max. record size
   ch       *pp;
   FILE     *fid1, *fid2;
   int      ss, ccout, nn;
   
   snprintf(infile,200,"%s/gui_en",translations_folder);                               //  input file gui_en
   
   fid1 = fopen(infile,"r");                                                           //  open intput file
   if (! fid1) {
      printf("*** gui_en_new file not found: %s \n",infile);
      return;
   }
   
   ss = 0;
   
   while (true)
   {
      ss++;                                                                            //  next sequence number
      snprintf(outfile,200,"/home/mico/Desktop/gui_en_%02d",ss);                       //  next outfile .../gui_en_NN

      fid2 = fopen(outfile,"w");                                                       //  open next outfile
      if (! fid2) goto filerror;

      ccout = 0;                                                                       //  output cc
      
      while (true)
      {
         pp = fgets(record,guixcc,fid1);                                               //  get next input record
         if (! pp) break;                                                              //  input file EOF
         
         nn = fputs(record,fid2);                                                      //  add to output file
         if (nn < 0) goto filerror;

         ccout += strlen(record);                                                      //  accumulate output cc

         if (ccout > 1000 * KBlimit) break;                                            //  limit reached
      }
      
      fclose(fid2);                                                                    //  close output file

      if (! pp) break;                                                                 //  input EOF
   }
   
   fclose(fid1);                                                                       //  close input file
   return;


filerror:
   printf("*** output file error %s: %s \n",strerror(errno),outfile);
   fclose(fid1);
   return;
}


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

   Translation Process

   Files gui_en_nn are translated one by one to produce gui_xx_nn for language code xx.
   gui_xx_nn retains sequence numbers from gui_en_nn, so that the translated texts 
   can be matched to the english texts. All imbedded format codes must be retained, 
   and in the same sequence.
   
   After translation, combine the translated pieces: gui_xx_01, gui_xx_02 ... --> gui_xx 
   The lines and sequence numbers of gui_xx should match those of gui_en.

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

//  Combine the files gui_en and gui_xx to make a translation table containing 
//  both the english and translated texts. The output file is gui_en_xx.
//  gui_en_xx contains pairs of text lines with matching sequence numbers.
//       0001 english text containing imbedded format codes
//       0001 translated text with same format codes in same sequence
//       0002 next pair of text records, english and translation
//       0002 ... etc.

void m_transtab_GUI(GtkWidget *, ch *)
{
   ch       gui_en[200], gui_xx[200];
   ch       xx[4];                                                                     //  xx is 2-char. language code
   ch       *pp;
   zlist_t  *zlist_en = 0, *zlist_xx = 0, *zlist_en_xx = 0;
   int      nn, ii, id1, id2;
   
   pp = zdialog_text1(Mwin,"language code","en",4);                                    //  get 2-char. language code xx
   if (! pp || strlen(pp) != 2) {
      zmessageACK(Mwin,"invalid 2-char. language code");
      return;
   }
   
   strncpy0(xx,pp,3);                                                                  //  2 chars. + null

   snprintf(gui_en,200,"%s/gui_en",translations_folder);                               //  gui_en        input
   snprintf(gui_xx,200,"%s/gui_%s",translations_folder,xx);                            //  gui_xx        input and output

   zlist_en = zlist_from_file(gui_en);                                                 //  english texts
   if (! zlist_en) {
      zmessageACK(Mwin,"cannot find file %s - nothing done",gui_en);
      goto cleanup;
   }

   zlist_xx = zlist_from_file(gui_xx);                                                 //  translation texts
   if (! zlist_xx) {
      zmessageACK(Mwin,"cannot find file %s - nothing done",gui_xx);
      goto cleanup;
   }
   
   nn = zlist_count(zlist_en);
   if (! nn) {
      zmessageACK(Mwin,"english line count is zero");
      goto cleanup;
   }

   if (nn != zlist_count(zlist_xx)) {                                                  //  line counts must match
      zmessageACK(Mwin,"english and translation counts mismatch");
      goto cleanup;
   }
   
   zlist_en_xx = zlist_new(nn*2);                                                      //  en+xx output, 2x line count

   for (ii = 0; ii < nn; ii++)                                                         //  scan both inputs in parallel
   {
      id1 = atoi(zlist_get(zlist_en,ii));                                              //  sequence numbers must match
      id2 = atoi(zlist_get(zlist_xx,ii));
      if (id1 != id2) {
         zmessageACK(Mwin,"sequence number mismatch: %d %d",id1,id2);
         goto cleanup;
      }
      zlist_put(zlist_en_xx,zlist_get(zlist_en,ii),ii*2);                              //  english + translation texts
      zlist_put(zlist_en_xx,zlist_get(zlist_xx,ii),ii*2+1);                            //    --> output zlist
   }
   
   zlist_to_file(zlist_en_xx,gui_xx);                                                  //  output file gui_xx
   zmessageACK(Mwin,"completed");

cleanup:
   if (zlist_en) zlist_free(zlist_en);
   if (zlist_xx) zlist_free(zlist_xx);
   if (zlist_en_xx) zlist_free(zlist_en_xx);
   return;
}


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

//  Audit a translation file gui_xx to insure matching pairs of text records
//    with consistent imbedded format codes.

void m_audit_GUI(GtkWidget *, ch *)
{
   ch       gui_xx[200];                                                               //  input file
   ch       xx[4];
   ch       *pp, *pp1, *pp2, *pp3, *pp4;
   zlist_t  *zlist_xx;
   int      ii, Ferr;
   ch       text_en[guixcc], text_xx[guixcc];                                          //  max. GUI text cc

   pp = zdialog_text1(Mwin,"language code","en",4);                                    //  get 2-char. language code xx
   if (! pp || strlen(pp) != 2) {
      zmessageACK(Mwin,"invalid 2-char. language code");
      return;
   }
   strncpy0(xx,pp,3);                                                                  //  2 chars. + null

   snprintf(gui_xx,200,"%s/gui_%s",translations_folder,xx);                            //  translation file gui_xx

   zlist_xx = zlist_from_file(gui_xx);
   if (! zlist_xx) {
      zmessageACK(Mwin,"translation file not found: %s",gui_xx);
      return;
   }

   Ferr = 0;

   for (ii = 0; ii < zlist_count(zlist_xx)-1; ii += 2)                                 //  loop pairs of text
   {
      pp1 = zlist_get(zlist_xx,ii);                                                    //  text pair, english and translation
      pp2 = zlist_get(zlist_xx,ii+1);
      strncpy0(text_en,pp1,guixcc);
      strncpy0(text_xx,pp2,guixcc);

      if (! strmatchN(pp1,pp2,5)) {                                                    //  check ID numbers match
         zmessageACK(Mwin,"ID number missmatch, line: %d",ii);
         break;
      }

      pp3 = pp1;
      pp4 = pp2;

//  problem: '%' as character (not printf format) may be converted to '%%' by AI translator
//  this must be repaired manually

      while (true)                                                                     //  scan one en and xx line pair
      {
         while (*pp3 && *pp3 != '%' && *pp3 != '\\') pp3++;                            //  check '%' and '\' sequence match
         while (*pp4 && *pp4 != '%' && *pp4 != '\\') pp4++;
         if (! *pp3 && ! *pp4) break;                                                  //  lines done
         if (*pp3 == *pp4) {
            pp3++; pp4++;                                                              //  formats match, continue scan
            continue;
         }
         zmessageACK(Mwin,"printf format missmatch: \n %s \n %s",text_en,text_xx);
         Ferr++;
         break;
      }
      
      if (Ferr > 5) break;
   }
   
   if (! Ferr) zmessageACK(Mwin,"%d lines audited, no errors found",ii);

   zlist_free(zlist_xx);
   return;
}


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

//   Slice ug_en into pieces for AI translation, each piece ending on a blank line.
//   Blank lines are represeted by "-\n".
//   Output: ug_en.01, ug_en.02 ...

void m_slice_UG(GtkWidget *, ch *)                                                     //  26.5
{
   ch       ugfile[200];                                                               //  english ug_en input file
   ch       outfile[200];
   ch       record[ugxcc];                                                             //  max UG record size
   ch       *pp;
   FILE     *fid1, *fid2;
   int      ss, ccout, nn;
   
   snprintf(ugfile,200,"%s/ug_en",translations_folder);                                //  input file ug_en
   
   fid1 = fopen(ugfile,"r");                                                           //  open intput file
   if (! fid1) {
      printf("*** userguide file not found: %s \n",ugfile);
      return;
   }
   
   ss = 0;
   
   while (true)
   {
      ss++;                                                                            //  next sequence number
      snprintf(outfile,200,"/home/mico/Desktop/ug_en.%02d",ss);                        //  next outfile .../ug_en.NN

      fid2 = fopen(outfile,"w");                                                       //  open next outfile
      if (! fid2) goto filerror;

      ccout = 0;                                                                       //  output cc
      
      while (true)
      {
         pp = fgets(record,ugxcc,fid1);                                                //  get next input record
         if (! pp) break;                                                              //  input file EOF
         
         nn = fputs(record,fid2);                                                      //  add to output file
         if (nn < 0) goto filerror;

         ccout += strlen(record);                                                      //  accumulate output cc

         if (*record == '-' && ccout > 1000 * KBlimit) break;                          //  blank line and limit reached
      }
      
      fclose(fid2);                                                                    //  close output file

      if (! pp) break;                                                                 //  input EOF
   }
   
   fclose(fid1);                                                                       //  close input file
   return;


filerror:
   printf("*** output file error %s: %s \n",strerror(errno),outfile);
   fclose(fid1);
   return;
}


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

   Translation Process

   Files ug_en_nn are translated to produce ug_xx_nn for language xx.
   After translation, combine the translated pieces: ug_xx_01, ug_xx_02 ... --> ug_xx 

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

   Synchronize userguide translations with GUI translations.
      1. read ug_en and ug_xx  in parallel
      2. match pairs of TOPIC: xxxxx, {xxxxx}, and [xxxxx]
      3. for each xxxxx in ug_xx, get matching english eeeee
          from ug_en, translate via TX(eeeee), replace xxxxx
      
   Input files: <fotocx home>/translations/ug_en  and  ug_xx
   Output file: <fotocx home>/translations/ug_xx_new

***/

void m_sync_UG(GtkWidget *, ch *)                                                      //  26.5
{
   ch       ug_en[200], ug_xx[200], ug_xx_new[200];
   ch       buff1[ugxcc], buff2[ugxcc], buff3[ugxcc], buff4[ugxcc];
   ch       text1[ugtpxcc], text2[ugtpxcc], text3[ugtpxcc];
   FILE     *fid1 = 0, *fid2 = 0, *fid3 = 0;
   ch       *pp1, *pp1e, *pp2, *pp2e;
   int      cc, yn, record;
   
   printf("Sync User Guide Translations \n");
   printf("userguide file: %s \n",UG_file);
   
   yn = zmessageYN(Mwin,"proceed with language: %s \n",lc);
   if (! yn) return;
   
   snprintf(ug_en,200,"%s/ug_en",translations_folder);                                 //  /.../ug_en      input 1
   snprintf(ug_xx,200,"%s/ug_%s",translations_folder,lc);                              //  /.../ug_xx      input 2
   snprintf(ug_xx_new,200,"%s/ug_%s_new",translations_folder,lc);                      //  /.../ug_xx_new  output
   
   fid1 = fopen(ug_en,"r");                                                            //  open ug_en for read
   if (! fid1) {
      zmessageACK(Mwin,"cannot open file: %s \n",ug_en);
      goto closeall;
   }

   fid2 = fopen(ug_xx,"r");                                                            //  open ug_xx for read
   if (! fid2) {
      zmessageACK(Mwin,"cannot open file: %s \n",ug_xx);
      goto closeall;
   }

   fid3 = fopen(ug_xx_new,"w");                                                        //  open ug_xx_new for write
   if (! fid3) {
      zmessageACK(Mwin,"cannot open file: %s \n",ug_xx_new);
      goto closeall;
   }

   for (record = 1; ; record++)
   {
      pp1 = fgets(buff1,ugxcc,fid1);                                                   //  get next ug_en record
      pp2 = fgets(buff2,ugxcc,fid2);                                                   //  get next ug_xx record
      if (! pp1 && ! pp2) goto complete;                                               //  both EOF, OK
      if (! pp1 || ! pp2) goto missmatch;                                              //  one EOF, bad
      
      pp1 = strstr(buff1,"TOPIC:");                                                    //  scan both records for "TOPIC:"
      pp2 = strstr(buff2,"TOPIC:");
      if ((pp1 && ! pp2) || (! pp1 && pp2)) goto missmatch;

      if (pp1 && pp2) {                                                                //  "TOPIC:" found in both records
         pp1 += 6;
         while (*pp1 == ' ') pp1++;
         strncpy0(text1,pp1,ugtpxcc);                                                  //  english text
         cc = strlen(text1);
         while (text1[cc-1] <= ' ') cc--;                                              //  remove trailing blanks and \n
         if (cc < 1)  goto missmatch;
         text1[cc] = 0;
         snprintf(text2,ugtpxcc,"TOPIC: %s",TX(text1));                                //  translation or input text if missing
         fputs(text2,fid3);                                                            //  output TOPIC: translation
         fputs("\n",fid3);
         continue;                                                                     //  do not scan rest of record
      }

      strcpy(buff3,buff2);                                                             //  initial ug_xx-new output record

      pp1 = buff1;                                                                     //  ug_en input record
      pp2 = buff2;                                                                     //  ug_xx input record
      
      while (true)                                                                     //  scan both inputs for {xxxxxx} 
      {
         pp1 = strchr(pp1,'{');                                                        //  find next {english} in en record
         pp2 = strchr(pp2,'{');                                                        //  find next {xxxxxx} in xx record
         
         if ((pp1 && ! pp2) || (! pp1 && pp2)) goto missmatch;
         if (! pp1 && ! pp2) break;                                                    //  not found, this record done
         
         pp1e = strchr(pp1,'}');                                                       //  {english} in en record
         pp2e = strchr(pp2,'}');                                                       //  {xxxxxx} in xx record

         cc = pp1e - pp1;
         if (cc < 2 || cc > ugxcc-3) goto missmatch;
         strncpy0(text1,pp1+1,cc);                                                     //  translate {english}
         snprintf(text3,ugtpxcc,"{%s}",TX(text1));                                     //  text3 = {translation} = new text
         cc = pp2e - pp2;
         if (cc < 2 || cc > ugxcc-3) goto missmatch;
         strncpy0(text2,pp2,cc+2);                                                     //  text2 = {xxxxxx} = old text
         
         repl_1str(buff3,buff4,ugxcc,text2,text3);                                     //  replace {xxxxxx} with {translation} 
         strncpy0(buff3,buff4,ugxcc);                                                  //  update output record
         
         pp1 = pp1e;                                                                   //  skip over {english}
         pp2 = pp2e;                                                                   //  skip over {xxxxxx}
      }
      
      pp1 = buff1;                                                                     //  ug_en input record
      pp2 = buff2;                                                                     //  ug_xx input record

      while (true)                                                                     //  scan both inputs for [xxxxxx] 
      {
         pp1 = strchr(pp1,'[');                                                        //  find next [english] in en record
         pp2 = strchr(pp2,'[');                                                        //  find next [xxxxxx] in xx record
         
         if ((pp1 && ! pp2) || (! pp1 && pp2)) goto missmatch;
         if (! pp1 && ! pp2) break;                                                    //  not found, this record done
         
         pp1e = strchr(pp1,']');                                                       //  [english] in en record
         pp2e = strchr(pp2,']');                                                       //  [xxxxxx] in xx record

         cc = pp1e - pp1;
         if (cc < 2 || cc > ugxcc-3) goto missmatch;
         strncpy0(text1,pp1+1,cc);                                                     //  translate [english]
         snprintf(text3,ugtpxcc,"[%s]",TX(text1));                                     //  text3 = [translation] = new text
         cc = pp2e - pp2;
         if (cc < 2 || cc > ugxcc-3) goto missmatch;
         strncpy0(text2,pp2,cc+2);                                                     //  text2 = [xxxxxx] = old text
         
         repl_1str(buff3,buff4,ugxcc,text2,text3);                                     //  replace [xxxxxx] with [translation] 
         strncpy0(buff3,buff4,ugxcc);                                                  //  update output record
         
         pp1 = pp1e;                                                                   //  skip over [english]
         pp2 = pp2e;                                                                   //  skip over [xxxxxx]
      }
      
      fputs(buff3,fid3);                                                               //  write output record
   }

complete:
   zmessageACK(Mwin,"file %s created \n audit and replace %s \n",ug_xx_new,ug_xx);
   goto closeall;

missmatch:
   strncpy0(buff3,buff1,100);                                                          //  limit to 100 chars.
   utf8substring(buff4,buff2,0,100);                                                   //  limit to 100 utf8 chars.
   zmessageACK(Mwin,"missmatch: line %d \n en: %s \n xx: %s \n",record,buff3,buff4);

closeall:
   if (fid1) fclose(fid1);
   if (fid2) fclose(fid2);
   if (fid3) fclose(fid3);
   return;
}


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

//  Check that all F1 help topics are present in the user guide,
//    and that all internal links in the userguide are valid.
//  inputs: source files *.cc, gui_xx, ug_xx 

void m_audit_UG(GtkWidget *, ch *)                                                     //  26.4
{
   cch         *file;
   ch          buff[200];
   ch          topicname[100];
   ch          *sourcetopics[500], *ugtopics[500];
   ch          *pp, *pp1, *pp2;
   int         Ntopics, Nugtopics, maxtopics = 500;
   int         cc, nn, ii, jj;
   FILE        *fid1, *fid2;
   ch          topix[14] = "F1_help_topix"; topix[12] = 'c';                           //  stop false text match                 26.5

   printf("Audit User Guide \n");

   pp = strrchr(sourcefolder,'/');                                                     //  fotocx source folder
   strcpy(pp+1,Frelease);                                                              //  get my release NN.N

   snprintf(command,200,"find %s/*.cc",sourcefolder);

   fid1 = popen(command,"r");
   if (! fid1) {
      printf("invalid source folder \n");
      return;
   }
   
   nn = 0;

   while (true)                                                                        //  find all source files *.cc
   {
      file = fgets_trim(buff,200,fid1);
      if (! file) break;
      printf("source file: %s \n",file);

      fid2 = fopen(file,"r");                                                          //  open source file
      if (! fid2) {
         printf("*** file error %s \n",strerror(errno));
         continue;
      }
      
      while (true)
      {
         pp = fgets_trim(buff,200,fid2);                                               //  read source file records
         if (! pp) break;
         pp = strstr(pp,topix);                                                        //  search for "F1_help_topic"            26.5
         if (! pp) continue;
         pp1 = strchr(pp+15,'"');                                                      //  get following topic name
         if (! pp1) continue;
         pp2 = strchr(pp1+3,'"');
         if (! pp2) continue;
         cc = pp2 - pp1;
         if (cc < 2 || cc > 99) continue;
         strncpy0(topicname,pp1+1,cc);
         for (int ii = 0; ii < cc; ii++)                                               //  force uppercase  
            topicname[ii] = toupper(topicname[ii]);
         pp = TX(topicname);                                                           //  get translation
         if (! pp) continue;
         sourcetopics[nn] = zstrdup(pp,"audit_UG");                                    //  save all translated topics
         nn++;
         if (nn == maxtopics) {
            printf("*** max %d source topics exceeded \n",maxtopics);
            return;
         }
      }
      
      fclose(fid2);
   }
   
   pclose(fid1);

   Ntopics = nn;
   printf("source file topics: %d \n",Ntopics);

   printf("userguide file: %s \n",UG_file);                                            //  file: ug_xx

   fid2 = fopen(UG_file,"r");
   if (! fid2) {
      printf("*** userguide file error %s \n",strerror(errno));
      return;
   }

   nn = 0;
   
   while (true)
   {
      pp = fgets_trim(buff,200,fid2);                                                  //  read userguide file records
      if (! pp) break;
      pp = strstr(buff,"TOPIC:");                                                      //  find TOPIC: XXXXXXX record
      if (! pp) continue;
      pp += 6;
      while (*pp && *pp == ' ') pp++;
      cc = strlen(pp);
      while (cc && pp[cc-1] == ' ') cc--;                                              //  prevent trailing blanks
      if (cc == 0) continue;
      pp[cc] = 0;
      ugtopics[nn] = zstrdup(pp,"audit_UG");                                           //  save all UG topics
      nn++;
      if (nn == maxtopics) {
         printf("*** max %d userguide topics exceeded \n",maxtopics);
         return;
      }
   }      

   fclose(fid2);
      
   Nugtopics = nn;
   printf("userguide topics: %d \n",Nugtopics);
      
   for (ii = 0; ii < Ntopics; ii++)                                                    //  find source file topics 
   {                                                                                   //    in userguide topics
      for (jj = 0; jj < Nugtopics; jj++)
         if (strmatch(sourcetopics[ii],ugtopics[jj])) break;
      if (jj == Nugtopics)
         printf("*** source topic %s not found in userguide topics \n",sourcetopics[ii]);
   }

   for (ii = 0; ii < Ntopics; ii++)                                                    //  free memory
      zfree(sourcetopics[ii]);
   for (ii = 0; ii < Nugtopics; ii++)
      zfree(ugtopics[ii]);   

   show_help("validate");                                                              //  audit userguide internal links
   return;
}


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

   TRANSLATE RESUME          continue translation text scan from here
                             see m_capture_GUI() above

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

   UG_extra_topics

   These are userguide topics not present in the source programs.
   This gets them added to the english translation file: gui_en
   m_sync_UG() will find and translate them in the userguide.

   TX("ALBUMS MENU")
   TX("AREA OVERVIEW")
   TX("AUTOSEARCH")
   TX("BATCH MENU")
   TX("BATCH PROCESSING")
   TX("BATCH SCRIPT")
   TX("COMBINE MENU")
   TX("CURVE EDITING")
   TX("DIALOG BUTTONS")
   TX("DRAG AND DROP")
   TX("EDIT ALBUM")
   TX("EDIT BOOKMARKS")
   TX("EDIT MENU")
   TX("EDIT PROCEDURES")
   TX("EDIT SCRIPT")
   TX("EDIT WORKFLOW")
   TX("EFFECTS MENU")
   TX("FILE MENU")
   TX("FILE VIEW")
   TX("FIRST STARTUP")
   TX("FOTOCX FOR WINDOWS 11") 
   TX("FOTOCX LIMITATIONS")
   TX("FOTOCX USAGE")
   TX("GALLERY END")
   TX("GALLERY HOME")
   TX("GALLERY MENU")
   TX("GALLERY OVERVIEW")
   TX("GALLERY VIEW")
   TX("KEYBOARD ESCAPE KEY")
   TX("KEYWORDS OVERVIEW")
   TX("MAP MENU")
   TX("MAP VIEW")
   TX("MENU SHORTCUTS")
   TX("METADATA MENU")
   TX("MOUSE ALT-RIGHT-BUTTON")
   TX("MOUSE FUNCTIONS")
   TX("MOUSE OWNERSHIP")
   TX("NEWLINE CHARACTERS IN METADATA")
   TX("NON-STANDARD DIALOG WIDGETS")
   TX("OTHER TOPICS")
   TX("PROBLEM FILES")
   TX("PROBLEM REPORTING")
   TX("QUESTIONS, SUGGESTIONS")
   TX("REFINE MENU")
   TX("REPAIR MENU")
   TX("RUN SCRIPT")
   TX("SELECT FILES")
   TX("SELECT IMAGE FILES")
   TX("SELECT MENU")
   TX("SHORTCUTS")
   TX("SLIDES AND NEGATIVES")
   TX("TOOLS MENU")
   TX("TRANSLATIONS")
   TX("USER GUIDE")
   TX("VIEW ALL META")
   TX("WARP IMAGE")
   TX("WARP MENU")
   TX("WINDOW VIEWS AND MENUS")
   TX("WORLD MAP")

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


