[gimp] app, devel-docs: saving the item sets in XCF (bumping to XCF 16).



commit 362fae9147c0db8f55b28556294bd0fe75bc9b1c
Author: Jehan <jehan girinstud io>
Date:   Wed Dec 22 22:53:15 2021 +0100

    app, devel-docs: saving the item sets in XCF (bumping to XCF 16).
    
    We now save and load layer and channel item sets. Only missing set types
    are path ones, but the whole path item is just its own exception in the
    XCF format, and adding support for it, while keeping compatibility with
    older XCF seem like a small headache. I could do it, but I actually
    wonder if it is worth it. Would people really need to store sets of
    paths?
    
    Also this commit finally gets rid of any remnant of the old item "link"
    concept (I think), so we are getting close to merging the branch.

 app/core/gimpimage.c            |  29 +++++++-
 app/core/gimpitem.c             |  27 --------
 app/core/gimpitem.h             |   3 -
 app/core/gimpitemlist.c         |  39 +++++++++--
 app/core/gimpitemlist.h         |   5 +-
 app/gimpcore.def                |   1 -
 app/pdb/gimp-pdb-compat.c       |   6 --
 app/widgets/gimplayertreeview.c |   2 +-
 app/xcf/xcf-load.c              | 149 +++++++++++++++++++++++++++++++++++++++-
 app/xcf/xcf-private.h           |   5 ++
 app/xcf/xcf-save.c              | 111 ++++++++++++++++++++++++++++--
 devel-docs/app/app-sections.txt |   1 -
 devel-docs/xcf.txt              |  48 ++++++++++++-
 13 files changed, 369 insertions(+), 57 deletions(-)
---
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index 850a6f34f1..76f87202b7 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -2914,6 +2914,21 @@ gimp_image_get_xcf_version (GimpImage    *image,
         }
     }
 
+  if (gimp_image_get_stored_item_sets (image, GIMP_TYPE_LAYER) ||
+      gimp_image_get_stored_item_sets (image, GIMP_TYPE_CHANNEL))
+    {
+      ADD_REASON (g_strdup_printf (_("Item set and pattern search in item's name were "
+                                     "added in %s"), "GIMP 3.0.0"));
+      version = MAX (16, version);
+    }
+  if (g_list_length (gimp_image_get_selected_channels (image)) > 1)
+    {
+      ADD_REASON (g_strdup_printf (_("Multiple channel selection was "
+                                     "added in %s"), "GIMP 3.0.0"));
+      version = MAX (16, version);
+    }
+
+
 #undef ADD_REASON
 
   switch (version)
