/* gap_lib.c
 * 1997.11.18 hof (Wolfgang Hofer)
 *
 * GAP ... Gimp Animation Plugins
 *
 * basic anim functions:
 *   Delete, Duplicate, Exchange, Shift 
 *   Next, Prev, First, Last, Goto
 * 
 *
 */
/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* revision history:
 * 1.2.2a;  2001/10/21   hof: bufix # 61677 (error in duplicate frames GUI) 
 *                            and disable duplicate for Unsaved/untitled Images.
 *                            (creating frames from such images with a default name may cause problems
 *                             as unexpected overwriting frames or mixing animations with different sized frames)
 * 1.2.1a;  2001/07/07   hof: p_file_copy use binary modes in fopen (hope that fixes bug #52890 in video/duplicate)
 * 1.1.29a; 2000/11/23   hof: gap locking (changed to procedures and placed here)
 * 1.1.28a; 2000/11/05   hof: check for GIMP_PDB_SUCCESS (not for FALSE)
 * 1.1.20a; 2000/04/25   hof: new: p_get_video_paste_name p_vid_edit_clear
 * 1.1.17b; 2000/02/27   hof: bug/style fixes
 * 1.1.14a; 1999/12/18   hof: handle .xvpics on fileops (copy, rename and delete)
 *                            new: p_get_frame_nr,
 * 1.1.9a;  1999/09/14   hof: handle frame filenames with framenumbers
 *                            that are not the 4digit style. (like frame1.xcf)
 * 1.1.8a;  1999/08/31   hof: for AnimFrame Filtypes != XCF:
 *                            p_decide_save_as does save INTERACTIVE at 1.st time
 *                            and uses GIMP_RUN_WITH_LAST_VALS for subsequent calls
 *                            (this enables to set Fileformat specific save-Parameters
 *                            at least at the 1.st call, using the save dialog
 *                            of the selected (by gimp_file_save) file_save procedure.
 *                            in NONINTERACTIVE mode we have no access to
 *                            the Fileformat specific save-Parameters
 *          1999/07/22   hof: accept anim framenames without underscore '_'
 *                            (suggested by Samuel Meder)
 * 0.99.00; 1999/03/15   hof: prepared for win/dos filename conventions
 * 0.98.00; 1998/11/30   hof: started Port to GIMP 1.1:
 *                               exchange of Images (by next frame) is now handled in the
 *                               new module: gap_exchange_image.c
 * 0.96.02; 1998/07/30   hof: extended gap_dup (duplicate range instead of singele frame)
 *                            added gap_shift
 * 0.96.00               hof: - now using gap_arr_dialog.h
 * 0.95.00               hof:  increased duplicate frames limit from 50 to 99
 * 0.93.01               hof: fixup bug when frames are not in the current directory
 * 0.90.00;              hof: 1.st (pre) release
 */
#include "config.h"
 
/* SYSTEM (UNIX) includes */ 
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <signal.h>           /* for kill */
#ifdef HAVE_SYS_TIMES_H
#include <sys/times.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

/* GIMP includes */
#include "gtk/gtk.h"
#include "libgimp/stdplugins-intl.h"
#include "libgimp/gimp.h"

#ifdef G_OS_WIN32
#include <io.h>
#  ifndef S_ISDIR
#    define S_ISDIR(m) ((m) & _S_IFDIR)
#  endif
#  ifndef S_ISREG
#    define S_ISREG(m) ((m) & _S_IFREG)
#  endif
#endif

#ifdef G_OS_WIN32
#include <direct.h>		/* For _mkdir() */
#define mkdir(path,mode) _mkdir(path)
#endif

#ifdef G_OS_WIN32
#include <process.h>		/* For _getpid() */
#endif

/* GAP includes */
#include "gap_layer_copy.h"
#include "gap_lib.h"
#include "gap_pdb_calls.h"
#include "gap_arr_dialog.h"
#include "gap_exchange_image.h"

extern      int gap_debug; /* ==0  ... dont print debug infos */
 
/* ------------------------------------------ */
/* forward  working procedures */
/* ------------------------------------------ */

static int          p_save_old_frame(t_anim_info *ainfo_ptr);

static int   p_rename_frame(t_anim_info *ainfo_ptr, long from_nr, long to_nr);
static int   p_delete_frame(t_anim_info *ainfo_ptr, long nr);
static int   p_del(t_anim_info *ainfo_ptr, long cnt);
static int   p_decide_save_as(gint32 image_id, char *sav_name);

/* ============================================================================
 * p_alloc_fname_thumbnail
 *   return the thumbnail name (in .xvpics subdir)
 *   for the given filename
 * ============================================================================
 */
char *
p_alloc_fname_thumbnail(char *name)
{
  int   l_len;
  int   l_len2;
  int   l_idx;
  char *l_str;
  
  if(name == NULL)
  {
    return(g_strdup("\0"));
  }

  l_len = strlen(name);
  l_len2 = l_len + 10;
  l_str = g_malloc(l_len2);
  strcpy(l_str, name);
  if(l_len > 0)
  {
     for(l_idx = l_len -1; l_idx > 0; l_idx--)
     {
        if((name[l_idx] == G_DIR_SEPARATOR) || (name[l_idx] == DIR_ROOT))
	{
	   l_idx++;
	   break;
	}
     }
     g_snprintf(&l_str[l_idx], l_len2 - l_idx, ".xvpics%s%s", G_DIR_SEPARATOR_S, &name[l_idx]);      
  }
  if(gap_debug) printf("p_alloc_fname_thumbnail: thumbname=%s:\n", l_str );
  return(l_str);
}

/* ============================================================================
 * p_strdup_*_underscore
 *   duplicate string and if last char is no underscore add one at end.
 *   duplicate string and delete last char if it is the underscore
 * ============================================================================
 */
char *
p_strdup_add_underscore(char *name)
{
  int   l_len;
  char *l_str;
  if(name == NULL)
  {
    return(g_strdup("\0"));
  }

  l_len = strlen(name);
  l_str = g_malloc(l_len+1);
  strcpy(l_str, name);
  if(l_len > 0)
  {
    if (name[l_len-1] != '_')
    {
       l_str[l_len    ] = '_';
       l_str[l_len +1 ] = '\0';
    }
      
  }
  return(l_str);
}

char *
p_strdup_del_underscore(char *name)
{
  int   l_len;
  char *l_str;
  if(name == NULL)
  {
    return(g_strdup("\0"));
  }

  l_len = strlen(name);
  l_str = g_strdup(name);
  if(l_len > 0)
  {
    if (l_str[l_len-1] == '_')
    {
       l_str[l_len -1 ] = '\0';
    }
      
  }
  return(l_str);
}

/* ============================================================================
 * p_msg_win
 *   print a message both to stderr
 *   and to a gimp info window with OK button (only when run INTERACTIVE)
 * ============================================================================
 */

void p_msg_win(GimpRunMode run_mode, char *msg)
{
  static t_but_arg  l_argv[1];
  int               l_argc;  
  
  l_argv[0].but_txt  = GTK_STOCK_OK;
  l_argv[0].but_val  = 0;
  l_argc             = 1;

  if(msg)
  {
    if(*msg)
    {
       fwrite(msg, 1, strlen(msg), stderr);
       fputc('\n', stderr);

       if(run_mode == GIMP_RUN_INTERACTIVE) p_buttons_dialog  (_("GAP Message"), msg, l_argc, l_argv, -1);
    }
  }
}    /* end  p_msg_win */

/* ============================================================================
 * p_file_exists
 *
 * return 0  ... file does not exist, or is not accessable by user,
 *               or is empty,
 *               or is not a regular file
 *        1  ... file exists
 * ============================================================================
 */
int p_file_exists(char *fname)
{
  struct stat  l_stat_buf;
  long         l_len;

  /* File Laenge ermitteln */
  if (0 != stat(fname, &l_stat_buf))
  {
    /* stat error (file does not exist) */
    return(0);
  }
  
  if(!S_ISREG(l_stat_buf.st_mode))
  {
    return(0);
  }
  
  l_len = (long)l_stat_buf.st_size;
  if(l_len < 1)
  {
    /* File is empty*/
    return(0);
  }
  
  return(1);
}	/* end p_file_exists */

/* ============================================================================
 * p_image_file_copy
 *    (copy the imagefile and its thumbnail)
 * ============================================================================
 */
int p_image_file_copy(char *fname, char *fname_copy)
{
   char          *l_from_fname_thumbnail;
   char          *l_to_fname_thumbnail;
   int            l_rc;

   l_from_fname_thumbnail = p_alloc_fname_thumbnail(fname);
   l_to_fname_thumbnail = p_alloc_fname_thumbnail(fname_copy);
   
   l_rc = p_file_copy(fname, fname_copy);
   if((l_from_fname_thumbnail) 
   && (l_to_fname_thumbnail))
   {
     p_file_copy(l_from_fname_thumbnail, l_to_fname_thumbnail);
   }
   
   if(l_from_fname_thumbnail) g_free(l_from_fname_thumbnail);
   if(l_to_fname_thumbnail) g_free(l_to_fname_thumbnail);
   return(l_rc);
}

/* ============================================================================
 * p_file_copy
 * ============================================================================
 */
int p_file_copy(char *fname, char *fname_copy)
{
  FILE	      *l_fp;
  char                     *l_buffer;
  struct stat               l_stat_buf;
  long	       l_len;

  if(gap_debug) printf("p_file_copy src:%s dst:%s\n", fname, fname_copy);

  /* File Laenge ermitteln */
  if (0 != stat(fname, &l_stat_buf))
  {
    fprintf (stderr, "stat error on '%s'\n", fname);
    return -1;
  }
  l_len = (long)l_stat_buf.st_size;

  /* Buffer allocate */
  l_buffer=(char *)g_malloc0((size_t)l_len+1);
  if(l_buffer == NULL)
  {
    fprintf(stderr, "file_copy: calloc error (%ld Bytes not available)\n", l_len);
    return -1;
  }

  /* load File into Buffer */
  l_fp = fopen(fname, "rb");		    /* open read */
  if(l_fp == NULL)
  {
    fprintf (stderr, "open(read) error on '%s'\n", fname);
    g_free(l_buffer);
    return -1;
  }
  fread(l_buffer, 1, (size_t)l_len, l_fp);
  fclose(l_fp);
  
  l_fp = fopen(fname_copy, "wb");		    /* open write */
  if(l_fp == NULL)
  {
    fprintf (stderr, "file_copy: open(write) error on '%s' \n", fname_copy);
    g_free(l_buffer);
    return -1;
  }

  if(l_len > 0)
  {
     fwrite(l_buffer,  l_len, 1, l_fp);
  }

  fclose(l_fp);
  g_free(l_buffer);
  return 0;           /* all done OK */
}	/* end p_file_copy */


/* ============================================================================
 * p_delete_frame
 * ============================================================================
 */
int p_delete_frame(t_anim_info *ainfo_ptr, long nr)
{
   char          *l_fname;
   char          *l_fname_thumbnail;
   int            l_rc;
   
   l_fname = p_alloc_fname(ainfo_ptr->basename, nr, ainfo_ptr->extension);
   if(l_fname == NULL) { return(1); }

   l_fname_thumbnail = p_alloc_fname_thumbnail(l_fname);
   if(l_fname_thumbnail == NULL) { return(1); }
     
   if(gap_debug) fprintf(stderr, "\nDEBUG p_delete_frame: %s\n", l_fname);
   l_rc = remove(l_fname);
     
   if(gap_debug) fprintf(stderr, "\nDEBUG p_delete_frame: %s\n", l_fname_thumbnail);
   remove(l_fname_thumbnail);
   
   g_free(l_fname);
   g_free(l_fname_thumbnail);
   
   return(l_rc);
   
}    /* end p_delete_frame */

/* ============================================================================
 * p_rename_frame
 * ============================================================================
 */
int p_rename_frame(t_anim_info *ainfo_ptr, long from_nr, long to_nr)
{
   char          *l_from_fname;
   char          *l_to_fname;
   char          *l_from_fname_thumbnail;
   char          *l_to_fname_thumbnail;
   int            l_rc;
   
   l_from_fname = p_alloc_fname(ainfo_ptr->basename, from_nr, ainfo_ptr->extension);
   if(l_from_fname == NULL) { return(1); }
   
   l_to_fname = p_alloc_fname(ainfo_ptr->basename, to_nr, ainfo_ptr->extension);
   if(l_to_fname == NULL) { g_free(l_from_fname); return(1); }
   
   l_from_fname_thumbnail = p_alloc_fname_thumbnail(l_from_fname);
   if(l_from_fname_thumbnail == NULL) { return(1); }
   
   l_to_fname_thumbnail = p_alloc_fname_thumbnail(l_to_fname);
   if(l_to_fname_thumbnail == NULL) { g_free(l_from_fname_thumbnail); return(1); }
   
     
   if(gap_debug) fprintf(stderr, "\nDEBUG p_rename_frame: %s ..to.. %s\n", l_from_fname, l_to_fname);
   l_rc = rename(l_from_fname, l_to_fname);
   if(gap_debug) fprintf(stderr, "\nDEBUG p_rename_frame: %s ..to.. %s\n", l_from_fname_thumbnail, l_to_fname_thumbnail);
   rename(l_from_fname_thumbnail, l_to_fname_thumbnail);
   
   g_free(l_from_fname);
   g_free(l_to_fname);
   g_free(l_from_fname_thumbnail);
   g_free(l_to_fname_thumbnail);
   
   return(l_rc);
   
}    /* end p_rename_frame */


/* ============================================================================
 * p_alloc_basename
 *
 * build the basename from an images name
 * Extension and trailing digits ("0000.xcf") are cut off.
 * return name or NULL (if malloc fails)
 * Output: number contains the integer representation of the stripped off
 *         number String. (or 0 if there was none)
 * ============================================================================
 */
char*  p_alloc_basename(char *imagename, long *number)
{
  char *l_fname;
  char *l_ptr;
  long  l_idx;

  *number = 0;
  if(imagename == NULL) return (NULL);
  l_fname = (char *)g_malloc(strlen(imagename) + 1);
  if(l_fname == NULL)   return (NULL);

  /* copy from imagename */
  strcpy(l_fname, imagename);

  if(gap_debug) fprintf(stderr, "DEBUG p_alloc_basename  source: '%s'\n", l_fname);
  /* cut off extension */
  l_ptr = &l_fname[strlen(l_fname)];
  while(l_ptr != l_fname)
  {
    if((*l_ptr == G_DIR_SEPARATOR) || (*l_ptr == DIR_ROOT))  { break; }  /* dont run into dir part */
    if(*l_ptr == '.')                                        { *l_ptr = '\0'; break; }
    l_ptr--;
  }
  if(gap_debug) fprintf(stderr, "DEBUG p_alloc_basename (ext_off): '%s'\n", l_fname);

  /* cut off trailing digits (0000) */
  l_ptr = &l_fname[strlen(l_fname)];
  if(l_ptr != l_fname) l_ptr--;
  l_idx = 1;
  while(l_ptr != l_fname)
  {
    if((*l_ptr >= '0') && (*l_ptr <= '9'))
    { 
      *number += ((*l_ptr - '0') * l_idx);
       l_idx = l_idx * 10;
      *l_ptr = '\0'; 
       l_ptr--; 
    }
    else
    {
      /* do not cut off underscore any longer */
      /*
       * if(*l_ptr == '_')
       * { 
       *    *l_ptr = '\0';
       * }
       */
       break;
    }
  }
  
  if(gap_debug) fprintf(stderr, "DEBUG p_alloc_basename  result:'%s'\n", l_fname);

  return(l_fname);
   
}    /* end p_alloc_basename */



/* ============================================================================
 * p_alloc_extension
 *
 * input: a filename
 * returns: a copy of the filename extension (incl "." )
 *          if the filename has no extension, a pointer to
 *          an empty string is returned ("\0")
 *          NULL if allocate mem for extension failed.
 * ============================================================================
 */
char*  p_alloc_extension(char *imagename)
{
  int   l_exlen;
  char *l_ext;
  char *l_ptr;
  
  l_exlen = 0;
  l_ptr = &imagename[strlen(imagename)];
  while(l_ptr != imagename)
  {
    if((*l_ptr == G_DIR_SEPARATOR) || (*l_ptr == DIR_ROOT))  { break; }     /* dont run into dir part */
    if(*l_ptr == '.')                                        { l_exlen = strlen(l_ptr); break; }
    l_ptr--;
  }
  
  l_ext = g_malloc0((size_t)(l_exlen + 1));
  if(l_ext == NULL)
      return (NULL);
  
  
  if(l_exlen > 0)
     strcpy(l_ext, l_ptr);
     
  return(l_ext);
}


/* ============================================================================
 * p_alloc_fname
 *
 * build the name of a frame using "basename_0000.ext"
 * 
 * return name or NULL (if malloc fails)
 * ============================================================================
 */
char*  p_alloc_fname(char *basename, long nr, char *extension)
{
  gchar *l_fname;
  gint   l_leading_zeroes;
  gint   l_len;
  long   l_nr_chk;
  
  if(basename == NULL) return (NULL);
  l_len = (strlen(basename)  + strlen(extension) + 10);
  l_fname = (char *)g_malloc(l_len);

    l_leading_zeroes = TRUE;
    if(nr < 1000)
    {
       /* try to figure out if the frame numbers are in
        * 4-digit style, with leading zeroes  "frame_0001.xcf"
        * or not                              "frame_1.xcf"
        */
       l_nr_chk = nr;

       while(l_nr_chk >= 0)
       {
         /* check if frame is on disk with 4-digit style framenumber */
         g_snprintf(l_fname, l_len, "%s%04ld%s", basename, l_nr_chk, extension);
         if (p_file_exists(l_fname))
         {
            break;
         }

         /* now check for filename without leading zeroes in the framenumber */
         g_snprintf(l_fname, l_len, "%s%ld%s", basename, l_nr_chk, extension);
         if (p_file_exists(l_fname))
         {
            l_leading_zeroes = FALSE;
            break;
         }
         l_nr_chk--;
         
         /* if the frames nr and nr-1  were not found
          * try to check frames 1 and 0
          * to limit down the loop to max 4 cycles
          */
         if((l_nr_chk == nr -2) && (l_nr_chk > 1))
         {
           l_nr_chk = 1;
         }
      }
    }
    else
    {
      l_leading_zeroes = FALSE;
    }

  g_free(l_fname);

  if(l_leading_zeroes) l_fname = g_strdup_printf("%s%04ld%s", basename, nr, extension);
  else                 l_fname = g_strdup_printf("%s%ld%s", basename, nr, extension);

  return(l_fname);
}    /* end p_alloc_fname */




/* ============================================================================
 * p_alloc_ainfo
 *
 * allocate and init an ainfo structure from the given image.
 * ============================================================================
 */
t_anim_info *p_alloc_ainfo(gint32 image_id, GimpRunMode run_mode)
{
   t_anim_info   *l_ainfo_ptr;

   l_ainfo_ptr = (t_anim_info*)g_malloc(sizeof(t_anim_info));
   if(l_ainfo_ptr == NULL) return(NULL);
   
   l_ainfo_ptr->basename = NULL;
   l_ainfo_ptr->new_filename = NULL;
   l_ainfo_ptr->extension = NULL;
   l_ainfo_ptr->image_id = image_id;
 
   /* get current gimp images name  */   
   l_ainfo_ptr->old_filename = gimp_image_get_filename(image_id);
   if(l_ainfo_ptr->old_filename == NULL)
   {
     l_ainfo_ptr->old_filename = g_strdup("frame_0001.xcf");    /* assign a defaultname */
     gimp_image_set_filename (image_id, l_ainfo_ptr->old_filename);
   }

   l_ainfo_ptr->basename = p_alloc_basename(l_ainfo_ptr->old_filename, &l_ainfo_ptr->frame_nr);
   if(NULL == l_ainfo_ptr->basename)
   {
       p_free_ainfo(&l_ainfo_ptr);
       return(NULL);
   }

   l_ainfo_ptr->extension = p_alloc_extension(l_ainfo_ptr->old_filename);

   l_ainfo_ptr->curr_frame_nr = l_ainfo_ptr->frame_nr;
   l_ainfo_ptr->first_frame_nr = -1;
   l_ainfo_ptr->last_frame_nr = -1;
   l_ainfo_ptr->frame_cnt = 0;
   l_ainfo_ptr->run_mode = run_mode;


   return(l_ainfo_ptr);
 

}    /* end p_init_ainfo */

/* ============================================================================
 * p_dir_ainfo
 *
 * fill in more items into the given ainfo structure.
 * - first_frame_nr
 * - last_frame_nr
 * - frame_cnt
 *
 * to get this information, the directory entries have to be checked
 * ============================================================================
 */
int p_dir_ainfo(t_anim_info *ainfo_ptr)
{
   char          *l_dirname;
   char          *l_dirname_ptr;
   char          *l_ptr;
   char          *l_exptr;
   char          *l_dummy;
   /* int            l_cmp_len;   */
   GDir          *l_dirp;
   const gchar   *l_entry;
   long           l_nr;
   long           l_maxnr;
   long           l_minnr;
   short          l_dirflag;
   char           dirname_buff[1024];

   l_dirname = g_malloc(strlen(ainfo_ptr->basename) +1);
   if(l_dirname == NULL)
     return -1;

   ainfo_ptr->frame_cnt = 0;
   l_dirp = NULL;
   l_minnr = 99999999;
   l_maxnr = 0;
   strcpy(l_dirname, ainfo_ptr->basename);

   l_ptr = &l_dirname[strlen(l_dirname)];
   while(l_ptr != l_dirname)
   {
      if ((*l_ptr == G_DIR_SEPARATOR) || (*l_ptr == DIR_ROOT))
      { 
        *l_ptr = '\0';   /* split the string into dirpath 0 basename */
        l_ptr++;
        break;           /* stop at char behind the slash */
      }
      l_ptr--;
   }
   /* l_cmp_len = strlen(l_ptr); */

   if(gap_debug) fprintf(stderr, "DEBUG p_dir_ainfo: BASENAME:%s\n", l_ptr);
   
   if      (l_ptr == l_dirname)   { l_dirname_ptr = ".";  l_dirflag = 0; }
   else if (*l_dirname == '\0')   { l_dirname_ptr = G_DIR_SEPARATOR_S ; l_dirflag = 1; }
   else                           { l_dirname_ptr = l_dirname; l_dirflag = 2; }

   if(gap_debug) fprintf(stderr, "DEBUG p_dir_ainfo: DIRNAME:%s\n", l_dirname_ptr);
   l_dirp = g_dir_open( l_dirname_ptr, 0, NULL );  
   
   if(!l_dirp) fprintf(stderr, "ERROR p_dir_ainfo: can't read directory %s\n", l_dirname_ptr);
   else
   {
     while ( (l_entry = g_dir_read_name( l_dirp )) != NULL )
     {

       /* if(gap_debug) fprintf(stderr, "DEBUG p_dir_ainfo: l_entry:%s\n", l_entry); */
       
       
       /* findout extension of the directory entry name */
       l_exptr = &l_entry[strlen(l_entry)];
       while(l_exptr != l_entry)
       {
         if(*l_exptr == G_DIR_SEPARATOR) { break; }                 /* dont run into dir part */
         if(*l_exptr == '.')       { break; }
         l_exptr--;
       }
       /* l_exptr now points to the "." of the direntry (or to its begin if has no ext) */
       /* now check for equal extension */
       if((*l_exptr == '.') && (0 == strcmp(l_exptr, ainfo_ptr->extension)))
       {
         /* build full pathname (to check if file exists) */
         switch(l_dirflag)
         {
           case 0:
            g_snprintf(dirname_buff, sizeof(dirname_buff), "%s", l_entry);
            break;
           case 1:
            g_snprintf(dirname_buff, sizeof(dirname_buff), "%c%s",  G_DIR_SEPARATOR, l_entry);
            break;
           default:
            /* UNIX:  "/dir/file"
             * DOS:   "drv:\dir\file"
             */
            g_snprintf(dirname_buff, sizeof(dirname_buff), "%s%c%s", l_dirname_ptr,  G_DIR_SEPARATOR,  l_entry);
            break;
         }
         
         if(1 == p_file_exists(dirname_buff)) /* check for regular file */
         {
           /* get basename and frame number of the directory entry */
           l_dummy = p_alloc_basename(l_entry, &l_nr);
           if(l_dummy != NULL)
           { 
               /* check for files, with equal basename (frames)
                * (length must be greater than basepart+extension
                * because of the frame_nr part "0000")
                */
               if((0 == strcmp(l_ptr, l_dummy))
               && ( strlen(l_entry) > strlen(l_dummy) + strlen(l_exptr)  ))
               {
                 ainfo_ptr->frame_cnt++;


                 if(gap_debug) fprintf(stderr, "DEBUG p_dir_ainfo:  %s NR=%ld\n", l_entry, l_nr);

                 if (l_nr > l_maxnr)
                    l_maxnr = l_nr;
                 if (l_nr < l_minnr)
                    l_minnr = l_nr;
               }

               g_free(l_dummy);
           }
         }
       }
     }
     g_dir_close( l_dirp );
   }

  g_free(l_dirname);

  /* set first_frame_nr and last_frame_nr (found as "_0099" in diskfile namepart) */
  ainfo_ptr->last_frame_nr = l_maxnr;
  ainfo_ptr->first_frame_nr = MIN(l_minnr, l_maxnr);

  return 0;           /* OK */
}	/* end p_dir_ainfo */
 

/* ============================================================================
 * p_free_ainfo
 *
 * free ainfo and its allocated stuff
 * ============================================================================
 */

void p_free_ainfo(t_anim_info **ainfo)
{
  t_anim_info *aptr;
  
  if((aptr = *ainfo) == NULL)
     return;
  
  if(aptr->basename)
     g_free(aptr->basename);
  
  if(aptr->extension)
     g_free(aptr->extension);
   
  if(aptr->new_filename)
     g_free(aptr->new_filename);

  if(aptr->old_filename)
     g_free(aptr->old_filename);
     
  g_free(aptr);
}


/* ============================================================================
 * p_get_frame_nr
 * ============================================================================
 */
long
p_get_frame_nr_from_name(char *fname)
{
  long number;
  int  len;
  char *basename;
  if(fname == NULL) return(-1);
  
  basename = p_alloc_basename(fname, &number);
  if(basename == NULL) return(-1);
  
  len = strlen(basename);
  g_free(basename);
  
  if(number > 0)  return(number);

  if(fname[len]  == '0') return(number);
/*
 *   if(fname[len]  == '_')
 *   {
 *     if(fname[len+1]  == '0') return(TRUE);
 *   }
 */
  return(-1);
}

long
p_get_frame_nr(gint32 image_id)
{
  char *fname;
  long  number;
  
  number = -1;
  fname = gimp_image_get_filename(image_id);
  if(fname)
  {
     number = p_get_frame_nr_from_name(fname);
     g_free(fname);
  }
  return (number);
}

/* ============================================================================
 * p_chk_framechange
 *
 * check if the current frame nr has changed. 
 * useful after dialogs, because the user may have renamed the current image
 * (using save as)
 * while the gap-plugin's dialog was open.
 * return: 0 .. OK
 *        -1 .. Changed (or error occured)
 * ============================================================================
 */
int p_chk_framechange(t_anim_info *ainfo_ptr)
{
  int l_rc;
  t_anim_info *l_ainfo_ptr;
 
  l_rc = -1;
  l_ainfo_ptr = p_alloc_ainfo(ainfo_ptr->image_id, ainfo_ptr->run_mode);
  if(l_ainfo_ptr != NULL)
  {
    if(ainfo_ptr->curr_frame_nr == l_ainfo_ptr->curr_frame_nr )
    { 
       l_rc = 0;
    }
    else
    {
       p_msg_win(ainfo_ptr->run_mode,
         _("OPERATION CANCELLED.\n"
	   "Current frame changed while dialog was open."));
    }
    p_free_ainfo(&l_ainfo_ptr);
  }

  return l_rc;   
}	/* end p_chk_framechange */

/* ============================================================================
 * p_chk_framerange
 *
 * check ainfo struct if there is a framerange (of at least one frame)
 * return: 0 .. OK
 *        -1 .. No frames there (print error)
 * ============================================================================
 */
int p_chk_framerange(t_anim_info *ainfo_ptr)
{
  if(ainfo_ptr->frame_cnt == 0)
  {
     p_msg_win(ainfo_ptr->run_mode,
	       _("OPERATION CANCELLED.\n"
                 "GAP plug-ins only work with filenames\n"
                 "that end in numbers like _0001.xcf.\n"
                 "==> Rename your image, then try again."));
     return -1;
  }
  return 0;
}	/* end p_chk_framerange */

/* ============================================================================
 * p_gzip
 *   gzip or gunzip the file to a temporary file.
 *   zip == "zip"    compress
 *   zip == "unzip"  decompress
 *   return a pointer to the temporary created (by gzip) file.
 *          NULL  in case of errors
 * ============================================================================
 */
char * p_gzip (char *orig_name, char *new_name, char *zip)
{
  gchar*   l_cmd;
  gchar*   l_tmpname;
  gint     l_rc, l_rc2;

  if(zip == NULL) return NULL;
  
  l_cmd = NULL;
  l_tmpname = new_name;
  
  /* used gzip options:
   *     -c --stdout --to-stdout
   *          Write  output  on  standard  output
   *     -d --decompress --uncompress
   *          Decompress.
   *     -f --force
   *           Force compression or decompression even if the file
   */

  if(*zip == 'u')
  {
    l_cmd = g_strdup_printf("gzip -cfd <\"%s\"  >\"%s\"", orig_name, l_tmpname);
  }
  else
  {
    l_cmd = g_strdup_printf("gzip -cf  <\"%s\"  >\"%s\"", orig_name, l_tmpname);
  }

  if(gap_debug) fprintf(stderr, "system: %s\n", l_cmd);
  
  l_rc = system(l_cmd);
  if(l_rc != 0)
  {
     /* Shift 8 Bits gets Retcode of the executed Program */
     l_rc2 = l_rc >> 8;
     fprintf(stderr, "ERROR system: %s\nreturncodes %d %d", l_cmd, l_rc, l_rc2);
     l_tmpname = NULL;
  }
  g_free(l_cmd);
  return l_tmpname;
  
}	/* end p_gzip */

/* ============================================================================
 * p_decide_save_as
 *   decide what to to, when attempt to save a frame in any image format 
 *  (other than xcf)
 *   Let the user decide if the frame is to save "as it is" or "flattened"
 *   ("as it is" will save only the backround layer in most fileformat types)
 *   The SAVE_AS_MODE is stored , and reused
 *   (without displaying the dialog) on subsequent calls.
 *
 *   return -1  ... CANCEL (do not save)
 *           0  ... save the image (may be flattened)
 * ============================================================================
 */
int p_decide_save_as(gint32 image_id, char *sav_name)
{
  static char *l_msg;


  static char l_save_as_name[80];
  
  static t_but_arg  l_argv[3];
  int               l_argc;  
  int               l_save_as_mode;
  GimpRunMode      l_run_mode;  

  l_msg = _("You are using a file format != xcf\n"
	    "Save Operations may result\n"
	    "in loss of layer information.");
  /* check if there are SAVE_AS_MODE settings (from privious calls within one gimp session) */
  l_save_as_mode = -1;
  /* g_snprintf(l_save_as_name, sizeof(l_save_as_name), "plug_in_gap_plugins_SAVE_AS_MODE_%d", (int)image_id);*/
  g_snprintf(l_save_as_name, sizeof(l_save_as_name), "plug_in_gap_plugins_SAVE_AS_MODE");
  gimp_get_data (l_save_as_name, &l_save_as_mode);

  if(l_save_as_mode == -1)
  {
    /* no defined value found (this is the 1.st call for this image_id)
     * ask what to do with a 3 Button dialog
     */
    l_argv[0].but_txt  = GTK_STOCK_CANCEL;
    l_argv[0].but_val  = -1;
    l_argv[1].but_txt  = _("Save Flattened");
    l_argv[1].but_val  = 1;
    l_argv[2].but_txt  = _("Save As Is");
    l_argv[2].but_val  = 0;
    l_argc             = 3;

    l_save_as_mode =  p_buttons_dialog  (_("GAP Question"), l_msg, l_argc, l_argv, -1);
    
    if(gap_debug) fprintf(stderr, "DEBUG: decide SAVE_AS_MODE %d\n", (int)l_save_as_mode);
    
    if(l_save_as_mode < 0) return -1;
    l_run_mode = GIMP_RUN_INTERACTIVE;
  }
  else
  {
    l_run_mode = GIMP_RUN_WITH_LAST_VALS;
  }

  gimp_set_data (l_save_as_name, &l_save_as_mode, sizeof(l_save_as_mode));
   
  
  if(l_save_as_mode == 1)
  {
      gimp_image_flatten (image_id);
  }

  return(p_save_named_image(image_id, sav_name, l_run_mode));
}	/* end p_decide_save_as */



/* ============================================================================
 * p_save_named_image
 * ============================================================================
 */
gint32 p_save_named_image(gint32 image_id, char *sav_name, GimpRunMode run_mode)
{
  GimpDrawable  *l_drawable;
  gint        l_nlayers;
  gint32     *l_layers_list;
  GimpParam     *l_params;
  gint        l_retvals;

  if(gap_debug) fprintf(stderr, "DEBUG: before   p_save_named_image: '%s'\n", sav_name);

  l_layers_list = gimp_image_get_layers(image_id, &l_nlayers);
  if(l_layers_list == NULL)
     return -1;

  l_drawable =  gimp_drawable_get(l_layers_list[l_nlayers -1]);  /* use the background layer */
  if(l_drawable == NULL)
  {
     fprintf(stderr, "ERROR: p_save_named_image gimp_drawable_get failed '%s' nlayers=%d\n",
                     sav_name, (int)l_nlayers);
     g_free (l_layers_list);
     return -1;
  }
  
  l_params = gimp_run_procedure ("gimp_file_save",
			       &l_retvals,
			       GIMP_PDB_INT32,    run_mode,
			       GIMP_PDB_IMAGE,    image_id,
			       GIMP_PDB_DRAWABLE, l_drawable->drawable_id,
			       GIMP_PDB_STRING, sav_name,
			       GIMP_PDB_STRING, sav_name, /* raw name ? */
			       GIMP_PDB_END);

  if(gap_debug) fprintf(stderr, "DEBUG: after    p_save_named_image: '%s' nlayers=%d image=%d drw=%d run_mode=%d\n", sav_name, (int)l_nlayers, (int)image_id, (int)l_drawable->drawable_id, (int)run_mode);

  p_gimp_file_save_thumbnail(image_id, sav_name);

  g_free (l_layers_list);
  g_free (l_drawable);


  if (l_params[0].data.d_status != GIMP_PDB_SUCCESS)
  {
    fprintf(stderr, "ERROR: p_save_named_image  gimp_file_save failed '%s'\n", sav_name);
    g_free(l_params);
    return -1;
  }
  else
  {
    g_free(l_params);
    return image_id;
  }
   
}	/* end p_save_named_image */



/* ============================================================================
 * p_save_named_frame
 *  save frame into temporary image,
 *  on success rename it to desired framename.
 *  (this is done, to avoid corrupted frames on disk in case of
 *   crash in one of the save procedures)
 * ============================================================================
 */
int p_save_named_frame(gint32 image_id, char *sav_name)
{
  GimpParam *l_params;
  gchar     *l_ext;
  char      *l_tmpname;
  gint       l_retvals;
  int        l_gzip;
  int        l_xcf;
  int        l_rc;

  l_tmpname = NULL;
  l_rc   = -1;
  l_gzip = 0;          /* dont zip */
  l_xcf  = 0;          /* assume no xcf format */
  
  /* check extension to decide if savd file will be zipped */      
  l_ext = p_alloc_extension(sav_name);
  if(l_ext == NULL)  return -1;
  
  if(0 == strcmp(l_ext, ".xcf"))
  { 
    l_xcf  = 1;
  }
  else if(0 == strcmp(l_ext, ".xcfgz"))
  { 
    l_gzip = 1;          /* zip it */
    l_xcf  = 1;
  }
  else if(0 == strcmp(l_ext, ".gz"))
  { 
    l_gzip = 1;          /* zip it */
  }

  /* find a temp name 
   * that resides on the same filesystem as sav_name
   * and has the same extension as the original sav_name 
   */
  l_tmpname = g_strdup_printf("%s.gtmp%s", sav_name, l_ext);
  if(1 == p_file_exists(l_tmpname))
  {
      /* FILE exists: let gimp find another temp name */
      l_params = gimp_run_procedure ("gimp_temp_name",
                                &l_retvals,
                                GIMP_PDB_STRING, 
                                &l_ext[1],
                                GIMP_PDB_END);

      if(l_params[1].data.d_string != NULL)
      {
         l_tmpname = l_params[1].data.d_string;
      }
      g_free(l_params);
  }

  g_free(l_ext);


   if(gap_debug)
   {
     l_ext = (gchar *) g_getenv("GAP_NO_SAVE");
     if(l_ext != NULL)
     {
       fprintf(stderr, "DEBUG: GAP_NO_SAVE is set: save is skipped: '%s'\n", l_tmpname);
       g_free(l_tmpname);  /* free if it was a temporary name */
       return 0;
     }
   }

  if(gap_debug) fprintf(stderr, "DEBUG: before   p_save_named_frame: '%s'\n", l_tmpname);

  if(l_xcf != 0)
  {
    /* save current frame as xcf image
     * xcf_save does operate on the complete image,
     * the drawable is ignored. (we can supply a dummy value)
     */
    l_params = gimp_run_procedure ("gimp_xcf_save",
			         &l_retvals,
			         GIMP_PDB_INT32,    GIMP_RUN_NONINTERACTIVE,
			         GIMP_PDB_IMAGE,    image_id,
			         GIMP_PDB_DRAWABLE, 0,
			         GIMP_PDB_STRING, l_tmpname,
			         GIMP_PDB_STRING, l_tmpname, /* raw name ? */
			         GIMP_PDB_END);
    if(gap_debug) fprintf(stderr, "DEBUG: after   xcf  p_save_named_frame: '%s'\n", l_tmpname);

    if (l_params[0].data.d_status == GIMP_PDB_SUCCESS)
    {
       l_rc = image_id;
    }
    g_free(l_params);
  }
  else
  {
     /* let gimp try to save (and detect filetype by extension)
      * Note: the most imagefileformats do not support multilayer
      *       images, and extra channels
      *       the result may not contain the whole imagedata
      *
      * To Do: Should warn the user at 1.st attempt to do this.
      */
      
     l_rc = p_decide_save_as(image_id, l_tmpname);
  } 

  if(l_rc < 0)
  {
     remove(l_tmpname);
     g_free(l_tmpname);  /* free temporary name */
     return l_rc;
  }

  if(l_gzip == 0)
  {
     /* remove sav_name, then rename tmpname ==> sav_name */
     remove(sav_name);
     if (0 != rename(l_tmpname, sav_name))
     {
        /* if tempname is located on another filesystem (errno == EXDEV)
         * rename will not work.
         * so lets try a  copy ; remove sequence
         */
         if(gap_debug) fprintf(stderr, "DEBUG: p_save_named_frame: RENAME 2nd try\n");
         if(0 == p_file_copy(l_tmpname, sav_name))
	 {
	    remove(l_tmpname);
	 }
         else
         {
            fprintf(stderr, "ERROR in p_save_named_frame: can't rename %s to %s\n",
                            l_tmpname, sav_name);
            return -1;
         }
     }
  }
  else
  {
    /* ZIP tmpname ==> sav_name */
    if(NULL != p_gzip(l_tmpname, sav_name, "zip"))
    {
       /* OK zip created compressed file named sav_name
        * now delete the uncompressed l_tempname
        */
       remove(l_tmpname);
    }
  }

  p_gimp_file_save_thumbnail(image_id, sav_name);

  g_free(l_tmpname);  /* free temporary name */

  return l_rc;
   
}	/* end p_save_named_frame */


/* ============================================================================
 * p_save_old_frame
 * ============================================================================
 */
int p_save_old_frame(t_anim_info *ainfo_ptr)
{
  /* (here we should check if image has unsaved changes
   * before save)
   */ 
  if(1)
  {
    return (p_save_named_frame(ainfo_ptr->image_id, ainfo_ptr->old_filename));
  }
  else
  {
    return -1;
  }
}	/* end p_save_old_frame */



/* ============================================================================
 * p_load_image
 * load image of any type by filename, and return its image id
 * (or -1 in case of errors)
 * ============================================================================
 */
gint32 p_load_image (char *lod_name)
{
  GimpParam* l_params;
  char  *l_ext;
  char  *l_tmpname;
  gint   l_retvals;
  gint32 l_tmp_image_id;
  int    l_rc;
  
  l_rc = 0;
  l_tmpname = NULL;
  l_ext = p_alloc_extension(lod_name);
  if(l_ext != NULL)
  {
    if((0 == strcmp(l_ext, ".xcfgz"))
    || (0 == strcmp(l_ext, ".gz")))
    { 

      /* find a temp name */
      l_params = gimp_run_procedure ("gimp_temp_name",
			           &l_retvals,
			           GIMP_PDB_STRING, 
			           &l_ext[1],           /* extension */
			           GIMP_PDB_END);

      if(l_params[1].data.d_string != NULL)
      {
         /* try to unzip file, before loading it */
         l_tmpname = p_gzip(lod_name, l_params[1].data.d_string, "unzip");
      }
 
      g_free(l_params);
    }
    else l_tmpname = lod_name;
    g_free(l_ext);
  }

  if(l_tmpname == NULL)
  {
    return -1;
  }


  if(gap_debug) fprintf(stderr, "DEBUG: before   p_load_image: '%s'\n", l_tmpname);

  l_params = gimp_run_procedure ("gimp_file_load",  /* "gimp_xcf_load" */
			       &l_retvals,
			       GIMP_PDB_INT32,  GIMP_RUN_NONINTERACTIVE,
			       GIMP_PDB_STRING, l_tmpname,
			       GIMP_PDB_STRING, l_tmpname, /* raw name ? */
			       GIMP_PDB_END);

  l_tmp_image_id = l_params[1].data.d_int32;
  
  if(gap_debug) fprintf(stderr, "DEBUG: after    p_load_image: '%s' id=%d\n\n",
                               l_tmpname, (int)l_tmp_image_id);

  if(l_tmpname != lod_name)
  {
    remove(l_tmpname);
    g_free(l_tmpname);  /* free if it was a temporary name */
  }
  

  g_free(l_params);
  return l_tmp_image_id;

}	/* end p_load_image */



/* ============================================================================
 * p_load_named_frame
 * load new frame, replacing the existing image
 * file must be of same type and size
 * ============================================================================
 */
int p_load_named_frame (gint32 image_id, char *lod_name)
{
  gint32 l_tmp_image_id;
  int    l_rc;
  
  l_rc = 0;

  l_tmp_image_id = p_load_image(lod_name);
  
  if(gap_debug) fprintf(stderr, "DEBUG: after    p_load_named_frame: '%s' id=%d  new_id=%d\n\n",
                               lod_name, (int)image_id, (int)l_tmp_image_id);

  if(l_tmp_image_id < 0)
      return -1;


   /* replace image_id with the content of l_tmp_image_id */
   if(p_exchange_image (image_id, l_tmp_image_id) < 0)
   {
      /* in case of errors the image will be trashed */
      image_id = -1;
   }

   /* delete the temporary image (old content of the original image) */
   if(gap_debug)  printf("p_load_named_frame: BEFORE gimp_image_delete %d\n", (int)l_tmp_image_id);
   gimp_image_delete(l_tmp_image_id);
   if(gap_debug)  printf("p_load_named_frame: AFTER gimp_image_delete %d\n", (int)l_tmp_image_id);

   /* use the original lod_name */
   gimp_image_set_filename (image_id, lod_name);
   
   /* dont consider image dirty after load */
   gimp_image_clean_all(image_id);
   return image_id;

}	/* end p_load_named_frame */



/* ============================================================================
 * p_replace_image
 *
 * make new_filename of next file to load, check if that file does exist on disk
 * then save current image and replace it, by loading the new_filename
 * ============================================================================
 */
int p_replace_image(t_anim_info *ainfo_ptr)
{
  if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
  ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                      ainfo_ptr->frame_nr,
                                      ainfo_ptr->extension);
  if(ainfo_ptr->new_filename == NULL)
     return -1;

  if(0 == p_file_exists(ainfo_ptr->new_filename ))
     return -1;

  if(p_save_old_frame(ainfo_ptr) <0)
  {
    return -1;
  }
  else
  {
    return (p_load_named_frame(ainfo_ptr->image_id, ainfo_ptr->new_filename));
  }
}	/* end p_replace_image */



/* ============================================================================
 * p_del
 *
 * delete cnt frames starting at current
 * all following frames are renamed (renumbered down by cnt) 
 * ============================================================================
 */
int p_del(t_anim_info *ainfo_ptr, long cnt)
{
   long  l_lo, l_hi, l_curr, l_idx;

   if(gap_debug) fprintf(stderr, "DEBUG  p_del\n");

   if(cnt < 1) return -1;

   l_curr =  ainfo_ptr->curr_frame_nr;
   if((1 + ainfo_ptr->last_frame_nr - l_curr) < cnt)
   {
      /* limt cnt to last existing frame */
      cnt = 1 + ainfo_ptr->frame_cnt - l_curr;
   }

   if(cnt >= ainfo_ptr->frame_cnt)
   {
      /* dont want to delete all frames
       * so we have to leave a rest of one frame
       */
      cnt--;
   }

   
   l_idx   = l_curr;
   while(l_idx < (l_curr + cnt))
   {
      p_delete_frame(ainfo_ptr, l_idx);
      l_idx++;
   }
   
   /* rename (renumber) all frames with number greater than current
    */
   l_lo   = l_curr;
   l_hi   = l_curr + cnt;
   while(l_hi <= ainfo_ptr->last_frame_nr)
   {
     if(0 != p_rename_frame(ainfo_ptr, l_hi, l_lo))
     {
        gchar *tmp_errtxt;
	
        tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld") ,l_hi, l_lo);
        p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	g_free(tmp_errtxt);
        return -1;
     }
     l_lo++;
     l_hi++;
   }

   /* calculate how much frames are left */
   ainfo_ptr->frame_cnt -= cnt;
   ainfo_ptr->last_frame_nr = ainfo_ptr->first_frame_nr + ainfo_ptr->frame_cnt -1;
   
   /* set current position to previous frame (if there is one) */
   if(l_curr > ainfo_ptr->first_frame_nr) 
   { 
     ainfo_ptr->frame_nr = l_curr -1;
   }
   else
   { 
      ainfo_ptr->frame_nr = ainfo_ptr->first_frame_nr;
   }

   /* make filename, then load the new current frame */
   if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
   ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                      ainfo_ptr->frame_nr,
                                      ainfo_ptr->extension);

   if(ainfo_ptr->new_filename != NULL)
      return (p_load_named_frame(ainfo_ptr->image_id, ainfo_ptr->new_filename));
   else
      return -1;
      
}        /* end p_del */

