diff --git a/app/actions/file-commands.c b/app/actions/file-commands.c index c5b939f397..8236dc8a95 100644 --- a/app/actions/file-commands.c +++ b/app/actions/file-commands.c @@ -843,7 +843,7 @@ file_revert_confirm_response (GtkWidget *dialog, GIMP_PROGRESS (display), file, 0, 0, FALSE, NULL, GIMP_RUN_INTERACTIVE, - &status, NULL, &error); + NULL, &status, NULL, &error); if (new_image) { diff --git a/app/actions/layers-actions.c b/app/actions/layers-actions.c index 1153737a2d..ddaa66a204 100644 --- a/app/actions/layers-actions.c +++ b/app/actions/layers-actions.c @@ -30,6 +30,7 @@ #include "core/gimpimage.h" #include "core/gimplayer.h" #include "core/gimplayer-floating-selection.h" +#include "core/gimplinklayer.h" #include "text/gimptextlayer.h" @@ -173,6 +174,18 @@ static const GimpActionEntry layers_actions[] = image_flatten_image_cmd_callback, GIMP_HELP_IMAGE_FLATTEN }, + { "layers-link-discard", GIMP_ICON_TOOL_TEXT, + NC_("layers-action", "_Discard Link Information"), NULL, { NULL }, + NC_("layers-action", "Turn this link layer into a normal layer"), + layers_link_discard_cmd_callback, + GIMP_HELP_LAYER_TEXT_DISCARD }, + + { "layers-link-monitor", GIMP_ICON_TOOL_TEXT, + NC_("layers-action", "_Monitor Linked Image"), NULL, { NULL }, + NC_("layers-action", "Discard any transformation and monitor the linked file again"), + layers_link_monitor_cmd_callback, + GIMP_HELP_LAYER_TEXT_DISCARD }, + { "layers-text-discard", GIMP_ICON_TOOL_TEXT, NC_("layers-action", "_Discard Text Information"), NULL, { NULL }, NC_("layers-action", "Turn these text layers into normal layers"), @@ -757,6 +770,7 @@ layers_actions_update (GimpActionGroup *group, gboolean lock_alpha = TRUE; gboolean can_lock_alpha = FALSE; gboolean text_layer = FALSE; + gboolean link_layer = FALSE; gboolean bs_mutable = FALSE; /* At least 1 selected layers' blend space is mutable. */ gboolean cs_mutable = FALSE; /* At least 1 selected layers' composite space is mutable. */ gboolean cm_mutable = FALSE; /* At least 1 selected layers' composite mode is mutable. */ @@ -978,6 +992,7 @@ layers_actions_update (GimpActionGroup *group, gimp_action_group_set_action_active (group, action, TRUE); text_layer = gimp_item_is_text_layer (GIMP_ITEM (layer)); + link_layer = gimp_item_is_link_layer (GIMP_ITEM (layer)); } } @@ -1038,6 +1053,9 @@ layers_actions_update (GimpActionGroup *group, SET_SENSITIVE ("layers-merge-layers", n_selected_layers > 0 && !fs && !ac); SET_SENSITIVE ("layers-flatten-image", !fs && !ac); + SET_VISIBLE ("layers-link-discard", link_layer && !ac); + SET_VISIBLE ("layers-link-monitor", GIMP_IS_LINK_LAYER (layer) && ! link_layer && !ac); + SET_VISIBLE ("layers-text-discard", n_text_layers > 0 && !ac); SET_VISIBLE ("layers-text-to-vectors", n_text_layers > 0 && !ac); SET_VISIBLE ("layers-text-along-vectors", text_layer && !ac); diff --git a/app/actions/layers-commands.c b/app/actions/layers-commands.c index bde74e484a..9ce64224c1 100644 --- a/app/actions/layers-commands.c +++ b/app/actions/layers-commands.c @@ -48,6 +48,8 @@ #include "core/gimplayerpropundo.h" #include "core/gimplayer-floating-selection.h" #include "core/gimplayer-new.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "core/gimplist.h" #include "core/gimppickable.h" #include "core/gimppickable-auto-shrink.h" @@ -66,6 +68,7 @@ #include "widgets/gimpaction.h" #include "widgets/gimpdock.h" #include "widgets/gimphelp-ids.h" +#include "widgets/gimpopendialog.h" #include "widgets/gimpprogressdialog.h" #include "display/gimpdisplay.h" @@ -101,6 +104,7 @@ static void layers_new_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, @@ -124,6 +128,7 @@ static void layers_edit_attributes_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, @@ -172,6 +177,9 @@ static gint layers_mode_index (GimpLayerMode layer_mode const GimpLayerMode *modes, gint n_modes); +static void layers_link_dialog_response (GtkDialog *dialog, + gint response_id, + GimpImage *image); /* private variables */ @@ -427,6 +435,22 @@ layers_new_last_vals_cmd_callback (GimpAction *action, return; } + if (config->layer_new_fill_type == GIMP_FILL_LINK) + { + GtkWidget *dialog; + + dialog = gimp_open_dialog_new (image->gimp); + gtk_window_set_title (GTK_WINDOW (dialog), _("Select Linked Image")); + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), FALSE); + + g_signal_connect (dialog, "response", + G_CALLBACK (layers_link_dialog_response), + image); + gtk_widget_show (dialog); + + return; + } + layer_mode = config->layer_new_mode; if (layer_mode == GIMP_LAYER_MODE_NORMAL || @@ -1045,6 +1069,42 @@ layers_delete_cmd_callback (GimpAction *action, gimp_image_flush (image); } +void +layers_link_discard_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data) +{ + GimpImage *image; + GList *layers; + GList *iter; + return_if_no_layers (image, layers, data); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_PROPERTIES, + _("Discard Links")); + for (iter = layers; iter; iter = iter->next) + if (GIMP_IS_LINK_LAYER (iter->data)) + gimp_link_layer_discard (GIMP_LINK_LAYER (iter->data)); + gimp_image_undo_group_end (image); +} + +void +layers_link_monitor_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data) +{ + GimpImage *image; + GList *layers; + GList *iter; + return_if_no_layers (image, layers, data); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_PROPERTIES, + _("Monitor Links")); + for (iter = layers; iter; iter = iter->next) + if (GIMP_IS_LINK_LAYER (iter->data)) + gimp_link_layer_monitor (GIMP_LINK_LAYER (iter->data)); + gimp_image_undo_group_end (image); +} + void layers_text_discard_cmd_callback (GimpAction *action, GVariant *value, @@ -2263,6 +2323,7 @@ layers_new_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, @@ -2324,17 +2385,32 @@ layers_new_callback (GtkWidget *dialog, position = 0; } - layer = gimp_layer_new (image, layer_width, layer_height, - gimp_image_get_layer_format (image, TRUE), - config->layer_new_name, - config->layer_new_opacity, - config->layer_new_mode); + if (layer_fill_type == GIMP_FILL_LINK) + { + if (link) + { + gimp_link_set_size (link, layer_width, layer_height); + layer = gimp_link_layer_new (image, link); + + if (layer_mode != gimp_layer_get_mode (layer)) + gimp_layer_set_mode (layer, layer_mode, TRUE); + } + } + else + { + layer = gimp_layer_new (image, layer_width, layer_height, + gimp_image_get_layer_format (image, TRUE), + config->layer_new_name, + config->layer_new_opacity, + config->layer_new_mode); + } if (layer) { gimp_item_set_offset (GIMP_ITEM (layer), layer_offset_x, layer_offset_y); - gimp_drawable_fill (GIMP_DRAWABLE (layer), context, - config->layer_new_fill_type); + if (layer_fill_type != GIMP_FILL_LINK) + gimp_drawable_fill (GIMP_DRAWABLE (layer), context, + config->layer_new_fill_type); gimp_item_set_visible (GIMP_ITEM (layer), layer_visible, FALSE); gimp_item_set_color_tag (GIMP_ITEM (layer), layer_color_tag, FALSE); gimp_item_set_lock_content (GIMP_ITEM (layer), layer_lock_pixels, @@ -2353,6 +2429,16 @@ layers_new_callback (GtkWidget *dialog, new_layers = g_list_prepend (new_layers, layer); } + else if (layer_fill_type == GIMP_FILL_LINK) + { + /* We special-case the link layers because chances of failure are + * higher and especially source of failure may be user choices. + * For such case, we don't want to generate WARNINGS (which are + * for code bugs), but an error dialog instead. + */ + gimp_message_literal (image->gimp, NULL, GIMP_MESSAGE_WARNING, + _("Invalid image file")); + } else { g_warning ("%s: could not allocate new layer", G_STRFUNC); @@ -2379,6 +2465,7 @@ layers_edit_attributes_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType unused1, + GimpLink *link, gint unused2, gint unused3, gint layer_offset_x, @@ -2407,7 +2494,8 @@ layers_edit_attributes_callback (GtkWidget *dialog, layer_lock_pixels != gimp_item_get_lock_content (item) || layer_lock_position != gimp_item_get_lock_position (item) || layer_lock_visibility != gimp_item_get_lock_visibility (item) || - layer_lock_alpha != gimp_layer_get_lock_alpha (layer)) + layer_lock_alpha != gimp_layer_get_lock_alpha (layer) || + link) { gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_PROPERTIES, @@ -2468,6 +2556,9 @@ layers_edit_attributes_callback (GtkWidget *dialog, if (layer_lock_alpha != gimp_layer_get_lock_alpha (layer)) gimp_layer_set_lock_alpha (layer, layer_lock_alpha, TRUE); + if (GIMP_IS_LINK_LAYER (layer) && link) + gimp_link_layer_set_link (GIMP_LINK_LAYER (layer), link, TRUE); + gimp_image_undo_group_end (image); gimp_image_flush (image); } @@ -2649,3 +2740,45 @@ layers_mode_index (GimpLayerMode layer_mode, return i; } + +static void +layers_link_dialog_response (GtkDialog *dialog, + gint response_id, + GimpImage *image) +{ + if (response_id == GTK_RESPONSE_OK) + { + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (file) + { + GimpLayer *layer; + GimpLink *link; + + link = gimp_link_new (image->gimp, file); + layer = gimp_link_layer_new (image, link); + + if (layer) + { + GimpDialogConfig *config; + + config = GIMP_DIALOG_CONFIG (image->gimp->config); + gimp_layer_set_blend_space (layer, + config->layer_new_blend_space, FALSE); + gimp_layer_set_composite_space (layer, + config->layer_new_composite_space, FALSE); + gimp_layer_set_composite_mode (layer, + config->layer_new_composite_mode, FALSE); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_LAYER_ADD, + _("Add Link Layer")); + gimp_image_add_layer (image, layer, GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + gimp_image_undo_group_end (image); + gimp_image_flush (image); + } + } + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} diff --git a/app/actions/layers-commands.h b/app/actions/layers-commands.h index 6e28d641e5..283d186eda 100644 --- a/app/actions/layers-commands.h +++ b/app/actions/layers-commands.h @@ -75,6 +75,12 @@ void layers_merge_group_cmd_callback (GimpAction *action, void layers_delete_cmd_callback (GimpAction *action, GVariant *value, gpointer data); +void layers_link_discard_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data); +void layers_link_monitor_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data); void layers_text_discard_cmd_callback (GimpAction *action, GVariant *value, gpointer data); diff --git a/app/core/core-enums.c b/app/core/core-enums.c index 827f0b5f11..794e0d347a 100644 --- a/app/core/core-enums.c +++ b/app/core/core-enums.c @@ -1257,6 +1257,7 @@ gimp_undo_type_get_type (void) { GIMP_UNDO_LAYER_MODE, "GIMP_UNDO_LAYER_MODE", "layer-mode" }, { GIMP_UNDO_LAYER_OPACITY, "GIMP_UNDO_LAYER_OPACITY", "layer-opacity" }, { GIMP_UNDO_LAYER_LOCK_ALPHA, "GIMP_UNDO_LAYER_LOCK_ALPHA", "layer-lock-alpha" }, + { GIMP_UNDO_LINK_LAYER, "GIMP_UNDO_LINK_LAYER", "link-layer" }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, "GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE", "group-layer-suspend-resize" }, { GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, "GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE", "group-layer-resume-resize" }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, "GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK", "group-layer-suspend-mask" }, @@ -1370,6 +1371,7 @@ gimp_undo_type_get_type (void) { GIMP_UNDO_LAYER_MODE, NC_("undo-type", "Set layer mode"), NULL }, { GIMP_UNDO_LAYER_OPACITY, NC_("undo-type", "Set layer opacity"), NULL }, { GIMP_UNDO_LAYER_LOCK_ALPHA, NC_("undo-type", "Lock/Unlock alpha channel"), NULL }, + { GIMP_UNDO_LINK_LAYER, NC_("undo-type", "Link layer"), NULL }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, NC_("undo-type", "Suspend group layer resize"), NULL }, { GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, NC_("undo-type", "Resume group layer resize"), NULL }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, NC_("undo-type", "Suspend group layer mask"), NULL }, diff --git a/app/core/core-enums.h b/app/core/core-enums.h index 5db3dfd541..70bdd260c4 100644 --- a/app/core/core-enums.h +++ b/app/core/core-enums.h @@ -611,6 +611,7 @@ typedef enum /*< pdb-skip >*/ GIMP_UNDO_LAYER_MODE, /*< desc="Set layer mode" >*/ GIMP_UNDO_LAYER_OPACITY, /*< desc="Set layer opacity" >*/ GIMP_UNDO_LAYER_LOCK_ALPHA, /*< desc="Lock/Unlock alpha channel" >*/ + GIMP_UNDO_LINK_LAYER, /*< desc="Link layer" >*/ GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, /*< desc="Suspend group layer resize" >*/ GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, /*< desc="Resume group layer resize" >*/ GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, /*< desc="Suspend group layer mask" >*/ diff --git a/app/core/core-types.h b/app/core/core-types.h index 93cb8958c5..adbf8dc56d 100644 --- a/app/core/core-types.h +++ b/app/core/core-types.h @@ -92,6 +92,7 @@ typedef struct _GimpViewable GimpViewable; typedef struct _GimpFilter GimpFilter; typedef struct _GimpItem GimpItem; typedef struct _GimpAuxItem GimpAuxItem; +typedef struct _GimpLink GimpLink; typedef struct _Gimp Gimp; typedef struct _GimpImage GimpImage; @@ -169,6 +170,7 @@ typedef struct _GimpLayerMask GimpLayerMask; typedef struct _GimpSelection GimpSelection; typedef struct _GimpLayer GimpLayer; typedef struct _GimpGroupLayer GimpGroupLayer; +typedef struct _GimpLinkLayer GimpLinkLayer; /* auxiliary image items */ diff --git a/app/core/gimpimage-undo-push.c b/app/core/gimpimage-undo-push.c index 468146e13a..0f13cc3141 100644 --- a/app/core/gimpimage-undo-push.c +++ b/app/core/gimpimage-undo-push.c @@ -48,6 +48,8 @@ #include "gimplayermaskundo.h" #include "gimplayerpropundo.h" #include "gimplayerundo.h" +#include "gimplinklayer.h" +#include "gimplinklayerundo.h" #include "gimpmaskundo.h" #include "gimpsamplepoint.h" #include "gimpsamplepointundo.h" @@ -876,6 +878,25 @@ gimp_image_undo_push_text_layer_convert (GimpImage *image, NULL); } +/**********************/ +/* Link Layer Undos */ +/**********************/ + +GimpUndo * +gimp_image_undo_push_link_layer (GimpImage *image, + const gchar *undo_desc, + GimpLinkLayer *layer) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), NULL); + g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL); + + return gimp_image_undo_push (image, GIMP_TYPE_LINK_LAYER_UNDO, + GIMP_UNDO_LINK_LAYER, undo_desc, + GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE, + "item", layer, + NULL); +} /**********************/ /* Layer Mask Undos */ diff --git a/app/core/gimpimage-undo-push.h b/app/core/gimpimage-undo-push.h index c13850ce98..3e8e09fda7 100644 --- a/app/core/gimpimage-undo-push.h +++ b/app/core/gimpimage-undo-push.h @@ -215,6 +215,11 @@ GimpUndo * gimp_image_undo_push_text_layer_convert (GimpImage *image, const gchar *undo_desc, GimpTextLayer *layer); +/* link layer undos */ + +GimpUndo * gimp_image_undo_push_link_layer (GimpImage *image, + const gchar *undo_desc, + GimpLinkLayer *layer); /* layer mask undos */ diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c index 803dc47ee8..ec84d35149 100644 --- a/app/core/gimpimage.c +++ b/app/core/gimpimage.c @@ -71,6 +71,7 @@ #include "gimplayer-floating-selection.h" #include "gimplayermask.h" #include "gimplayerstack.h" +#include "gimplinklayer.h" #include "gimpmarshal.h" #include "gimppalette.h" #include "gimpparasitelist.h" @@ -3013,6 +3014,14 @@ gimp_image_get_xcf_version (GimpImage *image, */ version = MAX (23, version); } + + /* Need version 24 TODO for link layers. */ + if (GIMP_IS_LINK_LAYER (layer)) + { + ADD_REASON (g_strdup_printf (_("Link layers were added in %s"), + "GIMP TODO")); + version = MAX (14, version); + } } g_list_free (items); diff --git a/app/core/gimpimagefile.c b/app/core/gimpimagefile.c index d1128d1a0f..01156677aa 100644 --- a/app/core/gimpimagefile.c +++ b/app/core/gimpimagefile.c @@ -520,7 +520,7 @@ gimp_imagefile_create_thumbnail (GimpImagefile *imagefile, private->file, size, size, FALSE, NULL, GIMP_RUN_NONINTERACTIVE, - &status, &mime_type, error); + NULL, &status, &mime_type, error); if (image) gimp_thumbnail_set_info_from_image (private->thumbnail, diff --git a/app/core/gimplink.c b/app/core/gimplink.c new file mode 100644 index 0000000000..0a079c4839 --- /dev/null +++ b/app/core/gimplink.c @@ -0,0 +1,403 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLink + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpbase/gimpbase.h" + +#include "core-types.h" + +#include "gimp.h" +#include "gimpimage.h" +#include "gimplink.h" +#include "gimpmarshal.h" +#include "gimppickable.h" +#include "gimpprojection.h" + +#include "file/file-open.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_FILE +}; + +enum +{ + CHANGED, + LAST_SIGNAL +}; + +struct _GimpLinkPrivate +{ + Gimp *gimp; + GFile *file; + GFileMonitor *monitor; + + gboolean broken; + guint idle_changed_source; + + gboolean is_vector; + gint width; + gint height; +}; + +static void gimp_link_finalize (GObject *object); +static void gimp_link_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_link_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_link_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GimpLink *link); +static gboolean gimp_link_emit_changed (gpointer data); + +G_DEFINE_TYPE_WITH_PRIVATE (GimpLink, gimp_link, GIMP_TYPE_OBJECT) + +#define parent_class gimp_link_parent_class + +static guint link_signals[LAST_SIGNAL] = { 0 }; + +static void +gimp_link_class_init (GimpLinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + link_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpLinkClass, changed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + object_class->finalize = gimp_link_finalize; + object_class->get_property = gimp_link_get_property; + object_class->set_property = gimp_link_set_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_FILE, + g_param_spec_object ("file", NULL, NULL, + G_TYPE_FILE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_link_init (GimpLink *link) +{ + link->p = gimp_link_get_instance_private (link); + link->p->gimp = NULL; + link->p->file = NULL; + link->p->monitor = NULL; + link->p->broken = TRUE; + link->p->width = 0; + link->p->height = 0; + + link->p->idle_changed_source = 0; +} + +static void +gimp_link_finalize (GObject *object) +{ + GimpLink *link = GIMP_LINK (object); + + g_clear_object (&link->p->file); + g_clear_object (&link->p->monitor); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_link_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLink *link = GIMP_LINK (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, link->p->gimp); + break; + case PROP_FILE: + g_value_set_object (value, link->p->file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLink *link = GIMP_LINK (object); + + switch (property_id) + { + case PROP_GIMP: + link->p->gimp = g_value_get_object (value); + break; + case PROP_FILE: + if (link->p->file) + g_object_unref (link->p->file); + if (link->p->monitor) + g_object_unref (link->p->monitor); + + link->p->is_vector = FALSE; + link->p->file = g_value_dup_object (value); + + if (link->p->file) + { + gchar *basename; + + basename = g_file_get_basename (link->p->file); + link->p->monitor = g_file_monitor_file (link->p->file, G_FILE_MONITOR_NONE, NULL, NULL); + g_signal_connect (link->p->monitor, "changed", + G_CALLBACK (gimp_link_file_changed), + link); + gimp_object_set_name_safe (GIMP_OBJECT (object), basename); + g_free (basename); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GimpLink *link) +{ + switch (event_type) + { + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + if (link->p->idle_changed_source == 0) + link->p->idle_changed_source = g_idle_add_full (G_PRIORITY_LOW, + gimp_link_emit_changed, + link, NULL); + break; + case G_FILE_MONITOR_EVENT_CREATED: + g_signal_emit (link, link_signals[CHANGED], 0); + break; + case G_FILE_MONITOR_EVENT_DELETED: + link->p->broken = TRUE; + break; + + default: + /* No need to signal for changes where nothing can be done anyway. + * In particular a file deletion, the link is broken, yet we don't + * want to re-render. + * Don't emit either on G_FILE_MONITOR_EVENT_CHANGED because too + * many such events may be emitted for a single file writing. + */ + break; + } + +} + +static gboolean +gimp_link_emit_changed (gpointer data) +{ + GimpLink *link = GIMP_LINK (data); + + g_signal_emit (link, link_signals[CHANGED], 0); + link->p->idle_changed_source = 0; + + return G_SOURCE_REMOVE; +} + +/* public functions */ + +/** + * gimp_link_new: + * @gimp: #Gimp object. + * @file: a #GFile object. + * + * Creates a new text layer. + * + * Return value: a new #GimpLink or %NULL in case of a problem + **/ +GimpLink * +gimp_link_new (Gimp *gimp, + GFile *file) +{ + GimpObject *link; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (G_IS_FILE (file), NULL); + + link = g_object_new (GIMP_TYPE_LINK, + "gimp", gimp, + "file", file, + NULL); + + return GIMP_LINK (link); +} + +GFile * +gimp_link_get_file (GimpLink *link) +{ + g_return_val_if_fail (GIMP_IS_LINK (link), NULL); + + return link->p->file; +} + +void +gimp_link_set_file (GimpLink *link, + GFile *file) +{ + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (link))); + g_return_if_fail (G_IS_FILE (file) || file == NULL); + + if (file && g_file_equal (file, link->p->file)) + return; + + g_object_set (link, + "file", file, + NULL); +} + +gboolean +gimp_link_is_broken (GimpLink *link, + gboolean recheck) +{ + GeglBuffer *buffer; + + if (recheck) + { + buffer = gimp_link_get_buffer (link, NULL, NULL); + g_clear_object (&buffer); + } + + return link->p->broken; +} + +GimpLink * +gimp_link_duplicate (GimpLink *link) +{ + g_return_val_if_fail (GIMP_IS_LINK (link), NULL); + + return gimp_link_new (link->p->gimp, link->p->file); +} + +void +gimp_link_set_size (GimpLink *link, + gint width, + gint height) +{ + link->p->width = width; + link->p->height = height; +} + +void +gimp_link_get_size (GimpLink *link, + gint *width, + gint *height) +{ + *width = link->p->width; + *height = link->p->height; +} + +gboolean +gimp_link_is_vector (GimpLink *link) +{ + return link->p->is_vector; +} + +GeglBuffer * +gimp_link_get_buffer (GimpLink *link, + GimpProgress *progress, + GError **error) +{ + GeglBuffer *buffer = NULL; + + if (link->p->file) + { + GimpImage *image; + GimpPDBStatusType status; + const gchar *mime_type = NULL; + + image = file_open_image (link->p->gimp, + gimp_get_user_context (link->p->gimp), + progress, + link->p->file, + link->p->width, link->p->height, + FALSE, NULL, + /* XXX We might want interactive opening + * for a first opening (when done through + * GUI), but not for every re-render. + */ + GIMP_RUN_NONINTERACTIVE, + &link->p->is_vector, + &status, &mime_type, error); + + if (image && status == GIMP_PDB_SUCCESS) + { + /* If we don't flush the projection first, the buffer may be empty. + * I do wonder if the flushing and updating of the link could + * not be multi-threaded with gimp_projection_flush() instead, + * then notifying the update through signals. For very heavy + * images, would it be a better UX? XXX + */ + gimp_projection_flush_now (gimp_image_get_projection (image), TRUE); + buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (image)); + g_object_ref (buffer); + } + + /* Only keep the buffer, free the rest. */ + g_clear_object (&image); + } + + link->p->broken = (buffer == NULL); + + return buffer; +} diff --git a/app/core/gimplink.h b/app/core/gimplink.h new file mode 100644 index 0000000000..c62d3c557c --- /dev/null +++ b/app/core/gimplink.h @@ -0,0 +1,79 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLink + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#ifndef __GIMP_LINK_H__ +#define __GIMP_LINK_H__ + + +#include "gimpitem.h" + + +#define GIMP_TYPE_LINK (gimp_link_get_type ()) +#define GIMP_LINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINK, GimpLink)) +#define GIMP_LINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINK, GimpLinkClass)) +#define GIMP_IS_LINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINK)) +#define GIMP_IS_LINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINK)) +#define GIMP_LINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINK, GimpLinkClass)) + + +typedef struct _GimpLinkClass GimpLinkClass; +typedef struct _GimpLinkPrivate GimpLinkPrivate; + +struct _GimpLink +{ + GimpObject parent_instance; + + GimpLinkPrivate *p; +}; + +struct _GimpLinkClass +{ + GimpObjectClass parent_class; + + void (* changed) (GimpLink *link); +}; + + +GType gimp_link_get_type (void) G_GNUC_CONST; + +GimpLink * gimp_link_new (Gimp *gimp, + GFile *file); + +GFile * gimp_link_get_file (GimpLink *link); +void gimp_link_set_file (GimpLink *layer, + GFile *file); + +gboolean gimp_link_is_broken (GimpLink *link, + gboolean recheck); +GimpLink * gimp_link_duplicate (GimpLink *link); + +void gimp_link_set_size (GimpLink *link, + gint width, + gint height); +void gimp_link_get_size (GimpLink *link, + gint *width, + gint *height); +gboolean gimp_link_is_vector (GimpLink *link); + +GeglBuffer * gimp_link_get_buffer (GimpLink *link, + GimpProgress *progress, + GError **error); + +#endif /* __GIMP_LINK_H__ */ diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c new file mode 100644 index 0000000000..e38653c013 --- /dev/null +++ b/app/core/gimplinklayer.c @@ -0,0 +1,830 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLinkLayer + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "core-types.h" + +#include "gegl/gimp-gegl-loops.h" +#include "gegl/gimp-gegl-utils.h" + +#include "gimp.h" +#include "gimpcontext.h" +#include "gimpimage.h" +#include "gimpimage-color-profile.h" +#include "gimpimage-undo.h" +#include "gimpimage-undo-push.h" +#include "gimpitemtree.h" +#include "gimpobjectqueue.h" +#include "gimpprogress.h" + +#include "gimplink.h" +#include "gimplinklayer.h" + +#include "gimp-intl.h" + + +enum +{ + LINK_LAYER_XCF_NONE = 0, + LINK_LAYER_XCF_DONT_AUTO_RENAME = 1 << 0, + LINK_LAYER_XCF_MODIFIED = 1 << 1 +}; + +enum +{ + PROP_0, + PROP_LINK, + PROP_AUTO_RENAME, + PROP_MODIFIED, + PROP_SCALED_ONLY +}; + +struct _GimpLinkLayerPrivate +{ + GimpLink *link; + gboolean modified; + gboolean scaled_only; + gboolean auto_rename; +}; + +static void gimp_link_layer_finalize (GObject *object); +static void gimp_link_layer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_link_layer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static gint64 gimp_link_layer_get_memsize (GimpObject *object, + gint64 *gui_size); + +static GimpItem * gimp_link_layer_duplicate (GimpItem *item, + GType new_type); +static gboolean gimp_link_layer_rename (GimpItem *item, + const gchar *new_name, + const gchar *undo_desc, + GError **error); +static void gimp_link_layer_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation_type, + GimpProgress *progress); + +static void gimp_link_layer_set_buffer (GimpDrawable *drawable, + gboolean push_undo, + const gchar *undo_desc, + GeglBuffer *buffer, + const GeglRectangle *bounds); +static void gimp_link_layer_push_undo (GimpDrawable *drawable, + const gchar *undo_desc, + GeglBuffer *buffer, + gint x, + gint y, + gint width, + gint height); + +static void gimp_link_layer_convert_type (GimpLayer *layer, + GimpImage *dest_image, + const Babl *new_format, + GimpColorProfile *src_profile, + GimpColorProfile *dest_profile, + GeglDitherMethod layer_dither_type, + GeglDitherMethod mask_dither_type, + gboolean push_undo, + GimpProgress *progress); + +static void gimp_link_layer_link_changed (GimpLinkLayer *layer); +static gboolean gimp_link_layer_render (GimpLinkLayer *layer); + +static void gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, + guint32 flags); + +G_DEFINE_TYPE_WITH_PRIVATE (GimpLinkLayer, gimp_link_layer, GIMP_TYPE_LAYER) + +#define parent_class gimp_link_layer_parent_class + + +static void +gimp_link_layer_class_init (GimpLinkLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + GimpItemClass *item_class = GIMP_ITEM_CLASS (klass); + GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass); + GimpLayerClass *layer_class = GIMP_LAYER_CLASS (klass); + + object_class->finalize = gimp_link_layer_finalize; + object_class->get_property = gimp_link_layer_get_property; + object_class->set_property = gimp_link_layer_set_property; + + gimp_object_class->get_memsize = gimp_link_layer_get_memsize; + + viewable_class->default_icon_name = "emblem-symbolic-link"; + + item_class->duplicate = gimp_link_layer_duplicate; + item_class->rename = gimp_link_layer_rename; + item_class->scale = gimp_link_layer_scale; + + item_class->default_name = _("Link Layer"); + item_class->rename_desc = _("Rename Link Layer"); + item_class->translate_desc = _("Move Link Layer"); + item_class->scale_desc = _("Scale Link Layer"); + item_class->resize_desc = _("Resize Link Layer"); + item_class->flip_desc = _("Flip Link Layer"); + item_class->rotate_desc = _("Rotate Link Layer"); + item_class->transform_desc = _("Transform Link Layer"); + + drawable_class->set_buffer = gimp_link_layer_set_buffer; + drawable_class->push_undo = gimp_link_layer_push_undo; + + layer_class->convert_type = gimp_link_layer_convert_type; + + g_object_class_install_property (object_class, PROP_LINK, + g_param_spec_object ("link", + NULL, NULL, + GIMP_TYPE_LINK, + GIMP_PARAM_READWRITE)); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_AUTO_RENAME, + "auto-rename", + NULL, NULL, + TRUE, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_MODIFIED, + "modified", + NULL, NULL, + FALSE, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SCALED_ONLY, + "scaled-only", + NULL, NULL, + FALSE, + GIMP_PARAM_STATIC_STRINGS); +} + +static void +gimp_link_layer_init (GimpLinkLayer *layer) +{ + layer->p = gimp_link_layer_get_instance_private (layer); + layer->p->link = NULL; +} + +static void +gimp_link_layer_finalize (GObject *object) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + + g_clear_object (&layer->p->link); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_link_layer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + + switch (property_id) + { + case PROP_LINK: + g_value_set_object (value, layer->p->link); + break; + case PROP_AUTO_RENAME: + g_value_set_boolean (value, layer->p->auto_rename); + break; + case PROP_MODIFIED: + g_value_set_boolean (value, layer->p->modified); + break; + case PROP_SCALED_ONLY: + g_value_set_boolean (value, layer->p->scaled_only); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_layer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + + switch (property_id) + { + case PROP_LINK: + gimp_link_layer_set_link (layer, g_value_get_object (value), TRUE); + break; + case PROP_AUTO_RENAME: + layer->p->auto_rename = g_value_get_boolean (value); + break; + case PROP_MODIFIED: + layer->p->modified = g_value_get_boolean (value); + break; + case PROP_SCALED_ONLY: + layer->p->scaled_only = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint64 +gimp_link_layer_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + gint64 memsize = 0; + + memsize += gimp_object_get_memsize (GIMP_OBJECT (layer->p->link), + gui_size); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static GimpItem * +gimp_link_layer_duplicate (GimpItem *item, + GType new_type) +{ + GimpItem *new_item; + + g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL); + + new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type); + + if (GIMP_IS_LINK_LAYER (new_item)) + { + GimpLinkLayer *layer = GIMP_LINK_LAYER (item); + GimpLinkLayer *new_layer = GIMP_LINK_LAYER (new_item); + + gimp_config_sync (G_OBJECT (layer), G_OBJECT (new_layer), 0); + + if (layer->p->link) + { + GimpLink *link = gimp_link_duplicate (layer->p->link); + + gimp_link_layer_set_link (new_layer, link, FALSE); + } + } + + return new_item; +} + +static gboolean +gimp_link_layer_rename (GimpItem *item, + const gchar *new_name, + const gchar *undo_desc, + GError **error) +{ + if (GIMP_ITEM_CLASS (parent_class)->rename (item, new_name, undo_desc, error)) + { + g_object_set (item, "auto-rename", FALSE, NULL); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_link_layer_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation_type, + GimpProgress *progress) +{ + GimpLinkLayer *link_layer = GIMP_LINK_LAYER (item); + GimpLayer *layer = GIMP_LAYER (item); + GimpObjectQueue *queue = NULL; + + if (progress && layer->mask) + { + GimpLayerMask *mask; + + queue = gimp_object_queue_new (progress); + progress = GIMP_PROGRESS (queue); + + /* temporarily set layer->mask to NULL, so that its size won't be counted + * when pushing the layer to the queue. + */ + mask = layer->mask; + layer->mask = NULL; + + gimp_object_queue_push (queue, layer); + gimp_object_queue_push (queue, mask); + + layer->mask = mask; + } + + if (queue) + gimp_object_queue_pop (queue); + + if (gimp_link_is_vector (link_layer->p->link) && ! link_layer->p->modified) + { + /* Non-modified vector images are always recomputed from the + * source file and therefore are always sharp. + */ + gimp_link_set_size (link_layer->p->link, new_width, new_height); + gimp_link_layer_render (link_layer); + } + else + { + gboolean scaled_only = FALSE; + + if (! link_layer->p->modified || link_layer->p->scaled_only) + { + /* Raster images whose only modification are previous scaling + * are scaled back from the source file. Though they are still + * considered "modified" after a scaling (unlike vector + * images) and therefore are demoted to work like normal + * layers, scaling is still special-cased for better image + * quality. + */ + gimp_link_layer_render (link_layer); + scaled_only = TRUE; + } + + GIMP_ITEM_CLASS (parent_class)->scale (GIMP_ITEM (layer), + new_width, new_height, + new_offset_x, new_offset_y, + interpolation_type, progress); + g_object_set (layer, "scaled-only", scaled_only, NULL); + } + + if (layer->mask) + { + if (queue) + gimp_object_queue_pop (queue); + + gimp_item_scale (GIMP_ITEM (layer->mask), + new_width, new_height, + new_offset_x, new_offset_y, + interpolation_type, progress); + } + + g_clear_object (&queue); +} + +static void +gimp_link_layer_set_buffer (GimpDrawable *drawable, + gboolean push_undo, + const gchar *undo_desc, + GeglBuffer *buffer, + const GeglRectangle *bounds) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable); + GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer)); + + if (push_undo && ! layer->p->modified) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE_MOD, + undo_desc); + + GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable, + push_undo, undo_desc, + buffer, + bounds); + + if (push_undo && ! layer->p->modified) + { + gimp_image_undo_push_link_layer (image, NULL, layer); + + g_object_set (drawable, "modified", TRUE, NULL); + g_object_set (drawable, "scaled-only", FALSE, NULL); + + gimp_image_undo_group_end (image); + } +} + +static void +gimp_link_layer_push_undo (GimpDrawable *drawable, + const gchar *undo_desc, + GeglBuffer *buffer, + gint x, + gint y, + gint width, + gint height) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable); + GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer)); + + if (! layer->p->modified) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE, undo_desc); + + GIMP_DRAWABLE_CLASS (parent_class)->push_undo (drawable, undo_desc, + buffer, + x, y, width, height); + + if (! layer->p->modified) + { + gimp_image_undo_push_link_layer (image, NULL, layer); + + g_object_set (drawable, "modified", TRUE, NULL); + + gimp_image_undo_group_end (image); + } +} + +static void +gimp_link_layer_convert_type (GimpLayer *layer, + GimpImage *dest_image, + const Babl *new_format, + GimpColorProfile *src_profile, + GimpColorProfile *dest_profile, + GeglDitherMethod layer_dither_type, + GeglDitherMethod mask_dither_type, + gboolean push_undo, + GimpProgress *progress) +{ + GimpLinkLayer *link_layer = GIMP_LINK_LAYER (layer); + GimpImage *image = gimp_item_get_image (GIMP_ITEM (link_layer)); + + if (! link_layer->p->link || + link_layer->p->modified || + layer_dither_type != GEGL_DITHER_NONE) + { + GIMP_LAYER_CLASS (parent_class)->convert_type (layer, dest_image, + new_format, + src_profile, + dest_profile, + layer_dither_type, + mask_dither_type, + push_undo, + progress); + } + else + { + if (push_undo) + gimp_image_undo_push_link_layer (image, NULL, link_layer); + + gimp_link_layer_render (link_layer); + } +} + + +/* public functions */ + +/** + * gimp_link_layer_new: + * @image: the #GimpImage the layer should belong to + * @text: a #GimpText object + * + * Creates a new link layer. + * + * Return value: a new #GimpLinkLayer or %NULL in case of a problem + **/ +GimpLayer * +gimp_link_layer_new (GimpImage *image, + GimpLink *link) +{ + GimpLinkLayer *layer; + + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + layer = + GIMP_LINK_LAYER (gimp_drawable_new (GIMP_TYPE_LINK_LAYER, + image, NULL, + 0, 0, 1, 1, + gimp_image_get_layer_format (image, + TRUE))); + gimp_layer_set_mode (GIMP_LAYER (layer), + gimp_image_get_default_new_layer_mode (image), + FALSE); + if (! gimp_link_layer_set_link (layer, link, FALSE)) + { + g_object_unref (layer); + return NULL; + } + + return GIMP_LAYER (layer); +} + +GimpLink * +gimp_link_layer_get_link (GimpLinkLayer *layer) +{ + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), NULL); + + return layer->p->link; +} + +gboolean +gimp_link_layer_set_link (GimpLinkLayer *layer, + GimpLink *link, + gboolean push_undo) +{ + gboolean rendered = FALSE; + + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE); + g_return_val_if_fail (GIMP_IS_LINK (link), FALSE); + + /* TODO: look deeper into the link paths. */ + if (layer->p->link == link) + return ! gimp_link_is_broken (link, FALSE); + + if (gimp_item_is_attached (GIMP_ITEM (layer)) && push_undo) + gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)), + _("Set layer link"), layer); + + if (layer->p->link) + { + g_signal_handlers_disconnect_by_func (layer->p->link, + G_CALLBACK (gimp_link_layer_link_changed), + layer); + + g_clear_object (&layer->p->link); + } + + if (link) + { + layer->p->link = g_object_ref (link); + + g_signal_connect_object (link, "changed", + G_CALLBACK (gimp_link_layer_link_changed), + layer, G_CONNECT_SWAPPED); + + g_object_set (layer, "modified", FALSE, NULL); + rendered = gimp_link_layer_render (layer); + } + + g_object_notify (G_OBJECT (layer), "link"); + gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer)); + + return rendered; +} + +/** + * gimp_link_layer_discard: + * @layer: a #GimpLinkLayer + * + * Discards the link. This makes @layer behave like a + * normal layer. + */ +void +gimp_link_layer_discard (GimpLinkLayer *layer) +{ + g_return_if_fail (GIMP_IS_LINK_LAYER (layer)); + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer))); + + gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)), + _("Discard Link"), layer); + + layer->p->modified = TRUE; +} + +void +gimp_link_layer_monitor (GimpLinkLayer *layer) +{ + g_return_if_fail (GIMP_IS_LINK_LAYER (layer)); + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer))); + + gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)), + _("Monitor Link"), layer); + + layer->p->modified = FALSE; + gimp_link_layer_render (layer); +} + +gboolean +gimp_item_is_link_layer (GimpItem *item) +{ + return (GIMP_IS_LINK_LAYER (item) && + ! GIMP_LINK_LAYER (item)->p->modified); +} + +guint32 +gimp_link_layer_get_xcf_flags (GimpLinkLayer *link_layer) +{ + guint flags = 0; + + g_return_val_if_fail (GIMP_IS_LINK_LAYER (link_layer), 0); + + if (! link_layer->p->auto_rename) + flags |= LINK_LAYER_XCF_DONT_AUTO_RENAME; + + if (link_layer->p->modified) + flags |= LINK_LAYER_XCF_MODIFIED; + + return flags; +} + +/** + * gimp_link_layer_from_layer: + * @layer: a #GimpLayer object + * @link: a #GimpLink object + * @flags: flags as retrieved from the XCF file. + * + * Converts a standard #GimpLayer into a #GimpLinkLayer. + * The new link layer takes ownership of the @link. + * The old @layer object is freed and replaced in-place by the new + * #GimpLinkLayer. + * + * This is a hack similar to the one used to load text layers from XCF, + * since at first they are loaded as normal layers, and only later + * promoted to link layers when the corresponding property is read from + * the file. + **/ +void +gimp_link_layer_from_layer (GimpLayer **layer, + GimpLink *link, + guint32 flags) +{ + GimpLinkLayer *link_layer; + GimpDrawable *drawable; + + g_return_if_fail (GIMP_IS_LAYER (*layer)); + g_return_if_fail (GIMP_IS_LINK (link)); + + link_layer = g_object_new (GIMP_TYPE_LINK_LAYER, + "image", gimp_item_get_image (GIMP_ITEM (*layer)), + NULL); + + gimp_item_replace_item (GIMP_ITEM (link_layer), GIMP_ITEM (*layer)); + + drawable = GIMP_DRAWABLE (link_layer); + gimp_drawable_steal_buffer (drawable, GIMP_DRAWABLE (*layer)); + + gimp_layer_set_opacity (GIMP_LAYER (link_layer), + gimp_layer_get_opacity (*layer), FALSE); + gimp_layer_set_mode (GIMP_LAYER (link_layer), + gimp_layer_get_mode (*layer), FALSE); + gimp_layer_set_blend_space (GIMP_LAYER (link_layer), + gimp_layer_get_blend_space (*layer), FALSE); + gimp_layer_set_composite_space (GIMP_LAYER (link_layer), + gimp_layer_get_composite_space (*layer), FALSE); + gimp_layer_set_composite_mode (GIMP_LAYER (link_layer), + gimp_layer_get_composite_mode (*layer), FALSE); + gimp_layer_set_lock_alpha (GIMP_LAYER (link_layer), + gimp_layer_get_lock_alpha (*layer), FALSE); + + gimp_link_layer_set_link (link_layer, link, FALSE); + gimp_link_layer_set_xcf_flags (link_layer, flags); + + g_object_unref (link); + g_object_unref (*layer); + + *layer = GIMP_LAYER (link_layer); +} + +/* private functions */ + +static void +gimp_link_layer_link_changed (GimpLinkLayer *layer) +{ + if (! layer->p->modified) + gimp_link_layer_render (layer); +} + +static gboolean +gimp_link_layer_render (GimpLinkLayer *layer) +{ + GimpDrawable *drawable; + GimpItem *item; + GimpImage *image; + GeglBuffer *buffer; + gdouble xres; + gdouble yres; + gint width; + gint height; + + if (! layer->p->link) + return FALSE; + + drawable = GIMP_DRAWABLE (layer); + item = GIMP_ITEM (layer); + image = gimp_item_get_image (item); + + gimp_image_get_resolution (image, &xres, &yres); + /* TODO: I could imagine a GimpBusyBox (to be made as GimpProgress) in + * the later list showing layer update. + */ + buffer = gimp_link_get_buffer (layer->p->link, NULL, NULL); + + if (! buffer) + return FALSE; + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + g_object_freeze_notify (G_OBJECT (drawable)); + + if ((width != gimp_item_get_width (item) || + height != gimp_item_get_height (item))) + { + GeglBuffer *new_buffer; + + new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height), + gimp_drawable_get_format (drawable)); + gimp_drawable_set_buffer (drawable, FALSE, NULL, new_buffer); + g_object_unref (new_buffer); + + if (gimp_layer_get_mask (GIMP_LAYER (layer))) + { + GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (layer)); + + static GimpContext *unused_eek = NULL; + + if (! unused_eek) + unused_eek = gimp_context_new (image->gimp, "eek", NULL); + + gimp_item_resize (GIMP_ITEM (mask), + unused_eek, GIMP_FILL_TRANSPARENT, + width, height, 0, 0); + } + } + + if (layer->p->auto_rename) + { + GimpItem *item = GIMP_ITEM (layer); + gchar *name = NULL; + + if (layer->p->link) + { + name = g_strdup (gimp_object_get_name (layer->p->link)); + } + + if (! name || ! name[0]) + { + g_free (name); + name = g_strdup (_("Link Layer")); + } + + if (gimp_item_is_attached (item)) + { + gimp_item_tree_rename_item (gimp_item_get_tree (item), item, + name, FALSE, NULL); + g_free (name); + } + else + { + gimp_object_take_name (GIMP_OBJECT (layer), name); + } + } + + gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, + gimp_drawable_get_buffer (drawable), NULL); + g_object_unref (buffer); + + g_object_thaw_notify (G_OBJECT (drawable)); + + gimp_drawable_update (drawable, 0, 0, width, height); + gimp_image_flush (image); + + return (width > 0 && height > 0); +} + +static void +gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, + guint32 flags) +{ + g_return_if_fail (GIMP_IS_LINK_LAYER (layer)); + + g_object_set (layer, + "auto-rename", (flags & LINK_LAYER_XCF_DONT_AUTO_RENAME) == 0, + "modified", (flags & LINK_LAYER_XCF_MODIFIED) != 0, + NULL); +} diff --git a/app/core/gimplinklayer.h b/app/core/gimplinklayer.h new file mode 100644 index 0000000000..379d7959c0 --- /dev/null +++ b/app/core/gimplinklayer.h @@ -0,0 +1,74 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLinkLayer + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#ifndef __GIMP_LINK_LAYER_H__ +#define __GIMP_LINK_LAYER_H__ + + +#include "gimplayer.h" + + +#define GIMP_TYPE_LINK_LAYER (gimp_link_layer_get_type ()) +#define GIMP_LINK_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINK_LAYER, GimpLinkLayer)) +#define GIMP_LINK_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINK_LAYER, GimpLinkLayerClass)) +#define GIMP_IS_LINK_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINK_LAYER)) +#define GIMP_IS_LINK_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINK_LAYER)) +#define GIMP_LINK_LAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINK_LAYER, GimpLinkLayerClass)) + + +typedef struct _GimpLinkLayerClass GimpLinkLayerClass; +typedef struct _GimpLinkLayerPrivate GimpLinkLayerPrivate; + +struct _GimpLinkLayer +{ + GimpLayer layer; + + GimpLinkLayerPrivate *p; +}; + +struct _GimpLinkLayerClass +{ + GimpLayerClass parent_class; +}; + + +GType gimp_link_layer_get_type (void) G_GNUC_CONST; + +GimpLayer * gimp_link_layer_new (GimpImage *image, + GimpLink *link); + +GimpLink * gimp_link_layer_get_link (GimpLinkLayer *layer); +gboolean gimp_link_layer_set_link (GimpLinkLayer *layer, + GimpLink *link, + gboolean push_undo); + +void gimp_link_layer_discard (GimpLinkLayer *layer); +void gimp_link_layer_monitor (GimpLinkLayer *layer); +gboolean gimp_item_is_link_layer (GimpItem *item); + + +/* Only to be used for XCF loading/saving. */ + +guint32 gimp_link_layer_get_xcf_flags (GimpLinkLayer *layer); +void gimp_link_layer_from_layer (GimpLayer **layer, + GimpLink *link, + guint32 flags); + +#endif /* __GIMP_LINK_LAYER_H__ */ diff --git a/app/core/gimplinklayerundo.c b/app/core/gimplinklayerundo.c new file mode 100644 index 0000000000..52e828ddbd --- /dev/null +++ b/app/core/gimplinklayerundo.c @@ -0,0 +1,198 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" + +#include "core-types.h" + +#include "gimpimage.h" +#include "gimplayer.h" +#include "gimplink.h" +#include "gimplinklayer.h" +#include "gimplinklayerundo.h" + + +enum +{ + PROP_0, + PROP_PREV_MODIFIED, + PROP_PREV_LINK +}; + + +static void gimp_link_layer_undo_constructed (GObject *object); +static void gimp_link_layer_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_link_layer_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint64 gimp_link_layer_undo_get_memsize (GimpObject *object, + gint64 *gui_size); + +static void gimp_link_layer_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum); + + +G_DEFINE_TYPE (GimpLinkLayerUndo, gimp_link_layer_undo, GIMP_TYPE_ITEM_UNDO) + +#define parent_class gimp_link_layer_undo_parent_class + + +static void +gimp_link_layer_undo_class_init (GimpLinkLayerUndoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass); + + object_class->constructed = gimp_link_layer_undo_constructed; + object_class->set_property = gimp_link_layer_undo_set_property; + object_class->get_property = gimp_link_layer_undo_get_property; + + gimp_object_class->get_memsize = gimp_link_layer_undo_get_memsize; + + undo_class->pop = gimp_link_layer_undo_pop; + + g_object_class_install_property (object_class, PROP_PREV_MODIFIED, + g_param_spec_boolean ("prev-modified", NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PREV_LINK, + g_param_spec_object ("prev-link", + NULL, NULL, + GIMP_TYPE_LINK, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_link_layer_undo_init (GimpLinkLayerUndo *undo) +{ +} + +static void +gimp_link_layer_undo_constructed (GObject *object) +{ + GimpLinkLayerUndo *undo = GIMP_LINK_LAYER_UNDO (object); + GimpLinkLayer *layer; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_LINK_LAYER (GIMP_ITEM_UNDO (object)->item)); + + layer = GIMP_LINK_LAYER (GIMP_ITEM_UNDO (undo)->item); + + undo->link = gimp_link_layer_get_link (layer); + undo->modified = ! gimp_item_is_link_layer (GIMP_ITEM (layer)); +} + +static void +gimp_link_layer_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayerUndo *undo = GIMP_LINK_LAYER_UNDO (object); + + switch (property_id) + { + case PROP_PREV_MODIFIED: + undo->modified = g_value_get_boolean (value); + break; + case PROP_PREV_LINK: + undo->link = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_layer_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayerUndo *undo = GIMP_LINK_LAYER_UNDO (object); + + switch (property_id) + { + case PROP_PREV_MODIFIED: + g_value_set_boolean (value, undo->modified); + break; + case PROP_PREV_LINK: + g_value_set_object (value, undo->link); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint64 +gimp_link_layer_undo_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object); + gint64 memsize = 0; + + if (! gimp_item_is_attached (item_undo->item)) + memsize += gimp_object_get_memsize (GIMP_OBJECT (item_undo->item), + gui_size); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static void +gimp_link_layer_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum) +{ + GimpLinkLayerUndo *layer_undo = GIMP_LINK_LAYER_UNDO (undo); + GimpLinkLayer *layer = GIMP_LINK_LAYER (GIMP_ITEM_UNDO (undo)->item); + GimpLink *link; + gboolean modified; + + GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum); + + modified = ! gimp_item_is_link_layer (GIMP_ITEM (layer)); + link = gimp_link_layer_get_link (layer); + + gimp_link_layer_set_link (layer, layer_undo->link, FALSE); + g_object_set (layer, "modified", layer_undo->modified, NULL); + + layer_undo->modified = modified; + layer_undo->link = link; +} diff --git a/app/core/gimplinklayerundo.h b/app/core/gimplinklayerundo.h new file mode 100644 index 0000000000..7428bce3bf --- /dev/null +++ b/app/core/gimplinklayerundo.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#ifndef __GIMP_LINK_LAYER_UNDO_H__ +#define __GIMP_LINK_LAYER_UNDO_H__ + + +#include "gimpitemundo.h" + + +#define GIMP_TYPE_LINK_LAYER_UNDO (gimp_link_layer_undo_get_type ()) +#define GIMP_LINK_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINK_LAYER_UNDO, GimpLinkLayerUndo)) +#define GIMP_LINK_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINK_LAYER_UNDO, GimpLinkLayerUndoClass)) +#define GIMP_IS_LINK_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINK_LAYER_UNDO)) +#define GIMP_IS_LINK_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINK_LAYER_UNDO)) +#define GIMP_LINK_LAYER_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINK_LAYER_UNDO, GimpLinkLayerUndoClass)) + + +typedef struct _GimpLinkLayerUndo GimpLinkLayerUndo; +typedef struct _GimpLinkLayerUndoClass GimpLinkLayerUndoClass; + +struct _GimpLinkLayerUndo +{ + GimpItemUndo parent_instance; + + gboolean modified; + GimpLink *link; +}; + +struct _GimpLinkLayerUndoClass +{ + GimpItemUndoClass parent_class; +}; + + +GType gimp_link_layer_undo_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_LINK_LAYER_UNDO_H__ */ + diff --git a/app/core/meson.build b/app/core/meson.build index a7430dafc0..eefb789df1 100644 --- a/app/core/meson.build +++ b/app/core/meson.build @@ -193,6 +193,9 @@ libappcore_sources = [ 'gimplayerstack.c', 'gimplayerundo.c', 'gimplineart.c', + 'gimplink.c', + 'gimplinklayer.c', + 'gimplinklayerundo.c', 'gimplist.c', 'gimpmaskundo.c', 'gimpmybrush-load.c', diff --git a/app/dialogs/item-options-dialog.c b/app/dialogs/item-options-dialog.c index 3413d7920d..f6d7f05b04 100644 --- a/app/dialogs/item-options-dialog.c +++ b/app/dialogs/item-options-dialog.c @@ -322,6 +322,21 @@ item_options_dialog_get_vbox (GtkWidget *dialog) return private->left_vbox; } +GtkWidget * +item_options_dialog_get_right_vbox (GtkWidget *dialog) +{ + ItemOptionsDialog *private; + + g_return_val_if_fail (GIMP_IS_VIEWABLE_DIALOG (dialog), NULL); + + private = g_object_get_data (G_OBJECT (dialog), + "item-options-dialog-private"); + + g_return_val_if_fail (private != NULL, NULL); + + return private->right_vbox; +} + GtkWidget * item_options_dialog_get_grid (GtkWidget *dialog, gint *next_row) diff --git a/app/dialogs/item-options-dialog.h b/app/dialogs/item-options-dialog.h index 15d7b2e205..1b56aee329 100644 --- a/app/dialogs/item-options-dialog.h +++ b/app/dialogs/item-options-dialog.h @@ -56,6 +56,7 @@ GtkWidget * item_options_dialog_new (GimpImage *image, gpointer user_data); GtkWidget * item_options_dialog_get_vbox (GtkWidget *dialog); +GtkWidget * item_options_dialog_get_right_vbox (GtkWidget *dialog); GtkWidget * item_options_dialog_get_grid (GtkWidget *dialog, gint *next_row); GtkWidget * item_options_dialog_get_name_entry (GtkWidget *dialog); diff --git a/app/dialogs/layer-options-dialog.c b/app/dialogs/layer-options-dialog.c index afcd354537..caf524baf1 100644 --- a/app/dialogs/layer-options-dialog.c +++ b/app/dialogs/layer-options-dialog.c @@ -32,12 +32,15 @@ #include "core/gimpdrawable-filters.h" #include "core/gimpimage.h" #include "core/gimplayer.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "text/gimptext.h" #include "text/gimptextlayer.h" #include "widgets/gimpcontainertreeview.h" #include "widgets/gimplayermodebox.h" +#include "widgets/gimpopendialog.h" #include "widgets/gimpviewabledialog.h" #include "item-options-dialog.h" @@ -50,6 +53,7 @@ typedef struct _LayerOptionsDialog LayerOptionsDialog; struct _LayerOptionsDialog { + Gimp *gimp; GimpLayer *layer; GimpLayerMode mode; GimpLayerColorSpace blend_space; @@ -68,6 +72,8 @@ struct _LayerOptionsDialog GtkWidget *composite_mode_combo; GtkWidget *size_se; GtkWidget *offset_se; + + GimpLink *link; }; @@ -94,6 +100,11 @@ static void layer_options_dialog_rename_toggled (GtkWidget *widget, LayerOptionsDialog *private); +static void layer_options_fill_changed (GtkWidget *combo, + GtkWidget *file_select); +static void layer_options_file_set (GtkFileChooserButton *widget, + LayerOptionsDialog *private); + /* public functions */ GtkWidget * @@ -127,6 +138,7 @@ layer_options_dialog_new (GimpImage *image, GtkWidget *grid; GtkListStore *space_model; GtkWidget *combo; + GtkWidget *file_select; GtkWidget *scale; GtkWidget *label; GtkAdjustment *adjustment; @@ -144,6 +156,7 @@ layer_options_dialog_new (GimpImage *image, private = g_slice_new0 (LayerOptionsDialog); + private->gimp = image->gimp; private->layer = layer; private->mode = layer_mode; private->blend_space = layer_blend_space; @@ -374,15 +387,35 @@ layer_options_dialog_new (GimpImage *image, if (! layer) { + GtkWidget *right_vbox = item_options_dialog_get_right_vbox (dialog); + GtkWidget *open_dialog; + /* The fill type */ combo = gimp_enum_combo_box_new (GIMP_TYPE_FILL_TYPE); - gimp_grid_attach_aligned (GTK_GRID (grid), 0, row++, + gimp_grid_attach_aligned (GTK_GRID (grid), 0, row, _("_Fill with:"), 0.0, 0.5, combo, 1); + + /* File chooser dialog. */ + open_dialog = gimp_open_dialog_new (private->gimp); + gtk_window_set_title (GTK_WINDOW (open_dialog), + _("Select Linked Image")); + + /* File chooser button. */ + file_select = gtk_file_chooser_button_new_with_dialog (open_dialog); + gtk_box_pack_end (GTK_BOX (right_vbox), file_select, FALSE, FALSE, 1); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), private->fill_type, G_CALLBACK (gimp_int_combo_box_get_active), &private->fill_type, NULL); + g_signal_connect (combo, "changed", + G_CALLBACK (layer_options_fill_changed), + file_select); + g_signal_connect (file_select, "file-set", + G_CALLBACK (layer_options_file_set), + private); + layer_options_fill_changed (combo, file_select); } if (layer) @@ -402,6 +435,31 @@ layer_options_dialog_new (GimpImage *image, GIMP_VIEW_SIZE_SMALL, 0); gtk_container_add (GTK_CONTAINER (frame), view); gtk_widget_show (view); + + if (GIMP_IS_LINK_LAYER (layer)) + { + GtkWidget *open_dialog; + GimpLink *link; + + /* File chooser dialog. */ + open_dialog = gimp_open_dialog_new (private->gimp); + gtk_window_set_title (GTK_WINDOW (open_dialog), + _("Select Linked Image")); + + /* File chooser button. */ + file_select = gtk_file_chooser_button_new_with_dialog (open_dialog); + link = gimp_link_layer_get_link (GIMP_LINK_LAYER (layer)); + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (file_select), + gimp_link_get_file (link), NULL); + gtk_widget_show (file_select); + gimp_grid_attach_aligned (GTK_GRID (grid), 0, row++, + _("_Linked image:"), 0.0, 0.5, + file_select, 1); + + g_signal_connect (file_select, "file-set", + G_CALLBACK (layer_options_file_set), + private); + } } button = item_options_dialog_get_lock_position (dialog); @@ -502,6 +560,7 @@ layer_options_dialog_callback (GtkWidget *dialog, private->composite_mode, private->opacity / 100.0, private->fill_type, + private->link, width, height, offset_x, @@ -574,3 +633,44 @@ layer_options_dialog_rename_toggled (GtkWidget *widget, } } } + +static void +layer_options_fill_changed (GtkWidget *combo, + GtkWidget *file_select) +{ + gint value = GIMP_FILL_FOREGROUND; + + g_return_if_fail (GIMP_IS_ENUM_COMBO_BOX (combo)); + g_return_if_fail (GTK_IS_WIDGET (file_select)); + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &value); + gtk_widget_set_visible (file_select, (value == GIMP_FILL_LINK)); +} + +static void +layer_options_file_set (GtkFileChooserButton *widget, + LayerOptionsDialog *private) +{ + GimpLink *link = NULL; + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget)); + if (file) + { + link = gimp_link_new (private->gimp, file); + if (gimp_link_is_broken (link, TRUE)) + { + g_clear_object (&link); + g_signal_handlers_block_by_func (widget, + G_CALLBACK (layer_options_file_set), + private); + gtk_file_chooser_unselect_file (GTK_FILE_CHOOSER (widget), file); + g_signal_handlers_unblock_by_func (widget, + G_CALLBACK (layer_options_file_set), + private); + } + } + g_clear_object (&file); + + private->link = link; +} diff --git a/app/dialogs/layer-options-dialog.h b/app/dialogs/layer-options-dialog.h index 0cac5e8013..5fbf9a2b84 100644 --- a/app/dialogs/layer-options-dialog.h +++ b/app/dialogs/layer-options-dialog.h @@ -30,6 +30,7 @@ typedef void (* GimpLayerOptionsCallback) (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, diff --git a/app/file/file-open.c b/app/file/file-open.c index c6dd4eb05f..2137612c66 100644 --- a/app/file/file-open.c +++ b/app/file/file-open.c @@ -76,6 +76,7 @@ file_open_image (Gimp *gimp, gboolean as_new, GimpPlugInProcedure *file_proc, GimpRunMode run_mode, + gboolean *file_proc_handles_vector, GimpPDBStatusType *status, const gchar **mime_type, GError **error) @@ -210,6 +211,9 @@ file_open_image (Gimp *gimp, return NULL; } + if (file_proc_handles_vector) + *file_proc_handles_vector = file_proc->handles_vector; + if (progress) g_object_add_weak_pointer (G_OBJECT (progress), (gpointer) &progress); @@ -534,6 +538,7 @@ file_open_with_proc_and_display (Gimp *gimp, as_new, file_proc, run_mode, + NULL, status, &mime_type, error); @@ -636,7 +641,7 @@ file_open_layers (Gimp *gimp, FALSE, file_proc, run_mode, - status, &mime_type, error); + NULL, status, &mime_type, error); if (new_image) { diff --git a/app/file/file-open.h b/app/file/file-open.h index 600af8b58a..2c50571fa6 100644 --- a/app/file/file-open.h +++ b/app/file/file-open.h @@ -30,6 +30,7 @@ GimpImage * file_open_image (Gimp *gimp, gboolean as_new, GimpPlugInProcedure *file_proc, GimpRunMode run_mode, + gboolean *file_proc_handles_vector, GimpPDBStatusType *status, const gchar **mime_type, GError **error); diff --git a/app/tests/test-save-and-export.c b/app/tests/test-save-and-export.c index 73e1d36193..ffaf325252 100644 --- a/app/tests/test-save-and-export.c +++ b/app/tests/test-save-and-export.c @@ -122,6 +122,7 @@ opened_xcf_file_files (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); @@ -163,6 +164,7 @@ imported_file_files (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); @@ -212,6 +214,7 @@ saved_imported_file_files (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); @@ -320,6 +323,7 @@ clear_import_file_after_export (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); diff --git a/app/tests/test-xcf.c b/app/tests/test-xcf.c index c4138bf583..161bd6caa3 100644 --- a/app/tests/test-xcf.c +++ b/app/tests/test-xcf.c @@ -282,6 +282,7 @@ gimp_test_load_image (Gimp *gimp, FALSE /*as_new*/, proc, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &unused /*status*/, NULL /*mime_type*/, NULL /*error*/); diff --git a/app/widgets/gimpviewrendererlayer.c b/app/widgets/gimpviewrendererlayer.c index 6c4c6cb5ee..f5266bf0fe 100644 --- a/app/widgets/gimpviewrendererlayer.c +++ b/app/widgets/gimpviewrendererlayer.c @@ -32,6 +32,7 @@ #include "core/gimp.h" #include "core/gimpcontainer.h" #include "core/gimpimage.h" +#include "core/gimplinklayer.h" #include "text/gimptextlayer.h" @@ -71,7 +72,8 @@ gimp_view_renderer_layer_render (GimpViewRenderer *renderer, { icon_name = GIMP_ICON_LAYER_FLOATING_SELECTION; } - else if (gimp_item_is_text_layer (GIMP_ITEM (renderer->viewable))) + else if (gimp_item_is_text_layer (GIMP_ITEM (renderer->viewable)) || + gimp_item_is_link_layer (GIMP_ITEM (renderer->viewable))) { icon_name = gimp_viewable_get_icon_name (renderer->viewable); } diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c index a530db281c..f880459358 100644 --- a/app/xcf/xcf-load.c +++ b/app/xcf/xcf-load.c @@ -59,6 +59,8 @@ #include "core/gimplayer-floating-selection.h" #include "core/gimplayer-new.h" #include "core/gimplayermask.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "core/gimpparasitelist.h" #include "core/gimpprogress.h" #include "core/gimpselection.h" @@ -131,7 +133,8 @@ static gboolean xcf_load_layer_props (XcfInfo *info, static gboolean xcf_check_layer_props (XcfInfo *info, GList **item_path, gboolean *is_group_layer, - gboolean *is_text_layer); + gboolean *is_text_layer, + gboolean *is_link_layer); static gboolean xcf_load_channel_props (XcfInfo *info, GimpImage *image, GimpChannel **channel, @@ -1961,6 +1964,30 @@ xcf_load_layer_props (XcfInfo *info, xcf_read_int32 (info, text_layer_flags, 1); break; + case PROP_LINK_LAYER_DATA: + { + GimpLink *link; + gchar *path; + guint32 flags; + gboolean is_selected_layer; + + xcf_read_int32 (info, &flags, 1); + xcf_read_string (info, &path, 1); + + link = gimp_link_new (info->gimp, g_file_new_for_path (path)); + g_free (path); + + is_selected_layer = (g_list_find (info->selected_layers, *layer ) != NULL); + if (is_selected_layer) + info->selected_layers = g_list_remove (info->selected_layers, *layer); + + gimp_link_layer_from_layer (layer, link, flags); + + if (is_selected_layer) + info->selected_layers = g_list_prepend (info->selected_layers, *layer); + } + break; + case PROP_GROUP_ITEM: { GimpLayer *group; @@ -2062,13 +2089,15 @@ static gboolean xcf_check_layer_props (XcfInfo *info, GList **item_path, gboolean *is_group_layer, - gboolean *is_text_layer) + gboolean *is_text_layer, + gboolean *is_link_layer) { PropType prop_type; guint32 prop_size; g_return_val_if_fail (*is_group_layer == FALSE, FALSE); g_return_val_if_fail (*is_text_layer == FALSE, FALSE); + g_return_val_if_fail (*is_link_layer == TRUE, FALSE); while (TRUE) { @@ -2087,6 +2116,13 @@ xcf_check_layer_props (XcfInfo *info, return FALSE; break; + case PROP_LINK_LAYER_DATA: + *is_link_layer = TRUE; + + if (! xcf_skip_unknown_prop (info, prop_size)) + return FALSE; + break; + case PROP_GROUP_ITEM: case PROP_GROUP_ITEM_FLAGS: *is_group_layer = TRUE; @@ -3095,16 +3131,18 @@ xcf_load_layer (XcfInfo *info, { gboolean is_group_layer = FALSE; gboolean is_text_layer = FALSE; + gboolean is_link_layer = FALSE; goffset saved_pos; saved_pos = info->cp; /* Load item path and check if this is a group or text layer. */ - xcf_check_layer_props (info, item_path, &is_group_layer, &is_text_layer); - if ((is_text_layer || is_group_layer) && + xcf_check_layer_props (info, item_path, &is_group_layer, + &is_text_layer, &is_link_layer); + if ((is_text_layer || is_group_layer || is_link_layer) && xcf_seek_pos (info, saved_pos, NULL)) { /* Something is wrong, but leave a chance to the layer because - * anyway group and text layer depends on their contents. + * anyway group, text and link layer depends on their contents. */ width = height = 1; g_clear_pointer (item_path, g_list_free); diff --git a/app/xcf/xcf-private.h b/app/xcf/xcf-private.h index 944b0388c9..a65435207e 100644 --- a/app/xcf/xcf-private.h +++ b/app/xcf/xcf-private.h @@ -72,7 +72,8 @@ typedef enum PROP_SELECTED_PATH = 43, PROP_FILTER_REGION = 44, PROP_FILTER_ARGUMENT = 45, - PROP_FILTER_CLIP = 46 + PROP_FILTER_CLIP = 46, + PROP_LINK_LAYER_DATA = 47, } PropType; typedef enum diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c index 39b162b7a6..80c3841e52 100644 --- a/app/xcf/xcf-save.c +++ b/app/xcf/xcf-save.c @@ -54,6 +54,8 @@ #include "core/gimpitemlist.h" #include "core/gimplayer.h" #include "core/gimplayermask.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "core/gimplist.h" #include "core/gimpparasitelist.h" #include "core/gimpprogress.h" @@ -721,6 +723,9 @@ xcf_save_layer_props (XcfInfo *info, flags), ;); } + if (GIMP_IS_LINK_LAYER (layer)) + xcf_check_error (xcf_save_prop (info, image, PROP_LINK_LAYER_DATA, error, layer), ;); + if (gimp_viewable_get_children (GIMP_VIEWABLE (layer))) { gint32 flags = 0; @@ -1664,6 +1669,27 @@ xcf_save_prop (XcfInfo *info, } break; + case PROP_LINK_LAYER_DATA: + { + GimpLinkLayer *layer = va_arg (args, GimpLinkLayer *); + GFile *file; + const gchar *path; + guint32 flags; + + flags = gimp_link_layer_get_xcf_flags (layer); + file = gimp_link_get_file (gimp_link_layer_get_link (layer)); + path = g_file_peek_path (file); + + size = 4 + strlen (path) ? strlen (path) + 5 : 4; + + xcf_write_prop_type_check_error (info, prop_type, va_end (args)); + xcf_write_int32_check_error (info, &size, 1, va_end (args)); + + xcf_write_int32_check_error (info, &flags, 1, va_end (args)); + xcf_write_string_check_error (info, (gchar **) &path, 1, va_end (args)); + } + break; + case PROP_ITEM_PATH: { GList *path = va_arg (args, GList *); diff --git a/libgimpbase/gimpbaseenums.c b/libgimpbase/gimpbaseenums.c index eee17911a9..b7e5c0ef86 100644 --- a/libgimpbase/gimpbaseenums.c +++ b/libgimpbase/gimpbaseenums.c @@ -511,6 +511,7 @@ gimp_fill_type_get_type (void) { GIMP_FILL_WHITE, "GIMP_FILL_WHITE", "white" }, { GIMP_FILL_TRANSPARENT, "GIMP_FILL_TRANSPARENT", "transparent" }, { GIMP_FILL_PATTERN, "GIMP_FILL_PATTERN", "pattern" }, + { GIMP_FILL_LINK, "GIMP_FILL_LINK", "link" }, { 0, NULL, NULL } }; @@ -522,6 +523,7 @@ gimp_fill_type_get_type (void) { GIMP_FILL_WHITE, NC_("fill-type", "White"), NULL }, { GIMP_FILL_TRANSPARENT, NC_("fill-type", "Transparency"), NULL }, { GIMP_FILL_PATTERN, NC_("fill-type", "Pattern"), NULL }, + { GIMP_FILL_LINK, NC_("fill-type", "Image link"), NULL }, { 0, NULL, NULL } }; diff --git a/libgimpbase/gimpbaseenums.h b/libgimpbase/gimpbaseenums.h index 5960533e9f..72cc5529ae 100644 --- a/libgimpbase/gimpbaseenums.h +++ b/libgimpbase/gimpbaseenums.h @@ -368,6 +368,7 @@ typedef enum * @GIMP_FILL_WHITE: White * @GIMP_FILL_TRANSPARENT: Transparency * @GIMP_FILL_PATTERN: Pattern + * @GIMP_FILL_LINK: Image link * * Types of filling. **/ @@ -382,7 +383,8 @@ typedef enum GIMP_FILL_CIELAB_MIDDLE_GRAY, /*< desc="Middle Gray (CIELAB)" >*/ GIMP_FILL_WHITE, /*< desc="White" >*/ GIMP_FILL_TRANSPARENT, /*< desc="Transparency" >*/ - GIMP_FILL_PATTERN /*< desc="Pattern" >*/ + GIMP_FILL_PATTERN, /*< desc="Pattern" >*/ + GIMP_FILL_LINK /*< desc="Image link" >*/ } GimpFillType; diff --git a/menus/layers-menu.ui b/menus/layers-menu.ui index e780d64387..8027f58002 100644 --- a/menus/layers-menu.ui +++ b/menus/layers-menu.ui @@ -58,6 +58,10 @@ app.layers-merge-group app.layers-delete +
+ app.layers-link-discard + app.layers-link-monitor +
app.layers-text-discard app.layers-text-to-vectors diff --git a/pdb/enums.pl b/pdb/enums.pl index e34077c828..c9be03859f 100644 --- a/pdb/enums.pl +++ b/pdb/enums.pl @@ -168,13 +168,15 @@ package Gimp::CodeGen::enums; header => 'libgimpbase/gimpbaseenums.h', symbols => [ qw(GIMP_FILL_FOREGROUND GIMP_FILL_BACKGROUND GIMP_FILL_CIELAB_MIDDLE_GRAY GIMP_FILL_WHITE - GIMP_FILL_TRANSPARENT GIMP_FILL_PATTERN) ], + GIMP_FILL_TRANSPARENT GIMP_FILL_PATTERN + GIMP_FILL_LINK) ], mapping => { GIMP_FILL_FOREGROUND => '0', GIMP_FILL_BACKGROUND => '1', GIMP_FILL_CIELAB_MIDDLE_GRAY => '2', GIMP_FILL_WHITE => '3', GIMP_FILL_TRANSPARENT => '4', - GIMP_FILL_PATTERN => '5' } + GIMP_FILL_PATTERN => '5', + GIMP_FILL_LINK => '6' } }, GimpForegroundExtractMode => { contig => 1,