[gimp/wip/Jehan/layers-dockable-refresh: 52/126] app: allow storing regular expression-based item sets.
- From: Jehan <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/wip/Jehan/layers-dockable-refresh: 52/126] app: allow storing regular expression-based item sets.
- Date: Wed, 15 Dec 2021 23:16:46 +0000 (UTC)
commit e23335a346e4325cf0ae374e77476a944c3a228e
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),
+ ®_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]