/* ============================================================================
 * p_dup
 *
 * all following frames are renamed (renumbered up by cnt) 
 * current frame is duplicated (cnt) times
 * ============================================================================
 */
int p_dup(t_anim_info *ainfo_ptr, long cnt, long range_from, long range_to)
{
   long  l_lo, l_hi;
   long  l_cnt2;
   long  l_step;
   long  l_src_nr, l_src_nr_min, l_src_nr_max;
   char  *l_dup_name;
   char  *l_curr_name;
   gdouble    l_percentage, l_percentage_step;
 
   if(gap_debug) fprintf(stderr, "DEBUG  p_dup fr:%d to:%d cnt:%d extension:%s: basename:%s frame_cnt:%d\n",
                         (int)range_from, (int)range_to, (int)cnt, ainfo_ptr->extension, ainfo_ptr->basename, (int)ainfo_ptr->frame_cnt);

   if(cnt < 1) return -1;

   l_curr_name = p_alloc_fname(ainfo_ptr->basename, ainfo_ptr->curr_frame_nr, ainfo_ptr->extension);
   /* save current frame  */   
   if(p_save_named_frame(ainfo_ptr->image_id, l_curr_name) < 0)
   {
     gchar *tmp_errtxt;
     tmp_errtxt = g_strdup_printf(_("Error: could not save frame %s"), l_curr_name);
     p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
     g_free(tmp_errtxt);
     return -1;
   }

   /* use a new name (0001.xcf Konvention) */ 
   gimp_image_set_filename (ainfo_ptr->image_id, l_curr_name);
   g_free(l_curr_name);


   if((range_from <0 ) && (range_to < 0 ))
   {
      /* set range to one single current frame
       * (used for the old non_interactive PDB-interface without range params)
       */
      range_from = ainfo_ptr->curr_frame_nr;
      range_to   = ainfo_ptr->curr_frame_nr;
   }

   /* clip range */
   if(range_from > ainfo_ptr->last_frame_nr)  range_from = ainfo_ptr->last_frame_nr;
   if(range_to   > ainfo_ptr->last_frame_nr)  range_to   = ainfo_ptr->last_frame_nr;
   if(range_from < ainfo_ptr->first_frame_nr) range_from = ainfo_ptr->first_frame_nr;
   if(range_to   < ainfo_ptr->first_frame_nr) range_to   = ainfo_ptr->first_frame_nr;

   if(range_to < range_from)
   {
       /* invers range */
      l_cnt2 = ((range_from - range_to ) + 1) * cnt;
      l_step = -1;
      l_src_nr_max = range_from;
      l_src_nr_min = range_to;
   }
   else
   {
      l_cnt2 = ((range_to - range_from ) + 1) * cnt;  
      l_step = 1;
      l_src_nr_max = range_to;
      l_src_nr_min = range_from;
   }    

   if(gap_debug) fprintf(stderr, "DEBUG  p_dup fr:%d to:%d cnt2:%d l_src_nr_max:%d\n",
                         (int)range_from, (int)range_to, (int)l_cnt2, (int)l_src_nr_max);
   

   l_percentage = 0.0;  
   if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
   { 
     gimp_progress_init( _("Duplicating frames..."));
   }

   /* rename (renumber) all frames with number greater than current
    */
   l_lo   = ainfo_ptr->last_frame_nr;
   l_hi   = l_lo + l_cnt2;
   while(l_lo > l_src_nr_max)
   {     
     if(0 != p_rename_frame(ainfo_ptr, l_lo, l_hi))
     {
        gchar *tmp_errtxt;
        tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld"), l_lo, l_hi);
        p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	g_free(tmp_errtxt);
        return -1;
     }
     l_lo--;
     l_hi--;
   }


   l_percentage_step = 1.0 / ((1.0 + l_hi) - l_src_nr_max);
   if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
   { 
      l_percentage += l_percentage_step;
      gimp_progress_update (l_percentage);
   }

   /* copy cnt duplicates */   
   l_src_nr = range_to;
   while(l_hi > l_src_nr_max)
   {
      l_curr_name = p_alloc_fname(ainfo_ptr->basename, l_src_nr, ainfo_ptr->extension);  
      l_dup_name = p_alloc_fname(ainfo_ptr->basename, l_hi, ainfo_ptr->extension);
      if((l_dup_name != NULL) && (l_curr_name != NULL))
      {
         p_image_file_copy(l_curr_name, l_dup_name);
         g_free(l_dup_name);
         g_free(l_curr_name);
      }
      if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
      { 
         l_percentage += l_percentage_step;
         gimp_progress_update (l_percentage);
      }
      
      
      l_src_nr -= l_step;
      if(l_src_nr < l_src_nr_min) l_src_nr = l_src_nr_max;
      if(l_src_nr > l_src_nr_max) l_src_nr = l_src_nr_min;
      
      l_hi--;
   }

   /* restore current position */
   ainfo_ptr->frame_cnt += l_cnt2;
   ainfo_ptr->last_frame_nr = ainfo_ptr->first_frame_nr + ainfo_ptr->frame_cnt -1;

   /* load from the "new" current frame */   
   if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
   ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                      ainfo_ptr->curr_frame_nr,
                                      ainfo_ptr->extension);
   return (p_load_named_frame(ainfo_ptr->image_id, ainfo_ptr->new_filename));
}        /* end p_dup */

