[gimp-gap/gap-2-8] added LAYER GROUP Support in the MovePath dialog.



commit 43c26576a914d168e81139fc8dffa0b7e34cf669
Author: Wolfgang Hofer <wolfgangh svn gnome org>
Date:   Sun Feb 2 09:37:41 2014 +0100

    added LAYER GROUP Support in the MovePath dialog.

 ChangeLog             |   30 +++
 gap/gap_image.c       |  491 +++++++++++++++++++++++++++++++++++++++++++++++++
 gap/gap_image.h       |   24 +++
 gap/gap_mov_dialog.c  |  255 +++++++++++++++++++-------
 gap/gap_mov_dialog.h  |   13 +-
 gap/gap_mov_exec.c    |  228 ++++++++++++++++-------
 gap/gap_mov_exec.h    |    2 +
 gap/gap_mov_render.c  |   21 ++-
 gap/gap_mov_xml_par.c |  238 ++++++++++++++----------
 gap/gap_xml_util.c    |   27 +++-
 gap/gap_xml_util.h    |    4 +-
 11 files changed, 1086 insertions(+), 247 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index e0c9253..e8c7b77 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2014-02-02 Wolfgang Hofer <hof gimp org>
+
+- added LAYER GROUP Support in the "MovePath" dialog.
+     Tis dialog now provides a "Target Group" and Delimiter entry
+     To specifiy a Groupname where the rendered layer(s) shall be
+     inserted in all processed frame images. 
+     The groupname may also refere a subgroup when
+     it is separeted by the specified delimiter character
+     Example: 
+        Delimiter: /
+        Target Group: "animals/flying/birds"
+        
+     Note that an empty groupname refers to the image.
+     (e.g. the rendered layer(s) are inserted at image level)
+        
+     The Stepmode loop for layer specific stepmodes 
+     (loop, loop reverse, once, once reverse, ping pong)
+     now iterates over the layers that are member of the same group as the selected
+     Source Image/Layer. Selecting a toplevel Layer (or toplevel Grouplayer) iterates
+     over all toplevel layers including all toplevel Grouplayers of the Source image.
+
+ * gap/gap_image.c [.h]
+ * gap/gap_mov_dialog.c [.h]
+ * gap/gap_mov_exec.c [.h]
+ * gap/gap_mov_main.c
+ * gap/gap_mov_render.c [.h]
+ * gap/gap_mov_xml_par.c
+ * gap/gap_xml_util.c [.h]
+
+
 2014-01-12 Wolfgang Hofer <hof gimp org>
 - support for RAW files as "readonly" frame sequences for processing 
   as high bit depth video source in MovePath operations or Storyboard Clips.
diff --git a/gap/gap_image.c b/gap/gap_image.c
index 5d03fce..5a1a764 100644
--- a/gap/gap_image.c
+++ b/gap/gap_image.c
@@ -33,6 +33,7 @@
 #include <string.h>
 
 
+#include <glib.h>
 #include <gap_image.h>
 #include <gap_base.h>
 #include <gap_layer_copy.h>
@@ -566,3 +567,493 @@ gap_image_create_unicolor_image(gint32 *layer_id, gint32 width , gint32 height
   }
   return(l_image_id);
 }       /* end gap_image_create_unicolor_image */
+
+
+/* -----------------------------------------------
+ * gap_image_get_tree_position_list
+ * -----------------------------------------------
+ * return a list that represents the stack positions
+ * of the specified item_id (typically a layer)
+ * which contains positions within the image
+ *  and within all its parent group layers.
+ *
+ * The 1st element in the stack position list refers to the stackposition
+ * at toplevel of the image.
+ * Note:
+ * The caller shall g_free the returned list
+ * after use by calling: gap_image_gfree_tree_position_list
+ */
+GapImageStackPositionsList *
+gap_image_get_tree_position_list(gint32 item_id)
+{
+   gint32 l_image_id;
+   gint32 l_curr_item_id;
+   GapImageStackPositionsList *posRootPtr;
+
+
+   posRootPtr = NULL;
+   l_image_id =  gimp_item_get_image (item_id);
+
+   if(gap_debug)
+   {
+     printf("gap_image_get_tree_position_list Start image_id: %d\n"
+         , (int)l_image_id
+         );
+   }
+
+   if (l_image_id < 0)
+   {
+     return (NULL);
+   }
+
+   l_curr_item_id = item_id;
+
+   while(l_curr_item_id > 0)
+   {
+     GapImageStackPositionsList *posPtr;
+
+     posPtr = g_new(GapImageStackPositionsList, 1);
+     posPtr->stack_position = gimp_image_get_item_position (l_image_id,
+                                                           l_curr_item_id);
+     if(gap_debug)
+     {
+       printf("item_id:%d stack_position: %d name:%s\n"
+         , (int)l_curr_item_id
+         , (int)posPtr->stack_position
+         , gimp_item_get_name(l_curr_item_id)
+         );
+     }
+
+     posPtr->next = posRootPtr;
+     posRootPtr = posPtr;
+
+     l_curr_item_id = gimp_item_get_parent (l_curr_item_id);
+
+   }
+
+   return (posRootPtr);
+
+}  /* end gap_image_get_tree_position_list */
+
+
+
+/* -----------------------------------------------
+ * gap_image_gfree_tree_position_list
+ * -----------------------------------------------
+ */
+void
+gap_image_gfree_tree_position_list(GapImageStackPositionsList *rootPosPtr)
+{
+  GapImageStackPositionsList *posPtr;
+  GapImageStackPositionsList *nextPosPtr;
+
+  posPtr = rootPosPtr;
+  while (posPtr != NULL)
+  {
+     nextPosPtr = posPtr->next;
+     g_free(posPtr);
+
+     posPtr = nextPosPtr;
+  }
+}  /* end  gap_image_gfree_tree_position_list */
+
+
+/* -----------------------------------------------
+ * gap_image_get_layer_id_by_tree_position_list
+ * -----------------------------------------------
+ * return the item_id of the layer that matches
+ * the specified stack position list.
+ * (where each list element describes the position
+ * in the next tree level
+ * The 1st element in the stack position list refers to the stackposition
+ * at toplevel of the image)
+ *
+ */
+gint32
+gap_image_get_layer_id_by_tree_position_list(gint32 image_id, GapImageStackPositionsList *rootPosPtr)
+{
+  GapImageStackPositionsList *posPtr;
+  gint        l_nlayers;
+  gint        l_treelevel;
+  gint32     *l_src_layers;
+  gint32      l_layer_id;
+  gint32      l_wanted_layer_id;
+
+  l_layer_id = -1;
+  l_wanted_layer_id = -1;
+  l_treelevel = 0;
+  posPtr = rootPosPtr;
+  if (posPtr == NULL)
+  {
+    return (l_layer_id);
+  }
+  l_src_layers = gimp_image_get_layers (image_id, &l_nlayers);
+  if (l_src_layers == NULL)
+  {
+    return (-1);
+  }
+
+  while(posPtr != NULL)
+  {
+    if ((posPtr->stack_position >= 0) && (posPtr->stack_position < l_nlayers))
+    {
+      l_layer_id = l_src_layers[posPtr->stack_position];
+      if(gap_debug)
+      {
+        printf("get_layer_id_by_tree_pos layer_id: treelevel:%d %d name:%s N:%d stackPos:%d\n"
+           , (int)l_treelevel
+           , (int)l_layer_id
+           , gimp_item_get_name(l_layer_id)
+           , l_nlayers
+           , posPtr->stack_position
+           );
+      }
+    }
+    g_free(l_src_layers);
+    if (l_layer_id < 0)
+    {
+      if(gap_debug)
+      {
+        printf("get_layer_id_by_tree_pos l_treelevel:%d stack_position:%d NOT found!\n"
+           , (int)l_treelevel
+           , (int)posPtr->stack_position
+           );
+      }
+      break;
+    }
+    posPtr = posPtr->next;
+    if(posPtr != NULL)
+    {
+       if (gimp_item_is_group(l_layer_id))
+       {
+         l_src_layers = gimp_item_get_children (l_layer_id, &l_nlayers);
+         if (l_src_layers == NULL)
+         {
+           if(gap_debug)
+           {
+             printf("get_layer_id_by_tree_pos l_treelevel:%d NO CHILD LAYERS FOUND ! for item_id:%d\n"
+               , (int)l_treelevel
+               , (int)l_layer_id
+              );
+           }
+           return (-1);  /* failed to get group members */
+         }
+       }
+       else
+       {
+         if(gap_debug)
+         {
+           printf("get_layer_id_by_tree_pos l_treelevel:%d item_id:%d IS NOT A GROUP !\n"
+             , (int)l_treelevel
+             , (int)l_layer_id
+            );
+         }
+         return (-1); /* group was expected, but not found, cant evaluate rest of the list */
+       }
+    }
+    else
+    {
+      l_wanted_layer_id = l_layer_id;
+    }
+    l_treelevel++;
+  }  /* end while */
+
+  return (l_wanted_layer_id);
+
+
+}  /* end gap_image_get_layer_id_by_tree_position_list */
+
+
+/* -----------------------------------------------
+ * gap_image_greate_group_layer_path
+ * -----------------------------------------------
+ * create group layer specified by nameArray starting with
+ * the element at start_idx end at 1st NULL pointer element.
+ * in case the rest in this nameArray contains more than one name
+ * a hierarchy of group layers is created, where each name
+ * has the previous name as parent.
+ */
+gint32
+gap_image_greate_group_layer_path(gint32 image_id
+                             , gint32 parent_id      /* or 0 for top imagelevel */
+                             , gint32 stackposition  /* where 0 is on top position */
+                             , gchar  **nameArray
+                             , gint   start_idx
+                             )
+{
+  gint32 parent_layer_id;
+  gint32 group_layer_id;
+  gint32 position;
+  gchar *namePtr;
+  gint   l_ii;
+
+  position = stackposition;
+  parent_layer_id = 0;
+  if (nameArray != NULL)
+  {
+    for(l_ii = 0; nameArray[l_ii] != NULL; l_ii++)
+    {
+      if(l_ii >= start_idx)
+      {
+        namePtr = nameArray[l_ii];
+        group_layer_id = gimp_layer_group_new(image_id);
+        gimp_item_set_name(group_layer_id, namePtr);
+        gimp_image_insert_layer(image_id, group_layer_id, parent_layer_id, position);
+        if(gap_debug)
+        {
+          printf("gap_image_greate_group_layer_path: name[%d]:%s group_layer_id:%d\n"
+                 , (int)l_ii
+                 , namePtr
+                 , (int)group_layer_id
+                 );
+        }
+
+        parent_layer_id =  group_layer_id;
+      }
+      position = 0;
+    }
+  }
+
+  return (group_layer_id);
+
+}  /* end gap_image_greate_group_layer_path */
+
+
+
+
+
+/* -----------------------------------------------
+ * gap_image_find_or_create_group_layer
+ * -----------------------------------------------
+ * find the group layer where the name
+ * (and concateneated names of all its parent group layers when present)
+ * is equal to the specified group_name_path.
+ * and return its item_id
+ * Group_layer names levels are concatenated with the specified separator character.
+ *
+ * an empty  group_name_path_string (NULL or \0) will return 0
+ * which refers to the toplevel (indicating that no group is relevant)
+ *
+ * in case the wanted group was not found
+ * the result depends on the flag enableCreate.
+ * when enableCreate == TRUE
+ *    the group layer (and all missing parent groups) are created
+ *    and its item_id is returned.
+ * when enableCreate == FALSE
+ *    -1 is returned (indicating that the wanted group was not found)
+ *
+ * Example (separator character '/' is assumed)
+ *
+ *  layertree example
+ *  --------------------
+ *    toplayer                id:4
+ *    gr-sky                  id:3
+ *      subgr-1.1             id:7
+ *        layer-sun           id:13
+ *    gr-2                    id:2
+ *      subgr-animals         id:6
+ *        layer-cat           id:12
+ *        layer-dog           id:11
+ *      subgr-house           id:5
+ *        layer-roof          id:10
+ *        layer-window        id:9
+ *        layer-wall          id:8
+ *    background              id:1
+ *
+ * o) a call with group_name_path_string = "gr-2/subgr-animals"
+ *    will return 6, because the group layer with name "subgr-animals"
+ *    can be found.
+ * o) a call with group_name_path_string = "gr-2/subgr-animals/flying/birds"
+ *    will either return -1
+ *    or create both missing groups "flying" and "birds"
+ *    and will return the item_id of the "birds" group.
+ *    gr-2                    id:2
+ *      subgr-animals         id:6
+ *        flying              id:14   # newly created group layer
+ *          birds             id:15   # newly created group layer
+ *        layer-cat           id:12
+ *        layer-dog           id:11
+ * o) a call with group_name_path_string = "toplayer"
+ *    will return -1, because this layer already exists
+ *    but is not a group layer.
+ *
+ */
+gint32
+gap_image_find_or_create_group_layer(gint32 image_id
+    , gchar *group_name_path_string
+    , gchar *delimiter
+    , gint stackposition
+    , gboolean enableCreate
+)
+{
+  gchar     **nameArray;
+  gchar      *namePtr;
+  gint        l_nlayers;
+  gint        l_treelevel;
+  gint        l_ii;
+  gint        l_idx;
+  gint        l_position;
+  gint32     *l_src_layers;
+  gint32      l_layer_id;
+
+  gint32      l_parent_layer_id;
+  gint32      l_group_layer_id;
+
+  if (group_name_path_string == NULL)
+  {
+    return (0);
+  }
+  if (*group_name_path_string == '\0')
+  {
+    return (0);
+  }
+
+  l_parent_layer_id = 0; /* start at top imagelevel */
+  l_group_layer_id = -1;
+  l_position = stackposition;
+
+  if(delimiter != NULL)
+  {
+    nameArray = g_strsplit(group_name_path_string
+                       , delimiter
+                       , -1   /* max_tokens  less than 1 splits the string completely. */
+                       );
+  }
+  else
+  {
+    nameArray = g_malloc(sizeof(gchar*) * 2);
+    nameArray[0] = g_strdup(group_name_path_string);
+    nameArray[1] = NULL;
+  }
+
+  if (nameArray == NULL)
+  {
+    return (0);
+  }
+  l_src_layers = gimp_image_get_layers (image_id, &l_nlayers);
+  if (l_src_layers == NULL)
+  {
+    if (enableCreate)
+    {
+      l_group_layer_id = gap_image_greate_group_layer_path(image_id
+                             , l_parent_layer_id  /* 0 is on top imagelevel */
+                             , l_position      /* 0 on top stackposition */
+                             , nameArray
+                             , 0
+                             );
+    }
+    g_strfreev(nameArray);
+    return (l_group_layer_id);
+  }
+
+  for(l_ii = 0; nameArray[l_ii] != NULL; l_ii++)
+  {
+    gboolean l_found;
+    l_found = FALSE;
+
+    namePtr = nameArray[l_ii];
+
+    if(gap_debug)
+    {
+      printf("gap_image_find_or_create_group_layer: name[%d]:%s\n"
+            , (int)l_ii
+            , namePtr
+            );
+    }
+
+
+    for(l_idx = 0; l_idx < l_nlayers; l_idx++)
+    {
+      gchar *l_name;
+
+      l_name = gimp_item_get_name(l_src_layers[l_idx]);
+      if(gap_debug)
+      {
+        printf("cmp: name[%d]:%s  with item[%d]:%d name:%s\n"
+            , (int)l_ii
+            , namePtr
+            , (int)l_idx
+            , (int)l_src_layers[l_idx]
+            , l_name
+            );
+      }
+      if (strcmp(l_name, namePtr) == 0)
+      {
+        if (gimp_item_is_group(l_src_layers[l_idx]))
+        {
+          l_parent_layer_id = l_src_layers[l_idx];
+          l_position = 0;
+          l_found = TRUE;
+        }
+        else
+        {
+          if(gap_debug)
+          {
+            printf("ERROR: gap_image_find_or_create_group_layer the path\n  %s\n"
+                   "  refers to an already exsiting item that is NOT a GROUP\n"
+                   , group_name_path_string
+                   );
+          }
+          g_free(l_name);
+          g_free(l_src_layers);
+          return (-1);
+        }
+      }
+      g_free(l_name);
+      if (l_found)
+      {
+        break;
+      }
+    }
+    g_free(l_src_layers);
+    l_src_layers = NULL;
+
+
+    if(gap_debug)
+    {
+      printf(" l_found:%d l_parent_layer_id:%d\n"
+                   , (int)l_found
+                   , (int)l_parent_layer_id
+                   );
+    }
+    if (l_found)
+    {
+      if (nameArray[l_ii +1] == NULL)
+      {
+        /* no more names to compare and all did match, we are done */
+        l_group_layer_id = l_parent_layer_id;
+      }
+      else
+      {
+        /* check next treelevel e.g. members of the current group */
+        l_src_layers = gimp_item_get_children (l_parent_layer_id, &l_nlayers);
+      }
+    }
+    else
+    {
+      if (enableCreate)
+      {
+        /* create all missing groups/subgroups */
+        l_group_layer_id = gap_image_greate_group_layer_path(image_id
+                             , l_parent_layer_id  /* 0 is on top imagelevel */
+                             , l_position      /* 0 on top stackposition */
+                             , nameArray
+                             , l_ii
+                             );
+      }
+      break;
+    }
+  }
+
+  if(gap_debug)
+  {
+    printf("gap_image_find_or_create_group_layer BEFORE g_strfreev l_group_layer_id:%d\n"
+          ,(int)l_group_layer_id
+          );
+  }
+
+  g_strfreev(nameArray);
+
+  return(l_group_layer_id);
+
+}  /* end gap_image_find_or_create_group_layer */
diff --git a/gap/gap_image.h b/gap/gap_image.h
index 59c7b2b..bfc04cc 100644
--- a/gap/gap_image.h
+++ b/gap/gap_image.h
@@ -42,6 +42,12 @@
 #include "gtk/gtk.h"
 #include "libgimp/gimp.h"
 