@@ -2945,6 +2960,7 @@ gimp_image_get_xcf_version (GimpImage    *image,
       break;
     case 14:
     case 15:
+    case 16:
       if (gimp_version)   *gimp_version   = 300;
       if (version_string) *version_string = "GIMP 3.0";
       break;
@@ -5439,8 +5455,16 @@ gimp_image_store_item_set (GimpImage    *image,
 
   for (iter = *stored_sets; iter; iter = iter->next)
     {
+      gboolean         is_pattern;
+      gboolean         is_pattern2;
+      GimpSelectMethod pattern_syntax;
+      GimpSelectMethod pattern_syntax2;
+
+      is_pattern  = gimp_item_list_is_pattern (iter->data, &pattern_syntax);
+      is_pattern2 = gimp_item_list_is_pattern (set, &pattern_syntax2);
+
       /* Remove a previous item set of same type and name. */
-      if (gimp_item_list_is_pattern (iter->data) == gimp_item_list_is_pattern (set) &&
+      if (is_pattern == is_pattern2 && (! is_pattern || pattern_syntax == pattern_syntax2) &&
           g_strcmp0 (gimp_object_get_name (iter->data), gimp_object_get_name (set)) == 0)
         break;
     }
@@ -5513,9 +5537,10 @@ gimp_image_unlink_item_set (GimpImage    *image,
 /*
  * @gimp_image_get_stored_item_sets:
  * @image:
+ * @item_type:
  *
  * Returns: (transfer none): the list of all the layer sets (which you
- *          should not modify). Order of items is not relevant.
+ *          should not modify). Order of items is relevant.
  */
 GList *
 gimp_image_get_stored_item_sets (GimpImage *image,
diff --git a/app/core/gimpitem.c b/app/core/gimpitem.c
index f4817bba6b..d4e2f81bfc 100644
--- a/app/core/gimpitem.c
+++ b/app/core/gimpitem.c
@@ -53,7 +53,6 @@ enum
 {
   REMOVED,
   VISIBILITY_CHANGED,
-  LINKED_CHANGED,
   COLOR_TAG_CHANGED,
   LOCK_CONTENT_CHANGED,
   LOCK_POSITION_CHANGED,
@@ -71,7 +70,6 @@ enum
   PROP_OFFSET_X,
   PROP_OFFSET_Y,
   PROP_VISIBLE,
-  PROP_LINKED,
   PROP_COLOR_TAG,
   PROP_LOCK_CONTENT,
   PROP_LOCK_POSITION,
@@ -97,7 +95,6 @@ struct _GimpItemPrivate
   guint             visible                : 1;  /*  item visibility             */
   guint             bind_visible_to_active : 1;  /*  visibility bound to active  */
 
-  guint             linked                 : 1;  /*  control linkage             */
   guint             lock_content           : 1;  /*  content editability         */
   guint             lock_position          : 1;  /*  content movability          */
   guint             lock_visibility        : 1;  /*  automatic visibility change */
@@ -204,14 +201,6 @@ gimp_item_class_init (GimpItemClass *klass)
                   NULL, NULL, NULL,
                   G_TYPE_NONE, 0);
 
-  gimp_item_signals[LINKED_CHANGED] =
-    g_signal_new ("linked-changed",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_FIRST,
-                  G_STRUCT_OFFSET (GimpItemClass, linked_changed),
-                  NULL, NULL, NULL,
-                  G_TYPE_NONE, 0);
-
   gimp_item_signals[COLOR_TAG_CHANGED] =
     g_signal_new ("color-tag-changed",
                   G_TYPE_FROM_CLASS (klass),
@@ -257,7 +246,6 @@ gimp_item_class_init (GimpItemClass *klass)
 
   klass->removed                   = NULL;
   klass->visibility_changed        = NULL;
-  klass->linked_changed            = NULL;
   klass->color_tag_changed         = NULL;
   klass->lock_content_changed      = NULL;
   klass->lock_position_changed     = NULL;
@@ -329,10 +317,6 @@ gimp_item_class_init (GimpItemClass *klass)
                                                         TRUE,
                                                         GIMP_PARAM_READABLE);
 
-  gimp_item_props[PROP_LINKED] = g_param_spec_boolean ("linked", NULL, NULL,
-                                                       FALSE,
-                                                       GIMP_PARAM_READABLE);
-
   gimp_item_props[PROP_COLOR_TAG] = g_param_spec_enum ("color-tag", NULL, NULL,
                                                        GIMP_TYPE_COLOR_TAG,
                                                        GIMP_COLOR_TAG_NONE,
@@ -453,9 +437,6 @@ gimp_item_get_property (GObject    *object,
     case PROP_VISIBLE:
       g_value_set_boolean (value, private->visible);
       break;
-    case PROP_LINKED:
-      g_value_set_boolean (value, private->linked);
-      break;
     case PROP_COLOR_TAG:
       g_value_set_enum (value, private->color_tag);
       break;
@@ -2380,14 +2361,6 @@ gimp_item_bind_visible_to_active (GimpItem *item,
     gimp_filter_set_active (GIMP_FILTER (item), gimp_item_get_visible (item));
 }
 
-gboolean
-gimp_item_get_linked (GimpItem *item)
-{
-  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
-
-  return GET_PRIVATE (item)->linked;
-}
-
 void
 gimp_item_set_color_tag (GimpItem     *item,
                          GimpColorTag  color_tag,
diff --git a/app/core/gimpitem.h b/app/core/gimpitem.h
index 53ae990430..38685fb2c4 100644
--- a/app/core/gimpitem.h
+++ b/app/core/gimpitem.h
@@ -44,7 +44,6 @@ struct _GimpItemClass
   /*  signals  */
   void            (* removed)               (GimpItem            *item);
   void            (* visibility_changed)    (GimpItem            *item);
-  void            (* linked_changed)        (GimpItem            *item);
   void            (* color_tag_changed)     (GimpItem            *item);
   void            (* lock_content_changed)  (GimpItem            *item);
   void            (* lock_position_changed) (GimpItem            *item);
@@ -364,8 +363,6 @@ gboolean        gimp_item_is_visible         (GimpItem           *item);
 void        gimp_item_bind_visible_to_active (GimpItem           *item,
                                               gboolean            bind);
 
-gboolean        gimp_item_get_linked         (GimpItem           *item);
-
 void            gimp_item_set_color_tag      (GimpItem           *item,
                                               GimpColorTag        color_tag,
                                               gboolean            push_undo);
diff --git a/app/core/gimpitemlist.c b/app/core/gimpitemlist.c
index 7dd5c08e89..bf8f548c9d 100644
--- a/app/core/gimpitemlist.c
+++ b/app/core/gimpitemlist.c
@@ -65,7 +65,6 @@ struct _GimpItemListPrivate
 {
   GimpImage        *image;
 
-  gchar            *label;         /* Item set name or pattern.                      */
   gboolean          is_pattern;    /* Whether a named fixed set or a pattern-search. */
   GimpSelectMethod  select_method; /* Pattern format if is_pattern is TRUE           */
 
@@ -174,7 +173,6 @@ gimp_item_list_init (GimpItemList *set)
 {
   set->p = gimp_item_list_get_instance_private (set);
 
-  set->p->label         = NULL;
   set->p->items         = NULL;
   set->p->select_method = GIMP_SELECT_PLAIN_TEXT;
   set->p->is_pattern    = FALSE;
@@ -188,7 +186,6 @@ gimp_item_list_constructed (GObject *object)
   G_OBJECT_CLASS (parent_class)->constructed (object);
 
   gimp_assert (GIMP_IS_IMAGE (set->p->image));
-  gimp_assert (set->p->items != NULL || set->p->is_pattern);
   gimp_assert (set->p->item_type == GIMP_TYPE_LAYER   ||
                set->p->item_type == GIMP_TYPE_VECTORS ||
                set->p->item_type == GIMP_TYPE_CHANNEL);
@@ -244,7 +241,6 @@ gimp_item_list_finalize (GObject *object)
   g_list_free (set->p->items);
   g_list_free_full (set->p->deleted_items,
                     (GDestroyNotify) gimp_item_list_free_deleted_item);
-  g_free (set->p->label);
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -460,14 +456,47 @@ gimp_item_list_get_items (GimpItemList  *set,
   return items;
 }
 
+/**
+ * gimp_item_list_is_pattern:
+ * @set:            The #GimpItemList.
+ * @pattern_syntax: The type of patterns @set handles.
+ *
+ * Indicate if @set is a pattern list. If the returned value is %TRUE,
+ * then @pattern_syntax will be set to the syntax we are dealing with.
+ *
+ * Returns: %TRUE if @set is a pattern list, %FALSE if it is a named
+ *          list.
+ */
 gboolean
-gimp_item_list_is_pattern (GimpItemList *set)
+gimp_item_list_is_pattern (GimpItemList      *set,
+                           GimpSelectMethod  *pattern_syntax)
 {
   g_return_val_if_fail (GIMP_IS_ITEM_LIST (set), FALSE);
 
+  if (set->p->is_pattern && pattern_syntax)
+    *pattern_syntax = set->p->select_method;
+
   return (set->p->is_pattern);
 }
 
+/**
+ * gimp_item_list_is_pattern:
+ * @set:  The #GimpItemList.
+ * @item: #GimpItem to add to @set.
+ *
+ * Add @item to the named list @set whose item type must also agree.
+ */
+void
+gimp_item_list_add (GimpItemList *set,
+                    GimpItem     *item)
+{
+  g_return_if_fail (GIMP_IS_ITEM_LIST (set));
+  g_return_if_fail (! gimp_item_list_is_pattern (set, NULL));
+  g_return_if_fail (g_type_is_a (G_TYPE_FROM_INSTANCE (item), set->p->item_type));
+
+  set->p->items = g_list_prepend (set->p->items, item);
+}
+
 
 /*  Private functions  */
 
diff --git a/app/core/gimpitemlist.h b/app/core/gimpitemlist.h
index 180beae5c0..5575d7ad73 100644
--- a/app/core/gimpitemlist.h
+++ b/app/core/gimpitemlist.h
@@ -61,7 +61,10 @@ GimpItemList  * gimp_item_list_pattern_new  (GimpImage        *image,
 GType          gimp_item_list_get_item_type (GimpItemList     *set);
 GList        * gimp_item_list_get_items     (GimpItemList     *set,
                                              GError          **error);
-gboolean       gimp_item_list_is_pattern    (GimpItemList     *set);
+gboolean       gimp_item_list_is_pattern    (GimpItemList     *set,
+                                             GimpSelectMethod *pattern_syntax);
 
+void           gimp_item_list_add           (GimpItemList     *set,
+                                             GimpItem         *item);
 
 #endif /* __GIMP_ITEM_LIST_H__ */
diff --git a/app/gimpcore.def b/app/gimpcore.def
index b93cbb91ba..30c66ee0bd 100644
--- a/app/gimpcore.def
+++ b/app/gimpcore.def
@@ -394,7 +394,6 @@ EXPORTS
        gimp_item_get_by_ID
        gimp_item_get_ID
        gimp_item_get_image
-       gimp_item_get_linked
        gimp_item_get_type
        gimp_item_get_visible
        gimp_item_height
diff --git a/app/pdb/gimp-pdb-compat.c b/app/pdb/gimp-pdb-compat.c
index 77e0aaf364..98e84abe7e 100644
--- a/app/pdb/gimp-pdb-compat.c
+++ b/app/pdb/gimp-pdb-compat.c
@@ -60,12 +60,10 @@ gimp_pdb_compat_procs_register (GimpPDB           *pdb,
     { "gimp-image-active-drawable",         "gimp-image-get-active-drawable"  },
     { "gimp-image-floating-selection",      "gimp-image-get-floating-sel"     },
     { "gimp-layer-delete",                  "gimp-item-delete"                },
-    { "gimp-layer-get-linked",              "gimp-item-get-linked"            },
     { "gimp-layer-get-name",                "gimp-item-get-name"              },
     { "gimp-layer-get-tattoo",              "gimp-item-get-tattoo"            },
     { "gimp-layer-get-visible",             "gimp-item-get-visible"           },
     { "gimp-layer-mask",                    "gimp-layer-get-mask"             },
-    { "gimp-layer-set-linked",              "gimp-item-set-linked"            },
     { "gimp-layer-set-name",                "gimp-item-set-name"              },
     { "gimp-layer-set-tattoo",              "gimp-item-set-tattoo"            },
     { "gimp-layer-set-visible",             "gimp-item-set-visible"           },
@@ -115,8 +113,6 @@ gimp_pdb_compat_procs_register (GimpPDB           *pdb,
     { "gimp-drawable-set-name",             "gimp-item-set-name"              },
     { "gimp-drawable-get-visible",          "gimp-item-get-visible"           },
     { "gimp-drawable-set-visible",          "gimp-item-set-visible"           },
-    { "gimp-drawable-get-linked",           "gimp-item-get-linked"            },
-    { "gimp-drawable-set-linked",           "gimp-item-set-linked"            },
     { "gimp-drawable-get-tattoo",           "gimp-item-get-tattoo"            },
     { "gimp-drawable-set-tattoo",           "gimp-item-set-tattoo"            },
     { "gimp-drawable-parasite-find",        "gimp-item-get-parasite"          },
@@ -142,8 +138,6 @@ gimp_pdb_compat_procs_register (GimpPDB           *pdb,
     { "gimp-vectors-set-name",              "gimp-item-set-name"              },
     { "gimp-vectors-get-visible",           "gimp-item-get-visible"           },
     { "gimp-vectors-set-visible",           "gimp-item-set-visible"           },
-    { "gimp-vectors-get-linked",            "gimp-item-get-linked"            },
-    { "gimp-vectors-set-linked",            "gimp-item-set-linked"            },
     { "gimp-vectors-get-tattoo",            "gimp-item-get-tattoo"            },
     { "gimp-vectors-set-tattoo",            "gimp-item-set-tattoo"            },
     { "gimp-vectors-parasite-find",         "gimp-item-get-parasite"          },
diff --git a/app/widgets/gimplayertreeview.c b/app/widgets/gimplayertreeview.c
index 83715ca94a..764bb289dd 100644
--- a/app/widgets/gimplayertreeview.c
+++ b/app/widgets/gimplayertreeview.c
@@ -1122,7 +1122,7 @@ gimp_layer_tree_view_layer_links_changed (GimpImage         *image,
 
       label = gtk_label_new (gimp_object_get_name (iter->data));
       gtk_label_set_xalign (GTK_LABEL (label), 0.0);
-      if (gimp_item_list_is_pattern (iter->data))
+      if (gimp_item_list_is_pattern (iter->data, NULL))
         {
           PangoAttrList *attrs = pango_attr_list_new ();
 
diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c
index 6920956a81..53d625d594 100644
--- a/app/xcf/xcf-load.c
+++ b/app/xcf/xcf-load.c
@@ -256,6 +256,10 @@ xcf_load_image (Gimp     *gimp,
 
   GIMP_LOG (XCF, "image props loaded");
 
+  /* Order matters for item sets. */
+  info->layer_sets = g_list_reverse (info->layer_sets);
+  info->channel_sets = g_list_reverse (info->channel_sets);
+
   /* check for a GimpGrid parasite */
   parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
                                        gimp_grid_parasite_name ());
@@ -705,18 +709,48 @@ xcf_load_image (Gimp     *gimp,
                                       _("Linked Channels"),
                                       info->linked_channels);
       gimp_image_store_item_set (image, set);
-      g_clear_pointer (&info->linked_layers, g_list_free);
+      g_clear_pointer (&info->linked_channels, g_list_free);
     }
   if (info->linked_paths)
     {
+      /* It is kind of ugly but vectors are really implemented as
+       * exception in our XCF spec and building over it seems like a
+       * mistake. Since I'm seriously not sure this would be much of an
+       * issue, I'll let it as it for now.
+       * Note that it's still possible to multi-select paths. It's only
+       * not possible to store these selections.
+       *
+       * Only warn for more than 1 linked path. Less is kind of
+       * pointless and doesn't deserve worrying people for no reason.
+       */
+      if (g_list_length (info->linked_paths) > 1)
+        g_printerr ("xcf: some paths were linked. "
+                    "GIMP does not support linked paths since version 3.0.\n");
+
+#if 0
       GimpItemList *set;
 
       set = gimp_item_list_named_new (image, GIMP_TYPE_VECTORS,
                                       _("Linked Paths"),
                                       info->linked_paths);
       gimp_image_store_item_set (image, set);
-      g_clear_pointer (&info->linked_layers, g_list_free);
+#endif
+      g_clear_pointer (&info->linked_paths, g_list_free);
+    }
+
+  for (iter = g_list_last (info->layer_sets); iter; iter = iter->prev)
+    {
+      if (iter->data)
+        gimp_image_store_item_set (image, iter->data);
+    }
+  g_list_free (info->layer_sets);
+
+  for (iter = g_list_last (info->channel_sets); iter; iter = iter->prev)
+    {
+      if (iter->data)
+        gimp_image_store_item_set (image, iter->data);
     }
+  g_list_free (info->channel_sets);
 
   if (info->file)
     gimp_image_set_file (image, info->file);
@@ -1203,6 +1237,74 @@ xcf_load_image_props (XcfInfo   *info,
           }
           break;
 
+        case PROP_ITEM_SET:
+          {
+            GimpItemList *set       = NULL;
+            gchar        *label;
+            GType         item_type = 0;
+            guint32       itype;
+            guint32       method;
+
+            xcf_read_int32  (info, &itype, 1);
+            xcf_read_int32  (info, &method, 1);
+            xcf_read_string (info, &label, 1);
+
+            if (itype == 0)
+              item_type = GIMP_TYPE_LAYER;
+            else
+              item_type = GIMP_TYPE_CHANNEL;
+
+            if (itype > 1)
+              {
+                g_printerr ("xcf: unsupported item set '%s' type: %d (skipping)\n",
+                            label ? label : "unnamed", itype);
+                /* Only case where we break because we wouldn't even
+                 * know where to categorize the item set anyway. */
+                break;
+              }
+            else if (label == NULL)
+              {
+                g_printerr ("xcf: item set without a name or pattern (skipping)\n");
+              }
+            else if (method != G_MAXUINT32 && method > GIMP_SELECT_GLOB_PATTERN)
+              {
+                g_printerr ("xcf: unsupported item set '%s' selection method attribute: 0x%x (skipping)\n",
+                            label, method);
+              }
+            else
+              {
+                if (method == G_MAXUINT32)
+                  {
+                    /* Don't use gimp_item_list_named_new() because it
+                     * doesn't allow NULL items (it would try to get the
+                     * selected items instead).
+                     */
+                    set = g_object_new (GIMP_TYPE_ITEM_LIST,
+                                        "image",      image,
+                                        "name",       label,
+                                        "is-pattern", FALSE,
+                                        "item-type",  item_type,
+                                        "items",      NULL,
+                                        NULL);
+                  }
+                else
+                  {
+                    set = gimp_item_list_pattern_new (image, item_type,
+                                                      method, label);
+                  }
+              }
+
+            /* Note: we are still adding invalid item sets as NULL on
+             * purpose, in order not to break order-base association
+             * between PROP_ITEM_SET and PROP_ITEM_SET_ITEM.
+             */
+            if (item_type == GIMP_TYPE_LAYER)
+              info->layer_sets = g_list_prepend (info->layer_sets, set);
+            else
+              info->channel_sets = g_list_prepend (info->channel_sets, set);
+          }
+          break;
+
         default:
 #ifdef GIMP_UNSTABLE
           g_printerr ("unexpected/unknown image property: %d (skipping)\n",
@@ -1579,6 +1681,29 @@ xcf_load_layer_props (XcfInfo    *info,
           xcf_read_int32 (info, group_layer_flags, 1);
           break;
 
+        case PROP_ITEM_SET_ITEM:
+            {
+              GimpItemList *set;
+              guint32       n;
+
+              xcf_read_int32 (info, &n, 1);
+              set = g_list_nth_data (info->layer_sets, n);
+              if (set == NULL)
+                g_printerr ("xcf: layer '%s' cannot be added to unknown layer set at index %d (skipping)\n",
+                            gimp_object_get_name (*layer), n);
+              else if (! g_type_is_a (G_TYPE_FROM_INSTANCE (*layer),
+                                      gimp_item_list_get_item_type (set)))
+                g_printerr ("xcf: layer '%s' cannot be added to item set '%s' with item type %s 
(skipping)\n",
+                            gimp_object_get_name (*layer), gimp_object_get_name (set),
+                            g_type_name (gimp_item_list_get_item_type (set)));
+              else if (gimp_item_list_is_pattern (set, NULL))
+                g_printerr ("xcf: layer '%s' cannot be added to pattern item set '%s' (skipping)\n",
+                            gimp_object_get_name (*layer), gimp_object_get_name (set));
+              else
+                gimp_item_list_add (set, GIMP_ITEM (*layer));
+            }
+          break;
+
         default:
 #ifdef GIMP_UNSTABLE
           g_printerr ("unexpected/unknown layer property: %d (skipping)\n",
@@ -1672,6 +1797,7 @@ xcf_check_layer_props (XcfInfo    *info,
         case PROP_COMPOSITE_MODE:
         case PROP_TATTOO:
         case PROP_PARASITES:
+        case PROP_ITEM_SET_ITEM:
           if (! xcf_skip_unknown_prop (info, prop_size))
             return FALSE;
           /* Just ignore for now. */
@@ -1896,6 +2022,25 @@ xcf_load_channel_props (XcfInfo      *info,
           }
           break;
 
+        case PROP_ITEM_SET_ITEM:
+            {
+              GimpItemList *set;
+              guint32       n;
+
+              xcf_read_int32 (info, &n, 1);
+              set = g_list_nth_data (info->channel_sets, n);
+              if (set == NULL)
+                g_printerr ("xcf: unknown channel set: %d (skipping)\n", n);
+              else if (! g_type_is_a (G_TYPE_FROM_INSTANCE (*channel),
+                                      gimp_item_list_get_item_type (set)))
+                g_printerr ("xcf: channel '%s' cannot be added to item set '%s' with item type %s 
(skipping)\n",
+                            gimp_object_get_name (*channel), gimp_object_get_name (set),
+                            g_type_name (gimp_item_list_get_item_type (set)));
+              else
+                gimp_item_list_add (set, GIMP_ITEM (*channel));
+            }
+          break;
+
         default:
 #ifdef GIMP_UNSTABLE
           g_printerr ("unexpected/unknown channel property: %d (skipping)\n",
diff --git a/app/xcf/xcf-private.h b/app/xcf/xcf-private.h
index b9fecf8f9a..f371dac15d 100644
--- a/app/xcf/xcf-private.h
+++ b/app/xcf/xcf-private.h
@@ -65,6 +65,8 @@ typedef enum
   PROP_BLEND_SPACE        = 37,
   PROP_FLOAT_COLOR        = 38,
   PROP_SAMPLE_POINTS      = 39,
+  PROP_ITEM_SET           = 40,
+  PROP_ITEM_SET_ITEM      = 41,
 } PropType;
 
 typedef enum
@@ -116,6 +118,9 @@ struct _XcfInfo
   GList              *linked_channels;
   GList              *linked_paths;
 
+  GList              *layer_sets;
+  GList              *channel_sets;
+
   GimpDrawable       *floating_sel_drawable;
   GimpLayer          *floating_sel;
   goffset             floating_sel_offset;
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
index b325243687..56b773788b 100644
--- a/app/xcf/xcf-save.c
+++ b/app/xcf/xcf-save.c
@@ -46,6 +46,7 @@
 #include "core/gimpimage-private.h"
 #include "core/gimpimage-sample-points.h"
 #include "core/gimpimage-symmetry.h"
+#include "core/gimpitemlist.h"
 #include "core/gimplayer.h"
 #include "core/gimplayermask.h"
 #include "core/gimpparasitelist.h"
@@ -487,6 +488,14 @@ xcf_save_image_props (XcfInfo    *info,
   g_list_free_full (symmetry_parasites,
                     (GDestroyNotify) gimp_parasite_free);
 
+  info->layer_sets = gimp_image_get_stored_item_sets (image, GIMP_TYPE_LAYER);
+  info->channel_sets = gimp_image_get_stored_item_sets (image, GIMP_TYPE_CHANNEL);
+
+  for (iter = info->layer_sets; iter; iter = iter->next)
+    xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET, error, iter->data));
+  for (iter = info->channel_sets; iter; iter = iter->next)
+    xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET, error, iter->data));
+
   xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
 
   return TRUE;
@@ -499,6 +508,7 @@ xcf_save_layer_props (XcfInfo    *info,
                       GError    **error)
 {
   GimpParasiteList *parasites;
+  GList            *iter;
   gint              offset_x;
   gint              offset_y;
 
@@ -531,8 +541,6 @@ xcf_save_layer_props (XcfInfo    *info,
                                   gimp_layer_get_opacity (layer)));
   xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
                                   gimp_item_get_visible (GIMP_ITEM (layer))));
-  xcf_check_error (xcf_save_prop (info, image, PROP_LINKED, error,
-                                  gimp_item_get_linked (GIMP_ITEM (layer))));
   xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
                                   gimp_item_get_color_tag (GIMP_ITEM (layer))));
   xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
@@ -612,6 +620,22 @@ xcf_save_layer_props (XcfInfo    *info,
                                       parasites));
     }
 
+  for (iter = info->layer_sets; iter; iter = iter->next)
+    {
+      GimpItemList *set = iter->data;
+
+      if (! gimp_item_list_is_pattern (set, NULL))
+        {
+          GList *items = gimp_item_list_get_items (set, NULL);
+
+          if (g_list_find (items, GIMP_ITEM (layer)))
+            xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET_ITEM, error,
+                                            g_list_position (info->layer_sets, iter)));
+
+          g_list_free (items);
+        }
+    }
+
   xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
 
   return TRUE;
@@ -624,6 +648,7 @@ xcf_save_channel_props (XcfInfo      *info,
                         GError      **error)
 {
   GimpParasiteList *parasites;
+  GList            *iter;
 
   if (g_list_find (gimp_image_get_selected_channels (image), channel))
     xcf_check_error (xcf_save_prop (info, image, PROP_ACTIVE_CHANNEL, error));
@@ -637,8 +662,6 @@ xcf_save_channel_props (XcfInfo      *info,
                                   gimp_channel_get_opacity (channel)));
   xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
                                   gimp_item_get_visible (GIMP_ITEM (channel))));
-  xcf_check_error (xcf_save_prop (info, image, PROP_LINKED, error,
-                                  gimp_item_get_linked (GIMP_ITEM (channel))));
   xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
                                   gimp_item_get_color_tag (GIMP_ITEM (channel))));
   xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
@@ -662,6 +685,22 @@ xcf_save_channel_props (XcfInfo      *info,
                                       parasites));
     }
 
+  for (iter = info->channel_sets; iter; iter = iter->next)
+    {
+      GimpItemList *set = iter->data;
+
+      if (! gimp_item_list_is_pattern (set, NULL))
+        {
+          GList *items = gimp_item_list_get_items (set, NULL);
+
+          if (g_list_find (items, GIMP_ITEM (channel)))
+            xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET_ITEM, error,
+                                            g_list_position (info->channel_sets, iter)));
+
+          g_list_free (items);
+        }
+    }
+
   xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
 
   return TRUE;