/* ============================================================================
 * p_exchg
 *
 * save current frame, exchange its name with destination frame on disk 
 * and reload current frame (now has contents of dest. and vice versa)
 * ============================================================================
 */
int p_exchg(t_anim_info *ainfo_ptr, long dest)
{
   long  l_tmp_nr;
   gchar *tmp_errtxt;

   l_tmp_nr = ainfo_ptr->last_frame_nr + 4;  /* use a free frame_nr for temp name */

   if((dest < 1) || (dest == ainfo_ptr->curr_frame_nr)) 
      return -1;

   if(p_save_named_frame(ainfo_ptr->image_id, ainfo_ptr->old_filename) < 0)
      return -1;
   

   /* rename (renumber) frames */
   if(0 != p_rename_frame(ainfo_ptr, dest, l_tmp_nr))
   {
        tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld"), dest, l_tmp_nr);
        p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	g_free(tmp_errtxt);
        return -1;
   }
   if(0 != p_rename_frame(ainfo_ptr, ainfo_ptr->curr_frame_nr, dest))
   {
        tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld"), ainfo_ptr->curr_frame_nr, dest);
        p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	g_free(tmp_errtxt);
        return -1;
   }
   if(0 != p_rename_frame(ainfo_ptr, l_tmp_nr, ainfo_ptr->curr_frame_nr))
   {
        tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld"), l_tmp_nr, ainfo_ptr->curr_frame_nr);
        p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	g_free(tmp_errtxt);
        return -1;
   }

   /* load from the "new" current frame */   
   if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
   ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                      ainfo_ptr->curr_frame_nr,
                                      ainfo_ptr->extension);
   return (p_load_named_frame(ainfo_ptr->image_id, ainfo_ptr->new_filename));

}        /* end p_exchg */