+typedef struct {
+  gint stack_position;
+  void *next;
+} GapImageStackPositionsList;
+
+
 void      gap_image_delete_immediate (gint32 image_id);
 gint32    gap_image_merge_visible_layers(gint32 image_id, GimpMergeType mergemode);
 void      gap_image_prevent_empty_image(gint32 image_id);
@@ -61,5 +67,23 @@ gint32    gap_image_create_unicolor_image(gint32 *layer_id, gint32 width , gint3
                        , gdouble r_f, gdouble g_f, gdouble b_f, gdouble a_f);
 
 
+GapImageStackPositionsList * gap_image_get_tree_position_list(gint32 item_id);
+void     gap_image_gfree_tree_position_list(GapImageStackPositionsList *rootPosPtr);
+gint32   gap_image_get_layer_id_by_tree_position_list(gint32 image_id, GapImageStackPositionsList 
*rootPosPtr);
+gint32   gap_image_greate_group_layer_path(gint32 image_id
+                             , gint32 parent_id      /* or 0 for top imagelevel */
+                             , gint32 stackposition  /* where 0 is on top position */
+                             , gchar  **nameArray
+                             , gint   start_idx
+                             );
+
+gint32   gap_image_find_or_create_group_layer(gint32 image_id
+            , gchar *group_name_path_string
+            , gchar *delimiter
+            , gint stackposition
+            , gboolean enableCreate
+            );
+
+
 #endif
 
diff --git a/gap/gap_mov_dialog.c b/gap/gap_mov_dialog.c
index e81f32d..0806426 100644
--- a/gap/gap_mov_dialog.c
+++ b/gap/gap_mov_dialog.c
@@ -140,6 +140,10 @@ extern      int gap_debug; /* ==0  ... dont print debug infos */
 #define SPINBUTTON_WIDTH 60
 #define SCALE_WIDTH 125
 
+#define ENTRY_DELIMITER_WIDTH 20
+#define ENTRY_GROUP_PATH_WIDTH 200
+
+
 /* instant apply is implemented via timer, configured to fire 10 times per second (100 msec)
  * this collects instant_apply_requests set by other widget callbacks and events
  * and then update only once.
@@ -302,7 +306,7 @@ typedef struct
    * TRUE
    *   (typically called from the storyboard for update xml file settings)
    * - invoke from any image is tolerated
-   * - rendering of frames is DISABLED 
+   * - rendering of frames is DISABLED
    *   (Animated preview rendering is allowed)
    * - frame range limits are 1 upto 999999.
    * - the moving object (source image) is fixed by the caller,
@@ -325,18 +329,22 @@ typedef struct
    */
   gboolean isRecordOnlyMode;
   gboolean isStandaloneGui;
-  
+
   t_close_movepath_edit_callback_fptr close_fptr;
   gpointer callback_data;
-  
+
   gchar        xml_paramfile[GAP_MOVPATH_XML_FILENAME_MAX_LENGTH];
 
+  // GtkWidget     *clip_to_img_check_button;
+  GtkWidget            *dstGroupPathEntry;
+  GtkWidget            *dstGroupDelimiterEntry;
+
 } t_mov_gui_stuff;
 
 
 /* Declare local functions.
  */
-static long        p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
+static void        p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
                        , GapMovData *mov_ptr
                        , gboolean isRecordOnlyMode
                        , gboolean isStandaloneGui
@@ -448,6 +456,8 @@ static void mov_stepmode_menu_callback  (GtkWidget *, t_mov_gui_stuff *mgp);
 static void mov_tweenlayer_sensitivity(t_mov_gui_stuff *mgp);
 static void mov_tracelayer_sensitivity(t_mov_gui_stuff *mgp);
 static void mov_gint_toggle_callback    (GtkWidget *, gpointer);
+static void on_dstGroupPathEntry_changed       (GtkEditable     *editable, t_mov_gui_stuff *mgp);
+static void on_dstGroupDelimiterEntry_changed       (GtkEditable     *editable, t_mov_gui_stuff *mgp);
 static void mov_force_visibility_toggle_callback ( GtkWidget *widget, gpointer data );
 static void mov_bluebox_callback        (GtkWidget *, gpointer);
 static void mov_tracelayer_callback     (GtkWidget *, gpointer);
@@ -539,20 +549,20 @@ gap_mov_dlg_move_dialog    (GapMovData *mov_ptr)
   mgp = g_new( t_mov_gui_stuff, 1 );
   mgp->shell = NULL;
   mgp->pointfile_name = NULL;
-  
+
   isRecordOnlyMode = FALSE;
   isStandaloneGui = TRUE;
   p_gap_mov_dlg_move_dialog(mgp, mov_ptr, isRecordOnlyMode, isStandaloneGui, NULL, NULL, 1);
   p_free_mgp_resources(mgp);
   g_free(mgp);
 
-  
+
   if(mov_int.run == TRUE)
   {
     return 0;  /* OK */
   }
   return  -1;  /* Cancel or error occured */
- 
+
 }
 
 
@@ -568,7 +578,7 @@ gap_mov_dlg_move_dialog    (GapMovData *mov_ptr)
  *
  * This procedure is typically called be the Storyboard transition attributes dialog
  */