@@ -850,6 +889,9 @@ xcf_save_prop (XcfInfo    *info,
       break;
 
     case PROP_LINKED:
+      /* This code should not be called any longer. */
+      g_return_val_if_reached (FALSE);
+#if 0
       {
         guint32 linked = va_arg (args, guint32);
 
@@ -860,6 +902,7 @@ xcf_save_prop (XcfInfo    *info,
 
         xcf_write_int32_check_error (info, &linked, 1);
       }
+#endif
       break;
 
     case PROP_COLOR_TAG:
@@ -1344,6 +1387,60 @@ xcf_save_prop (XcfInfo    *info,
         xcf_write_int32_check_error (info, &flags, 1);
       }
       break;
+
+    case PROP_ITEM_SET:
+      {
+        GimpItemList *set = va_arg (args, GimpItemList *);
+        const gchar  *string;
+        guint32       method;
+        guint32       item_type;
+        goffset       base;
+        goffset       pos;
+
+        size = 0;
+
+        xcf_write_prop_type_check_error (info, prop_type);
+        pos = info->cp;
+        xcf_write_int32_check_error (info, &size, 1);
+        base = info->cp;
+
+        if (gimp_item_list_get_item_type (set) == GIMP_TYPE_LAYER)
+          item_type = 0;
+        else if (gimp_item_list_get_item_type (set) == GIMP_TYPE_CHANNEL)
+          item_type = 1;
+        else if (gimp_item_list_get_item_type (set) == GIMP_TYPE_VECTORS)
+          item_type = 2;
+        else
+          g_return_val_if_reached (FALSE);
+        xcf_write_int32_check_error (info, &item_type, 1);
+
+        if (! gimp_item_list_is_pattern (set, &method))
+          method = G_MAXUINT32;
+        xcf_write_int32_check_error (info, &method, 1);
+
+        string = gimp_object_get_name (set);
+        xcf_write_string_check_error (info, (gchar **) &string, 1);
+
+        /* go back to the saved position and write the length */
+        size = info->cp - base;
+        xcf_check_error (xcf_seek_pos (info, pos, error));
+        xcf_write_int32_check_error (info, &size, 1);
+
+        xcf_check_error (xcf_seek_pos (info, base + size, error));
+      }
+      break;
+
+    case PROP_ITEM_SET_ITEM:
+      {
+        guint32 set_n = va_arg (args, guint32);
+
+        size = 4;
+
+        xcf_write_prop_type_check_error (info, prop_type);
+        xcf_write_int32_check_error (info, &size, 1);
+        xcf_write_int32_check_error (info, &set_n, 1);
+      }
+      break;
     }
 
   va_end (args);
@@ -2074,7 +2171,8 @@ xcf_save_old_paths (XcfInfo    *info,
        * around to fix that cruft  */
 
       name     = (gchar *) gimp_object_get_name (vectors);
-      locked   = gimp_item_get_linked (GIMP_ITEM (vectors));
+      /* The 'linked' concept does not exist anymore in GIMP 3.0 and over. */
+      locked   = 0;
       state    = closed ? 4 : 2;  /* EDIT : ADD  (editing state, 1.2 compat) */
       version  = 3;
       pathtype = 1;  /* BEZIER  (1.2 compat) */
@@ -2175,7 +2273,8 @@ xcf_save_vectors (XcfInfo    *info,
 
       name          = gimp_object_get_name (vectors);
       visible       = gimp_item_get_visible (GIMP_ITEM (vectors));
-      linked        = gimp_item_get_linked (GIMP_ITEM (vectors));
+      /* The 'linked' concept does not exist anymore in GIMP 3.0 and over. */
+      linked        = 0;
       tattoo        = gimp_item_get_tattoo (GIMP_ITEM (vectors));
       parasites     = gimp_item_get_parasites (GIMP_ITEM (vectors));
       num_parasites = gimp_parasite_list_persistent_length (parasites);
diff --git a/devel-docs/app/app-sections.txt b/devel-docs/app/app-sections.txt
index 19b2ddf2b3..faab30eb7e 100644
--- a/devel-docs/app/app-sections.txt
+++ b/devel-docs/app/app-sections.txt
@@ -2513,7 +2513,6 @@ gimp_item_parasite_list
 gimp_item_get_visible
 gimp_item_set_visible
 gimp_item_is_visible
-gimp_item_get_linked
 gimp_item_get_lock_content
 gimp_item_set_lock_content
 gimp_item_can_lock_content
diff --git a/devel-docs/xcf.txt b/devel-docs/xcf.txt
index 0692b9c17e..faf5125a02 100644
--- a/devel-docs/xcf.txt
+++ b/devel-docs/xcf.txt
@@ -185,6 +185,15 @@ Since GIMP 3.0.0, released on TODO.
 PROP_GUIDES now allows off-canvas guide positions, i.e. negative
 positions and over canvas-dimensions positions.
 
+Version 16:
+Since GIMP 3.0.0, released on TODO.
+- Allows multiple channels to have the property PROP_ACTIVE_CHANNEL,
+  hence multiple channels selected at once.
+- PROP_LINKED is deprecated. Old XCF files loaded by newer GIMP will
+  transform linked items into stored item sets PROP_ITEM_SET.
+- New PROP_ITEM_SET and PROP_ITEM_SET_ITEM to store sets of layers,
+  channels or paths.
+
 1. BASIC CONCEPTS
 =================
 
@@ -641,6 +650,10 @@ PROP_LINKED (editing state)
   all other linked elements will be transformed the same way.
   It appears in the property list for layers, channels and paths.
 
+  PROP_LINKED property is deprecated and must not be used since XCF
+  version 16. XCF readers and writers are expected to convert linked
+  items into item sets instead (see PROP_ITEM_SET).
+
 PROP_LOCK_CONTENT (since version 3, editing state)
   uint32  28         Type identification
   uint32  4          Four bytes of payload
@@ -713,6 +726,19 @@ PROP_VISIBLE (essential)
   When reading old XCF files that lack this property, assume that
   layers are visible and channels are not.
 
+PROP_ITEM_SET_ITEM (since GIMP 3.0)
+  uint32  41         Type identification
+  uint32  4          Four bytes of payload
+  uint32  set        The PROP_ITEM_SET this item is listed in.
+
+  PROP_ITEM_SET_ITEM can be assigned to layers, channels and paths. They are
+  only organisational properties and have no consequence on render.
+
+  The 'set' attribute corresponds to the numbered PROP_ITEM_SET this
+  item belongs to, considering that the appearance order of
+  PROP_ITEM_SET properties matter. It can only belong to a named item
+  set and all items in a set must be of the proper type.
+
 
 3. THE IMAGE STRUCTURE
 ======================
@@ -921,7 +947,7 @@ PROP_PATHS
 
   Note: the attribute 'linked' was formerly erroneously called 'locked'
   (but meant 'linked' anyway).
-  
+
   A closed path is a path which has the last and the first point connected,
   for instance a triangle.
 
@@ -956,7 +982,7 @@ PROP_RESOLUTION (not editing state, but not _really_ essential either)
   resolution.
 
 PROP_SAMPLE_POINTS
-  uint32   17       Type identification
+  uint32   39       Type identification
   uint32   plength  Total length of the following payload in bytes
   ,---------------- Repeat for each sample point:
   | uint32 x        X coordinate
@@ -1040,6 +1066,24 @@ PROP_VECTORS
   without parsing the individual parasites. (Note that this is _not_
   the case for PROP_PATHS).
 
+PROP_ITEM_SET (since GIMP 3.0)
+  uint32  40         Type identification
+  uint32  plength    Total length of the following payload in bytes
+  uint32  item_type  The type of item in this set:
+                     0: layers
+                     1: channels
+                     2: paths
+  uint32  method     Selection method:
+                     0: basic text search
+                     1: regular expression search
+                     2: glob pattern search
+                     0xffffffff (max uint32): named item set
+  string  label      Pattern to use for selection or name of the item
+                     set if method is 0xffffffff.
+
+  They are only organisational properties and have no consequence on
+  render. The order matters for display and also for PROP_ITEM_SET_ITEM.
+
 
 4. THE CHANNEL STRUCTURE
 ========================


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