/* ============================================================================
 * p_shift
 *
 * all frmaes in the given range are renumbered (shifted)
 * according to cnt:
 *  example:  cnt == 1 :  range before 3, 4, 5, 6, 7
 *                        range after  4, 5, 6, 7, 3
 * ============================================================================
 */
static int
p_shift(t_anim_info *ainfo_ptr, long cnt, long range_from, long range_to)
{
   long  l_lo, l_hi, l_curr, l_dst;
   long  l_upper;
   long  l_shift;
   gchar *l_curr_name;
   gchar *tmp_errtxt;
	
   gdouble    l_percentage, l_percentage_step;  

   if(gap_debug) fprintf(stderr, "DEBUG  p_shift fr:%d to:%d cnt:%d\n",
                         (int)range_from, (int)range_to, (int)cnt);

   if(range_from == range_to) return -1;

   /* clip range */
   if(range_from > ainfo_ptr->last_frame_nr)  range_from = ainfo_ptr->last_frame_nr;
   if(range_to   > ainfo_ptr->last_frame_nr)  range_to   = ainfo_ptr->last_frame_nr;
   if(range_from < ainfo_ptr->first_frame_nr) range_from = ainfo_ptr->first_frame_nr;
   if(range_to   < ainfo_ptr->first_frame_nr) range_to   = ainfo_ptr->first_frame_nr;

   if(range_to < range_from)
   {
      l_lo = range_to;
      l_hi = range_from;
   }
   else
   {
      l_lo = range_from;
      l_hi = range_to;
   }
   
   /* limit shift  amount to number of frames in range */
   l_shift = cnt % (l_hi - l_lo);
   if(gap_debug) fprintf(stderr, "DEBUG  p_shift shift:%d\n",
                         (int)l_shift);
   if(l_shift == 0) return -1;

   l_curr_name = p_alloc_fname(ainfo_ptr->basename, ainfo_ptr->curr_frame_nr, ainfo_ptr->extension);
   /* save current frame  */   
   p_save_named_frame(ainfo_ptr->image_id, l_curr_name);
   g_free(l_curr_name);

   l_percentage = 0.0;  
   if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
   { 
     gimp_progress_init( _("Renumber Framesequence..."));
   }

   /* rename (renumber) all frames (using high numbers)
    */

   l_upper = ainfo_ptr->last_frame_nr +100;
   l_percentage_step = 0.5 / ((1.0 + l_lo) - l_hi);
   for(l_curr = l_lo; l_curr <= l_hi; l_curr++)
   {
     if(0 != p_rename_frame(ainfo_ptr, l_curr, l_curr + l_upper))
     {
        tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld"), l_lo, l_hi);
        p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	g_free(tmp_errtxt);
        return -1;
     }
     if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
     { 
       l_percentage += l_percentage_step;
       gimp_progress_update (l_percentage);
     }
   }

   /* rename (renumber) all frames (using desied destination numbers)
    */
   l_dst = l_lo + l_shift;
   if (l_dst > l_hi) { l_dst -= (l_lo -1); }
   if (l_dst < l_lo) { l_dst += ((l_hi - l_lo) +1); }
   for(l_curr = l_upper + l_lo; l_curr <= l_upper + l_hi; l_curr++)
   {
     if (l_dst > l_hi) { l_dst = l_lo; }
     if(0 != p_rename_frame(ainfo_ptr, l_curr, l_dst))
     {
        tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld"), l_lo, l_hi);
        p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	g_free(tmp_errtxt);
        return -1;
     }
     if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
     { 
       l_percentage += l_percentage_step;
       gimp_progress_update (l_percentage);
     }

     l_dst ++;     
   }


   /* load from the "new" current frame */   
   if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
   ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                      ainfo_ptr->curr_frame_nr,
                                      ainfo_ptr->extension);
   return (p_load_named_frame(ainfo_ptr->image_id, ainfo_ptr->new_filename));
}  /* end p_shift */





