[gimp/wip/Jehan/layers-dockable-refresh: 15/87] app: new concept of sets of layers stored in GimpImage.
- From: Jehan <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/wip/Jehan/layers-dockable-refresh: 15/87] app: new concept of sets of layers stored in GimpImage.
- Date: Mon, 15 Nov 2021 01:38:08 +0000 (UTC)
commit 444842a52acd45d984454ecaa44103ebc26fc3dd
Author: Jehan <jehan girinstud io>
Date: Thu Feb 4 18:54:22 2021 +0100
app: new concept of sets of layers stored in GimpImage.
The eventual goal is to replace the "linked layers" concept, which is
why I am using similar vocabulary. The point is that linked layers are
mostly useless/redundant now with multiplie layer selection, except for
one thing: they kind of serve like a way to "save" a selection of layers
(to be moved/transformed together mostly). Apart from this, multiple
selection is more powerful on any way. You can do much more than
transforming the layers together (you can reorganize them together,
delete them, crop them and so on).
Therefore this new feature is the way to fill the only weakness of layer
selection: its ephemerality. Now we can save a given set of layers, not
even only one, but as many as we want, and under a meaningful name, for
later reuse.
Moreover it will make layer-handling core code much simpler as we
currently have 2 concepts of layer set: multiple selection and links.
The new stored links are only a way to recreate multiple selections.
More is to come, for instance right now, these are not stored in the XCF
format. Also it would be awesome to add logical operators (Shift for
union of layer sets, Ctrl for subtraction and Shift-Ctrl for
intersection). And finally I was thinking about a way to select by
pattern (regular expression? Shell-style glob patterns?) and even store
these patterns. So if you save a "Marmot .*" selection pattern, then
when you select it later, new layers matching this pattern will be
included too (instead of fixed-in-time list of layers).
app/core/gimpimage-private.h | 1 +
app/core/gimpimage.c | 180 ++++++++++++++++++++++++++++++++
app/core/gimpimage.h | 12 +++
app/widgets/gimplayertreeview.c | 221 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 414 insertions(+)
---
diff --git a/app/core/gimpimage-private.h b/app/core/gimpimage-private.h
index 0c77b9a281..4a1e78f103 100644
--- a/app/core/gimpimage-private.h
+++ b/app/core/gimpimage-private.h
@@ -110,6 +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;
GQuark layer_offset_x_handler;
GQuark layer_offset_y_handler;
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index 510f62cafb..b5f147ec40 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -126,6 +126,7 @@ enum
PARASITE_DETACHED,
COLORMAP_CHANGED,
UNDO_EVENT,
+ LAYER_LINKS_CHANGED,
LAST_SIGNAL
};
@@ -572,6 +573,14 @@ gimp_image_class_init (GimpImageClass *klass)
GIMP_TYPE_UNDO_EVENT,
GIMP_TYPE_UNDO);
+ gimp_image_signals[LAYER_LINKS_CHANGED] =
+ g_signal_new ("layer-links-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, layer_links_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
object_class->constructed = gimp_image_constructed;
object_class->set_property = gimp_image_set_property;
object_class->get_property = gimp_image_get_property;
@@ -776,6 +785,10 @@ 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);
+
g_signal_connect (private->projection, "notify::buffer",
G_CALLBACK (gimp_image_projection_buffer_notify),
image);
@@ -1119,6 +1132,8 @@ 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,
@@ -5347,6 +5362,171 @@ gimp_image_add_layers (GimpImage *image,
gimp_image_undo_group_end (image);
}
+/*
+ * gimp_image_link_layers:
+ * @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.
+ *
+ * Returns: %TRUE if a new set was created, %FALSE otherwise (e.g. no
+ * list provided and no layers currently selected).
+ */
+gboolean
+gimp_image_link_layers (GimpImage *image,
+ const GList *layers,
+ const gchar *link_name)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (! layers)
+ {
+ layers = gimp_image_get_selected_layers (image);
+
+ if (! layers)
+ return FALSE;
+ }
+
+ g_hash_table_insert (private->linked_layers,
+ g_strdup (link_name),
+ g_list_copy ((GList *) layers));
+ g_signal_emit (image, gimp_image_signals[LAYER_LINKS_CHANGED], 0);
+
+ return TRUE;
+}
+
+/*
+ * @gimp_image_unlink_layers:
+ * @image:
+ * @link_name:
+ *
+ * Remove the set of layers named @link_name.
+ *
+ * Returns: %TRUE if the set was removed, %FALSE if no sets with this
+ * name existed.
+ */
+gboolean
+gimp_image_unlink_layers (GimpImage *image,
+ const gchar *link_name)
+{
+ GimpImagePrivate *private;
+ 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);
+ if (success)
+ g_signal_emit (image, gimp_image_signals[LAYER_LINKS_CHANGED], 0);
+
+ return success;
+}
+
+/*
+ * @gimp_image_select_linked_layers:
+ * @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.
+ */
+void
+gimp_image_select_linked_layers (GimpImage *image,
+ const gchar *link_name)
+{
+ GimpImagePrivate *private;
+ GList *linked;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (link_name != 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);
+}
+
+/*
+ * @gimp_image_add_linked_layers:
+ * @image:
+ * @link_name:
+ *
+ * Add the layers belonging to the set named @link_name (which must
+ * exist) to the layers currently selected in @image.
+ *
+ * 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.
+ */
+void
+gimp_image_add_linked_layers (GimpImage *image,
+ const gchar *link_name)
+{
+ GimpImagePrivate *private;
+ GList *linked;
+ GList *layers;
+ GList *iter;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (link_name != NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ linked = g_hash_table_lookup (private->linked_layers,
+ link_name);
+
+ 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);
+ }
+
+ 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;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return g_hash_table_get_keys (private->linked_layers);
+}
+
/* channels */
diff --git a/app/core/gimpimage.h b/app/core/gimpimage.h
index 41bfedf153..42406f9191 100644
--- a/app/core/gimpimage.h
+++ b/app/core/gimpimage.h
@@ -104,6 +104,7 @@ struct _GimpImageClass
void (* undo_event) (GimpImage *image,
GimpUndoEvent event,
GimpUndo *undo);
+ void (* layer_links_changed) (GimpImage *image);
};
@@ -451,6 +452,17 @@ 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);
+void gimp_image_add_linked_layers (GimpImage *image,
+ const gchar *link_name);
+GList * gimp_image_get_linked_layer_names (GimpImage *image);
+
gboolean gimp_image_add_channel (GimpImage *image,
GimpChannel *channel,
GimpChannel *parent,
diff --git a/app/widgets/gimplayertreeview.c b/app/widgets/gimplayertreeview.c
index 6085738b63..91b550f1b7 100644
--- a/app/widgets/gimplayertreeview.c
+++ b/app/widgets/gimplayertreeview.c
@@ -75,6 +75,10 @@ struct _GimpLayerTreeViewPrivate
GtkWidget *lock_alpha_toggle;
GtkWidget *anchor_button;
+ GtkWidget *link_button;
+ GtkWidget *link_list;
+ GtkWidget *link_entry;
+
gint model_column_mask;
gint model_column_mask_visible;
@@ -141,6 +145,18 @@ static void gimp_layer_tree_view_set_image (GimpItemTreeV
static GimpItem * gimp_layer_tree_view_item_new (GimpImage *image);
static void gimp_layer_tree_view_floating_selection_changed (GimpImage *image,
GimpLayerTreeView *view);
+
+static void gimp_layer_tree_view_layer_links_changed (GimpImage *image,
+ GimpLayerTreeView *view);
+static void gimp_layer_tree_view_link_activated (GtkListBox *list,
+ GtkListBoxRow *row,
+ GimpLayerTreeView *view);
+static void gimp_layer_tree_view_new_link_clicked (GtkButton *button,
+ GimpLayerTreeView *view);
+static gboolean gimp_layer_tree_view_unlink_clicked (GtkWidget *widget,
+ GdkEvent *event,
+ GimpLayerTreeView *view);
+
static void gimp_layer_tree_view_layer_mode_box_callback (GtkWidget *widget,
const GParamSpec *pspec,
GimpLayerTreeView *view);
@@ -349,6 +365,9 @@ 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 *grid;
+ GtkIconSize button_size;
G_OBJECT_CLASS (parent_class)->constructed (object);
@@ -430,6 +449,73 @@ gimp_layer_tree_view_constructed (GObject *object)
GIMP_TYPE_LAYER);
gtk_box_reorder_child (gimp_editor_get_button_box (GIMP_EDITOR (layer_view)),
button, 7);
+
+ /* Link popover menu. */
+
+ layer_view->priv->link_button = gtk_menu_button_new ();
+ gtk_widget_style_get (GTK_WIDGET (layer_view),
+ "button-icon-size", &button_size,
+ NULL);
+ gtk_button_set_image (GTK_BUTTON (layer_view->priv->link_button),
+ gtk_image_new_from_icon_name (GIMP_ICON_LINKED,
+ button_size));
+ gtk_box_pack_start (gimp_editor_get_button_box (GIMP_EDITOR (layer_view)),
+ layer_view->priv->link_button, TRUE, TRUE, 0);
+ gtk_widget_show (layer_view->priv->link_button);
+ gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (layer_view),
+ GTK_BUTTON (layer_view->priv->link_button),
+ GIMP_TYPE_LAYER);
+ 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);
+
+ grid = gtk_grid_new ();
+
+ /* Link popover: existing links. */
+ layer_view->priv->link_list = gtk_list_box_new ();
+ gtk_grid_attach (GTK_GRID (grid),
+ layer_view->priv->link_list,
+ 0, 0, 2, 1);
+ gtk_widget_show (layer_view->priv->link_list);
+
+ gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (layer_view->priv->link_list),
+ TRUE);
+ g_signal_connect (layer_view->priv->link_list,
+ "row-activated",
+ G_CALLBACK (gimp_layer_tree_view_link_activated),
+ layer_view);
+
+ /* Link popover: new links. */
+
+ layer_view->priv->link_entry = gtk_entry_new ();
+ gtk_entry_set_placeholder_text (GTK_ENTRY (layer_view->priv->link_entry),
+ _("Named Selection"));
+ gtk_grid_attach (GTK_GRID (grid),
+ layer_view->priv->link_entry,
+ 0, 1, 1, 1);
+ gtk_widget_show (layer_view->priv->link_entry);
+
+ button = gtk_button_new_from_icon_name (GIMP_ICON_DOCUMENT_SAVE, button_size);
+ gtk_grid_attach (GTK_GRID (grid),
+ button,
+ 1, 1, 1, 1);
+ g_signal_connect (button,
+ "clicked",
+ G_CALLBACK (gimp_layer_tree_view_new_link_clicked),
+ layer_view);
+ gtk_widget_show (button);
+
+ /* Enter on entry activates the link creation. */
+ g_signal_connect_swapped (layer_view->priv->link_entry,
+ "activate",
+ G_CALLBACK (gtk_button_clicked),
+ button);
+
+ gtk_container_add (GTK_CONTAINER (popover), grid);
+ gtk_widget_show (grid);
}
static void
@@ -918,6 +1004,9 @@ gimp_layer_tree_view_set_image (GimpItemTreeView *view,
g_signal_handlers_disconnect_by_func (gimp_item_tree_view_get_image (view),
gimp_layer_tree_view_floating_selection_changed,
view);
+ g_signal_handlers_disconnect_by_func (gimp_item_tree_view_get_image (view),
+ G_CALLBACK (gimp_layer_tree_view_layer_links_changed),
+ view);
}
GIMP_ITEM_TREE_VIEW_CLASS (parent_class)->set_image (view, image);
@@ -928,6 +1017,10 @@ gimp_layer_tree_view_set_image (GimpItemTreeView *view,
"floating-selection-changed",
G_CALLBACK (gimp_layer_tree_view_floating_selection_changed),
view);
+ g_signal_connect (gimp_item_tree_view_get_image (view),
+ "layer-links-changed",
+ G_CALLBACK (gimp_layer_tree_view_layer_links_changed),
+ view);
/* call gimp_layer_tree_view_floating_selection_changed() now, to update
* the floating selection's row attributes.
@@ -936,6 +1029,9 @@ gimp_layer_tree_view_set_image (GimpItemTreeView *view,
gimp_item_tree_view_get_image (view),
layer_view);
}
+ /* Call this even with no image, allowing to empty the link list. */
+ gimp_layer_tree_view_layer_links_changed (gimp_item_tree_view_get_image (view),
+ layer_view);
gimp_layer_tree_view_update_highlight (layer_view);
}
@@ -1015,6 +1111,131 @@ gimp_layer_tree_view_floating_selection_changed (GimpImage *image,
gimp_layer_tree_view_update_highlight (layer_view);
}
+static void
+gimp_layer_tree_view_layer_links_changed (GimpImage *image,
+ GimpLayerTreeView *view)
+{
+ GtkWidget *grid;
+ GtkWidget *label;
+ GtkWidget *event_box;
+ GtkWidget *icon;
+ GtkSizeGroup *label_size;
+ GList *links;
+ GList *iter;
+
+ gtk_container_foreach (GTK_CONTAINER (view->priv->link_list),
+ (GtkCallback) gtk_widget_destroy, NULL);
+ gtk_widget_set_sensitive (view->priv->link_button, image != NULL);
+
+ if (! image)
+ return;
+
+ links = gimp_image_get_linked_layer_names (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);
+ g_object_set_data (G_OBJECT (grid), "link-name",
+ (gpointer) gtk_label_get_text (GTK_LABEL (label)));
+ 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);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
+ gtk_widget_show (label);
+
+ /* I don't use a GtkButton because the minimum size is 16 which is
+ * weird and ugly here. And somehow if I force smaller GtkImage
+ * size then add it to the GtkButton, I still get a giant button
+ * with a small image in it, which is even worse. XXX
+ */
+ 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_signal_connect (event_box, "button-release-event",
+ G_CALLBACK (gimp_layer_tree_view_unlink_clicked),
+ view);
+ gtk_grid_attach (GTK_GRID (grid), event_box, 2, 0, 1, 1);
+ gtk_widget_show (event_box);
+
+ icon = gtk_image_new_from_icon_name (GIMP_ICON_EDIT_DELETE, GTK_ICON_SIZE_MENU);
+ gtk_image_set_pixel_size (GTK_IMAGE (icon), 10);
+ gtk_container_add (GTK_CONTAINER (event_box), icon);
+ gtk_widget_show (icon);
+
+ gtk_list_box_prepend (GTK_LIST_BOX (view->priv->link_list), grid);
+ gtk_widget_show (grid);
+ }
+ g_object_unref (label_size);
+ gtk_list_box_unselect_all (GTK_LIST_BOX (view->priv->link_list));
+
+ g_list_free (links);
+}
+
+static void
+gimp_layer_tree_view_link_activated (GtkListBox *list,
+ GtkListBoxRow *row,
+ GimpLayerTreeView *view)
+{
+ GimpImage *image;
+ GtkWidget *grid;
+
+ image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view));
+ grid = gtk_bin_get_child (GTK_BIN (row));
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GTK_IS_GRID (grid));
+
+ gimp_image_select_linked_layers (image,
+ g_object_get_data (G_OBJECT (grid), "link-name"));
+}
+
+static void
+gimp_layer_tree_view_new_link_clicked (GtkButton *button,
+ GimpLayerTreeView *view)
+{
+ GimpImage *image;
+ const gchar *name;
+
+ image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view));
+
+ if (! image)
+ return;
+
+ 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), "");
+ }
+ else
+ {
+ gimp_widget_blink (view->priv->link_entry);
+ }
+}
+
+static gboolean
+gimp_layer_tree_view_unlink_clicked (GtkWidget *widget,
+ GdkEvent *event,
+ GimpLayerTreeView *view)
+{
+ GimpImage *image;
+
+ image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view));
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ gimp_image_unlink_layers (image,
+ g_object_get_data (G_OBJECT (widget),
+ "link-name"));
+
+ return TRUE;
+}
+
/* Paint Mode, Opacity and Lock alpha callbacks */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]