[gimp] app: allow storing regular expression-based item sets.



commit a0fc5a025ae3579609730ebabc3c84146385da76
Author: Jehan <jehan girinstud io>
Date:   Sun Feb 7 00:21:27 2021 +0100

    app: allow storing regular expression-based item sets.
    
    Instead of just storing list of layers, I created a new simple type
    GimpItemList (actually GimpItemSet would be better named, but
    unfortunately we use this name for an enum type). So far, this new class
    can handle 2 types of item sets: named fixed sets and pattern-generated
    sets.

 app/core/Makefile.am            |   2 +
 app/core/core-types.h           |   1 +
 app/core/gimpimage-private.h    |   2 +-
 app/core/gimpimage.c            | 328 ++++++++++------------
 app/core/gimpimage.h            |  30 +-
 app/core/gimpitemlist.c         | 587 ++++++++++++++++++++++++++++++++++++++++
 app/core/gimpitemlist.h         |  66 +++++
 app/core/meson.build            |   1 +
 app/widgets/gimplayertreeview.c | 187 ++++++++-----
 9 files changed, 924 insertions(+), 280 deletions(-)
---
diff --git a/app/core/Makefile.am b/app/core/Makefile.am
index 3d2e9ed8b5..f20a68fbed 100644
--- a/app/core/Makefile.am
+++ b/app/core/Makefile.am
@@ -351,6 +351,8 @@ libappcore_a_sources = \
        gimpitem-linked.h                       \
        gimpitem-preview.c                      \
        gimpitem-preview.h                      \
+       gimpitemlist.c                          \
+       gimpitemlist.h                          \
        gimpitempropundo.c                      \
        gimpitempropundo.h                      \
        gimpitemstack.c                         \
diff --git a/app/core/core-types.h b/app/core/core-types.h
index 1efeebf220..43b1a2f687 100644
--- a/app/core/core-types.h
+++ b/app/core/core-types.h
@@ -107,6 +107,7 @@ typedef struct _GimpDocumentList                GimpDocumentList;
 typedef struct _GimpDrawableStack               GimpDrawableStack;
 typedef struct _GimpFilteredContainer           GimpFilteredContainer;
 typedef struct _GimpFilterStack                 GimpFilterStack;
+typedef struct _GimpItemList                    GimpItemList;
 typedef struct _GimpItemStack                   GimpItemStack;
 typedef struct _GimpLayerStack                  GimpLayerStack;
 typedef struct _GimpTaggedContainer             GimpTaggedContainer;
diff --git a/app/core/gimpimage-private.h b/app/core/gimpimage-private.h
index 4a1e78f103..c965d06083 100644
--- a/app/core/gimpimage-private.h
+++ b/app/core/gimpimage-private.h
@@ -110,7 +110,7 @@ struct _GimpImagePrivate
   GimpItemTree      *channels;              /*  the tree of masks            */
   GimpItemTree      *vectors;               /*  the tree of vectors          */
   GSList            *layer_stack;           /*  the layers in MRU order      */
-  GHashTable        *linked_layers;
+  GList             *linked_layers;
 
   GQuark             layer_offset_x_handler;
   GQuark             layer_offset_y_handler;
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index d2795c21f6..4d37139988 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -63,6 +63,7 @@
 #include "gimpimage-symmetry.h"
 #include "gimpimage-undo.h"
 #include "gimpimage-undo-push.h"
+#include "gimpitemlist.h"
 #include "gimpitemtree.h"
 #include "gimplayer.h"
 #include "gimplayer-floating-selection.h"
@@ -785,9 +786,7 @@ gimp_image_init (GimpImage *image)
                                                      GIMP_TYPE_VECTORS);
   private->layer_stack         = NULL;
 