/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ 
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ 
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ 


/* ============================================================================
 * gap_next gap_prev
 *
 * store the current Gimp Image to the current anim Frame
 * and load it from the next/prev anim Frame on disk.
 * ============================================================================
 */
int gap_next(GimpRunMode run_mode, gint32 image_id)
{
  int rc;
  t_anim_info *ainfo_ptr;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    ainfo_ptr->frame_nr = ainfo_ptr->curr_frame_nr + 1;
    rc = p_replace_image(ainfo_ptr);
  
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    
}	/* end gap_next */

int gap_prev(GimpRunMode run_mode, gint32 image_id)
{
  int rc;
  t_anim_info *ainfo_ptr;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    ainfo_ptr->frame_nr = ainfo_ptr->curr_frame_nr - 1;
    rc = p_replace_image(ainfo_ptr);
  
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    
}	/* end gap_prev */

/* ============================================================================
 * gap_first  gap_last
 *
 * store the current Gimp Image to the current anim Frame
 * and load it from the first/last anim Frame on disk.
 * ============================================================================
 */

int gap_first(GimpRunMode run_mode, gint32 image_id)
{
  int rc;
  t_anim_info *ainfo_ptr;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    if (0 == p_dir_ainfo(ainfo_ptr))
    {
      ainfo_ptr->frame_nr = ainfo_ptr->first_frame_nr;
      rc = p_replace_image(ainfo_ptr);
    }
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    
}	/* end gap_first */