-GtkWidget * 
+GtkWidget *
 gap_mov_dlg_edit_movepath_dialog (gint32 frame_image_id, gint32 drawable_id
    , const char *xml_paramfile
    , GapAnimInfo *ainfo_ptr
@@ -607,7 +617,7 @@ gap_mov_dlg_edit_movepath_dialog (gint32 frame_image_id, gint32 drawable_id
 
 
   pvals = pvals_edit;
-  
+
   pvals->dst_image_id = frame_image_id;
   pvals->bbp_pv = gap_bluebox_bbp_new(-1);
 
@@ -617,7 +627,7 @@ gap_mov_dlg_edit_movepath_dialog (gint32 frame_image_id, gint32 drawable_id
   {
     g_snprintf(mgp->xml_paramfile, sizeof(mgp->xml_paramfile), "%s", xml_paramfile);
     mgp->pointfile_name  = g_strdup_printf("%s", xml_paramfile);
- 
+
     /* attempt to init settings in case the xml_paramfile
      * already contains valid settings
      */
@@ -641,7 +651,7 @@ gap_mov_dlg_edit_movepath_dialog (gint32 frame_image_id, gint32 drawable_id
     pvals->bbp  = NULL;
     pvals->bbp_pv  = NULL;
     pvals->clip_to_img  = 0;
-    
+
     pvals->step_speed_factor = 1.0;
     pvals->tracelayer_enable = FALSE;
     pvals->trace_opacity_initial = 100.0;
@@ -688,7 +698,7 @@ gap_mov_dlg_edit_movepath_dialog (gint32 frame_image_id, gint32 drawable_id
  * ------------------------------------------
  *
  */
-static long
+static void
 p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
    , GapMovData *mov_ptr
    , gboolean isRecordOnlyMode
@@ -750,6 +760,8 @@ p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
   mgp->dst_layerstack_adj = NULL;
   mgp->src_force_visible_check_button = NULL;
   mgp->clip_to_img_check_button = NULL;
+  mgp->dstGroupPathEntry = NULL;
+  mgp->dstGroupDelimiterEntry = NULL;
   mgp->tracelayer_enable_check_button = NULL;
   mgp->src_apply_bluebox_check_button = NULL;
   mgp->bluebox_keycolor_color_button = NULL;
@@ -762,7 +774,7 @@ p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
 
   pvals = mov_ptr->val_ptr;
 
-  
+
   if(mgp->pointfile_name == NULL)
   {
     if(mov_ptr->dst_ainfo_ptr->basename != NULL)
@@ -797,11 +809,11 @@ p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
     l_last  = mov_ptr->dst_ainfo_ptr->last_frame_nr;
     l_curr  = mov_ptr->dst_ainfo_ptr->curr_frame_nr;
     l_max   = l_last;
-    
+
     pvals->src_image_id = -1;
     pvals->src_layer_id = -1;
     pvals->src_stepmode = GAP_STEP_LOOP;
- 
+
   }
 
   /* init parameter values */
@@ -837,7 +849,7 @@ p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
     pvals->bbp  = NULL;
     pvals->bbp_pv  = NULL;
     pvals->clip_to_img  = 0;
-    
+
     pvals->step_speed_factor = 1.0;
     pvals->tracelayer_enable = FALSE;
     pvals->trace_opacity_initial = 100.0;
@@ -900,12 +912,12 @@ p_free_mgp_resources(t_mov_gui_stuff *mgp)
   {
     return;
   }
-  
+
   if(gap_debug)
   {
     printf("p_free_mgp_resources START\n");
   }
-  
+
   /* destroy the tmp image(s) */
   if(pvals->tmp_image_id >= 0)
   {
@@ -1158,7 +1170,7 @@ mov_dialog ( GimpDrawable *drawable, t_mov_gui_stuff *mgp,
     gtk_main ();
     gdk_flush ();
   }
-  
+
   if(gap_debug) printf("GAP-DEBUG: END mov_dialog\n");
 
   return mov_int.run;
@@ -1237,7 +1249,7 @@ mov_close_callback (GtkWidget *widget,
       gtk_widget_destroy (l_shell);
       p_free_mgp_resources(mgp);
     }
-    
+
     if(mgp->isStandaloneGui)
     {
       if(gap_debug)
@@ -1255,7 +1267,7 @@ mov_close_callback (GtkWidget *widget,
     }
     gtk_main_quit ();
   }
-  
+
 }  /* end mov_close_callback */
 
 
@@ -1335,7 +1347,7 @@ mov_upvw_callback (GtkWidget *widget,
          );
   }
   l_filename = NULL;
-  
+
   if(mgp->ainfo_ptr->ainfo_type == GAP_AINFO_FRAMES)
   {
     if(gap_debug)
@@ -1359,8 +1371,8 @@ mov_upvw_callback (GtkWidget *widget,
            );
     }
   }
-  
-  
+
+
   if(!mgp->instant_apply)
   {
      /* dont show waiting cursor at instant_apply
@@ -1413,9 +1425,9 @@ mov_upvw_callback (GtkWidget *widget,
      */
     l_new_tmp_image_id = gimp_image_duplicate(mgp->ainfo_ptr->image_id);
   }
-     
-     
-     
+
+
+
   if (l_new_tmp_image_id >= 0)
   {
      /* use the new loaded temporary image */
@@ -2549,7 +2561,7 @@ p_refresh_widgets_after_load(t_mov_gui_stuff *mgp)
   {
     gtk_adjustment_set_value(mgp->trace_opacity_desc_adj,  pvals->trace_opacity_desc);
   }
-  
+
   if(mgp->tracelayer_enable_check_button != NULL)
   {
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (mgp->tracelayer_enable_check_button),
@@ -2561,19 +2573,30 @@ p_refresh_widgets_after_load(t_mov_gui_stuff *mgp)
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (mgp->src_force_visible_check_button),
                                   pvals->src_force_visible);
   }
-  
+
   if (mgp->clip_to_img_check_button != NULL)
   {
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (mgp->clip_to_img_check_button),
                                   pvals->clip_to_img);
   }
-  
+
+  if ((mgp->dstGroupPathEntry != NULL) && (pvals->dst_group_name_path_string != NULL))
+  {
+    gtk_entry_set_text (GTK_ENTRY (mgp->dstGroupPathEntry),
+                                  pvals->dst_group_name_path_string);
+  }
+  if ((mgp->dstGroupDelimiterEntry != NULL) && (pvals->dst_group_name_delimiter != NULL))
+  {
+    gtk_entry_set_text (GTK_ENTRY (mgp->dstGroupDelimiterEntry),
+                                  pvals->dst_group_name_delimiter);
+  }
+
   if (mgp->src_apply_bluebox_check_button != NULL)
   {
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (mgp->src_apply_bluebox_check_button),
                                   pvals->src_apply_bluebox);
   }
-  
+
   if (mgp->bluebox_keycolor_color_button != NULL)
   {
     if(pvals->bbp != NULL)
@@ -2820,10 +2843,15 @@ mov_imglayer_menu_callback(GtkWidget *widget, t_mov_gui_stuff *mgp)
 static gint
 mov_imglayer_constrain(gint32 image_id, gint32 drawable_id, gpointer data)
 {
-  if(gap_debug) printf("GAP-DEBUG: mov_imglayer_constrain PROCEDURE image_id:%d drawable_id:%d\n"
+  if(gap_debug)
+  {
+   printf("GAP-DEBUG: mov_imglayer_constrain PROCEDURE image_id:%d drawable_id:%d name:%s\n"
                           ,(int)image_id
                           ,(int)drawable_id
+                          , gimp_item_get_name(drawable_id)
                           );
+  }
+
 
   if(drawable_id < 0)
   {
@@ -2838,6 +2866,11 @@ mov_imglayer_constrain(gint32 image_id, gint32 drawable_id, gpointer data)
      return(FALSE);
   }
 
+  if (!gimp_drawable_is_layer(drawable_id))
+  {
+     return(FALSE);
+  }
+
 
    /* dont accept layers from within the destination image id
     * or layers within the internal used tmporary images
@@ -2992,6 +3025,52 @@ mov_gint_toggle_callback(GtkWidget *w, gpointer   client_data)
 }  /* end mov_gint_toggle_callback */
 
 static void
+on_dstGroupPathEntry_changed       (GtkEditable     *editable,
+                                    t_mov_gui_stuff *mgp)
+{
+ if(gap_debug) printf("CB: on_dstGroupPathEntry_changed\n");
+
+ if (pvals)
+ {
+   if (pvals->dst_group_name_path_string != NULL)
+   {
+     g_free(pvals->dst_group_name_path_string);
+     pvals->dst_group_name_path_string = NULL;
+   }
+   pvals->dst_group_name_path_string = g_strdup(gtk_entry_get_text(GTK_ENTRY(editable)));
+ }
+
+ if(mgp)
+ {
+    mov_set_instant_apply_request(mgp);
+ }
+}  /* end on_dstGroupPathEntry_changed */
+
+static void
+on_dstGroupDelimiterEntry_changed       (GtkEditable     *editable,
+                                    t_mov_gui_stuff *mgp)
+{
+ if(gap_debug) printf("CB: on_dstGroupDelimiterEntry_changed\n");
+
+ if (pvals)
+ {
+   if (pvals->dst_group_name_delimiter != NULL)
+   {
+     g_free(pvals->dst_group_name_delimiter);
+     pvals->dst_group_name_delimiter = NULL;
+   }
+   pvals->dst_group_name_delimiter = g_strdup(gtk_entry_get_text(GTK_ENTRY(editable)));
+ }
+
+ if(mgp)
+ {
+    mov_set_instant_apply_request(mgp);
+ }
+}  /* end on_dstGroupDelimiterEntry_changed */
+
+
+
+static void
 mov_force_visibility_toggle_callback    (GtkWidget *widget, gpointer client_data)
 {
   t_mov_gui_stuff *mgp;
@@ -3105,7 +3184,7 @@ mov_remove_timer(t_mov_gui_stuff *mgp)
   {
     return;
   }
-  
+
   if(mgp->instant_timertag >= 0)
   {
     g_source_remove(mgp->instant_timertag);
@@ -3662,7 +3741,7 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
 
   gtk_widget_show(combo);
   mgp->src_layer_combo = combo;
-  
+
   if(mgp->isRecordOnlyMode)
   {
     gtk_widget_hide(label);
@@ -3711,17 +3790,17 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
   {
     gint initialValue;
     initialValue = GIMP_NORMAL_MODE;
-    
+
     if(pvals)
     {
       initialValue = pvals->src_paintmode;
     }
-    
+
     gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
                               initialValue,
                               G_CALLBACK (mov_paintmode_menu_callback),
                               mgp);
-    
+
   }
 
   gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 0, 1,
@@ -3771,16 +3850,16 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
                     G_CALLBACK (gimp_double_adjustment_update),
                     &pvals->step_speed_factor);
   mgp->step_speed_factor_adj = GTK_ADJUSTMENT(adj);
-  
+
   if(mgp->isRecordOnlyMode)
   {
     GtkWidget *widget;
-    
+
     widget = g_object_get_data(G_OBJECT (adj), "label");
     gtk_widget_hide(widget);
     widget = g_object_get_data(G_OBJECT (adj), "spinbutton");
     gtk_widget_hide(widget);
-    
+
   }
 
 
@@ -3813,7 +3892,7 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
                               G_CALLBACK (mov_stepmode_menu_callback),
                               mgp);
   }
-  
+
   gtk_table_attach(GTK_TABLE(sub_table), combo, 0, 1, 0, 1,
                    GTK_EXPAND | GTK_FILL, 0, 0, 0);
   gimp_help_set_help_data(combo,
@@ -3845,7 +3924,7 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
                                   _("Center"),        GAP_HANDLE_CENTER,
                                   NULL);
 
-  
+
   {
     gint initialValue;
 
@@ -3863,7 +3942,7 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
           break;
       }
     }
-    
+
     gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
                               initialValue,
                               G_CALLBACK (mov_handmode_menu_callback),
@@ -4399,6 +4478,8 @@ mov_path_framerange_box_create(t_mov_gui_stuff *mgp
   GtkWidget *table;
   GtkAdjustment *adj;
   GtkWidget *check_button;
+  GtkWidget *entry;
+  GtkWidget *label;
   gint  master_rows;
   gint  master_cols;
   gint  tabcol, tabrow, boxcol, boxrow;
@@ -4435,8 +4516,10 @@ mov_path_framerange_box_create(t_mov_gui_stuff *mgp
                   , GTK_FILL|GTK_EXPAND, GTK_FILL, 4, 0);
   gtk_widget_show (table);
 
+  row = 0;
+
   /* the start frame scale_entry */
-  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, 0,          /* table col, row */
+  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, row,        /* table col, row */
                           _("From Frame:"),                     /* label text */
                           SCALE_WIDTH, ENTRY_WIDTH,             /* scalesize spinsize */
                           (gdouble)pvals->dst_range_start,      /* value */
@@ -4457,8 +4540,10 @@ mov_path_framerange_box_create(t_mov_gui_stuff *mgp
                             mgp);
   mgp->dst_range_start_adj = adj;
 
+  row++;
+
   /* the end frame scale_entry */
-  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, 1,          /* table col, row */
+  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, row,          /* table col, row */
                           _("To Frame:"),                       /* label text */
                           SCALE_WIDTH, ENTRY_WIDTH,             /* scalesize spinsize */
                           (gdouble)pvals->dst_range_end,        /* value */
@@ -4479,8 +4564,10 @@ mov_path_framerange_box_create(t_mov_gui_stuff *mgp
                             mgp);
   mgp->dst_range_end_adj = adj;
 
+  row++;
+
   /* the Layerstack scale_entry */
-  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, 2,          /* table col, row */
+  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, row,          /* table col, row */
                           _("Layerstack:"),                     /* label text */
                           SCALE_WIDTH, ENTRY_WIDTH,             /* scalesize spinsize */
                           (gdouble)pvals->dst_layerstack,       /* value */
@@ -4499,8 +4586,63 @@ mov_path_framerange_box_create(t_mov_gui_stuff *mgp
                     &pvals->dst_layerstack);
   mgp->dst_layerstack_adj = adj;
 
+  row++;
+
+  /* destination group path */
+  label = gtk_label_new(_("Target Group:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+  gtk_widget_show (label);
+  gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1
+                  , GTK_FILL, GTK_FILL, 4, 0);
+
+  entry = gtk_entry_new();
+  gtk_widget_show (entry);
+  gtk_widget_set_size_request (entry, ENTRY_GROUP_PATH_WIDTH, -1);
+  gtk_table_attach (GTK_TABLE (table), entry, 1, 2, row, row+1,
+                      (GtkAttachOptions) (GTK_FILL | GTK_EXPAND),
+                      (GtkAttachOptions) (0), 0, 0);
+  gimp_help_set_help_data (entry, _("group/subgroup name path where to insert the rendered object. "
+                                    "note that the specified group (and subgroups) will be created "
+                                    "automatically in all processed target frames where they are not already 
present. "
+                                    "Leave the target group empty when insert into the image outside groups 
is desired")
+                                    , NULL);
+  if (pvals->dst_group_name_path_string != NULL)
+  {
+    gtk_entry_set_text (GTK_ENTRY (entry), pvals->dst_group_name_path_string);
+  }
+  g_signal_connect (G_OBJECT (entry), "changed",
+                      G_CALLBACK (on_dstGroupPathEntry_changed),
+                      mgp);
+  mgp->dstGroupPathEntry = entry;
+
+
+  entry = gtk_entry_new();
+  gtk_widget_show (entry);
+  gtk_widget_set_size_request (entry, ENTRY_DELIMITER_WIDTH, -1);
+  gtk_table_attach (GTK_TABLE (table), entry, 2, 3, row, row+1,
+                      (GtkAttachOptions) (GTK_FILL | GTK_EXPAND),
+                      (GtkAttachOptions) (0), 0, 0);
+  gimp_help_set_help_data (entry, _("delimiter to separate group/subgroup"), NULL);
+  if (pvals->dst_group_name_delimiter != NULL)
+  {
+    gtk_entry_set_text (GTK_ENTRY (entry), pvals->dst_group_name_delimiter);
+  }
+  g_signal_connect (G_OBJECT (entry), "changed",
+                      G_CALLBACK (on_dstGroupDelimiterEntry_changed),
+                      mgp);
+  mgp->dstGroupDelimiterEntry = entry;
+
+
+
+
+
+
+
+
+
+
   /* the table for checkbuttons and info labels */
-  table = gtk_table_new (3, 3, FALSE);
+  table = gtk_table_new (4, 3, FALSE);
   gtk_widget_show (table);
 
   row = 0;
@@ -5532,7 +5674,7 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
                     G_CALLBACK (mov_instant_int_adjustment_update),
                     &mgp->preview_frame_nr);
   mgp->preview_frame_nr_adj = GTK_ADJUSTMENT(adj);
-  
+
   if(mgp->ainfo_ptr->ainfo_type != GAP_AINFO_FRAMES)
   {
     GtkWidget *widget;
@@ -5552,7 +5694,7 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
     {
       gtk_widget_hide(widget);
     }
-    
+
   }
 
 
@@ -6380,21 +6522,8 @@ p_get_prevw_drawable (t_mov_gui_stuff *mgp)
     l_curr.accSelFeatherRadius = (gdouble)mgp->accSelFeatherRadius;
 
 
-    l_curr.src_layer_idx   = 0;
-    l_curr.src_layers      = gimp_image_get_layers (pvals->src_image_id, &l_nlayers);
+    gap_mov_exec_set_iteration_relevant_src_layers(&l_curr, pvals->src_layer_id, pvals->src_image_id);
 
-    if((l_curr.src_layers != NULL) && (l_nlayers > 0))
-    {
-      l_curr.src_last_layer  = l_nlayers -1;
-      /* findout index of src_layer_id */
-      for(l_curr.src_layer_idx = 0;
-          l_curr.src_layer_idx  < l_nlayers;
-          l_curr.src_layer_idx++)
-      {
-         if(l_curr.src_layers[l_curr.src_layer_idx] == pvals->src_layer_id)
-            break;
-      }
-    }
     if(pvals->src_stepmode >= GAP_STEP_FRAME)
     {
       gap_mov_render_fetch_src_frame (pvals, -1);  /* negative value fetches the selected frame number */
diff --git a/gap/gap_mov_dialog.h b/gap/gap_mov_dialog.h
index 3d311d9..f6f800a 100644
--- a/gap/gap_mov_dialog.h
+++ b/gap/gap_mov_dialog.h
@@ -92,13 +92,14 @@ typedef enum
 } GapMovSelMode;
 
 typedef struct {
-        long    dst_frame_nr;     /* current destination frame_nr */
-        long    src_layer_idx;    /* index of current layer (used for multilayer stepmodes) */
-        long    src_frame_idx;    /* current frame number (used for source frame stepmodes) */
+        gint32  dst_frame_nr;        /* current destination frame_nr */
+        gint32  src_layer_parent_id; /* 0 for toplevel layer that are not member of a layer group */
+        gint32  src_layer_idx;       /* index of current layer within group or image (used for multilayer 
stepmodes) */
+        gint32  src_frame_idx;       /* current frame number (used for source frame stepmodes) */
         gdouble src_layer_idx_dbl;
         gdouble src_frame_idx_dbl;
-        gint32 *src_layers;       /* array of source images layer id's (used for multilayer stepmodes) */
-        long    src_last_layer;   /* index of last layer 0 upto n-1 (used for multilayer stepmodes) */
+        gint32 *src_layers;          /* array of layer id's  in hte same layer group or image (used for 
multilayer stepmodes) */
+        gint32  src_last_layer_idx;  /* index of last layer 0 upto n-1 within group or image (used for 
multilayer stepmodes) */
         gdouble currX,  currY;
         gint    l_handleX;
         gint    l_handleY;
@@ -264,6 +265,8 @@ typedef struct {
 
         gdouble  rotate_threshold;
 
+        gchar  *dst_group_name_path_string;
+        gchar  *dst_group_name_delimiter;
 
 } GapMovValues;
 
diff --git a/gap/gap_mov_exec.c b/gap/gap_mov_exec.c
index 363fef3..9b4e840 100644
--- a/gap/gap_mov_exec.c
+++ b/gap/gap_mov_exec.c
@@ -137,6 +137,89 @@ static gint     p_calculate_settings_for_current_FrameTween(
 
 
 
+/* -----------------------------------------------
+ * gap_mov_exec_set_iteration_relevant_src_layers
+ * -----------------------------------------------
+ *    get the source layers that are in the same group or image
+ *    and therefore are relevant for iteration in the animation.
+ *    store them in the specified GapMovCurrent *cur_ptr
+ *     AS attributes:
+ *       curr_ptr->src_layers     
+ *          ## is set to array of relevant src_layer_ids (within group or image)
+ *       curr_ptr->src_layer_idx
+ *          ## is set to index of src layer within src_layer array.
+ *       curr_ptr->src_last_layer_idx  
+ *          ## is set to the last valid index in the src_layers array.
+ *        
+ *   Note that animation of a toplevel layer iterates over all toplevel layers
+ *   (where groups are treated as if they were a single layer)
+ *   
+ *   But if the src_layer_id is member of a layer group, the animation
+ *   itaration is done on the members of the same group.
+ *   (when the group contains sub groups, the sub groups act as if they were
+ *   a single layer)
+ */
+void
+gap_mov_exec_set_iteration_relevant_src_layers(GapMovCurrent *cur_ptr, gint32 src_layer_id, gint32 
src_image_id)
+{
+  gint32 l_src_layer_parent_id;   /* 0 for toplevel layer that is not member of a layer group */
+  gint   l_nlayers;
+
+  cur_ptr->src_layers = NULL;
+  cur_ptr->src_layer_idx   = 0;
+  cur_ptr->src_last_layer_idx = -1; /* indicate invalid src layer */
+  
+  if (src_layer_id < 0)
+  {
+    return;
+  }
+  
+  l_src_layer_parent_id = gimp_item_get_parent (src_layer_id);
+  if(gap_debug)
+  {
+    printf("gap_mov_exec_set_iteration_relevant_src_layers: src_layer_id:%d, src_layer_parent_id:%d\n"
+      , (int)src_layer_id
+      , (int)l_src_layer_parent_id
+      );
+  }
+  if (l_src_layer_parent_id > 0)
+  {
+    /* the src layer is member of a layergroup, get all members of the group */
+    cur_ptr->src_layers      = gimp_item_get_children (l_src_layer_parent_id, &l_nlayers);
+  }
+  else
+  {
+    /* the src layer is a toplevel layer, get all toplevel layers of the image */
+    cur_ptr->src_layers      = gimp_image_get_layers (src_image_id, &l_nlayers);
+  }
+
+
+  if((cur_ptr->src_layers != NULL) && (l_nlayers > 0))
+  {
+    cur_ptr->src_last_layer_idx  = l_nlayers -1;
+    /* findout index of src_layer_id (within group or image) */
+    for(cur_ptr->src_layer_idx = 0;
+        cur_ptr->src_layer_idx  < l_nlayers;
+        cur_ptr->src_layer_idx++)
+    {
+      if(gap_debug)
+      {
+        printf("gap_mov_exec_set_iteration_relevant_src_layers: l_nlayers:%d, src_layer_id:%d, 
cur_ptr->src_layer_idx:%d\n"
+          , (int)l_nlayers
+          , (int)cur_ptr->src_layers[cur_ptr->src_layer_idx]
+          , (int)cur_ptr->src_layer_idx
+          );
+      }
+      if(cur_ptr->src_layers[cur_ptr->src_layer_idx] == src_layer_id)
+      {
+         cur_ptr->src_layer_idx_dbl = (gdouble)cur_ptr->src_layer_idx;
+         return;
+      }
+    }
+  }
+  cur_ptr->src_layer_idx   = 0;
+    
+}  /* end gap_mov_exec_set_iteration_relevant_src_layers */
 
 
 
@@ -538,7 +621,7 @@ p_mov_advance_src_layer(GapMovCurrent *cur_ptr, GapMovValues  *pvals)
   gdouble l_round;
 
   /* limit step factor to number of available layers -1 */
-  l_step_speed_factor = MIN(pvals->step_speed_factor, (gdouble)cur_ptr->src_last_layer);
+  l_step_speed_factor = MIN(pvals->step_speed_factor, (gdouble)cur_ptr->src_last_layer_idx);
   if(pvals->tween_steps > 0)
   {
     /* when we have tweens, the speed_factor must be divided (the +1 is for the real frame) */
@@ -550,7 +633,7 @@ p_mov_advance_src_layer(GapMovCurrent *cur_ptr, GapMovValues  *pvals)
   {
     printf("p_mov_advance_src_layer: stepmode=%d last_layer=%d idx=%d (%.4f) speed_factor: %.4f\n",
                        (int)pvals->src_stepmode,
-                       (int)cur_ptr->src_last_layer,
+                       (int)cur_ptr->src_last_layer_idx,
                        (int)cur_ptr->src_layer_idx,
                        (float)cur_ptr->src_layer_idx_dbl,
                        (float)l_step_speed_factor
@@ -562,15 +645,15 @@ p_mov_advance_src_layer(GapMovCurrent *cur_ptr, GapMovValues  *pvals)
    *       therfore reverse loops have to count up
    *       forward loop is defined as sequence from BG to TOP layer
    */
-  if((cur_ptr->src_last_layer > 0 ) && (pvals->src_stepmode != GAP_STEP_NONE))
+  if((cur_ptr->src_last_layer_idx > 0 ) && (pvals->src_stepmode != GAP_STEP_NONE))
   {
     switch(pvals->src_stepmode)
     {
       case GAP_STEP_ONCE_REV:
         cur_ptr->src_layer_idx_dbl += l_step_speed_factor;
-        if(cur_ptr->src_layer_idx_dbl > cur_ptr->src_last_layer)
+        if(cur_ptr->src_layer_idx_dbl > cur_ptr->src_last_layer_idx)
         {
-           cur_ptr->src_layer_idx_dbl = (gdouble)cur_ptr->src_last_layer;
+           cur_ptr->src_layer_idx_dbl = (gdouble)cur_ptr->src_last_layer_idx;
         }
         break;
       case GAP_STEP_ONCE:
@@ -593,18 +676,18 @@ p_mov_advance_src_layer(GapMovCurrent *cur_ptr, GapMovValues  *pvals)
         }
         else
         {
-          if(cur_ptr->src_layer_idx_dbl >= (gdouble)(cur_ptr->src_last_layer +1))
+          if(cur_ptr->src_layer_idx_dbl >= (gdouble)(cur_ptr->src_last_layer_idx +1))
           {
-             cur_ptr->src_layer_idx_dbl = (gdouble)cur_ptr->src_last_layer - 1.0;
+             cur_ptr->src_layer_idx_dbl = (gdouble)cur_ptr->src_last_layer_idx - 1.0;
              l_ping = -1;
           }
         }
         break;
       case GAP_STEP_LOOP_REV:
         cur_ptr->src_layer_idx_dbl += l_step_speed_factor;
-        if(cur_ptr->src_layer_idx_dbl >= (gdouble)(cur_ptr->src_last_layer +1))
+        if(cur_ptr->src_layer_idx_dbl >= (gdouble)(cur_ptr->src_last_layer_idx +1))
         {
-           cur_ptr->src_layer_idx_dbl -= (gdouble)(cur_ptr->src_last_layer + 1);
+           cur_ptr->src_layer_idx_dbl -= (gdouble)(cur_ptr->src_last_layer_idx + 1);
         }
         break;
       case GAP_STEP_LOOP:
@@ -612,13 +695,22 @@ p_mov_advance_src_layer(GapMovCurrent *cur_ptr, GapMovValues  *pvals)
         cur_ptr->src_layer_idx_dbl -= l_step_speed_factor;
         if(cur_ptr->src_layer_idx_dbl < -0.5)
         {
-           cur_ptr->src_layer_idx_dbl += (gdouble)(cur_ptr->src_last_layer + 1);
+           cur_ptr->src_layer_idx_dbl += (gdouble)(cur_ptr->src_last_layer_idx + 1);
         }
         l_round = 0.5;
         break;
 
     }
     cur_ptr->src_layer_idx = MAX((long)(cur_ptr->src_layer_idx_dbl + l_round), 0);
+    if(gap_debug)
+    {
+         printf("p_advance_src_layer: src_layer_idx_dbl %f  l_step_speed_factor:%f layer_idx_dbl+round:%f 
src_layer_idx:%d\n"
+             ,(float)cur_ptr->src_layer_idx_dbl
+             ,(float)l_step_speed_factor
+             ,(float)(cur_ptr->src_layer_idx_dbl + l_round)
+             ,(int)cur_ptr->src_layer_idx
+             );
+    }
   }
 }       /* end  p_advance_src_layer */
 
@@ -1484,12 +1576,12 @@ p_log_current_render_params(GapMovData *mov_ptr, GapMovCurrent *cur_ptr)
 
     val_ptr = mov_ptr->val_ptr;
 
-    printf("\nCurrent Render Params: dst_frame_nr:%ld tweenIndex:%d src_layer_idx:%d (dbl:%f)\n"
+    printf("\nCurrent Render Params: dst_frame_nr:%d tweenIndex:%d src_layer_idx:%d (dbl:%f)\n"
                 "       currX:%f currY:%f\n"
                 "       Width:%f Height:%f\n"
                 "       Opacity:%f  Rotate:%f  clip_to_img:%d force_visibility:%d\n"
                 "       src_stepmode:%d handleX:%d handleY:%d currSelFeatherRadius:%f rotate_threshold:%f\n",
-                     cur_ptr->dst_frame_nr, (int)val_ptr->twix, (int)cur_ptr->src_layer_idx,
+                     (int)cur_ptr->dst_frame_nr, (int)val_ptr->twix, (int)cur_ptr->src_layer_idx,
                      (float)cur_ptr->src_layer_idx_dbl,
                      (float)cur_ptr->currX,
                      (float)cur_ptr->currY,
@@ -1912,32 +2004,20 @@ p_mov_execute_or_query(GapMovData *mov_ptr, GapMovQuery *mov_query)
          l_sel_channel_id = gimp_image_get_selection(val_ptr->src_image_id);
          gap_mov_render_create_or_replace_tempsel_image(l_sel_channel_id, val_ptr, l_all_empty);
        }
-
-       cur_ptr->src_layers = gimp_image_get_layers (val_ptr->src_image_id, &l_nlayers);
+       
+       
+       /* allocate and set array cur_ptr->src_layers with ids of relevant layers and findout index of 
src_layer_id */
+       gap_mov_exec_set_iteration_relevant_src_layers(cur_ptr, val_ptr->src_layer_id, val_ptr->src_image_id);
        if(cur_ptr->src_layers == NULL)
        {
          printf("ERROR (in p_mov_execute): Got no layers from SrcImage\n");
          return -1;
        }
-       if(l_nlayers < 1)
+       if(cur_ptr->src_last_layer_idx < 0)
        {
          printf("ERROR (in p_mov_execute): Source Image has no layers\n");
          return -1;
        }
-       cur_ptr->src_last_layer = l_nlayers -1;
-
-       /* findout index of src_layer_id */
-       for(cur_ptr->src_layer_idx = 0;
-           cur_ptr->src_layer_idx  < l_nlayers;
-           cur_ptr->src_layer_idx++)
-       {
-          if(cur_ptr->src_layers[cur_ptr->src_layer_idx] == val_ptr->src_layer_id)
-          {
-             cur_ptr->src_layer_idx_dbl = (gdouble)cur_ptr->src_layer_idx;
-             break;
-          }
-       }
-       cur_ptr->src_last_layer = l_nlayers -1;   /* index of last layer */
      }
      else
      {
@@ -2455,7 +2535,7 @@ p_mov_execute_singleframe(GapMovData *mov_ptr)
 
    cur_ptr->dst_frame_nr = 1;
    cur_ptr->src_layers = NULL;
-   cur_ptr->src_last_layer = -1;
+   cur_ptr->src_last_layer_idx = -1;
    p_init_curr_ptr_with_1st_controlpoint(cur_ptr, val_ptr, singleFramePtr);
 
    /* set offsets (in cur_ptr)  according to handle mode and src_img dimension */
@@ -2813,9 +2893,10 @@ gap_mov_exec_anim_preview(GapMovValues *pvals_orig, GapAnimInfo *ainfo_ptr, gint
   gint32      l_mlayer_image_id;
   GimpImageBaseType  l_type;
   guint       l_width, l_height;
-  gint32      l_stackpos;
-  gint        l_nlayers;
-  gint32     *l_src_layers;
+  gint32      l_stackpos;           /* toplevel stackpos within src image orignal */
+  GapImageStackPositionsList *l_stack_pos_list;
+  
+  
   gint        l_rc;
   gdouble    l_xresoulution, l_yresoulution;
   gint32     l_unit;
@@ -2841,6 +2922,7 @@ gap_mov_exec_anim_preview(GapMovValues *pvals_orig, GapAnimInfo *ainfo_ptr, gint
   /* -1 assume no tmp_image (use unscaled original source) */
   l_tmp_image_id = -1;
   l_stackpos = 0;
+  l_stack_pos_list = NULL;
 
   /* Scale (down) needed ? */
   if((l_pvals->apv_scalex != 100.0) || (l_pvals->apv_scaley != 100.0))
@@ -2866,48 +2948,48 @@ gap_mov_exec_anim_preview(GapMovValues *pvals_orig, GapAnimInfo *ainfo_ptr, gint
       l_size_y = MAX(1, (gimp_image_height(l_tmp_image_id) * l_pvals->apv_scaley) / 100);
       gimp_image_scale(l_tmp_image_id, l_size_x, l_size_y);
 
-       /* findout the src_layer id in the scaled copy by stackpos index */
-       l_pvals->src_layer_id = -1;
-       l_src_layers = gimp_image_get_layers (pvals_orig->src_image_id, &l_nlayers);
-       if(l_src_layers == NULL)
-       {
-         printf("ERROR: gap_mov_exec_anim_preview GOT no src_layers (original image_id %d)\n",
-                 (int)pvals_orig->src_image_id);
-       }
-       else
-       {
-         for(l_stackpos = 0;
-             l_stackpos  < l_nlayers;
-             l_stackpos++)
-         {
-            if(l_src_layers[l_stackpos] == pvals_orig->src_layer_id)
-               break;
-         }
-         g_free(l_src_layers);
+      /* findout the src_layer id in the scaled copy by its stackpositions in image and layergroups */
+      l_pvals->src_layer_id = -1;
+      l_stack_pos_list = gap_image_get_tree_position_list(pvals_orig->src_layer_id);
+      if (l_stack_pos_list == NULL)
+      {
+        printf("ERROR: gap_mov_exec_anim_preview GOT no stack position list (original image_id %d)\n",
+                (int)pvals_orig->src_image_id);
+        gimp_image_delete(l_tmp_image_id);
+        return (-1);
+      }
+      else
+      {
+        l_stackpos =  l_stack_pos_list->stack_position;
+        l_pvals->src_layer_id =
+           gap_image_get_layer_id_by_tree_position_list(l_tmp_image_id, l_stack_pos_list);
 
-         l_src_layers = gimp_image_get_layers (l_tmp_image_id, &l_nlayers);
-         if(l_src_layers == NULL)
-         {
-           printf("ERROR: gap_mov_exec_anim_preview GOT no src_layers (scaled copy image_id %d)\n",
-                  (int)l_tmp_image_id);
-         }
-         else
-         {
-            l_pvals->src_layer_id = l_src_layers[l_stackpos];
-            g_free(l_src_layers);
-         }
+        gap_image_gfree_tree_position_list(l_stack_pos_list);
+      }
+      if (l_pvals->src_layer_id < 0)
+      {
+        printf("ERROR: gap_mov_exec_anim_preview Failed to find corresponding layer orig image_id %d 
copy:%d)\n"
+                , (int)pvals_orig->src_image_id
+                , (int)l_tmp_image_id
+                );
+        gimp_image_delete(l_tmp_image_id);
+        return (-1);
+      }
 
-       }
 
       if(gap_debug)
       {
-        printf("gap_mov_exec_anim_preview: orig  src_image_id:%d src_layer:%d, stackpos:%d\n"
+        printf("gap_mov_exec_anim_preview: orig  src_image_id:%d src_layer:%d, stackpos:%d name:%s\n"
                ,(int)pvals_orig->src_image_id
                ,(int)pvals_orig->src_layer_id
-               ,(int)l_stackpos);
-        printf("   Scaled src_image_id:%d scaled_src_layer:%d\n"
+               ,(int)l_stackpos
+               , gimp_item_get_name(pvals_orig->src_layer_id)
+                 );
+        printf("   Scaled src_image_id:%d scaled_src_layer:%d name:%s\n"
                ,(int)l_tmp_image_id
-               ,(int)l_pvals->src_layer_id );
+               ,(int)l_pvals->src_layer_id
+               , gimp_item_get_name(l_pvals->src_layer_id)
+               );
       }
     }
   }  /* end if Scaledown needed */
@@ -3041,6 +3123,7 @@ gap_mov_exec_anim_preview(GapMovValues *pvals_orig, GapAnimInfo *ainfo_ptr, gint
   /* add a display for the animated preview multilayer image */
   gimp_display_new(l_mlayer_image_id);
 
+
   /* delete the scaled copy of the src image (if there is one) */
   if(l_tmp_image_id >= 0)
   {
@@ -3056,6 +3139,9 @@ gap_mov_exec_anim_preview(GapMovValues *pvals_orig, GapAnimInfo *ainfo_ptr, gint
 }       /* end gap_mov_exec_anim_preview */
 
 
+
+
+
 /* ============================================================================
  * p_conv_keyframe
  * ============================================================================
@@ -4010,9 +4096,9 @@ void gap_mov_exec_set_handle_offsets(GapMovValues *val_ptr, GapMovCurrent *cur_p
 }       /* end gap_mov_exec_set_handle_offsets */
 
 
-/* ------------------------------------
- * gap_mov_exec_new_GapMovValues
- * ------------------------------------
+/* ------------------------------------------
+ * gap_mov_exec_get_default_rotate_threshold
+ * ------------------------------------------
  */
 gdouble
 gap_mov_exec_get_default_rotate_threshold()
@@ -4081,6 +4167,8 @@ GapMovValues *gap_mov_exec_new_GapMovValues()
   pvals->tween_steps = 0;
   pvals->tween_opacity_initial = 80.0;
   pvals->tween_opacity_desc = 80.0;
+  
+  pvals->dst_group_name_delimiter = g_strdup("/");
 
   return(pvals);
 
diff --git a/gap/gap_mov_exec.h b/gap/gap_mov_exec.h
index 2d6eff6..7fe8cd6 100644
--- a/gap/gap_mov_exec.h
+++ b/gap/gap_mov_exec.h
@@ -41,6 +41,7 @@
 #include "libgimp/gimp.h"
 #include "gap_mov_dialog.h"
 
+void    gap_mov_exec_set_iteration_relevant_src_layers(GapMovCurrent *cur_ptr, gint32 src_layer_id, gint32 
src_image_id);
 gint32  gap_mov_exec_move_path(GimpRunMode run_mode, gint32 image_id, GapMovValues *pvals, gchar *pointfile, 
gint rotation_follow, gdouble startangle);
 gint32  gap_mov_exec_anim_preview(GapMovValues *pvals_orig, GapAnimInfo *ainfo_ptr, gint preview_frame_nr);
 gint32  gap_mov_exec_move_path_singleframe(GimpRunMode run_mode, gint32 image_id
@@ -61,6 +62,7 @@ GapMovValues *gap_mov_exec_new_GapMovValues();
 
 gboolean  gap_mov_exec_check_valid_xml_paramfile(const char *filename);
 
+
 /* ---------------------------------------------
  * gap_mov_exec_move_path_singleframe_directcall
  * ---------------------------------------------
diff --git a/gap/gap_mov_render.c b/gap/gap_mov_render.c
index 203d879..a1f2e6c 100644
--- a/gap/gap_mov_render.c
+++ b/gap/gap_mov_render.c
@@ -151,6 +151,7 @@ p_mov_selection_handling(gint32 orig_layer_id
                                   GIMP_RGBA_IMAGE,
                                  100.0,     /* full opaque */
                                  GIMP_NORMAL_MODE);
+  
   gimp_image_insert_layer(val_ptr->tmpsel_image_id, l_tmp_layer_id, 0, 0);
   gimp_layer_set_offsets(l_tmp_layer_id, src_offset_x, src_offset_y);
   gimp_selection_none(val_ptr->tmpsel_image_id);
@@ -734,6 +735,7 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
   GimpLayerModeEffects l_mode;
   gdouble              scaleWidthPercent;
   gdouble              scaleHeightPercent;
+  gint32       l_parent_id;
 
   if(gap_debug)
   {
@@ -757,6 +759,8 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
                     image_id
                     );
   }
+  
+  l_parent_id = 0;
 
   if(cur_ptr->isSingleFrame)
   {
@@ -797,7 +801,13 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
          cur_ptr->processedLayerId = -1;
          return -1;
       }
-      gimp_image_insert_layer(image_id, l_cp_layer_id, 0, val_ptr->dst_layerstack);
+      l_parent_id = gap_image_find_or_create_group_layer(image_id
+                        , val_ptr->dst_group_name_path_string
+                        , val_ptr->dst_group_name_delimiter
+                        , val_ptr->dst_layerstack     /* stackposition for the group in case it is created 
at toplvel */
+                        , TRUE  /* enableCreate */
+                        );
+      gimp_image_insert_layer(image_id, l_cp_layer_id, l_parent_id, val_ptr->dst_layerstack);
 
     }
 
@@ -854,7 +864,14 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
        return -1;
     }
 
-    gimp_image_insert_layer(image_id, l_cp_layer_id, 0,
+    l_parent_id = gap_image_find_or_create_group_layer(image_id
+                        , val_ptr->dst_group_name_path_string
+                        , val_ptr->dst_group_name_delimiter
+                        , val_ptr->dst_layerstack /* stackposition for the group in case it is created at 
toplvel */
+                        , TRUE  /* enableCreate */
+                        );
+
+    gimp_image_insert_layer(image_id, l_cp_layer_id, l_parent_id,
                          val_ptr->dst_layerstack);
     if(gap_debug)
     {
diff --git a/gap/gap_mov_xml_par.c b/gap/gap_mov_xml_par.c
index bd1dc19..73a819e 100644
--- a/gap/gap_mov_xml_par.c
+++ b/gap/gap_mov_xml_par.c
@@ -91,6 +91,9 @@
 #define GAP_MOVPATH_XML_TOKEN_SRC_SELMODE            "src_selmode"
 #define GAP_MOVPATH_XML_TOKEN_SRC_PAINTMODE          "src_paintmode"
 #define GAP_MOVPATH_XML_TOKEN_DST_LAYERSTACK         "dst_layerstack"
+#define GAP_MOVPATH_XML_TOKEN_DST_GROUP_PATH         "dst_group_name_path"
+#define GAP_MOVPATH_XML_TOKEN_DST_GROUP_DELIM        "dst_group_name_delimiter"
+
 #define GAP_MOVPATH_XML_TOKEN_STEP_SPEED_FACTOR      "step_speed_factor"
 #define GAP_MOVPATH_XML_TOKEN_SRC_FORCE_VISIBLE      "src_force_visible"
 #define GAP_MOVPATH_XML_TOKEN_CLIP_TO_IMG            "clip_to_img"
@@ -213,7 +216,7 @@ static void  p_xml_parse_element_controlpoint(const gchar         *element_name,
     const gchar        **attribute_values,
     GapMovXmlUserData   *userDataPtr,
     gint                 count);
-    
+
 
 
 
@@ -326,7 +329,7 @@ static const GEnumValue valuesGapBlueboxThresMode[] =
 
 
 
-/* 
+/*
  * XML PARSER procedures
  */
 
@@ -348,7 +351,7 @@ p_xml_parse_value_GapMovHandle(const gchar *attribute_value, GapMovHandle *valDe
     *valDestPtr = value;
   }
   return (isOk);
-  
+
 }  /* end p_xml_parse_value_GapMovHandle */
 
 
@@ -389,7 +392,7 @@ p_xml_parse_value_GapMovSelMode(const gchar *attribute_value, GapMovSelMode *val
     *valDestPtr = value;
   }
   return (isOk);
-  
+
 }  /* end p_xml_parse_value_GapMovSelMode */
 
 
@@ -409,7 +412,7 @@ p_xml_parse_value_GimpPaintmode_as_gint(const gchar *attribute_value, gint *valD
     *valDestPtr = value;
   }
   return (isOk);
-  
+
 }  /* end gap_xml_parse_value_GimpPaintmode */
 
 
@@ -429,7 +432,7 @@ p_xml_parse_value_GapBlueboxThresMode(const gchar *attribute_value, GapBlueboxTh
     *valDestPtr = value;
   }
   return (isOk);
-  
+
 }  /* end p_xml_parse_value_GapBlueboxThresMode */
 
 
@@ -439,7 +442,7 @@ p_xml_parse_value_GapBlueboxThresMode(const gchar *attribute_value, GapBlueboxTh
  * p_xml_parse_element_root
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_root(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -472,7 +475,7 @@ p_xml_parse_element_root(const gchar         *element_name,
  * p_xml_parse_element_frame_description
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_frame_description(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -509,7 +512,7 @@ p_xml_parse_element_frame_description(const gchar         *element_name,
     {
       userDataPtr->isParseOk = gap_xml_parse_value_gint32(*value_cursor, &userDataPtr->pvals->total_frames);
     }
-    
+
     name_cursor++;
     value_cursor++;
   }
@@ -520,7 +523,7 @@ p_xml_parse_element_frame_description(const gchar         *element_name,
  * p_xml_parse_element_tween
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_tween(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -561,7 +564,7 @@ p_xml_parse_element_tween(const gchar         *element_name,
  * p_xml_parse_element_trace
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_trace(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -602,7 +605,7 @@ p_xml_parse_element_trace(const gchar         *element_name,
  * p_xml_parse_element_moving_object
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_moving_object(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -647,6 +650,14 @@ p_xml_parse_element_moving_object(const gchar         *element_name,
     {
       userDataPtr->isParseOk = gap_xml_parse_value_gint(*value_cursor, &userDataPtr->pvals->dst_layerstack);
     }
+    else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_DST_GROUP_PATH) == 0)
+    {
+      userDataPtr->isParseOk = gap_xml_parse_value_utf8_string(*value_cursor, 
&userDataPtr->pvals->dst_group_name_path_string);
+    }
+    else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_DST_GROUP_DELIM) == 0)
+    {
+      userDataPtr->isParseOk = gap_xml_parse_value_utf8_string(*value_cursor, 
&userDataPtr->pvals->dst_group_name_delimiter);
+    }
     else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_STEP_SPEED_FACTOR) == 0)
     {
       userDataPtr->isParseOk = gap_xml_parse_value_gdouble(*value_cursor, 
&userDataPtr->pvals->step_speed_factor);
@@ -690,7 +701,7 @@ p_xml_parse_element_moving_object(const gchar         *element_name,
  * p_xml_parse_element_bluebox_parameters
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_bluebox_parameters(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -699,7 +710,7 @@ p_xml_parse_element_bluebox_parameters(const gchar         *element_name,
 {
   const gchar **name_cursor = attribute_names;
   const gchar **value_cursor = attribute_values;
-  
+
   GapBlueboxGlobalParams *bbp;
 
   if(count > 0)
@@ -710,7 +721,7 @@ p_xml_parse_element_bluebox_parameters(const gchar         *element_name,
   if(userDataPtr->pvals->bbp == NULL)
   {
     gint32 layer_id;
-   
+
     layer_id = -1;
     userDataPtr->pvals->bbp = gap_bluebox_bbp_new(layer_id);
     gap_bluebox_init_default_vals(userDataPtr->pvals->bbp);
@@ -799,7 +810,7 @@ p_xml_parse_element_bluebox_parameters(const gchar         *element_name,
  * p_xml_parse_element_controlpoints
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_controlpoints(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -808,12 +819,12 @@ p_xml_parse_element_controlpoints(const gchar         *element_name,
 {
   const gchar **name_cursor = attribute_names;
   const gchar **value_cursor = attribute_values;
-  
+
   if(count > 0)
   {
     userDataPtr->isParseOk = FALSE;
   }
-  
+
   while ((*name_cursor) && (userDataPtr->isParseOk))
   {
     if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_CURRENT_POINT) == 0)
@@ -828,7 +839,7 @@ p_xml_parse_element_controlpoints(const gchar         *element_name,
     {
       gint numberOfPoints;
       userDataPtr->isParseOk = gap_xml_parse_value_gint(*value_cursor, &numberOfPoints);
-      
+
       if(userDataPtr->isParseOk)
       {
         if((numberOfPoints < GAP_MOV_MAX_POINT) && (numberOfPoints > 0))
@@ -840,7 +851,7 @@ p_xml_parse_element_controlpoints(const gchar         *element_name,
           userDataPtr->isParseOk = FALSE;
         }
       }
-      
+
     }
 
     name_cursor++;
@@ -877,7 +888,7 @@ p_set_load_defaults_for_one_controlpoint(GapMovValues *pvals, gint idx)
     pvals->point[idx].sel_feather_radius = 0.0;
     pvals->point[idx].keyframe = 0;   /* 0: controlpoint is not fixed to keyframe */
     pvals->point[idx].keyframe_abs = 0;   /* 0: controlpoint is not fixed to keyframe */
-    
+
     pvals->point[idx].accPosition = 0;           /* 0: linear (e.g NO acceleration) is default */
     pvals->point[idx].accOpacity = 0;            /* 0: linear (e.g NO acceleration) is default */
     pvals->point[idx].accSize = 0;               /* 0: linear (e.g NO acceleration) is default */
@@ -886,7 +897,7 @@ p_set_load_defaults_for_one_controlpoint(GapMovValues *pvals, gint idx)
     pvals->point[idx].accSelFeatherRadius = 0;   /* 0: linear (e.g NO acceleration) is default */
 
   }
-  
+
 }  /* end p_set_load_defaults_for_one_controlpoint  */
 
 
@@ -895,7 +906,7 @@ p_set_load_defaults_for_one_controlpoint(GapMovValues *pvals, gint idx)
  * p_xml_parse_element_controlpoint
  * --------------------------------------
  */
-static void 
+static void
 p_xml_parse_element_controlpoint(const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
@@ -904,14 +915,14 @@ p_xml_parse_element_controlpoint(const gchar         *element_name,
 {
   const gchar **name_cursor = attribute_names;
   const gchar **value_cursor = attribute_values;
-  
+
   if(count >= GAP_MOV_MAX_POINT)
   {
     userDataPtr->isParseOk = FALSE;
   }
-  
+
   p_set_load_defaults_for_one_controlpoint(userDataPtr->pvals, count);
-  
+
   while ((*name_cursor) && (userDataPtr->isParseOk))
   {
     if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_PX) == 0)
@@ -925,7 +936,7 @@ p_xml_parse_element_controlpoint(const gchar         *element_name,
     else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_KEYFRAME) == 0)
     {
       gint keyframe;
-      
+
       userDataPtr->isParseOk = gap_xml_parse_value_gint(*value_cursor, &keyframe);
       userDataPtr->pvals->point[count].keyframe = keyframe;
       userDataPtr->pvals->point[count].keyframe_abs = gap_mov_exec_conv_keyframe_to_abs(keyframe, 
userDataPtr->pvals);
@@ -934,7 +945,7 @@ p_xml_parse_element_controlpoint(const gchar         *element_name,
     else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_KEYFRAME_ABS) == 0)
     {
       gint keyframe_abs;
-      
+
       userDataPtr->isParseOk = gap_xml_parse_value_gint(*value_cursor, &keyframe_abs);
       userDataPtr->pvals->point[count].keyframe_abs = keyframe_abs;
       userDataPtr->pvals->point[count].keyframe = gap_mov_exec_conv_keyframe_to_rel(keyframe_abs, 
userDataPtr->pvals);
@@ -1030,13 +1041,13 @@ p_xml_parse_element_controlpoint(const gchar         *element_name,
  * this handler is called each time the parser recognizes
  * the start event of an xml element.
  */
-static void 
+static void
 p_start_xml_element (GMarkupParseContext *context,
     const gchar         *element_name,
     const gchar        **attribute_names,
     const gchar        **attribute_values,
     GapMovXmlUserData   *userDataPtr,
-    GError             **error) 
+    GError             **error)
 {
   gint jj;
 
@@ -1060,8 +1071,8 @@ p_start_xml_element (GMarkupParseContext *context,
       }
       if(!userDataPtr->isScopeValid)
       {
-        /* stop parsing when outsided of known namespace 
-         * (and stop on duplicate root element too) 
+        /* stop parsing when outsided of known namespace
+         * (and stop on duplicate root element too)
          */
         return;
       }
@@ -1091,7 +1102,7 @@ p_start_xml_element (GMarkupParseContext *context,
  * this handler is called each time the parser recognizes
  * the end event of an xml element
  */
-static void 
+static void
 p_end_xml_element (GMarkupParseContext *context,
     const gchar         *element_name,
     GapMovXmlUserData   *userDataPtr,
@@ -1109,7 +1120,7 @@ p_end_xml_element (GMarkupParseContext *context,
   if(userDataPtr->isScopeValid)
   {
     gint jj;
-    
+
     for(jj=0; jmpTableElement[jj].name != NULL; jj++)
     {
       if(strcmp(jmpTableElement[jj].name, element_name) == 0)
@@ -1117,9 +1128,9 @@ p_end_xml_element (GMarkupParseContext *context,
         jmpTableElement[jj].count++;
       }
     }
-    
+
   }
-  
+
 }  /* end p_end_xml_element */
 
 
@@ -1140,20 +1151,20 @@ p_end_xml_element (GMarkupParseContext *context,
 static gint
 p_transform_path_coordinate(gint value, gint32 recordedSize, gint32 actualSize)
 {
-  
+
   if((recordedSize != 0) && (actualSize != recordedSize))
   {
     gdouble newValue;
     gint    newIntValue;
-    
+
     newValue = ((gdouble)value * (gdouble)actualSize) / (gdouble)recordedSize;
     newIntValue = rint(newValue);
-    
+
     return (newIntValue);
   }
 
   return(value);
-  
+
 }
 
 
@@ -1167,7 +1178,7 @@ p_copy_transformed_values(GapMovValues *dstValues, GapMovValues *srcValues
    , gint32 actualFrameWidth, gint32 actualFrameHeight)
 {
   gint ii;
-  
+
   dstValues->version = srcValues->version;
   dstValues->rotate_threshold = srcValues->rotate_threshold;
   dstValues->recordedFrameWidth = srcValues->recordedFrameWidth;
@@ -1188,6 +1199,25 @@ p_copy_transformed_values(GapMovValues *dstValues, GapMovValues *srcValues
   dstValues->src_selmode = srcValues->src_selmode;
   dstValues->src_paintmode = srcValues->src_paintmode;
   dstValues->dst_layerstack = srcValues->dst_layerstack;
+  if(dstValues->dst_group_name_path_string != NULL)
+  {
+    g_free(dstValues->dst_group_name_path_string);
+    dstValues->dst_group_name_path_string = NULL;
+  }
+  if(srcValues->dst_group_name_path_string != NULL)
+  {
+    dstValues->dst_group_name_path_string = g_strdup(srcValues->dst_group_name_path_string);
+  }
+
+  if(dstValues->dst_group_name_delimiter != NULL)
+  {
+    g_free(dstValues->dst_group_name_delimiter);
+    dstValues->dst_group_name_delimiter = NULL;
+  }
+  if(srcValues->dst_group_name_delimiter != NULL)
+  {
+    dstValues->dst_group_name_delimiter = g_strdup(srcValues->dst_group_name_delimiter);
+  }
   dstValues->step_speed_factor = srcValues->step_speed_factor;
   dstValues->src_force_visible = srcValues->src_force_visible;
   dstValues->clip_to_img = srcValues->clip_to_img;
@@ -1214,8 +1244,8 @@ p_copy_transformed_values(GapMovValues *dstValues, GapMovValues *srcValues
     dstValues->bbp = g_new(GapBlueboxGlobalParams, 1);
     memcpy(dstValues->bbp, srcValues->bbp, sizeof(GapBlueboxGlobalParams));
   }
-  
-  
+
+
   dstValues->point_idx = srcValues->point_idx;
   dstValues->point_idx_max = srcValues->point_idx_max;
 
@@ -1223,7 +1253,7 @@ p_copy_transformed_values(GapMovValues *dstValues, GapMovValues *srcValues
   for(ii=0; ii <= srcValues->point_idx_max; ii++)
   {
     memcpy(&dstValues->point[ii], &srcValues->point[ii], sizeof(GapMovPoint));
-    
+
     dstValues->point[ii].p_x = p_transform_path_coordinate(srcValues->point[ii].p_x
                                     , srcValues->recordedFrameWidth
                                     , actualFrameWidth
@@ -1232,7 +1262,7 @@ p_copy_transformed_values(GapMovValues *dstValues, GapMovValues *srcValues
                                     , srcValues->recordedFrameHeight
                                     , actualFrameHeight
                                     );
-    
+
   }
 
 }  /* end p_copy_transformed_values */
@@ -1255,28 +1285,28 @@ p_error_handler(GMarkupParseContext *context,
           ,(int)error->code
           ,error->message
           );
-    
+
   }
-  
+
   if(context != NULL)
   {
     gint line_number;
     gint char_number;
-    
+
     g_markup_parse_context_get_position (context, &line_number, &char_number);
-    
+
     printf("context: line_number:%d char_number:%d element:%s\n"
       ,(int)line_number
       ,(int)char_number
       ,g_markup_parse_context_get_element(context)
       );
   }
-  
+
   if(user_data != NULL)
   {
     GapMovXmlUserData *userDataPtr;
     userDataPtr = user_data;
-    
+
     printf("userDataPtr: isParseOk:%d isScopeValid:%d errorLineNumber:%d\n"
       ,(int)userDataPtr->isParseOk
       ,(int)userDataPtr->isScopeValid
@@ -1294,7 +1324,7 @@ p_error_handler(GMarkupParseContext *context,
  * (use  actualFrameWidth and actualFrameHeight value 0 in case no transformation
  * is desired)
  */
-gboolean 
+gboolean
 gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
     ,gint32 actualFrameWidth, gint32 actualFrameHeight)
 {
@@ -1316,7 +1346,7 @@ gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
   GapMovValues   *tmpValues;
   GapMovXmlUserData *userDataPtr;
   GError            *gError;
-  
+
   isOk = TRUE;
   gError = NULL;
   tmpValues = gap_mov_exec_new_GapMovValues();
@@ -1325,17 +1355,17 @@ gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
   userDataPtr = g_new(GapMovXmlUserData, 1);
   userDataPtr->pvals = tmpValues;
 
-  ///p_init_default_values(tmpValues);   /// (?) TODO 
-  
+  ///p_init_default_values(tmpValues);   /// (?) TODO
+
   userDataPtr->isScopeValid = FALSE;
   userDataPtr->isParseOk = TRUE;
   userDataPtr->errorLineNumber = 0;
-  
+
   for(jj=0; jmpTableElement[jj].name != NULL; jj++)
   {
     jmpTableElement[jj].count = 0;
   }
-  
+
   GMarkupParseContext *context = g_markup_parse_context_new (
         &parserFuctions  /* GMarkupParser */
       , 0                /* GMarkupParseFlags flags */
@@ -1343,21 +1373,21 @@ gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
       , NULL             /* GDestroyNotify user_data_dnotify */
       );
 
-  if (g_file_get_contents (filename, &textBuffer, &lengthTextBuffer, NULL) != TRUE) 
+  if (g_file_get_contents (filename, &textBuffer, &lengthTextBuffer, NULL) != TRUE)
   {
     printf("Couldn't load XML file:%s\n", filename);
     return(FALSE);
   }
-  
+
 
   if (g_markup_parse_context_parse (context, textBuffer, lengthTextBuffer, &gError) != TRUE)
   {
     printf("Parse failed of file: %s\n", filename);
     p_error_handler(context, gError, userDataPtr);
-    
+
     return(FALSE);
   }
-  
+
   /* check for mandatory elements */
   if(userDataPtr->isParseOk)
   {
@@ -1375,19 +1405,19 @@ gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
         }
       }
     }
-    
+
   }
 
   if(userDataPtr->isParseOk)
   {
       /* copy loaded values and transform coordinates from recorded frame size
-       * to actual frame size 
+       * to actual frame size
        */
       p_copy_transformed_values(productiveValues, tmpValues, actualFrameWidth, actualFrameHeight);
-  }  
-  
+  }
+
   isOk = userDataPtr->isParseOk;
-  
+
   g_free(textBuffer);
   g_markup_parse_context_free (context);
 
@@ -1402,8 +1432,8 @@ gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
   g_free(tmpValues);
 
   g_free(userDataPtr);
-  
-  
+
+
   return (isOk);
 
 }  /* end gap_mov_xml_par_load */
@@ -1412,7 +1442,7 @@ gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
 
 
 
-/* 
+/*
  * XML WRITER procedure
  */
 
@@ -1438,20 +1468,20 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
     gboolean writeRotateValues;
     gboolean writeOpacityValues;
     gboolean writeFeatherRadiusValues;
-    
+
     fprintf(l_fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-    
+
     /* root */
     fprintf(l_fp, "<%s ", GAP_MOVPATH_XML_TOKEN_ROOT);
     gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_VERSION, pvals->version);
     fprintf(l_fp, ">\n");
-    
+
     /* attributes for description of the processed frames */
     {
       gint32 total_frames;
-      
+
       total_frames = 1 + abs(pvals->dst_range_end - pvals->dst_range_start);
-    
+
       fprintf(l_fp, "  <%s ", GAP_MOVPATH_XML_TOKEN_FRAME_DESCRIPTION);
       gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_WIDTH, gimp_image_width(pvals->dst_image_id));
       gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_HEIGHT, gimp_image_height(pvals->dst_image_id));
@@ -1460,8 +1490,8 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_TOTAL_FRAMES, total_frames);
       fprintf(l_fp, "/>\n");
     }
-    
-           
+
+
     /* attributes for tween processing */
     fprintf(l_fp, "  <%s ", GAP_MOVPATH_XML_TOKEN_TWEEN);
     gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_TWEEN_STEPS, pvals->tween_steps);
@@ -1471,7 +1501,7 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_TWEEN_OPACITY_DESC, pvals->tween_opacity_desc, 
1, 3);
     }
     fprintf(l_fp, "/>\n");
-    
+
     /* attributes for trace layer generation */
     isTraceLayerEnabled = (pvals->tracelayer_enable != 0);
     fprintf(l_fp, "  <%s ", GAP_MOVPATH_XML_TOKEN_TRACE);
@@ -1482,12 +1512,12 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_TRACE_OPACITY_DESC, pvals->trace_opacity_desc, 
1, 3);
     }
     fprintf(l_fp, "/>\n");
-    
+
     /* attributes of the moving_object */
     {
       char   *src_filename;
       gint32  src_image_id;
- 
+
       src_image_id = gimp_drawable_get_image(pvals->src_layer_id);
 
       fprintf(l_fp, "  <%s ", GAP_MOVPATH_XML_TOKEN_MOVING_OBJECT);
@@ -1513,11 +1543,13 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       gap_xml_write_EnumValue(l_fp, GAP_MOVPATH_XML_TOKEN_SRC_PAINTMODE, pvals->src_paintmode, 
&valuesGimpPaintmode[0]);
       fprintf(l_fp, "\n    ");
       gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_DST_LAYERSTACK, pvals->dst_layerstack);
+      gap_xml_write_string_value(l_fp, GAP_MOVPATH_XML_TOKEN_DST_GROUP_PATH, 
pvals->dst_group_name_path_string);
+      gap_xml_write_string_value(l_fp, GAP_MOVPATH_XML_TOKEN_DST_GROUP_DELIM, 
pvals->dst_group_name_delimiter);
       gap_xml_write_gint_as_gboolean_value(l_fp, GAP_MOVPATH_XML_TOKEN_SRC_FORCE_VISIBLE, 
pvals->src_force_visible);
       gap_xml_write_gint_as_gboolean_value(l_fp, GAP_MOVPATH_XML_TOKEN_CLIP_TO_IMG, pvals->clip_to_img);
       gap_xml_write_gint_as_gboolean_value(l_fp, GAP_MOVPATH_XML_TOKEN_SRC_APPLY_BLUEBOX, 
pvals->src_apply_bluebox);
       fprintf(l_fp, "\n");
-      
+
       /* attributes for applying bluebox transparency processing */
       if((pvals->src_apply_bluebox) && (pvals->bbp != NULL))
       {
@@ -1546,13 +1578,13 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
         gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_KEYCOLOR_R, pvals->bbp->vals.keycolor.r, 1, 
3);
         gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_KEYCOLOR_G, pvals->bbp->vals.keycolor.g, 1, 
3);
         gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_KEYCOLOR_B, pvals->bbp->vals.keycolor.b, 1, 
3);
-  
+
         fprintf(l_fp, "\n");
         fprintf(l_fp, "      >\n");
         fprintf(l_fp, "    </%s>\n", GAP_MOVPATH_XML_TOKEN_BLUEBOX_PARAMETERS);
-      
+
       }
-      
+
       fprintf(l_fp, "    >\n");
       fprintf(l_fp, "  </%s>\n", GAP_MOVPATH_XML_TOKEN_MOVING_OBJECT);
     }
@@ -1567,7 +1599,7 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
     gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_ROTATE_THRESHOLD, pvals->rotate_threshold, 1, 7);
     fprintf(l_fp, " >\n");
 
-    /* check for conditonal write 
+    /* check for conditonal write
      * (to keep files smaller and more readable we skip writing of some information
      * that is equal to the default value in all controlpoints)
      */
@@ -1582,22 +1614,22 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       {
         writeResizeValues = TRUE;
       }
-      
+
       if(pvals->point[l_idx].opacity != 100.0)
       {
         writeOpacityValues = TRUE;
       }
-      
+
       if(pvals->point[l_idx].rotation != 0.0)
       {
         writeRotateValues = TRUE;
       }
-      
+
       if(pvals->point[l_idx].sel_feather_radius != 0.0)
       {
         writeFeatherRadiusValues = TRUE;
       }
-      
+
     }
 
     /* write controlpoints loop */
@@ -1607,13 +1639,13 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       gdouble  py;
       gboolean writeAccelerationCharacteristics;
       gboolean keyframeInNewLine;
-      
+
       keyframeInNewLine = FALSE;
       px = pvals->point[l_idx].p_x;
       py = pvals->point[l_idx].p_y;
-      
+
       /* write basic attributes of the controlpoint */
-      
+
       fprintf(l_fp, "    <%s ", GAP_MOVPATH_XML_TOKEN_CONTROLPOINT);
       gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_PX, px, 5, 0);
       gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_PY, py, 5, 0);
@@ -1634,7 +1666,7 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       {
         gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_SEL_FEATHER_RADIUS, 
pvals->point[l_idx].sel_feather_radius, 3, 1);
       }
-      
+
       /* conditional write perspective transformation (only if there is any) */
       if(pvals->point[l_idx].ttlx != 1.0
             || pvals->point[l_idx].ttly != 1.0
@@ -1670,7 +1702,7 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       ||  (pvals->point[l_idx].accPerspective != 0)
       ||  (pvals->point[l_idx].accSelFeatherRadius != 0))
       {
-        if (l_idx == 0) 
+        if (l_idx == 0)
         {
           writeAccelerationCharacteristics = TRUE;
         }
@@ -1682,9 +1714,9 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
             writeAccelerationCharacteristics = TRUE;
           }
         }
-        
+
       }
-      
+
       if (writeAccelerationCharacteristics == TRUE)
       {
         keyframeInNewLine = TRUE;
@@ -1707,30 +1739,30 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
           ,gap_mov_exec_conv_keyframe_to_rel(pvals->point[l_idx].keyframe_abs, pvals)
           );
       }
-      
+
       /* check for writing keyframe
        * (the implicite keyframes at first and last controlpoints are not written to file)
        */
       if((l_idx > 0)
       && (l_idx < pvals->point_idx_max)
       && ((int)pvals->point[l_idx].keyframe_abs > 0))
-      { 
+      {
         if(keyframeInNewLine == TRUE)
         {
           fprintf(l_fp, "\n      ");
         }
-        gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_KEYFRAME_ABS, 
+        gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_KEYFRAME_ABS,
                                 pvals->point[l_idx].keyframe_abs);
-      
+
       }
 
       fprintf(l_fp, "/>\n"); /* end of GAP_MOVPATH_XML_TOKEN_CONTROLPOINT */
-      
-    }    
-    
+
+    }
+
     fprintf(l_fp, "  </%s>\n", GAP_MOVPATH_XML_TOKEN_CONTROLPOINTS);
     fprintf(l_fp, "</%s>", GAP_MOVPATH_XML_TOKEN_ROOT);
-    
+
     fclose(l_fp);
     return 0;
   }
diff --git a/gap/gap_xml_util.c b/gap/gap_xml_util.c
index 6f4b83a..83b8a80 100644
--- a/gap/gap_xml_util.c
+++ b/gap/gap_xml_util.c
@@ -188,9 +188,32 @@ const GEnumValue *enumValuesTable)
 }  /* end gap_xml_parse_EnumValue_as_gint */
 
 
+/* --------------------------------------
+ * gap_xml_parse_value_utf8_string
+ * --------------------------------------
+ */
+gboolean
+gap_xml_parse_value_utf8_string(const gchar *attribute_value, gchar **valDestPtr)
+{
+  if(*valDestPtr != NULL)
+  {
+    g_free(*valDestPtr);
+    *valDestPtr = NULL;
+  }
+  if(attribute_value != NULL)
+  {
+    gboolean    utf8_compliant;
 
-
-
+    utf8_compliant = g_utf8_validate(attribute_value, -1, NULL);
+    if(utf8_compliant)
+    {
+       *valDestPtr = g_strdup(attribute_value);
+       return (TRUE);
+    }
+    
+  }
+  return (FALSE);
+}
 
 
 
diff --git a/gap/gap_xml_util.h b/gap/gap_xml_util.h
index 5ffa4a9..f7410ea 100644
--- a/gap/gap_xml_util.h
+++ b/gap/gap_xml_util.h
@@ -41,8 +41,8 @@ gboolean    gap_xml_parse_value_gint32(const gchar *attribute_value, gint32 *val
 gboolean    gap_xml_parse_value_gint(const gchar *attribute_value, gint *valDestPtr);
 gboolean    gap_xml_parse_value_gboolean(const gchar *attribute_value, gboolean *valDestPtr);
 gboolean    gap_xml_parse_value_gboolean_as_gint(const gchar *attribute_value, gint *valDestPtr);
-gboolean    gap_xml_parse_EnumValue_as_gint(const gchar *attribute_value, gint
-*valDestPtr, const GEnumValue *enumValuesTable);
+gboolean    gap_xml_parse_EnumValue_as_gint(const gchar *attribute_value, gint *valDestPtr, const GEnumValue 
*enumValuesTable);
+gboolean    gap_xml_parse_value_utf8_string(const gchar *attribute_value, gchar **valDestPtr);
 
 void        gap_xml_write_gboolean_value(FILE *fp, const char *name, gboolean value);
 void        gap_xml_write_gint_as_gboolean_value(FILE *fp, const char *name, gint value);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]