-  private->linked_layers       = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                        (GDestroyNotify) g_free,
-                                                        (GDestroyNotify) g_list_free);
+  private->linked_layers       = NULL;
 
   g_signal_connect (private->projection, "notify::buffer",
                     G_CALLBACK (gimp_image_projection_buffer_notify),
@@ -1066,6 +1065,8 @@ gimp_image_dispose (GObject *object)
 
   gimp_image_undo_free (image);
 
+  g_list_free_full (private->linked_layers, g_object_unref);
+
   g_signal_handlers_disconnect_by_func (private->layers->container,
                                         gimp_image_invalidate,
                                         image);
@@ -1132,8 +1133,6 @@ gimp_image_finalize (GObject *object)
   g_clear_object (&private->channels);
   g_clear_object (&private->vectors);
 
-  g_hash_table_destroy (private->linked_layers);
-
   if (private->layer_stack)
     {
       g_slist_free_full (private->layer_stack,
@@ -5363,45 +5362,46 @@ gimp_image_add_layers (GimpImage   *image,
 }
 
 /*
- * gimp_image_link_layers:
+ * gimp_image_store_item_set:
  * @image:
- * @layers:
- * @link_name:
- *
- * Create a new set of @layers under the name @link_name.
- * If @layers is empty, the currently selected layers are linked
- * instead.
- * If a set with the same name existed, this call will silently replace
- * it with the new set of layers.
+ * @set: (transfer full): a set of linked items which @images takes
+ *                        ownership of.
  *
- * Returns: %TRUE if a new set was created, %FALSE otherwise (e.g. no
- *          list provided and no layers currently selected).
+ * Store a new set of @layers.
+ * If a set with the same name and type existed, this call will silently
+ * replace it with the new set of layers.
  */
-gboolean
-gimp_image_link_layers (GimpImage   *image,
-                        const GList *layers,
-                        const gchar *link_name)
+void
+gimp_image_store_item_set (GimpImage    *image,
+                           GimpItemList *set)
 {
   GimpImagePrivate *private;
+  GList            *iter;
 
-  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+  g_return_if_fail (GIMP_IS_IMAGE (image));
+  g_return_if_fail (GIMP_IS_ITEM_LIST (set));
+  /* XXX Only layer sets supported so far as we haven't enabled
+   * multi-selection of channels and vectors yet.
+   */
+  g_return_if_fail (gimp_item_list_get_item_type (set) == GIMP_TYPE_LAYER);
 
   private = GIMP_IMAGE_GET_PRIVATE (image);
 
-  if (! layers)
+  for (iter = private->linked_layers; iter; iter = iter->next)
     {
-      layers = gimp_image_get_selected_layers (image);
-
-      if (! layers)
-        return FALSE;
+      /* Remove a previous item set of same type and name. */
+      if (gimp_item_list_is_pattern (iter->data) == gimp_item_list_is_pattern (set) &&
+          g_strcmp0 (gimp_object_get_name (iter->data), gimp_object_get_name (set)) == 0)
+        break;
+    }
+  if (iter)
+    {
+      g_object_unref (iter->data);
+      private->linked_layers = g_list_delete_link (private->linked_layers, iter);
     }
 
-  g_hash_table_insert (private->linked_layers,
-                       g_strdup (link_name),
-                       g_list_copy ((GList *) layers));
+  private->linked_layers = g_list_prepend (private->linked_layers, set);
   g_signal_emit (image, gimp_image_signals[LAYER_LINKS_CHANGED], 0);
-
-  return TRUE;
 }
 
 /*
@@ -5415,113 +5415,79 @@ gimp_image_link_layers (GimpImage   *image,
  *          name existed.
  */
 gboolean
-gimp_image_unlink_layers (GimpImage   *image,
-                          const gchar *link_name)
+gimp_image_unlink_item_set (GimpImage    *image,
+                            GimpItemList *set)
 {
   GimpImagePrivate *private;
+  GList            *found;
   gboolean          success;
 
   g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
 
   private = GIMP_IMAGE_GET_PRIVATE (image);
 
-  success = g_hash_table_remove (private->linked_layers, link_name);
+  found = g_list_find (private->linked_layers, set);
+  success = (found != NULL);
   if (success)
-    g_signal_emit (image, gimp_image_signals[LAYER_LINKS_CHANGED], 0);
+    {
+      private->linked_layers = g_list_delete_link (private->linked_layers, found);
+      g_object_unref (set);
+      g_signal_emit (image, gimp_image_signals[LAYER_LINKS_CHANGED], 0);
+    }
 
   return success;
 }
 
 /*
- * @gimp_image_select_linked_layers:
+ * @gimp_image_get_stored_item_sets:
  * @image:
- * @link_name:
- *
- * Replace currently selected layers in @image with the layers belonging
- * to the set named @link_name (which must exist).
  *
- * Returns: %TRUE if the selection change is done (even if it turned out
- *          selected layers stay the same), %FALSE if no sets with this
- *          name existed.
+ * Returns: (transfer none): the list of all the layer sets (which you
+ *          should not modify). Order of items is not relevant.
  */
-void
-gimp_image_select_linked_layers (GimpImage   *image,
-                                 const gchar *link_name)
+GList *
+gimp_image_get_stored_item_sets (GimpImage *image)
 {
   GimpImagePrivate *private;
-  GList            *linked;
 
-  g_return_if_fail (GIMP_IS_IMAGE (image));
-  g_return_if_fail (link_name != NULL);
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
 
   private = GIMP_IMAGE_GET_PRIVATE (image);
 
-  linked = g_hash_table_lookup (private->linked_layers,
-                                link_name);
-
-  g_return_if_fail (linked);
-
-  gimp_image_set_selected_layers (image, linked);
+  return private->linked_layers;
 }
 
 /*
- * @gimp_image_select_layers_by_regexp:
+ * @gimp_image_select_item_set:
  * @image:
- * @pattern:
- * @error:
- *
- * Replace currently selected layers in @image with the layers whose
- * names match with the @pattern regular expression.
+ * @set:
  *
- * Returns: %TRUE if some layers matched @pattern (even if it turned out
- *          selected layers stay the same), %FALSE otherwise or if
- *          @pattern is an invalid regular expression (in which case,
- *          @error will be filled with the appropriate error).
+ * Replace currently selected layers in @image with the layers belonging
+ * to @set.
  */
-gboolean
-gimp_image_select_layers_by_regexp (GimpImage    *image,
-                                    const gchar  *pattern,
-                                    GError      **error)
+void
+gimp_image_select_item_set (GimpImage    *image,
+                            GimpItemList *set)
 {
-  GList    *layers;
-  GList    *match = NULL;
-  GList    *iter;
-  GRegex   *regex;
-  gboolean  matched;
-
-  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
-  g_return_val_if_fail (pattern != NULL, FALSE);
-
-  regex = g_regex_new (pattern, 0, 0, error);
-
-  if (regex == NULL)
-    {
-      return FALSE;
-    }
-
-  layers = gimp_image_get_layer_list (image);
+  GList  *linked;
+  GError *error = NULL;
 
-  for (iter = layers; iter; iter = iter->next)
-    {
-      if (g_regex_match (regex,
-                         gimp_object_get_name (iter->data),
-                         0, NULL))
-        match = g_list_prepend (match, iter->data);
-    }
+  g_return_if_fail (GIMP_IS_IMAGE (image));
+  g_return_if_fail (GIMP_IS_ITEM_LIST (set));
 
-  gimp_image_set_selected_layers (image, match);
-  matched = (match != NULL);
+  linked = gimp_item_list_get_items (set, &error);
 
-  g_list_free (match);
-  g_regex_unref (regex);
+  if (! error)
+    gimp_image_set_selected_layers (image, linked);
 
-  return matched;
+  g_list_free (linked);
+  g_clear_error (&error);
 }
 
 /*
- * @gimp_image_add_linked_layers:
+ * @gimp_image_add_item_set:
  * @image:
- * @link_name:
+ * @set:
  *
  * Add the layers belonging to the set named @link_name (which must
  * exist) to the layers currently selected in @image.
@@ -5531,40 +5497,40 @@ gimp_image_select_layers_by_regexp (GimpImage    *image,
  *          name existed.
  */
 void
-gimp_image_add_linked_layers (GimpImage   *image,
-                              const gchar *link_name)
+gimp_image_add_item_set (GimpImage    *image,
+                         GimpItemList *set)
 {
-  GimpImagePrivate *private;
-  GList            *linked;
-  GList            *layers;
-  GList            *iter;
+  GList  *linked;
+  GError *error = NULL;
 
   g_return_if_fail (GIMP_IS_IMAGE (image));
-  g_return_if_fail (link_name != NULL);
+  g_return_if_fail (GIMP_IS_ITEM_LIST (set));
 
-  private = GIMP_IMAGE_GET_PRIVATE (image);
+  linked = gimp_item_list_get_items (set, &error);
 
-  linked = g_hash_table_lookup (private->linked_layers,
-                                link_name);
+  if (! error)
+    {
+      GList *layers;
+      GList *iter;
 
-  g_return_if_fail (linked);
+      layers = gimp_image_get_selected_layers (image);
+      layers = g_list_copy (layers);
+      for (iter = linked; iter; iter = iter->next)
+        {
+          if (! g_list_find (layers, iter->data))
+            layers = g_list_prepend (layers, iter->data);
+        }
 
-  layers = gimp_image_get_selected_layers (image);
-  layers = g_list_copy (layers);
-  for (iter = linked; iter; iter = iter->next)
-    {
-      if (! g_list_find (layers, iter->data))
-        layers = g_list_prepend (layers, iter->data);
+      gimp_image_set_selected_layers (image, layers);
+      g_list_free (layers);
     }
-
-  gimp_image_set_selected_layers (image, layers);
-  g_list_free (layers);
+  g_clear_error (&error);
 }
 
 /*
- * @gimp_image_remove_linked_layers:
+ * @gimp_image_remove_item_set:
  * @image:
- * @link_name:
+ * @set:
  *
  * Remove the layers belonging to the set named @link_name (which must
  * exist) from the layers currently selected in @image.
@@ -5574,42 +5540,42 @@ gimp_image_add_linked_layers (GimpImage   *image,
  *          name existed.
  */
 void
-gimp_image_remove_linked_layers (GimpImage   *image,
-                                 const gchar *link_name)
+gimp_image_remove_item_set (GimpImage    *image,
+                            GimpItemList *set)
 {
-  GimpImagePrivate *private;
-  GList            *linked;
-  GList            *layers;
-  GList            *iter;
+  GList  *linked;
+  GError *error = NULL;
 
   g_return_if_fail (GIMP_IS_IMAGE (image));
-  g_return_if_fail (link_name != NULL);
+  g_return_if_fail (GIMP_IS_ITEM_LIST (set));
 
-  private = GIMP_IMAGE_GET_PRIVATE (image);
+  linked = gimp_item_list_get_items (set, &error);
 
-  linked = g_hash_table_lookup (private->linked_layers,
-                                link_name);
+  if (! error)
+    {
+      GList *layers;
+      GList *iter;
 
-  g_return_if_fail (linked);
+      layers = gimp_image_get_selected_layers (image);
+      layers = g_list_copy (layers);
+      for (iter = linked; iter; iter = iter->next)
+        {
+          GList *remove;
 
-  layers = gimp_image_get_selected_layers (image);
-  layers = g_list_copy (layers);
-  for (iter = linked; iter; iter = iter->next)
-    {
-      GList *remove;
+          if ((remove = g_list_find (layers, iter->data)))
+            layers = g_list_delete_link (layers, remove);
+        }
 
-      if ((remove = g_list_find (layers, iter->data)))
-        layers = g_list_delete_link (layers, remove);
+      gimp_image_set_selected_layers (image, layers);
+      g_list_free (layers);
     }
-
-  gimp_image_set_selected_layers (image, layers);
-  g_list_free (layers);
+  g_clear_error (&error);
 }
 
 /*
- * @gimp_image_intersect_linked_layers:
+ * @gimp_image_intersect_item_set:
  * @image:
- * @link_name:
+ * @set:
  *
  * Remove any layers from the layers currently selected in @image if
  * they don't also belong to the set named @link_name (which must
@@ -5620,61 +5586,41 @@ gimp_image_remove_linked_layers (GimpImage   *image,
  *          name existed.
  */
 void
-gimp_image_intersect_linked_layers (GimpImage   *image,
-                                    const gchar *link_name)
+gimp_image_intersect_item_set (GimpImage    *image,
+                               GimpItemList *set)
 {
-  GimpImagePrivate *private;
-  GList            *linked;
-  GList            *layers;
-  GList            *remove = NULL;
-  GList            *iter;
+  GList *linked;
+  GError *error = NULL;
 
   g_return_if_fail (GIMP_IS_IMAGE (image));
-  g_return_if_fail (link_name != NULL);
+  g_return_if_fail (GIMP_IS_ITEM_LIST (set));
 
-  private = GIMP_IMAGE_GET_PRIVATE (image);
-
-  linked = g_hash_table_lookup (private->linked_layers,
-                                link_name);
+  linked = gimp_item_list_get_items (set, &error);
 
-  g_return_if_fail (linked);
-
-  layers = gimp_image_get_selected_layers (image);
-  layers = g_list_copy (layers);
-
-  /* Remove items in layers but not in linked. */
-  for (iter = layers; iter; iter = iter->next)
+  if (! error)
     {
-      if (! g_list_find (linked, iter->data))
-        remove = g_list_prepend (remove, iter);
-    }
-  for (iter = remove; iter; iter = iter->next)
-    layers = g_list_delete_link (layers, iter->data);
-  g_list_free (remove);
-
-  /* Finally select the intersection. */
-  gimp_image_set_selected_layers (image, layers);
-  g_list_free (layers);
-}
-
-/*
- * @gimp_image_get_linked_layer_names:
- * @image:
- *
- * Returns: the newly allocated list of all the link names (which you
- *          should not modify directly). Free the list with
- *          g_list_free().
- */
-GList *
-gimp_image_get_linked_layer_names (GimpImage *image)
-{
-  GimpImagePrivate *private;
+      GList *layers;
+      GList *remove = NULL;
+      GList *iter;
 
-  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+      layers = gimp_image_get_selected_layers (image);
+      layers = g_list_copy (layers);
 
-  private = GIMP_IMAGE_GET_PRIVATE (image);
+      /* Remove items in layers but not in linked. */
+      for (iter = layers; iter; iter = iter->next)
+        {
+          if (! g_list_find (linked, iter->data))
+            remove = g_list_prepend (remove, iter);
+        }
+      for (iter = remove; iter; iter = iter->next)
+        layers = g_list_delete_link (layers, iter->data);
+      g_list_free (remove);
 
-  return g_hash_table_get_keys (private->linked_layers);
+      /* Finally select the intersection. */
+      gimp_image_set_selected_layers (image, layers);
+      g_list_free (layers);
+    }
+  g_clear_error (&error);
 }
 
 
diff --git a/app/core/gimpimage.h b/app/core/gimpimage.h
index d45e9bfeb5..4e45e798b6 100644
--- a/app/core/gimpimage.h
+++ b/app/core/gimpimage.h
@@ -452,23 +452,19 @@ void            gimp_image_add_layers            (GimpImage          *image,
                                                   gint                height,
                                                   const gchar        *undo_desc);
 
-gboolean        gimp_image_link_layers           (GimpImage          *image,
-                                                  const GList        *layers,
-                                                  const gchar        *link_name);
-gboolean        gimp_image_unlink_layers         (GimpImage          *image,
-                                                  const gchar        *link_name);
-void            gimp_image_select_linked_layers  (GimpImage          *image,
-                                                  const gchar        *link_name);
-gboolean      gimp_image_select_layers_by_regexp (GimpImage          *image,
-                                                  const gchar        *pattern,
-                                                  GError            **error);
-void            gimp_image_add_linked_layers     (GimpImage          *image,
-                                                  const gchar        *link_name);
-void            gimp_image_remove_linked_layers  (GimpImage          *image,
-                                                  const gchar        *link_name);
-void            gimp_image_intersect_linked_layers (GimpImage        *image,
-                                                    const gchar      *link_name);
-GList         * gimp_image_get_linked_layer_names (GimpImage         *image);
+void            gimp_image_store_item_set        (GimpImage          *image,
+                                                  GimpItemList       *set);
+gboolean        gimp_image_unlink_item_set       (GimpImage          *image,
+                                                  GimpItemList       *set);
+GList         * gimp_image_get_stored_item_sets   (GimpImage         *image);
+void            gimp_image_select_item_set       (GimpImage          *image,
+                                                  GimpItemList       *set);
+void            gimp_image_add_item_set          (GimpImage          *image,
+                                                  GimpItemList       *set);
+void            gimp_image_remove_item_set       (GimpImage          *image,
+                                                  GimpItemList       *set);
+void            gimp_image_intersect_item_set    (GimpImage        *image,
+                                                  GimpItemList       *set);
 
 gboolean        gimp_image_add_channel           (GimpImage          *image,
                                                   GimpChannel        *channel,
diff --git a/app/core/gimpitemlist.c b/app/core/gimpitemlist.c
new file mode 100644
index 0000000000..4b7b494611
--- /dev/null
+++ b/app/core/gimpitemlist.c
@@ -0,0 +1,587 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpchannel.h"
+#include "gimpimage.h"
+#include "gimpitem.h"
+#include "gimpitemlist.h"
+#include "gimplayer.h"
+#include "gimpmarshal.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+  EMPTY,
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+  PROP_IMAGE,
+  PROP_IS_PATTERN,
+  PROP_ITEMS,
+  PROP_ITEM_TYPE,
+  N_PROPS
+};
+
+
+typedef struct _GimpItemListPrivate GimpItemListPrivate;
+
+struct _GimpItemListPrivate
+{
+  GimpImage *image;
+
+  gchar     *label;         /* Item set name or pattern.                      */
+  gboolean   is_pattern;    /* Whether a named fixed set or a pattern-search. */
+
+  GList     *items;         /* The fixed item list if is_pattern is FALSE.    */
+  GList     *deleted_items; /* Removed item list kept for undoes.             */
+  GType      item_type;
+};
+
+
+/*  local function prototypes  */
+
+static void       gimp_item_list_constructed         (GObject        *object);
+static void       gimp_item_list_dispose             (GObject        *object);
+static void       gimp_item_list_finalize            (GObject        *object);
+static void       gimp_item_list_set_property        (GObject        *object,
+                                                      guint           property_id,
+                                                      const GValue   *value,
+                                                      GParamSpec     *pspec);
+static void       gimp_item_list_get_property        (GObject        *object,
+                                                      guint           property_id,
+                                                      GValue         *value,
+                                                      GParamSpec     *pspec);
+
+static void       gimp_item_list_item_add            (GimpContainer  *container,
+                                                      GimpObject     *object,
+                                                      GimpItemList   *set);
+static void       gimp_item_list_item_remove         (GimpContainer  *container,
+                                                      GimpObject     *object,
+                                                      GimpItemList   *set);
+
+static GList *    gimp_item_list_get_items_by_regexp (GimpItemList   *set,
+                                                      const gchar    *pattern,
+                                                      GError        **error);
+static void       gimp_item_list_clean_deleted_items (GimpItemList   *set,
+                                                      GimpItem       *searched,
+                                                      gboolean       *found);
+static void       gimp_item_list_free_deleted_item   (GWeakRef       *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpItemList, gimp_item_list, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_item_list_parent_class
+
+static guint       gimp_item_list_signals[LAST_SIGNAL] = { 0 };
+static GParamSpec *gimp_item_list_props[N_PROPS]       = { NULL, };
+
+static void
+gimp_item_list_class_init (GimpItemListClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  /**
+   * GimpItemList::empty:
+   *
+   * Sent when the item set changed and would return an empty set of
+   * items.
+   */
+  gimp_item_list_signals[EMPTY] =
+    g_signal_new ("empty",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  G_STRUCT_OFFSET (GimpItemListClass, empty),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
+  object_class->constructed       = gimp_item_list_constructed;
+  object_class->dispose           = gimp_item_list_dispose;
+  object_class->finalize          = gimp_item_list_finalize;
+  object_class->set_property      = gimp_item_list_set_property;
+  object_class->get_property      = gimp_item_list_get_property;
+
+  gimp_item_list_props[PROP_IMAGE]      = g_param_spec_object ("image", NULL, NULL,
+                                                              GIMP_TYPE_IMAGE,
+                                                              GIMP_PARAM_READWRITE |
+                                                              G_PARAM_CONSTRUCT_ONLY);
+  gimp_item_list_props[PROP_IS_PATTERN] = g_param_spec_boolean ("is-pattern", NULL, NULL,
+                                                               FALSE,
+                                                               GIMP_PARAM_READWRITE |
+                                                               G_PARAM_CONSTRUCT_ONLY);
+  gimp_item_list_props[PROP_ITEMS]      = g_param_spec_pointer ("items",
+                                                                NULL, NULL,
+                                                                GIMP_PARAM_READWRITE |
+                                                                G_PARAM_CONSTRUCT_ONLY);
+  gimp_item_list_props[PROP_ITEM_TYPE]  = g_param_spec_gtype ("item-type",
+                                                              NULL, NULL,
+                                                              G_TYPE_NONE,
+                                                              GIMP_PARAM_READWRITE |
+                                                              G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, gimp_item_list_props);
+}
+
+static void
+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->is_pattern = FALSE;
+}
+
+static void
+gimp_item_list_constructed (GObject *object)
+{
+  GimpItemList *set = GIMP_ITEM_LIST (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);
+
+  if (! set->p->is_pattern)
+    {
+      GimpContainer *container;
+
+      if (set->p->item_type == GIMP_TYPE_LAYER)
+        container = gimp_image_get_layers (set->p->image);
+      else if (set->p->item_type == GIMP_TYPE_VECTORS)
+        container = gimp_image_get_vectors (set->p->image);
+      else
+        container = gimp_image_get_channels (set->p->image);
+      g_signal_connect (container, "remove",
+                        G_CALLBACK (gimp_item_list_item_remove),
+                        set);
+      g_signal_connect (container, "add",
+                        G_CALLBACK (gimp_item_list_item_add),
+                        set);
+    }
+}
+
+static void
+gimp_item_list_dispose (GObject *object)
+{
+  GimpItemList *set = GIMP_ITEM_LIST (object);
+
+  if (! set->p->is_pattern)
+    {
+      GimpContainer *container;
+
+      if (set->p->item_type == GIMP_TYPE_LAYER)
+        container = gimp_image_get_layers (set->p->image);
+      else if (set->p->item_type == GIMP_TYPE_VECTORS)
+        container = gimp_image_get_vectors (set->p->image);
+      else
+        container = gimp_image_get_channels (set->p->image);
+      g_signal_handlers_disconnect_by_func (container,
+                                            G_CALLBACK (gimp_item_list_item_remove),
+                                            set);
+      g_signal_handlers_disconnect_by_func (container,
+                                            G_CALLBACK (gimp_item_list_item_add),
+                                            set);
+    }
+}
+
+static void
+gimp_item_list_finalize (GObject *object)
+{
+  GimpItemList *set = GIMP_ITEM_LIST (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);
+}
+
+static void
+gimp_item_list_set_property (GObject      *object,
+                             guint         property_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GimpItemList *set = GIMP_ITEM_LIST (object);
+
+  switch (property_id)
+    {
+    case PROP_IMAGE:
+      set->p->image = g_value_get_object (value);
+      break;
+    case PROP_IS_PATTERN:
+      set->p->is_pattern = g_value_get_boolean (value);
+      break;
+    case PROP_ITEMS:
+      set->p->items = g_list_copy (g_value_get_pointer (value));
+      break;
+    case PROP_ITEM_TYPE:
+      set->p->item_type = g_value_get_gtype (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_item_list_get_property (GObject    *object,
+                            guint       property_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GimpItemList *set = GIMP_ITEM_LIST (object);
+
+  switch (property_id)
+    {
+    case PROP_IMAGE:
+      g_value_set_object (value, set->p->image);
+      break;
+    case PROP_IS_PATTERN:
+      g_value_set_boolean (value, set->p->is_pattern);
+      break;
+    case PROP_ITEMS:
+      g_value_set_pointer (value, set->p->items);
+      break;
+    case PROP_ITEM_TYPE:
+      g_value_set_gtype (value, set->p->item_type);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+
+/*  Public functions  */
+
+
+/**
+ * gimp_item_list_named_new:
+ * @image:     The new item_list's #GimpImage.
+ * @item_type: The type of #GimpItem in the list.
+ * @name:      The name to assign the item list.
+ * @items:     The items in the list.
+ *
+ * Create a fixed list of items made of items. It cannot be edited and
+ * will only auto-update when items get deleted from @image, until the
+ * list reaches 0 (in which case, the list will self-destroy).
+ *
+ * If @items is %NULL, the current item selection of type @item_type in
+ * @image is used. If this selection is empty, then %NULL is returned.
+ *
+ * Returns: The newly created #GimpItemList of %NULL if it corresponds
+ *          to no items.
+ */
+GimpItemList *
+gimp_item_list_named_new (GimpImage   *image,
+                          GType        item_type,
+                          const gchar *name,
+                          GList       *items)
+{
+  GimpItemList *set;
+  GList        *iter;
+
+  g_return_val_if_fail (g_type_is_a (item_type, GIMP_TYPE_ITEM), NULL);
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+  for (iter = items; iter; iter = iter->next)
+    g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (iter->data), item_type), NULL);
+
+  if (! items)
+    {
+      if (item_type == GIMP_TYPE_LAYER)
+        items = gimp_image_get_selected_layers (image);
+      else if (item_type == GIMP_TYPE_VECTORS)
+        items = gimp_image_get_selected_vectors (image);
+      else if (item_type == GIMP_TYPE_CHANNEL)
+        items = gimp_image_get_selected_channels (image);
+
+      if (! items)
+        return NULL;
+    }
+
+  set = g_object_new (GIMP_TYPE_ITEM_LIST,
+                      "image",      image,
+                      "name",       name,
+                      "is-pattern", FALSE,
+                      "item-type",  item_type,
+                      "items",      items,
+                      NULL);
+
+  return set;
+}
+
+/**
+ * gimp_item_list_pattern_new:
+ * @image:     The new item_list's #GimpImage.
+ * @item_type: The type of #GimpItem in the list.
+ * @pattern:   The pattern generating the contents of the list.
+ *
+ * Create a list of items generated from a pattern. It cannot be edited.
+ *
+ * Returns: The newly created #GimpItemList.
+ */
+GimpItemList *
+gimp_item_list_pattern_new (GimpImage   *image,
+                            GType        item_type,
+                            const gchar *pattern)
+{
+  GimpItemList *set;
+
+  g_return_val_if_fail (g_type_is_a (item_type, GIMP_TYPE_ITEM), NULL);
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+  /* TODO: check pattern first and fail if invalid. */
+  set = g_object_new (GIMP_TYPE_ITEM_LIST,
+                      "image",      image,
+                      "name",       pattern,
+                      "is-pattern", TRUE,
+                      "item-type",  item_type,
+                      NULL);
+
+  return set;
+}
+
+GType
+gimp_item_list_get_item_type (GimpItemList *set)
+{
+  g_return_val_if_fail (GIMP_IS_ITEM_LIST (set), FALSE);
+
+  return set->p->item_type;
+}
+
+/**
+ * gimp_item_list_get_items:
+ * @set:
+ *
+ * Returns: (transfer container): The unordered list of items
+ *          represented by @set to be freed with g_list_free().
+ */
+GList *
+gimp_item_list_get_items (GimpItemList  *set,
+                          GError       **error)
+{
+  GList *items;
+
+  g_return_val_if_fail (GIMP_IS_ITEM_LIST (set), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (set->p->is_pattern)
+    {
+      GError *reg_error = NULL;
+
+      items = gimp_item_list_get_items_by_regexp (set,
+                                                  gimp_object_get_name (set),
+                                                  &reg_error);
+      if (reg_error)
+        g_propagate_error (error, reg_error);
+    }
+  else
+    {
+      items = g_list_copy (set->p->items);
+    }
+
+  return items;
+}
+
+gboolean
+gimp_item_list_is_pattern (GimpItemList *set)
+{
+  g_return_val_if_fail (GIMP_IS_ITEM_LIST (set), FALSE);
+
+  return set->p->is_pattern;
+}
+
+
+/*  Private functions  */
+
+
+static void
+gimp_item_list_item_add (GimpContainer  *container,
+                         GimpObject     *object,
+                         GimpItemList   *set)
+{
+  gboolean found = FALSE;
+
+  gimp_item_list_clean_deleted_items (set, GIMP_ITEM (object), &found);
+
+  if (found)
+    {
+      /* Such an item can only have been added back as part of an redo
+       * step.
+       */
+      set->p->items = g_list_prepend (set->p->items, object);
+    }
+}
+
+static void
+gimp_item_list_item_remove (GimpContainer  *container,
+                            GimpObject     *object,
+                            GimpItemList   *set)
+{
+  GWeakRef *deleted_item = g_slice_new (GWeakRef);
+
+  /* Keep a weak link on object so that it disappears by itself when no
+   * other piece of code has a reference to it. In particular, we expect
+   * undo to keep references to deleted items. So if a redo happens we
+   * will get a "add" signal with the same object.
+   */
+  set->p->items = g_list_remove (set->p->items, object);
+
+  g_weak_ref_init (deleted_item, object);
+  set->p->deleted_items = g_list_prepend (set->p->deleted_items, deleted_item);
+}
+
+/*
+ * @gimp_item_list_get_items_by_regexp:
+ * @image:
+ * @pattern:
+ * @error:
+ *
+ * Replace currently selected items in @image with the items whose
+ * names match with the @pattern regular expression.
+ *
+ * Returns: %TRUE if some items matched @pattern (even if it turned out
+ *          selected items stay the same), %FALSE otherwise or if
+ *          @pattern is an invalid regular expression (in which case,
+ *          @error will be filled with the appropriate error).
+ */
+static GList *
+gimp_item_list_get_items_by_regexp (GimpItemList  *set,
+                                    const gchar   *pattern,
+                                    GError       **error)
+{
+  GList  *items;
+  GList  *match = NULL;
+  GList  *iter;
+  GRegex *regex;
+
+  g_return_val_if_fail (GIMP_IS_ITEM_LIST (set), FALSE);
+  g_return_val_if_fail (pattern != NULL, FALSE);
+  g_return_val_if_fail (error && *error == NULL, FALSE);
+
+  regex = g_regex_new (pattern, 0, 0, error);
+
+  if (regex == NULL)
+    return NULL;
+
+  if (set->p->item_type == GIMP_TYPE_LAYER)
+    {
+      items = gimp_image_get_layer_list (set->p->image);
+    }
+  else
+    {
+      g_critical ("%s: only list of GimpLayer supported for now.",
+                  G_STRFUNC);
+      return NULL;
+    }
+
+  for (iter = items; iter; iter = iter->next)
+    {
+      if (g_regex_match (regex,
+                         gimp_object_get_name (iter->data),
+                         0, NULL))
+        match = g_list_prepend (match, iter->data);
+    }
+  g_regex_unref (regex);
+
+  return match;
+}
+
+/*
+ * Remove all deleted items which don't have any reference left anywhere
+ * (only leaving the shell of the weak reference), hence whose deletion
+ * cannot be undone anyway.
+ * If @searched is not %NULL, check if it belonged to the deleted item
+ * list and return TRUE if so. In this case, you must call the function
+ * with @found set to %FALSE initially.
+ */
+static void
+gimp_item_list_clean_deleted_items (GimpItemList *set,
+                                    GimpItem     *searched,
+                                    gboolean     *found)
+{
+  GList *iter;
+
+  g_return_if_fail (GIMP_IS_ITEM_LIST (set));
+  g_return_if_fail (! searched || (found && *found == FALSE));
+
+  for (iter = set->p->deleted_items; iter; iter = iter->next)
+    {
+      GimpItem *item = g_weak_ref_get (iter->data);
+
+      if (item == NULL)
+        {
+          set->p->deleted_items = g_list_delete_link (set->p->deleted_items,
+                                                      iter);
+          break;
+        }
+      else
+        {
+          if (searched && item == searched)
+            {
+              set->p->deleted_items = g_list_delete_link (set->p->deleted_items,
+                                                          iter);
+              *found = TRUE;
+              g_object_unref (item);
+              break;
+            }
+          g_object_unref (item);
+        }
+    }
+
+  if (iter)
+    gimp_item_list_clean_deleted_items (set,
+                                        (found && *found) ? NULL : searched,
+                                        found);
+}
+
+static void
+gimp_item_list_free_deleted_item (GWeakRef *item)
+{
+  g_weak_ref_clear (item);
+
+  g_slice_free (GWeakRef, item);
+}
diff --git a/app/core/gimpitemlist.h b/app/core/gimpitemlist.h
new file mode 100644
index 0000000000..6be8dcb485
--- /dev/null
+++ b/app/core/gimpitemlist.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_LIST_H__
+#define __GIMP_ITEM_LIST_H__
+
+
+#define GIMP_TYPE_ITEM_LIST            (gimp_item_list_get_type ())
+#define GIMP_ITEM_LIST(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_LIST, 
GimpItemList))
+#define GIMP_ITEM_LIST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_LIST, 
GimpItemListClass))
+#define GIMP_IS_ITEM_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_LIST))
+#define GIMP_IS_ITEM_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_LIST))
+#define GIMP_ITEM_LIST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ITEM_LIST, 
GimpItemListClass))
+
+
+typedef struct _GimpItemListClass   GimpItemListClass;
+typedef struct _GimpItemListPrivate GimpItemListPrivate;
+
+struct _GimpItemList
+{
+  GimpObject          parent_instance;
+
+  GimpItemListPrivate *p;
+};
+
+struct _GimpItemListClass
+{
+  GimpObjectClass  parent_class;
+
+  /*  signals  */
+  void  (* empty) (GimpItemList *set);
+};
+
+
+GType          gimp_item_list_get_type  (void) G_GNUC_CONST;
+
+GimpItemList  * gimp_item_list_named_new    (GimpImage          *image,
+                                             GType               item_type,
+                                             const gchar        *name,
+                                             GList              *items);
+
+GimpItemList  * gimp_item_list_pattern_new  (GimpImage          *image,
+                                             GType               item_type,
+                                             const gchar        *pattern);
+
+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);
+
+
+#endif /* __GIMP_ITEM_LIST_H__ */
diff --git a/app/core/meson.build b/app/core/meson.build
index 89a205d32e..26f951e7d8 100644
--- a/app/core/meson.build
+++ b/app/core/meson.build
@@ -176,6 +176,7 @@ libappcore_sources = [
   'gimpitem-linked.c',
   'gimpitem-preview.c',
   'gimpitem.c',
+  'gimpitemlist.c',
   'gimpitempropundo.c',
   'gimpitemstack.c',
   'gimpitemtree.c',
diff --git a/app/widgets/gimplayertreeview.c b/app/widgets/gimplayertreeview.c
index 50f1a10d2c..f53fe33366 100644
--- a/app/widgets/gimplayertreeview.c
+++ b/app/widgets/gimplayertreeview.c
@@ -41,6 +41,7 @@
 #include "core/gimpcontainer.h"
 #include "core/gimpimage-undo.h"
 #include "core/gimpimage.h"
+#include "core/gimpitemlist.h"
 #include "core/gimpitemundo.h"
 #include "core/gimplayer.h"
 #include "core/gimplayer-floating-selection.h"
@@ -76,9 +77,11 @@ struct _GimpLayerTreeViewPrivate
   GtkWidget       *anchor_button;
 
   GtkWidget       *link_button;
+  GtkWidget       *link_popover;
   GtkWidget       *link_list;
   GtkWidget       *link_entry;
   GtkWidget       *link_regexp_entry;
+  GimpItemList    *link_regexp;
 
   gint             model_column_mask;
   gint             model_column_mask_visible;
@@ -155,8 +158,8 @@ static gboolean   gimp_layer_tree_view_link_clicked               (GtkWidget
 static void       gimp_layer_tree_view_regexp_modified            (GtkEntry                   *entry,
                                                                    const GParamSpec           *pspec,
                                                                    GimpLayerTreeView          *view);
-static void       gimp_layer_tree_view_new_link_clicked           (GtkButton                  *button,
-                                                                   GimpLayerTreeView          *view);
+static void       gimp_layer_tree_view_new_link_exit              (GimpLayerTreeView          *view);
+static gboolean   gimp_layer_tree_view_new_link_clicked           (GimpLayerTreeView          *view);
 static gboolean   gimp_layer_tree_view_unlink_clicked             (GtkWidget                  *widget,
                                                                    GdkEvent                   *event,
                                                                    GimpLayerTreeView          *view);
@@ -285,6 +288,8 @@ gimp_layer_tree_view_init (GimpLayerTreeView *view)
 
   view->priv = gimp_layer_tree_view_get_instance_private (view);
 
+  view->priv->link_regexp = NULL;
+
   view->priv->model_column_mask =
     gimp_container_tree_store_columns_add (tree_view->model_columns,
                                            &tree_view->n_model_columns,
@@ -369,7 +374,6 @@ gimp_layer_tree_view_constructed (GObject *object)
   GimpContainerTreeView *tree_view  = GIMP_CONTAINER_TREE_VIEW (object);
   GimpLayerTreeView     *layer_view = GIMP_LAYER_TREE_VIEW (object);
   GtkWidget             *button;
-  GtkWidget             *popover;
   GtkWidget             *placeholder;
   GtkWidget             *grid;
   PangoAttrList         *attrs;
@@ -474,9 +478,10 @@ gimp_layer_tree_view_constructed (GObject *object)
   gtk_box_reorder_child (gimp_editor_get_button_box (GIMP_EDITOR (layer_view)),
                          layer_view->priv->link_button, 8);
 
-  popover = gtk_popover_new (layer_view->priv->link_button);
-  gtk_popover_set_modal (GTK_POPOVER (popover), TRUE);
-  gtk_menu_button_set_popover (GTK_MENU_BUTTON (layer_view->priv->link_button), popover);
+  layer_view->priv->link_popover = gtk_popover_new (layer_view->priv->link_button);
+  gtk_popover_set_modal (GTK_POPOVER (layer_view->priv->link_popover), TRUE);
+  gtk_menu_button_set_popover (GTK_MENU_BUTTON (layer_view->priv->link_button),
+                               layer_view->priv->link_popover);
 
   grid = gtk_grid_new ();
 
@@ -496,15 +501,6 @@ gimp_layer_tree_view_constructed (GObject *object)
                     G_CALLBACK (gimp_layer_tree_view_regexp_modified),
                     layer_view);
 
-  /* Enter on regexp entry closes the popover dialog. This allows to use
-   * the feature for quick name-based layer selection, not for actually
-   * storing the selected items.
-   */
-  g_signal_connect_swapped (layer_view->priv->link_regexp_entry,
-                            "activate",
-                            G_CALLBACK (gtk_popover_popdown),
-                            popover);
-
   /* Link popover: existing links. */
   layer_view->priv->link_list = gtk_list_box_new ();
   placeholder = gtk_label_new (_("No layer set stored"));
@@ -539,19 +535,25 @@ gimp_layer_tree_view_constructed (GObject *object)
   gtk_grid_attach (GTK_GRID (grid),
                    button,
                    1, 2, 1, 1);
-  g_signal_connect (button,
-                    "clicked",
-                    G_CALLBACK (gimp_layer_tree_view_new_link_clicked),
-                    layer_view);
+  g_signal_connect_swapped (button,
+                            "clicked",
+                            G_CALLBACK (gimp_layer_tree_view_new_link_clicked),
+                            layer_view);
   gtk_widget_show (button);
 
-  /* Enter on entry activates the link creation. */
+  /* Enter on any entry activates the link creation then exits in case
+   * of success.
+   */
   g_signal_connect_swapped (layer_view->priv->link_entry,
                             "activate",
-                            G_CALLBACK (gtk_button_clicked),
-                            button);
+                            G_CALLBACK (gimp_layer_tree_view_new_link_exit),
+                            layer_view);
+  g_signal_connect_swapped (layer_view->priv->link_regexp_entry,
+                            "activate",
+                            G_CALLBACK (gimp_layer_tree_view_new_link_exit),
+                            layer_view);
 
-  gtk_container_add (GTK_CONTAINER (popover), grid);
+  gtk_container_add (GTK_CONTAINER (layer_view->priv->link_popover), grid);
   gtk_widget_show (grid);
 }
 
@@ -1167,14 +1169,23 @@ gimp_layer_tree_view_layer_links_changed (GimpImage         *image,
   if (! image)
     return;
 
-  links = gimp_image_get_linked_layer_names (image);
+  links = gimp_image_get_stored_item_sets (image);
 
   label_size = gtk_size_group_new (GTK_SIZE_GROUP_BOTH);
   for (iter = links; iter; iter = iter->next)
     {
       grid = gtk_grid_new ();
 
-      label = gtk_label_new (iter->data);
+      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))
+        {
+          PangoAttrList *attrs = pango_attr_list_new ();
+
+          pango_attr_list_insert (attrs, pango_attr_style_new (PANGO_STYLE_OBLIQUE));
+          gtk_label_set_attributes (GTK_LABEL (label), attrs);
+          pango_attr_list_unref (attrs);
+        }
       gtk_widget_set_hexpand (GTK_WIDGET (label), TRUE);
       gtk_widget_set_halign (GTK_WIDGET (label), GTK_ALIGN_START);
       gtk_size_group_add_widget (label_size, label);
@@ -1189,8 +1200,7 @@ gimp_layer_tree_view_layer_links_changed (GimpImage         *image,
       event_box = gtk_event_box_new ();
       gtk_event_box_set_above_child (GTK_EVENT_BOX (event_box), TRUE);
       gtk_widget_add_events (event_box, GDK_BUTTON_RELEASE_MASK);
-      g_object_set_data (G_OBJECT (event_box), "link-name",
-                         (gpointer) gtk_label_get_text (GTK_LABEL (label)));
+      g_object_set_data (G_OBJECT (event_box), "link-set", iter->data);
       g_signal_connect (event_box, "button-release-event",
                         G_CALLBACK (gimp_layer_tree_view_unlink_clicked),
                         view);
@@ -1212,8 +1222,7 @@ gimp_layer_tree_view_layer_links_changed (GimpImage         *image,
       event_box = gtk_event_box_new ();
       gtk_event_box_set_above_child (GTK_EVENT_BOX (event_box), FALSE);
       gtk_widget_add_events (event_box, GDK_BUTTON_RELEASE_MASK);
-      g_object_set_data (G_OBJECT (event_box), "link-name",
-                         (gpointer) gtk_label_get_text (GTK_LABEL (label)));
+      g_object_set_data (G_OBJECT (event_box), "link-set", iter->data);
       gtk_container_add (GTK_CONTAINER (event_box), grid);
       gtk_list_box_prepend (GTK_LIST_BOX (view->priv->link_list), event_box);
       gtk_widget_show (event_box);
@@ -1227,8 +1236,6 @@ gimp_layer_tree_view_layer_links_changed (GimpImage         *image,
     }
   g_object_unref (label_size);
   gtk_list_box_unselect_all (GTK_LIST_BOX (view->priv->link_list));
-
-  g_list_free (links);
 }
 
 static gboolean
@@ -1247,17 +1254,16 @@ gimp_layer_tree_view_link_clicked (GtkWidget         *box,
 
   modifiers = bevent->state & gimp_get_all_modifiers_mask ();
   if (modifiers == GDK_SHIFT_MASK)
-    gimp_image_add_linked_layers (image,
-                                  g_object_get_data (G_OBJECT (box), "link-name"));
+    gimp_image_add_item_set (image, g_object_get_data (G_OBJECT (box), "link-set"));
   else if (modifiers == GDK_CONTROL_MASK)
-    gimp_image_remove_linked_layers (image,
-                                     g_object_get_data (G_OBJECT (box), "link-name"));
+    gimp_image_remove_item_set (image, g_object_get_data (G_OBJECT (box), "link-set"));
   else if (modifiers == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
-    gimp_image_intersect_linked_layers (image,
-                                        g_object_get_data (G_OBJECT (box), "link-name"));
+    gimp_image_intersect_item_set (image, g_object_get_data (G_OBJECT (box), "link-set"));
   else
-    gimp_image_select_linked_layers (image,
-                                     g_object_get_data (G_OBJECT (box), "link-name"));
+    gimp_image_select_item_set (image, g_object_get_data (G_OBJECT (box), "link-set"));
+
+  gtk_entry_set_text (GTK_ENTRY (view->priv->link_regexp_entry), "");
+  /* TODO: if clicking on pattern link, fill in the pattern field? */
 
   return FALSE;
 }
@@ -1275,6 +1281,7 @@ gimp_layer_tree_view_regexp_modified (GtkEntry         *entry,
   gtk_widget_set_tooltip_text (view->priv->link_regexp_entry,
                                _("Select layers by regular expressions"));
   image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view));
+  g_clear_object (&view->priv->link_regexp);
 
   if (! image)
     return;
@@ -1282,34 +1289,44 @@ gimp_layer_tree_view_regexp_modified (GtkEntry         *entry,
   pattern = gtk_entry_get_text (GTK_ENTRY (view->priv->link_regexp_entry));
   if (pattern && strlen (pattern) > 0)
     {
-      GError *error = NULL;
+      GList        *items;
+      GError       *error = NULL;
 
       gtk_entry_set_text (GTK_ENTRY (view->priv->link_entry), "");
       gtk_widget_set_sensitive (view->priv->link_entry, FALSE);
-      if (! gimp_image_select_layers_by_regexp (image, pattern, &error))
+
+      view->priv->link_regexp = gimp_item_list_pattern_new (image,
+                                                            GIMP_TYPE_LAYER,
+                                                            pattern);
+      items = gimp_item_list_get_items (view->priv->link_regexp, &error);
+      if (error)
         {
-          if (error)
-            {
-              /* Invalid regular expression. */
-              PangoAttrList *attrs = pango_attr_list_new ();
-              gchar         *tooltip;
-
-              pango_attr_list_insert (attrs, pango_attr_strikethrough_new (TRUE));
-              tooltip = g_strdup_printf (_("Invalid regular expression: %s\n"),
-                                         error->message);
-              gtk_widget_set_tooltip_text (view->priv->link_regexp_entry,
-                                           tooltip);
-              gtk_entry_set_attributes (GTK_ENTRY (view->priv->link_regexp_entry),
-                                        attrs);
-              g_free (tooltip);
-              g_error_free (error);
-              pango_attr_list_unref (attrs);
-            }
-          else
-            {
-              /* Pattern does not match any results. */
-              gimp_widget_blink (view->priv->link_regexp_entry);
-            }
+          /* Invalid regular expression. */
+          PangoAttrList *attrs = pango_attr_list_new ();
+          gchar         *tooltip;
+
+          pango_attr_list_insert (attrs, pango_attr_strikethrough_new (TRUE));
+          tooltip = g_strdup_printf (_("Invalid regular expression: %s\n"),
+                                     error->message);
+          gtk_widget_set_tooltip_text (view->priv->link_regexp_entry,
+                                       tooltip);
+          gtk_entry_set_attributes (GTK_ENTRY (view->priv->link_regexp_entry),
+                                    attrs);
+          g_free (tooltip);
+          g_error_free (error);
+          pango_attr_list_unref (attrs);
+
+          g_clear_object (&view->priv->link_regexp);
+        }
+      else if (items == NULL)
+        {
+          /* Pattern does not match any results. */
+          gimp_widget_blink (view->priv->link_regexp_entry);
+        }
+      else
+        {
+          gimp_image_set_selected_layers (image, items);
+          g_list_free (items);
         }
     }
   else
@@ -1319,8 +1336,14 @@ gimp_layer_tree_view_regexp_modified (GtkEntry         *entry,
 }
 
 static void
-gimp_layer_tree_view_new_link_clicked (GtkButton         *button,
-                                       GimpLayerTreeView *view)
+gimp_layer_tree_view_new_link_exit (GimpLayerTreeView *view)
+{
+  if (gimp_layer_tree_view_new_link_clicked (view))
+    gtk_popover_popdown (GTK_POPOVER (view->priv->link_popover));
+}
+
+static gboolean
+gimp_layer_tree_view_new_link_clicked (GimpLayerTreeView *view)
 {
   GimpImage   *image;
   const gchar *name;
@@ -1328,18 +1351,40 @@ gimp_layer_tree_view_new_link_clicked (GtkButton         *button,
   image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view));
 
   if (! image)
-    return;
+    return TRUE;
 
   name = gtk_entry_get_text (GTK_ENTRY (view->priv->link_entry));
   if (name && strlen (name) > 0)
     {
-      if (gimp_image_link_layers (image, NULL, name))
-        gtk_entry_set_text (GTK_ENTRY (view->priv->link_entry), "");
+      GimpItemList *set;
+
+      set = gimp_item_list_named_new (image, GIMP_TYPE_LAYER, name, NULL);
+      if (set)
+        {
+          gimp_image_store_item_set (image, set);
+          gtk_entry_set_text (GTK_ENTRY (view->priv->link_entry), "");
+        }
+      else
+        {
+          /* No existing selection. */
+          return FALSE;
+        }
+    }
+  else if (view->priv->link_regexp != NULL)
+    {
+      gimp_image_store_item_set (image, view->priv->link_regexp);
+      view->priv->link_regexp = NULL;
+      gtk_entry_set_text (GTK_ENTRY (view->priv->link_regexp_entry), "");
     }
   else
     {
       gimp_widget_blink (view->priv->link_entry);
+      gimp_widget_blink (view->priv->link_regexp_entry);
+
+      return FALSE;
     }
+
+  return TRUE;
 }
 
 static gboolean
@@ -1353,9 +1398,9 @@ gimp_layer_tree_view_unlink_clicked (GtkWidget         *widget,
 
   g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
 
-  gimp_image_unlink_layers (image,
-                            g_object_get_data (G_OBJECT (widget),
-                                               "link-name"));
+  gimp_image_unlink_item_set (image,
+                              g_object_get_data (G_OBJECT (widget),
+                                                 "link-set"));
 
   return TRUE;
 }


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