int gap_last(GimpRunMode run_mode, gint32 image_id)
{
  int rc;
  t_anim_info *ainfo_ptr;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    if (0 == p_dir_ainfo(ainfo_ptr))
    {
      ainfo_ptr->frame_nr = ainfo_ptr->last_frame_nr;
      rc = p_replace_image(ainfo_ptr);
    }
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    
}	/* end gap_last */

/* ============================================================================
 * gap_goto
 * 
 * store the current Gimp Image to disk
 * and load it from the anim Frame on disk that has the specified frame Nr.
 * GIMP_RUN_INTERACTIVE:
 *    show dialogwindow where user can enter the destination frame Nr.
 * ============================================================================
 */

int gap_goto(GimpRunMode run_mode, gint32 image_id, int nr)
{
  int rc;
  t_anim_info *ainfo_ptr;

  long            l_dest;
  gchar          *l_hline;
  gchar          *l_title;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    if (0 == p_dir_ainfo(ainfo_ptr))
    {
      if(0 != p_chk_framerange(ainfo_ptr))   return -1;

      if(run_mode == GIMP_RUN_INTERACTIVE)
      {
        l_title = g_strdup_printf (_("Goto Frame (%ld/%ld)")
				   , ainfo_ptr->curr_frame_nr
				   , ainfo_ptr->frame_cnt);
        l_hline =  g_strdup_printf (_("Destination Frame Number (%ld  - %ld)")
				    , ainfo_ptr->first_frame_nr
				    , ainfo_ptr->last_frame_nr);

        l_dest = p_slider_dialog(l_title, l_hline, _("Number:"), NULL
                , ainfo_ptr->first_frame_nr
                , ainfo_ptr->last_frame_nr
                , ainfo_ptr->curr_frame_nr
                , TRUE);

	g_free (l_title);
	g_free (l_hline);
                
        if(l_dest < 0)
        {
           /* Cancel button: go back to current frame */
           l_dest = ainfo_ptr->curr_frame_nr;
        }  
        if(0 != p_chk_framechange(ainfo_ptr))
        {
           l_dest = -1;
        }
      }
      else
      {
        l_dest = nr;
      }

      if(l_dest >= 0)
      {
        ainfo_ptr->frame_nr = l_dest;
        rc = p_replace_image(ainfo_ptr);
      }

    }
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    
}	/* end gap_goto */

/* ============================================================================
 * gap_del
 * ============================================================================
 */
int gap_del(GimpRunMode run_mode, gint32 image_id, int nr)
{
  int rc;
  t_anim_info *ainfo_ptr;

  long           l_cnt;
  long           l_max;
  gchar         *l_hline;
  gchar         *l_title;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    if (0 == p_dir_ainfo(ainfo_ptr))
    {
      if(0 != p_chk_framerange(ainfo_ptr))   return -1;
      
      if(run_mode == GIMP_RUN_INTERACTIVE)
      {
        l_title = g_strdup_printf (_("Delete Frames (%ld/%ld)")
				   , ainfo_ptr->curr_frame_nr
				   , ainfo_ptr->frame_cnt);
        l_hline = g_strdup_printf (_("Delete Frames from %ld to (number)")
				   , ainfo_ptr->curr_frame_nr);

        l_max = ainfo_ptr->last_frame_nr;
        if(l_max == ainfo_ptr->curr_frame_nr)
        { 
          /* bugfix: the slider needs a maximum > minimum
           *         (if not an error is printed, and
           *          a default range 0 to 100 is displayed)
           */
          l_max++;
        }
        
        l_cnt = p_slider_dialog(l_title, l_hline, _("Number:"), NULL
              , ainfo_ptr->curr_frame_nr
              , l_max
              , ainfo_ptr->curr_frame_nr
              , TRUE);
                
	g_free (l_title);
	g_free (l_hline);
	
        if(l_cnt >= 0)
        {
           l_cnt = 1 + l_cnt - ainfo_ptr->curr_frame_nr;
        } 
        if(0 != p_chk_framechange(ainfo_ptr))
        {
           l_cnt = -1;
        }
      }
      else
      {
        l_cnt = nr;
      }
 
      if(l_cnt >= 0)
      {
         /* delete l_cnt number of frames (-1 CANCEL button) */

         rc = p_del(ainfo_ptr, l_cnt);
      }


    }
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    

}	/* end gap_del */


/* ============================================================================
 * p_dup_dialog
 *
 * ============================================================================
 */
int p_dup_dialog(t_anim_info *ainfo_ptr, long *range_from, long *range_to)
{
  static t_arr_arg  argv[3];
  gchar            *l_title;

  l_title = g_strdup_printf (_("Duplicate Frames (%ld/%ld)")
			     , ainfo_ptr->curr_frame_nr
			     , ainfo_ptr->frame_cnt);

  p_init_arr_arg(&argv[0], WGT_INT_PAIR);
  argv[0].label_txt = _("From:");
  argv[0].constraint = TRUE;
  argv[0].int_min   = (gint)ainfo_ptr->first_frame_nr;
  argv[0].int_max   = (gint)ainfo_ptr->last_frame_nr;
  argv[0].int_ret   = (gint)ainfo_ptr->curr_frame_nr;
  argv[0].help_txt  = _("Source Range starts at this framenumber");

  p_init_arr_arg(&argv[1], WGT_INT_PAIR);
  argv[1].label_txt = _("To:");
  argv[1].constraint = TRUE;
  argv[1].int_min   = (gint)ainfo_ptr->first_frame_nr;
  argv[1].int_max   = (gint)ainfo_ptr->last_frame_nr;
  argv[1].int_ret   = (gint)ainfo_ptr->curr_frame_nr;
  argv[1].help_txt  = _("Source Range ends at this framenumber");
    
  p_init_arr_arg(&argv[2], WGT_INT_PAIR);
  argv[2].label_txt = _("N times:");
  argv[2].constraint = FALSE;
  argv[2].int_min   = 1;
  argv[2].int_max   = 99;
  argv[2].int_ret   = 1;
  argv[2].umin      = 1;
  argv[2].umax      = 9999;
  argv[2].help_txt  = _("Copy selected Range n-times  \n(you may type in Values > 99)");


  if(TRUE == p_array_dialog(l_title, _("Duplicate Frame Range"),  3, argv))
  { 
    g_free (l_title);
    *range_from = (long)(argv[0].int_ret);
    *range_to   = (long)(argv[1].int_ret);
       return (int)(argv[2].int_ret);
  }
  else
  {
    g_free (l_title);
    return -1;
  }
   

}	/* end p_dup_dialog */


/* ============================================================================
 * gap_dup
 * ============================================================================
 */
int gap_dup(GimpRunMode run_mode, gint32 image_id, int nr,
            long range_from, long range_to)
{
  int rc;
  t_anim_info *ainfo_ptr;

  long           l_cnt, l_from, l_to;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    if (0 == p_dir_ainfo(ainfo_ptr))
    {
      if(run_mode == GIMP_RUN_INTERACTIVE)
      {
         if(0 != p_chk_framechange(ainfo_ptr)) { l_cnt = -1; }
         else
	 {
	   if(*ainfo_ptr->extension == '\0' && ainfo_ptr->frame_cnt == 0)
	   {
	     /* duplicate was called on a frame without extension and without framenumer in its name
	      * (typical for new created images named like 'Untitled' (or 'Unbenannt' for german GUI or .. in other languages)
	      */
	       p_msg_win(ainfo_ptr->run_mode,
		       _("OPERATION CANCELLED.\n"
			 "GAP plug-ins only work with filenames\n"
			 "that end in numbers like _0001.xcf.\n"
			 "==> Rename your image, then try again."));
	       return -1;
	   }
	   l_cnt = p_dup_dialog(ainfo_ptr, &l_from, &l_to);
	 }

         if((0 != p_chk_framechange(ainfo_ptr)) || (l_cnt < 1))
         {
            l_cnt = -1;
         }
                
      }
      else
      {
        l_cnt  = nr;
        l_from = range_from;
        l_to   = range_to;
      }
 
      if(l_cnt > 0)
      {
         /* make l_cnt duplicate frames (on disk) */
         rc = p_dup(ainfo_ptr, l_cnt, l_from, l_to);
      }


    }
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    

 
}	/* end gap_dup */


/* ============================================================================
 * gap_exchg
 * ============================================================================
 */

