/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "libgimpbase/gimpbase.h" #include "libgimpwidgets/gimpwidgets.h" #include "display-types.h" #include "core/gimp.h" #include "core/gimp-edit.h" #include "core/gimpbuffer.h" #include "core/gimpdrawable-edit.h" #include "core/gimpfilloptions.h" #include "core/gimpimage.h" #include "core/gimpimage-new.h" #include "core/gimpimage-undo.h" #include "core/gimplayer.h" #include "core/gimplayer-new.h" #include "core/gimplayermask.h" #include "core/gimppattern.h" #include "core/gimpprogress.h" #include "file/file-open.h" #include "text/gimptext.h" #include "text/gimptextlayer.h" #include "vectors/gimpvectors.h" #include "vectors/gimpvectors-import.h" #include "widgets/gimpdnd.h" #include "gimpdisplay.h" #include "gimpdisplayshell.h" #include "gimpdisplayshell-dnd.h" #include "gimpdisplayshell-transform.h" #include "gimp-log.h" #include "gimp-intl.h" /* local function prototypes */ static void gimp_display_shell_drop_drawable (GtkWidget *widget, gint x, gint y, GimpViewable *viewable, gpointer data); static void gimp_display_shell_drop_vectors (GtkWidget *widget, gint x, gint y, GimpViewable *viewable, gpointer data); static void gimp_display_shell_drop_svg (GtkWidget *widget, gint x, gint y, const guchar *svg_data, gsize svg_data_length, gpointer data); static void gimp_display_shell_drop_pattern (GtkWidget *widget, gint x, gint y, GimpViewable *viewable, gpointer data); static void gimp_display_shell_drop_color (GtkWidget *widget, gint x, gint y, const GimpRGB *color, gpointer data); static void gimp_display_shell_drop_buffer (GtkWidget *widget, gint x, gint y, GimpViewable *viewable, gpointer data); static void gimp_display_shell_drop_uri_list (GtkWidget *widget, gint x, gint y, GList *uri_list, gpointer data); static void gimp_display_shell_drop_component (GtkWidget *widget, gint x, gint y, GimpImage *image, GimpChannelType component, gpointer data); static void gimp_display_shell_drop_pixbuf (GtkWidget *widget, gint x, gint y, GdkPixbuf *pixbuf, gpointer data); /* public functions */ void gimp_display_shell_dnd_init (GimpDisplayShell *shell) { g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_LAYER, gimp_display_shell_drop_drawable, shell); gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_LAYER_MASK, gimp_display_shell_drop_drawable, shell); gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_CHANNEL, gimp_display_shell_drop_drawable, shell); gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_VECTORS, gimp_display_shell_drop_vectors, shell); gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_PATTERN, gimp_display_shell_drop_pattern, shell); gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_BUFFER, gimp_display_shell_drop_buffer, shell); gimp_dnd_color_dest_add (shell->canvas, gimp_display_shell_drop_color, shell); gimp_dnd_component_dest_add (shell->canvas, gimp_display_shell_drop_component, shell); gimp_dnd_uri_list_dest_add (shell->canvas, gimp_display_shell_drop_uri_list, shell); gimp_dnd_svg_dest_add (shell->canvas, gimp_display_shell_drop_svg, shell); gimp_dnd_pixbuf_dest_add (shell->canvas, gimp_display_shell_drop_pixbuf, shell); } /* private functions */ /* * Position the dropped item in the middle of the viewport. */ static void gimp_display_shell_dnd_position_item (GimpDisplayShell *shell, GimpImage *image, GimpItem *item) { gint item_width = gimp_item_get_width (item); gint item_height = gimp_item_get_height (item); gint off_x, off_y; if (item_width >= gimp_image_get_width (image) && item_height >= gimp_image_get_height (image)) { off_x = (gimp_image_get_width (image) - item_width) / 2; off_y = (gimp_image_get_height (image) - item_height) / 2; } else { gint x, y; gint width, height; gimp_display_shell_untransform_viewport ( shell, ! gimp_display_shell_get_infinite_canvas (shell), &x, &y, &width, &height); off_x = x + (width - item_width) / 2; off_y = y + (height - item_height) / 2; } gimp_item_translate (item, off_x - gimp_item_get_offset_x (item), off_y - gimp_item_get_offset_y (item), FALSE); } static void gimp_display_shell_dnd_flush (GimpDisplayShell *shell, GimpImage *image) { gimp_display_shell_present (shell); gimp_image_flush (image); gimp_context_set_display (gimp_get_user_context (shell->display->gimp), shell->display); } static void gimp_display_shell_drop_drawable (GtkWidget *widget, gint x, gint y, GimpViewable *viewable, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpImage *image = gimp_display_get_image (shell->display); GType new_type; GimpItem *new_item; GIMP_LOG (DND, NULL); if (shell->display->gimp->busy) return; if (! image) { image = gimp_image_new_from_drawable (shell->display->gimp, GIMP_DRAWABLE (viewable)); gimp_create_display (shell->display->gimp, image, GIMP_UNIT_PIXEL, 1.0, G_OBJECT (gtk_widget_get_screen (widget)), gimp_widget_get_monitor (widget)); g_object_unref (image); return; } if (GIMP_IS_LAYER (viewable)) new_type = G_TYPE_FROM_INSTANCE (viewable); else new_type = GIMP_TYPE_LAYER; new_item = gimp_item_convert (GIMP_ITEM (viewable), image, new_type); if (new_item) { GimpLayer *new_layer = GIMP_LAYER (new_item); gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, _("Drop New Layer")); gimp_display_shell_dnd_position_item (shell, image, new_item); gimp_item_set_visible (new_item, TRUE, FALSE); gimp_item_set_linked (new_item, FALSE, FALSE); gimp_image_add_layer (image, new_layer, GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); gimp_image_undo_group_end (image); gimp_display_shell_dnd_flush (shell, image); } } static void gimp_display_shell_drop_vectors (GtkWidget *widget, gint x, gint y, GimpViewable *viewable, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpImage *image = gimp_display_get_image (shell->display); GimpItem *new_item; GIMP_LOG (DND, NULL); if (shell->display->gimp->busy) return; if (! image) return; new_item = gimp_item_convert (GIMP_ITEM (viewable), image, G_TYPE_FROM_INSTANCE (viewable)); if (new_item) { GimpVectors *new_vectors = GIMP_VECTORS (new_item); gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, _("Drop New Path")); gimp_image_add_vectors (image, new_vectors, GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); gimp_image_undo_group_end (image); gimp_display_shell_dnd_flush (shell, image); } } static void gimp_display_shell_drop_svg (GtkWidget *widget, gint x, gint y, const guchar *svg_data, gsize svg_data_len, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpImage *image = gimp_display_get_image (shell->display); GError *error = NULL; GIMP_LOG (DND, NULL); if (shell->display->gimp->busy) return; if (! image) return; if (! gimp_vectors_import_buffer (image, (const gchar *) svg_data, svg_data_len, TRUE, FALSE, GIMP_IMAGE_ACTIVE_PARENT, -1, NULL, &error)) { gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), GIMP_MESSAGE_ERROR, error->message); g_clear_error (&error); } else { gimp_display_shell_dnd_flush (shell, image); } } static void gimp_display_shell_dnd_fill (GimpDisplayShell *shell, GimpFillOptions *options, const gchar *undo_desc) { GimpImage *image = gimp_display_get_image (shell->display); GimpDrawable *drawable; if (shell->display->gimp->busy) return; if (! image) return; drawable = gimp_image_get_active_drawable (image); if (! drawable) return; if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable))) { gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), GIMP_MESSAGE_ERROR, _("Cannot modify the pixels of layer groups.")); return; } if (gimp_item_is_content_locked (GIMP_ITEM (drawable))) { gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), GIMP_MESSAGE_ERROR, _("The active layer's pixels are locked.")); return; } /* FIXME: there should be a virtual method for this that the * GimpTextLayer can override. */ if (gimp_fill_options_get_style (options) == GIMP_FILL_STYLE_SOLID && gimp_item_is_text_layer (GIMP_ITEM (drawable))) { GimpRGB color; gimp_context_get_foreground (GIMP_CONTEXT (options), &color); gimp_text_layer_set (GIMP_TEXT_LAYER (drawable), NULL, "color", &color, NULL); } else { gimp_drawable_edit_fill (drawable, options, undo_desc); } gimp_display_shell_dnd_flush (shell, image); } static void gimp_display_shell_drop_pattern (GtkWidget *widget, gint x, gint y, GimpViewable *viewable, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpFillOptions *options = gimp_fill_options_new (shell->display->gimp, NULL, FALSE); GIMP_LOG (DND, NULL); gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN); gimp_context_set_pattern (GIMP_CONTEXT (options), GIMP_PATTERN (viewable)); gimp_display_shell_dnd_fill (shell, options, C_("undo-type", "Drop pattern to layer")); g_object_unref (options); } static void gimp_display_shell_drop_color (GtkWidget *widget, gint x, gint y, const GimpRGB *color, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpFillOptions *options = gimp_fill_options_new (shell->display->gimp, NULL, FALSE); GIMP_LOG (DND, NULL); gimp_fill_options_set_style (options, GIMP_FILL_STYLE_SOLID); gimp_context_set_foreground (GIMP_CONTEXT (options), color); gimp_display_shell_dnd_fill (shell, options, C_("undo-type", "Drop color to layer")); g_object_unref (options); } static void gimp_display_shell_drop_buffer (GtkWidget *widget, gint drop_x, gint drop_y, GimpViewable *viewable, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpImage *image = gimp_display_get_image (shell->display); GimpDrawable *drawable; GimpBuffer *buffer; GimpPasteType paste_type; gint x, y, width, height; GIMP_LOG (DND, NULL); if (shell->display->gimp->busy) return; if (! image) { image = gimp_image_new_from_buffer (shell->display->gimp, GIMP_BUFFER (viewable)); gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0, G_OBJECT (gtk_widget_get_screen (widget)), gimp_widget_get_monitor (widget)); g_object_unref (image); return; } paste_type = GIMP_PASTE_TYPE_FLOATING; drawable = gimp_image_get_active_drawable (image); if (drawable) { if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable))) { gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), GIMP_MESSAGE_INFO, _("Pasted as new layer because the " "target is a layer group.")); paste_type = GIMP_PASTE_TYPE_NEW_LAYER; } else if (gimp_item_is_content_locked (GIMP_ITEM (drawable))) { gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), GIMP_MESSAGE_ERROR, _("Pasted as new layer because the " "target's pixels are locked.")); paste_type = GIMP_PASTE_TYPE_NEW_LAYER; } } buffer = GIMP_BUFFER (viewable); gimp_display_shell_untransform_viewport ( shell, ! gimp_display_shell_get_infinite_canvas (shell), &x, &y, &width, &height); /* FIXME: popup a menu for selecting "Paste Into" */ gimp_edit_paste (image, drawable, GIMP_OBJECT (buffer), paste_type, x, y, width, height); gimp_display_shell_dnd_flush (shell, image); } static void gimp_display_shell_drop_uri_list (GtkWidget *widget, gint x, gint y, GList *uri_list, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpImage *image; GimpContext *context; GList *list; gboolean open_as_layers; /* If the app is already being torn down, shell->display might be * NULL here. Play it safe. */ if (! shell->display) return; image = gimp_display_get_image (shell->display); context = gimp_get_user_context (shell->display->gimp); GIMP_LOG (DND, NULL); open_as_layers = (image != NULL); if (image) g_object_ref (image); for (list = uri_list; list; list = g_list_next (list)) { GFile *file = g_file_new_for_uri (list->data); GimpPDBStatusType status; GError *error = NULL; gboolean warn = FALSE; if (! shell->display) { /* It seems as if GIMP is being torn down for quitting. Bail out. */ g_object_unref (file); g_clear_object (&image); return; } if (open_as_layers) { GList *new_layers; new_layers = file_open_layers (shell->display->gimp, context, GIMP_PROGRESS (shell->display), image, FALSE, file, GIMP_RUN_INTERACTIVE, NULL, &status, &error); if (new_layers) { gint x = 0; gint y = 0; gint width = gimp_image_get_width (image); gint height = gimp_image_get_height (image); if (gimp_display_get_image (shell->display)) { gimp_display_shell_untransform_viewport ( shell, ! gimp_display_shell_get_infinite_canvas (shell), &x, &y, &width, &height); } gimp_image_add_layers (image, new_layers, GIMP_IMAGE_ACTIVE_PARENT, -1, x, y, width, height, _("Drop layers")); g_list_free (new_layers); } else if (status != GIMP_PDB_CANCEL) { warn = TRUE; } } else if (gimp_display_get_image (shell->display)) { /* open any subsequent images in a new display */ GimpImage *new_image; new_image = file_open_with_display (shell->display->gimp, context, NULL, file, FALSE, G_OBJECT (gtk_widget_get_screen (widget)), gimp_widget_get_monitor (widget), &status, &error); if (! new_image && status != GIMP_PDB_CANCEL) warn = TRUE; } else { /* open the first image in the empty display */ image = file_open_with_display (shell->display->gimp, context, GIMP_PROGRESS (shell->display), file, FALSE, G_OBJECT (gtk_widget_get_screen (widget)), gimp_widget_get_monitor (widget), &status, &error); if (image) { g_object_ref (image); } else if (status != GIMP_PDB_CANCEL) { warn = TRUE; } } /* Something above might have run a few rounds of the main loop. Check * that shell->display is still there, otherwise ignore this as the app * is being torn down for quitting. */ if (warn && shell->display) { gimp_message (shell->display->gimp, G_OBJECT (shell->display), GIMP_MESSAGE_ERROR, _("Opening '%s' failed:\n\n%s"), gimp_file_get_utf8_name (file), error->message); g_clear_error (&error); } g_object_unref (file); } if (image) gimp_display_shell_dnd_flush (shell, image); g_clear_object (&image); } static void gimp_display_shell_drop_component (GtkWidget *widget, gint x, gint y, GimpImage *image, GimpChannelType component, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpImage *dest_image = gimp_display_get_image (shell->display); GimpChannel *channel; GimpItem *new_item; const gchar *desc; GIMP_LOG (DND, NULL); if (shell->display->gimp->busy) return; if (! dest_image) { dest_image = gimp_image_new_from_component (image->gimp, image, component); gimp_create_display (dest_image->gimp, dest_image, GIMP_UNIT_PIXEL, 1.0, G_OBJECT (gtk_widget_get_screen (widget)), gimp_widget_get_monitor (widget)); g_object_unref (dest_image); return; } channel = gimp_channel_new_from_component (image, component, NULL, NULL); new_item = gimp_item_convert (GIMP_ITEM (channel), dest_image, GIMP_TYPE_LAYER); g_object_unref (channel); if (new_item) { GimpLayer *new_layer = GIMP_LAYER (new_item); gimp_enum_get_value (GIMP_TYPE_CHANNEL_TYPE, component, NULL, NULL, &desc, NULL); gimp_object_take_name (GIMP_OBJECT (new_layer), g_strdup_printf (_("%s Channel Copy"), desc)); gimp_image_undo_group_start (dest_image, GIMP_UNDO_GROUP_EDIT_PASTE, _("Drop New Layer")); gimp_display_shell_dnd_position_item (shell, image, new_item); gimp_image_add_layer (dest_image, new_layer, GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); gimp_image_undo_group_end (dest_image); gimp_display_shell_dnd_flush (shell, dest_image); } } static void gimp_display_shell_drop_pixbuf (GtkWidget *widget, gint x, gint y, GdkPixbuf *pixbuf, gpointer data) { GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); GimpImage *image = gimp_display_get_image (shell->display); GimpLayer *new_layer; gboolean has_alpha = FALSE; GIMP_LOG (DND, NULL); if (shell->display->gimp->busy) return; if (! image) { image = gimp_image_new_from_pixbuf (shell->display->gimp, pixbuf, _("Dropped Buffer")); gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0, G_OBJECT (gtk_widget_get_screen (widget)), gimp_widget_get_monitor (widget)); g_object_unref (image); return; } if (gdk_pixbuf_get_n_channels (pixbuf) == 2 || gdk_pixbuf_get_n_channels (pixbuf) == 4) { has_alpha = TRUE; } new_layer = gimp_layer_new_from_pixbuf (pixbuf, image, gimp_image_get_layer_format (image, has_alpha), _("Dropped Buffer"), GIMP_OPACITY_OPAQUE, gimp_image_get_default_new_layer_mode (image)); if (new_layer) { GimpItem *new_item = GIMP_ITEM (new_layer); gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, _("Drop New Layer")); gimp_display_shell_dnd_position_item (shell, image, new_item); gimp_image_add_layer (image, new_layer, GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); gimp_image_undo_group_end (image); gimp_display_shell_dnd_flush (shell, image); } }