int gap_exchg(GimpRunMode run_mode, gint32 image_id, int nr)
{
  int rc;
  t_anim_info *ainfo_ptr;

  long           l_dest;
  long           l_initial;
  gchar         *l_title;

  rc = -1;
  l_initial = 1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    if (0 == p_dir_ainfo(ainfo_ptr))
    {
      if(0 != p_chk_framerange(ainfo_ptr))   return -1;
      
      if(run_mode == GIMP_RUN_INTERACTIVE)
      {
         if(ainfo_ptr->curr_frame_nr < ainfo_ptr->last_frame_nr)
         {
           l_initial = ainfo_ptr->curr_frame_nr + 1;
         }
         else
         {
           l_initial = ainfo_ptr->last_frame_nr; 
         }
         l_title = g_strdup_printf (_("Exchange current Frame (%ld)")
				    , ainfo_ptr->curr_frame_nr);

         l_dest = p_slider_dialog(l_title, 
				  _("With Frame (number)"), 
				  _("Number:"), NULL
				  , ainfo_ptr->first_frame_nr 
				  , ainfo_ptr->last_frame_nr
				  , l_initial
				  , TRUE);
	 g_free (l_title);
				  
         if(0 != p_chk_framechange(ainfo_ptr))
         {
            l_dest = -1;
         }
      }
      else
      {
        l_dest = nr;
      }
 
      if((l_dest >= ainfo_ptr->first_frame_nr ) && (l_dest <= ainfo_ptr->last_frame_nr ))
      {
         /* excange current frames with destination frame (on disk) */
         rc = p_exchg(ainfo_ptr, l_dest);
      }


    }
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    
}	/* end gap_exchg */

/* ============================================================================
 * p_shift_dialog
 *
 * ============================================================================
 */
int p_shift_dialog(t_anim_info *ainfo_ptr, long *range_from, long *range_to)
{
  static t_arr_arg  argv[3];
  gchar            *l_title;

  l_title = g_strdup_printf (_("Framesequence Shift (%ld/%ld)")
			     , ainfo_ptr->curr_frame_nr
			     , ainfo_ptr->frame_cnt);

  p_init_arr_arg(&argv[0], WGT_INT_PAIR);
  argv[0].label_txt = _("From:");
  argv[0].constraint = TRUE;
  argv[0].int_min   = (gint)ainfo_ptr->first_frame_nr;
  argv[0].int_max   = (gint)ainfo_ptr->last_frame_nr;
  argv[0].int_ret   = (gint)ainfo_ptr->curr_frame_nr;
  argv[0].help_txt  = _("Affected Range starts at this framenumber");

  p_init_arr_arg(&argv[1], WGT_INT_PAIR);
  argv[1].label_txt = _("To:");
  argv[1].constraint = TRUE;
  argv[1].int_min   = (gint)ainfo_ptr->first_frame_nr;
  argv[1].int_max   = (gint)ainfo_ptr->last_frame_nr;
  argv[1].int_ret   = (gint)ainfo_ptr->last_frame_nr;
  argv[1].help_txt  = _("Affected Range ends at this framenumber");
    
  p_init_arr_arg(&argv[2], WGT_INT_PAIR);
  argv[2].label_txt = _("N-Shift:");
  argv[2].constraint = TRUE;
  argv[2].int_min   = -1 * (gint)ainfo_ptr->last_frame_nr;
  argv[2].int_max   = (gint)ainfo_ptr->last_frame_nr;
  argv[2].int_ret   = 1;
  argv[2].help_txt  = _("Renumber the affected framesequence     \n(numbers are shifted in circle by N)");
  
  if(TRUE == p_array_dialog(l_title, _("Framesequence shift"),  3, argv))
  { 
    g_free (l_title);
    *range_from = (long)(argv[0].int_ret);
    *range_to   = (long)(argv[1].int_ret);
    return (int)(argv[2].int_ret);
  }
  else
  {
    g_free (l_title);
    return 0;
  }
   

}	/* end p_shift_dialog */


/* ============================================================================
 * gap_shift
 * ============================================================================
 */
int gap_shift(GimpRunMode run_mode, gint32 image_id, int nr,
            long range_from, long range_to)
{
  int rc;
  t_anim_info *ainfo_ptr;

  long           l_cnt, l_from, l_to;

  rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    if (0 == p_dir_ainfo(ainfo_ptr))
    {
      if(run_mode == GIMP_RUN_INTERACTIVE)
      {
         l_cnt = 1;
         if(0 != p_chk_framechange(ainfo_ptr)) { l_cnt = 0; }
         else { l_cnt = p_shift_dialog(ainfo_ptr, &l_from, &l_to); }

         if((0 != p_chk_framechange(ainfo_ptr)) || (l_cnt == 0))
         {
            l_cnt = 0;
         }
                
      }
      else
      {
        l_cnt  = nr;
        l_from = range_from;
        l_to   = range_to;
      }
 
      if(l_cnt != 0)
      {
         /* shift framesquence by l_cnt frames 
          * (rename all frames in the given range on disk)
          */
         rc = p_shift(ainfo_ptr, l_cnt, l_from, l_to);
      }


    }
    p_free_ainfo(&ainfo_ptr);
  }
  
  return(rc);    

 
}	/* end gap_shift */


/* ============================================================================
 * gap_video_paste Buffer procedures
 * ============================================================================
 */

gchar *
p_get_video_paste_basename(void)
{
  gchar *l_basename;
  
  l_basename = p_gimp_gimprc_query("video-paste-basename");
  if(l_basename == NULL)
  {
     l_basename = g_strdup("gap_video_pastebuffer_");
  }
  return(l_basename);
}

gchar *
p_get_video_paste_dir(void)
{
  gchar *l_dir;
  gint   l_len;
  
  l_dir = p_gimp_gimprc_query("video-paste-dir");
  if(l_dir == NULL)
  {
     l_dir = g_strdup("/tmp");
  }
  
  /* if dir is configured with trailing dir seprator slash
   * then cut it off
   */
  l_len = strlen(l_dir);
  if((l_dir[l_len -1] == G_DIR_SEPARATOR) && (l_len > 1))
  {
     l_dir[l_len -1] = '\0';
  }
  return(l_dir);
}

gchar *
p_get_video_paste_name(void)
{
  gchar *l_dir;
  gchar *l_basename;
  gchar *l_video_name;
  gchar *l_dir_thumb;
	 
  l_dir = p_get_video_paste_dir();
  l_basename = p_get_video_paste_basename();
  l_video_name = g_strdup_printf("%s%s%s", l_dir, G_DIR_SEPARATOR_S, l_basename);
  l_dir_thumb = g_strdup_printf("%s%s%s", l_dir, G_DIR_SEPARATOR_S, ".xvpics");

  mkdir (l_dir_thumb, 0755);

  g_free(l_dir);
  g_free(l_basename);
  g_free(l_dir_thumb);

  if(gap_debug) printf("p_get_video_paste_name: %s\n", l_video_name);
  return(l_video_name); 
}

static gint32
p_clear_or_count_video_paste(gint delete_flag)
{
  gchar *l_dir;
  gchar *l_basename;
  gchar *l_filename;
  gchar *l_fname_thumbnail;
  gint   l_len;
  gint32 l_framecount;
  GDir          *l_dirp;
  const char    *l_entry;

  l_dir = p_get_video_paste_dir();
  l_dirp = g_dir_open(l_dir, 0, NULL);  
  l_framecount = 0;
  
  if(!l_dirp)
  {
    printf("ERROR p_vid_edit_clear: can't read directory %s\n", l_dir);
    l_framecount = -1;
  }
  else
  {
     l_basename = p_get_video_paste_basename();
     
     l_len = strlen(l_basename);
     while ( (l_entry = g_dir_read_name( l_dirp )) != NULL )
     {
       if(strncmp(l_basename, l_entry, l_len) == 0)
       {
          l_filename = g_strdup_printf("%s%s%s", l_dir, G_DIR_SEPARATOR_S, l_entry);
          if(1 == p_file_exists(l_filename)) /* check for regular file */
	  {
             /* delete all files in the video paste directory
	      * with names matching the basename part
	      */
	     l_framecount++;
	     if(delete_flag)
	     {
        	if(gap_debug) printf("p_vid_edit_clear: remove file %s\n", l_filename);
		remove(l_filename);

		/* also delete thumbnail */
        	l_fname_thumbnail = p_alloc_fname_thumbnail(l_filename);
        	remove(l_fname_thumbnail);
                g_free(l_fname_thumbnail);
	     }
	  }
          g_free(l_filename);
       }
     }
     g_dir_close( l_dirp );
     g_free(l_basename);
   }
   g_free(l_dir);
   return(l_framecount);
}

gint32
p_vid_edit_clear(void)
{
  return(p_clear_or_count_video_paste(TRUE)); /* delete frames */
}

gint32
p_vid_edit_framecount()
{
  return (p_clear_or_count_video_paste(FALSE)); /* delete_flag is off, just count frames */
}


/* ============================================================================
 * gap_vid_edit_copy
 * ============================================================================
 */
gint
gap_vid_edit_copy(GimpRunMode run_mode, gint32 image_id, long range_from, long range_to)
{
  int rc;
  t_anim_info *ainfo_ptr;
  
  gchar *l_curr_name;
  gchar *l_fname ;
  gchar *l_fname_copy;
  gchar *l_basename;
  gint32 l_frame_nr;
  gint32 l_cnt_range;
  gint32 l_cnt2;
  gint32 l_idx;
  gint32 l_tmp_image_id;

  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr == NULL)
  {
     return (-1);
  }
  rc = 0;

  if((ainfo_ptr->curr_frame_nr >= MIN(range_to, range_from))
  && (ainfo_ptr->curr_frame_nr <= MAX(range_to, range_from)))
  {
    /* current frame is in the affected range
     * so we have to save current frame to file
     */   
    l_curr_name = p_alloc_fname(ainfo_ptr->basename, ainfo_ptr->curr_frame_nr, ainfo_ptr->extension);
    p_save_named_frame(ainfo_ptr->image_id, l_curr_name);
    g_free(l_curr_name);
  }
  
  l_basename = p_get_video_paste_name();
  l_cnt2 = p_vid_edit_framecount();  /* count frames in the video paste buffer */
  l_frame_nr = 1 + l_cnt2;           /* start at one, or continue (append) at end +1 */
  
  l_cnt_range = 1 + MAX(range_to, range_from) - MIN(range_to, range_from);
  for(l_idx = 0; l_idx < l_cnt_range;  l_idx++)
  {
     if(rc < 0)
     {
       break;
     }
     l_fname = p_alloc_fname(ainfo_ptr->basename,
                             MIN(range_to, range_from) + l_idx,
                             ainfo_ptr->extension);
     l_fname_copy = g_strdup_printf("%s%04ld.xcf", l_basename, (long)l_frame_nr);
     
     if(strcmp(ainfo_ptr->extension, ".xcf") == 0)
     {
        rc = p_image_file_copy(l_fname, l_fname_copy);
     }
     else
     {
        /* convert other fileformats to xcf before saving to video paste buffer */
	l_tmp_image_id = p_load_image(l_fname);
	rc = p_save_named_frame(l_tmp_image_id, l_fname_copy);
	gimp_image_delete(l_tmp_image_id);
     }
     g_free(l_fname);
     g_free(l_fname_copy);
     l_frame_nr++;
  }
  p_free_ainfo(&ainfo_ptr);
  return(rc);
}	/* end gap_vid_edit_copy */

/* ============================================================================
 * p_custom_palette_file
 *   write a gimp palette file
 * ============================================================================
 */

static gint p_custom_palette_file(char *filename, guchar *rgb, gint count)
{
  FILE *l_fp;

  l_fp= fopen(filename, "w");
  if (l_fp == NULL)
  {
    return -1;
  }
  
  fprintf(l_fp, "GIMP Palette\n");
  fprintf(l_fp, "# this file will be overwritten each time when video frames are converted to INDEXED\n");

  while (count > 0)
  {
    fprintf(l_fp, "%d %d %d\tUnknown\n", rgb[0], rgb[1], rgb[2]);
    rgb+= 3;
    --count;
  }


  fclose (l_fp);
  return 0;
}	/* end p_custom_palette_file */


/* ============================================================================
 * gap_vid_edit_paste
 * ============================================================================
 */
gint
gap_vid_edit_paste(GimpRunMode run_mode, gint32 image_id, long paste_mode)
{
#define CUSTOM_PALETTE_NAME "gap_cmap"
  int rc;
  t_anim_info *ainfo_ptr;
  
  gchar *l_curr_name;
  gchar *l_fname ;
  gchar *l_fname_copy;
  gchar *l_basename;
  gint32 l_frame_nr;
  gint32 l_dst_frame_nr;
  gint32 l_cnt2;
  gint32 l_lo, l_hi;
  gint32 l_insert_frame_nr;
  gint32 l_tmp_image_id;
  gint       l_rc;
  GimpParam     *l_params;
  gint        l_retvals;
  GimpImageBaseType  l_orig_basetype;

  l_cnt2 = p_vid_edit_framecount();
  if(gap_debug)
  {
    printf("gap_vid_edit_paste: paste_mode %d found %d frames to paste\n"
           , (int)paste_mode, (int)l_cnt2);
  }
  if (l_cnt2 < 1)
  {
    return(0);  /* video paste buffer is empty */
  }


  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr == NULL)
  {
     return (-1);
  }
  if (0 != p_dir_ainfo(ainfo_ptr))
  {
     return (-1);
  }

  rc = 0;

  l_insert_frame_nr = ainfo_ptr->curr_frame_nr;

  if(paste_mode != VID_PASTE_INSERT_AFTER)
  {
    /* we have to save current frame to file */   
    l_curr_name = p_alloc_fname(ainfo_ptr->basename, ainfo_ptr->curr_frame_nr, ainfo_ptr->extension);
    p_save_named_frame(ainfo_ptr->image_id, l_curr_name);
    g_free(l_curr_name);
  }
  
  if(paste_mode != VID_PASTE_REPLACE)
  {
     if(paste_mode == VID_PASTE_INSERT_AFTER)
     {
       l_insert_frame_nr = ainfo_ptr->curr_frame_nr +1;
     }
    
     /* rename (renumber) all frames with number greater (or greater equal)  than current
      */
     l_lo   = ainfo_ptr->last_frame_nr;
     l_hi   = l_lo + l_cnt2;

     if(gap_debug)
     {
       printf("gap_vid_edit_paste: l_insert_frame_nr %d l_lo:%d l_hi:%d\n"
           , (int)l_insert_frame_nr, (int)l_lo, (int)l_hi);
     }
     
     while(l_lo >= l_insert_frame_nr)
     {     
       if(0 != p_rename_frame(ainfo_ptr, l_lo, l_hi))
       {
          gchar *tmp_errtxt;
          tmp_errtxt = g_strdup_printf(_("Error: could not rename frame %ld to %ld"), (long int)l_lo, (long int)l_hi);
          p_msg_win(ainfo_ptr->run_mode, tmp_errtxt);
	  g_free(tmp_errtxt);
          return -1;
       }
       l_lo--;
       l_hi--;
     }
  }

  l_basename = p_get_video_paste_name();
  l_dst_frame_nr = l_insert_frame_nr;
  for(l_frame_nr = 1; l_frame_nr <= l_cnt2; l_frame_nr++)
  {
     l_fname = p_alloc_fname(ainfo_ptr->basename,
                             l_dst_frame_nr,
                             ainfo_ptr->extension);
     l_fname_copy = g_strdup_printf("%s%04ld.xcf", l_basename, (long)l_frame_nr);

     l_tmp_image_id = p_load_image(l_fname_copy);
     
     /* check size and resize if needed */
     if((gimp_image_width(l_tmp_image_id) != gimp_image_width(image_id))
     || (gimp_image_height(l_tmp_image_id) != gimp_image_height(image_id)))
     {
         GimpParam     *l_params;
         gint        l_retvals;
	 gint32      l_size_x, l_size_y;

         l_size_x = gimp_image_width(image_id);
         l_size_y = gimp_image_height(image_id);
	 if(gap_debug) printf("DEBUG: scale to size %d %d\n", (int)l_size_x, (int)l_size_y);
 
         l_params = gimp_run_procedure ("gimp_image_scale",
			         &l_retvals,
			         GIMP_PDB_IMAGE,    l_tmp_image_id,
			         GIMP_PDB_INT32,    l_size_x,
			         GIMP_PDB_INT32,    l_size_y,
			         GIMP_PDB_END);


     }
     
     /* check basetype and convert if needed */
     l_orig_basetype = gimp_image_base_type(image_id);
     if(gimp_image_base_type(l_tmp_image_id) != l_orig_basetype)
     {
       switch(l_orig_basetype)
       {
           gchar      *l_palette_filename;
           gchar      *l_gimp_dir;
           guchar     *l_cmap;
           gint        l_ncolors;
 
           /* convert tmp image to dest type */
           case GIMP_INDEXED:
             l_cmap = gimp_image_get_cmap(image_id, &l_ncolors);
             if(gap_debug) printf("DEBUG: convert to INDEXED %d colors\n", (int)l_ncolors);

             l_params = gimp_run_procedure ("gimp_gimprc_query",
			                    &l_retvals,
			                    GIMP_PDB_STRING, "gimp_dir",
			                    GIMP_PDB_END);

             l_gimp_dir = g_strdup(l_params[1].data.d_string);
             gimp_destroy_params(l_params, l_retvals);

             l_palette_filename = g_strdup_printf("%s%spalettes%s%s"
                                                 , l_gimp_dir
                                                 , G_DIR_SEPARATOR_S
                                                 , G_DIR_SEPARATOR_S
                                                 , CUSTOM_PALETTE_NAME);
 
             l_rc = p_custom_palette_file(l_palette_filename, l_cmap, l_ncolors);
             if(l_rc == 0)
             {
               l_params = gimp_run_procedure ("gimp_palette_refresh",
			                    &l_retvals,
			                    GIMP_PDB_END);
               gimp_destroy_params(l_params, l_retvals);
               
               l_params = gimp_run_procedure ("gimp_convert_indexed",
			                    &l_retvals,
			                    GIMP_PDB_IMAGE,    l_tmp_image_id,
			                    GIMP_PDB_INT32,    1,               /* dither  value 1== floyd-steinberg */
			                    GIMP_PDB_INT32,    4,               /* palette_type 4 == CUSTOM_PALETTE */
			                    GIMP_PDB_INT32,    l_ncolors,       /* number of colors */
			                    GIMP_PDB_INT32,    0,               /* alpha_dither */
			                    GIMP_PDB_INT32,    0,               /* remove_unused */
			                    GIMP_PDB_STRING,   CUSTOM_PALETTE_NAME, /* name of the custom palette */
			                    GIMP_PDB_END);
               gimp_destroy_params(l_params, l_retvals);
             }
             else
             {
               printf("ERROR: gap_vid_edit_paste: could not save custom palette %s\n", l_palette_filename);
             }
             g_free(l_cmap);
             g_free(l_palette_filename);
             g_free(l_gimp_dir);          
             break;

           case GIMP_GRAY:
             if(gap_debug) printf("DEBUG: convert to GRAY'\n");
             l_params = gimp_run_procedure ("gimp_convert_grayscale",
			                  &l_retvals,
			                  GIMP_PDB_IMAGE,    l_tmp_image_id,
			                  GIMP_PDB_END);
             gimp_destroy_params(l_params, l_retvals);
             break;

           case GIMP_RGB:
             if(gap_debug) printf("DEBUG: convert to RGB'\n");
             l_params = gimp_run_procedure ("gimp_convert_rgb",
			                  &l_retvals,
			                  GIMP_PDB_IMAGE,    l_tmp_image_id,
			                  GIMP_PDB_END);
             gimp_destroy_params(l_params, l_retvals);
             break;

           default:
             printf( "DEBUG: unknown image type\n");
             return -1;
             break;
        }
     }
     rc = p_save_named_frame(l_tmp_image_id, l_fname);
     gimp_image_delete(l_tmp_image_id);
     g_free(l_fname);
     g_free(l_fname_copy);

     l_dst_frame_nr++;
  }
  
  if((rc >= 0)  && (paste_mode != VID_PASTE_INSERT_AFTER))
  {
    /* load from the "new" current frame */   
    if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
    ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                      ainfo_ptr->curr_frame_nr,
                                      ainfo_ptr->extension);
    rc = p_load_named_frame(ainfo_ptr->image_id, ainfo_ptr->new_filename);
  }
  
  p_free_ainfo(&ainfo_ptr);
  
  return(rc);
}	/* end gap_vid_edit_paste */


gint32
p_getpid(void)
{
#ifndef G_OS_WIN32
  /* for UNIX */
  return ((gint32)getpid());
#else
  /* hof: dont know how to getpid on windows */
  return 0;
#endif
}

gint 
p_pid_is_alive(gint32 pid)
{
#ifndef G_OS_WIN32
  /* for UNIX */

  /* kill  with signal 0 checks only if the process is alive (no signal is sent)
   *       returns 0 if alive, 1 if no process with given pid found.
   */
  if (0 == kill(pid, 0))
  {
    return(TRUE);
  }
  return (FALSE);
#else
  /* hof: dont know how to check on Windows
   *      assume that process is always alive
   *      (therefore on Windows locks will not be cleared 
   *       automatically after crashes of the locking process)
   */
  return(TRUE);
#endif
}

static void
p_gap_lock_build_lockkey(t_gap_lockdata *lock,  gint32 image_id)
{
  g_snprintf(lock->key, sizeof(lock->key), "plug_in_gap_plugins_LOCK_%d", (int)image_id);
}

gint
p_gap_lock_is_locked(gint32 image_id, GimpRunMode run_mode)
{
  gint32          l_pid;
  t_gap_lockdata  l_lock;
  
  /* check for locks */
  l_pid = p_getpid();
  l_lock.lock = 0;
  p_gap_lock_build_lockkey(&l_lock, image_id);  
  gimp_get_data (l_lock.key, &l_lock);

  if((l_lock.lock != 0) && (l_lock.image_id == image_id))
  {
     if(p_pid_is_alive(l_lock.pid))
     {
       if(run_mode == GIMP_RUN_INTERACTIVE)
       {
          gchar *l_lockmsg;
       
          l_lockmsg = g_strdup_printf(_("Can't execute more than 1 Video Function\n"
                                        "on the same AnimFrame Image at the same time\n"
                                        "LOCK ID:%s\n")
                                   , l_lock.key);
          gimp_message(l_lockmsg);
          g_free(l_lockmsg);
       }
       printf("GAP plug-in is LOCKED  ID:%s PID:%d\n", l_lock.key, (int)l_lock.pid);
       
       return(TRUE);
     }
  }
  return(FALSE);
}

void
p_gap_lock_set(gint32 image_id)
{
  t_gap_lockdata  l_lock;
  
  p_gap_lock_build_lockkey(&l_lock, image_id);  

  /* set LOCK on image (for all gap_plugins) */
  l_lock.lock = 1;
  l_lock.image_id = image_id;
  l_lock.pid = p_getpid();
  gimp_set_data (l_lock.key, &l_lock, sizeof(l_lock));
}

void
p_gap_lock_remove(gint32 image_id)
{
  t_gap_lockdata  l_lock;
  
  p_gap_lock_build_lockkey(&l_lock, image_id);  

  /* remove LOCK on this image for all gap_plugins */
  l_lock.lock = 0;
  l_lock.image_id = -1;
  gimp_set_data (l_lock.key, &l_lock, sizeof(l_lock));
}
