/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 "libpikabase/pikabase.h" #include "libpikawidgets/pikawidgets.h" #include "display-types.h" #include "core/pika.h" #include "core/pika-edit.h" #include "core/pikabuffer.h" #include "core/pikadrawable-edit.h" #include "core/pikafilloptions.h" #include "core/pikaimage.h" #include "core/pikaimage-new.h" #include "core/pikaimage-undo.h" #include "core/pikalayer.h" #include "core/pikalayer-new.h" #include "core/pikalayermask.h" #include "core/pikapattern.h" #include "core/pikaprogress.h" #include "file/file-open.h" #include "text/pikatext.h" #include "text/pikatextlayer.h" #include "vectors/pikavectors.h" #include "vectors/pikavectors-import.h" #include "widgets/pikadnd.h" #include "pikadisplay.h" #include "pikadisplayshell.h" #include "pikadisplayshell-dnd.h" #include "pikadisplayshell-transform.h" #include "pika-log.h" #include "pika-intl.h" /* local function prototypes */ static void pika_display_shell_drop_drawable (GtkWidget *widget, gint x, gint y, PikaViewable *viewable, gpointer data); static void pika_display_shell_drop_vectors (GtkWidget *widget, gint x, gint y, PikaViewable *viewable, gpointer data); static void pika_display_shell_drop_svg (GtkWidget *widget, gint x, gint y, const guchar *svg_data, gsize svg_data_length, gpointer data); static void pika_display_shell_drop_pattern (GtkWidget *widget, gint x, gint y, PikaViewable *viewable, gpointer data); static void pika_display_shell_drop_color (GtkWidget *widget, gint x, gint y, const PikaRGB *color, gpointer data); static void pika_display_shell_drop_buffer (GtkWidget *widget, gint x, gint y, PikaViewable *viewable, gpointer data); static void pika_display_shell_drop_uri_list (GtkWidget *widget, gint x, gint y, GList *uri_list, gpointer data); static void pika_display_shell_drop_component (GtkWidget *widget, gint x, gint y, PikaImage *image, PikaChannelType component, gpointer data); static void pika_display_shell_drop_pixbuf (GtkWidget *widget, gint x, gint y, GdkPixbuf *pixbuf, gpointer data); /* public functions */ void pika_display_shell_dnd_init (PikaDisplayShell *shell) { g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell)); pika_dnd_viewable_dest_add (shell->canvas, PIKA_TYPE_LAYER, pika_display_shell_drop_drawable, shell); pika_dnd_viewable_dest_add (shell->canvas, PIKA_TYPE_LAYER_MASK, pika_display_shell_drop_drawable, shell); pika_dnd_viewable_dest_add (shell->canvas, PIKA_TYPE_CHANNEL, pika_display_shell_drop_drawable, shell); pika_dnd_viewable_dest_add (shell->canvas, PIKA_TYPE_VECTORS, pika_display_shell_drop_vectors, shell); pika_dnd_viewable_dest_add (shell->canvas, PIKA_TYPE_PATTERN, pika_display_shell_drop_pattern, shell); pika_dnd_viewable_dest_add (shell->canvas, PIKA_TYPE_BUFFER, pika_display_shell_drop_buffer, shell); pika_dnd_color_dest_add (shell->canvas, pika_display_shell_drop_color, shell); pika_dnd_component_dest_add (shell->canvas, pika_display_shell_drop_component, shell); pika_dnd_uri_list_dest_add (shell->canvas, pika_display_shell_drop_uri_list, shell); pika_dnd_svg_dest_add (shell->canvas, pika_display_shell_drop_svg, shell); pika_dnd_pixbuf_dest_add (shell->canvas, pika_display_shell_drop_pixbuf, shell); } /* private functions */ /* * Position the dropped item in the middle of the viewport. */ static void pika_display_shell_dnd_position_item (PikaDisplayShell *shell, PikaImage *image, PikaItem *item) { gint item_width = pika_item_get_width (item); gint item_height = pika_item_get_height (item); gint off_x, off_y; if (item_width >= pika_image_get_width (image) && item_height >= pika_image_get_height (image)) { off_x = (pika_image_get_width (image) - item_width) / 2; off_y = (pika_image_get_height (image) - item_height) / 2; } else { gint x, y; gint width, height; pika_display_shell_untransform_viewport ( shell, ! pika_display_shell_get_infinite_canvas (shell), &x, &y, &width, &height); off_x = x + (width - item_width) / 2; off_y = y + (height - item_height) / 2; } pika_item_translate (item, off_x - pika_item_get_offset_x (item), off_y - pika_item_get_offset_y (item), FALSE); } static void pika_display_shell_dnd_flush (PikaDisplayShell *shell, PikaImage *image) { pika_display_shell_present (shell); pika_image_flush (image); pika_context_set_display (pika_get_user_context (shell->display->pika), shell->display); } static void pika_display_shell_drop_drawable (GtkWidget *widget, gint x, gint y, PikaViewable *viewable, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaImage *image = pika_display_get_image (shell->display); GType new_type; PikaItem *new_item; PIKA_LOG (DND, NULL); if (shell->display->pika->busy) return; if (! image) { image = pika_image_new_from_drawable (shell->display->pika, PIKA_DRAWABLE (viewable)); pika_create_display (shell->display->pika, image, PIKA_UNIT_PIXEL, 1.0, G_OBJECT (pika_widget_get_monitor (widget))); g_object_unref (image); return; } if (PIKA_IS_LAYER (viewable)) new_type = G_TYPE_FROM_INSTANCE (viewable); else new_type = PIKA_TYPE_LAYER; new_item = pika_item_convert (PIKA_ITEM (viewable), image, new_type); if (new_item) { PikaLayer *new_layer = PIKA_LAYER (new_item); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE, _("Drop New Layer")); pika_display_shell_dnd_position_item (shell, image, new_item); pika_item_set_visible (new_item, TRUE, FALSE); pika_image_add_layer (image, new_layer, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_undo_group_end (image); pika_display_shell_dnd_flush (shell, image); } } static void pika_display_shell_drop_vectors (GtkWidget *widget, gint x, gint y, PikaViewable *viewable, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaImage *image = pika_display_get_image (shell->display); PikaItem *new_item; PIKA_LOG (DND, NULL); if (shell->display->pika->busy) return; if (! image) return; new_item = pika_item_convert (PIKA_ITEM (viewable), image, G_TYPE_FROM_INSTANCE (viewable)); if (new_item) { PikaVectors *new_vectors = PIKA_VECTORS (new_item); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE, _("Drop New Path")); pika_image_add_vectors (image, new_vectors, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_undo_group_end (image); pika_display_shell_dnd_flush (shell, image); } } static void pika_display_shell_drop_svg (GtkWidget *widget, gint x, gint y, const guchar *svg_data, gsize svg_data_len, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaImage *image = pika_display_get_image (shell->display); GError *error = NULL; PIKA_LOG (DND, NULL); if (shell->display->pika->busy) return; if (! image) return; if (! pika_vectors_import_buffer (image, (const gchar *) svg_data, svg_data_len, TRUE, FALSE, PIKA_IMAGE_ACTIVE_PARENT, -1, NULL, &error)) { pika_message_literal (shell->display->pika, G_OBJECT (shell->display), PIKA_MESSAGE_ERROR, error->message); g_clear_error (&error); } else { pika_display_shell_dnd_flush (shell, image); } } static void pika_display_shell_dnd_fill (PikaDisplayShell *shell, PikaFillOptions *options, const gchar *undo_desc) { PikaImage *image = pika_display_get_image (shell->display); GList *drawables; GList *iter; if (shell->display->pika->busy) return; if (! image) return; drawables = pika_image_get_selected_drawables (image); if (! drawables) return; for (iter = drawables; iter; iter = iter->next) { if (pika_viewable_get_children (iter->data)) { pika_message_literal (shell->display->pika, G_OBJECT (shell->display), PIKA_MESSAGE_ERROR, _("Cannot modify the pixels of layer groups.")); g_list_free (drawables); return; } if (pika_item_is_content_locked (iter->data, NULL)) { pika_message_literal (shell->display->pika, G_OBJECT (shell->display), PIKA_MESSAGE_ERROR, _("A selected layer's pixels are locked.")); g_list_free (drawables); return; } } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_PAINT, undo_desc); for (iter = drawables; iter; iter = iter->next) { /* FIXME: there should be a virtual method for this that the * PikaTextLayer can override. */ if (pika_item_is_text_layer (iter->data) && (pika_fill_options_get_style (options) == PIKA_FILL_STYLE_FG_COLOR || pika_fill_options_get_style (options) == PIKA_FILL_STYLE_BG_COLOR)) { PikaRGB color; if (pika_fill_options_get_style (options) == PIKA_FILL_STYLE_FG_COLOR) pika_context_get_foreground (PIKA_CONTEXT (options), &color); else pika_context_get_background (PIKA_CONTEXT (options), &color); pika_text_layer_set (iter->data, NULL, "color", &color, NULL); } else { pika_drawable_edit_fill (iter->data, options, undo_desc); } } g_list_free (drawables); pika_image_undo_group_end (image); pika_display_shell_dnd_flush (shell, image); } static void pika_display_shell_drop_pattern (GtkWidget *widget, gint x, gint y, PikaViewable *viewable, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaFillOptions *options = pika_fill_options_new (shell->display->pika, NULL, FALSE); PIKA_LOG (DND, NULL); pika_fill_options_set_style (options, PIKA_FILL_STYLE_PATTERN); pika_context_set_pattern (PIKA_CONTEXT (options), PIKA_PATTERN (viewable)); pika_display_shell_dnd_fill (shell, options, C_("undo-type", "Drop pattern to layer")); g_object_unref (options); } static void pika_display_shell_drop_color (GtkWidget *widget, gint x, gint y, const PikaRGB *color, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaFillOptions *options = pika_fill_options_new (shell->display->pika, NULL, FALSE); PIKA_LOG (DND, NULL); pika_fill_options_set_style (options, PIKA_FILL_STYLE_FG_COLOR); pika_context_set_foreground (PIKA_CONTEXT (options), color); pika_display_shell_dnd_fill (shell, options, C_("undo-type", "Drop color to layer")); g_object_unref (options); } static void pika_display_shell_drop_buffer (GtkWidget *widget, gint drop_x, gint drop_y, PikaViewable *viewable, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaImage *image = pika_display_get_image (shell->display); PikaContext *context; GList *drawables; PikaBuffer *buffer; PikaPasteType paste_type; gint x, y, width, height; PIKA_LOG (DND, NULL); if (shell->display->pika->busy) return; if (! image) { image = pika_image_new_from_buffer (shell->display->pika, PIKA_BUFFER (viewable)); pika_create_display (image->pika, image, PIKA_UNIT_PIXEL, 1.0, G_OBJECT (pika_widget_get_monitor (widget))); g_object_unref (image); return; } paste_type = PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING; drawables = pika_image_get_selected_drawables (image); context = pika_get_user_context (shell->display->pika); buffer = PIKA_BUFFER (viewable); pika_display_shell_untransform_viewport ( shell, ! pika_display_shell_get_infinite_canvas (shell), &x, &y, &width, &height); /* FIXME: popup a menu for selecting "Paste Into" */ g_list_free (pika_edit_paste (image, drawables, PIKA_OBJECT (buffer), paste_type, context, FALSE, x, y, width, height)); g_list_free (drawables); pika_display_shell_dnd_flush (shell, image); } static void pika_display_shell_drop_uri_list (GtkWidget *widget, gint x, gint y, GList *uri_list, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaImage *image; PikaContext *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 = pika_display_get_image (shell->display); context = pika_get_user_context (shell->display->pika); PIKA_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); PikaPDBStatusType status; GError *error = NULL; gboolean warn = FALSE; if (! shell->display) { /* It seems as if PIKA 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->pika, context, PIKA_PROGRESS (shell->display), image, FALSE, file, PIKA_RUN_INTERACTIVE, NULL, &status, &error); if (new_layers) { gint x = 0; gint y = 0; gint width = pika_image_get_width (image); gint height = pika_image_get_height (image); if (pika_display_get_image (shell->display)) { pika_display_shell_untransform_viewport ( shell, ! pika_display_shell_get_infinite_canvas (shell), &x, &y, &width, &height); } pika_image_add_layers (image, new_layers, PIKA_IMAGE_ACTIVE_PARENT, -1, x, y, width, height, _("Drop layers")); g_list_free (new_layers); } else if (status != PIKA_PDB_CANCEL && status != PIKA_PDB_SUCCESS) { warn = TRUE; } } else if (pika_display_get_image (shell->display)) { /* open any subsequent images in a new display */ PikaImage *new_image; new_image = file_open_with_display (shell->display->pika, context, NULL, file, FALSE, G_OBJECT (pika_widget_get_monitor (widget)), &status, &error); if (! new_image && status != PIKA_PDB_CANCEL && status != PIKA_PDB_SUCCESS) warn = TRUE; } else { /* open the first image in the empty display */ image = file_open_with_display (shell->display->pika, context, PIKA_PROGRESS (shell->display), file, FALSE, G_OBJECT (pika_widget_get_monitor (widget)), &status, &error); if (image) { g_object_ref (image); } else if (status != PIKA_PDB_CANCEL && status != PIKA_PDB_SUCCESS) { 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) { pika_message (shell->display->pika, G_OBJECT (shell->display), PIKA_MESSAGE_ERROR, _("Opening '%s' failed:\n\n%s"), pika_file_get_utf8_name (file), error->message); g_clear_error (&error); } g_object_unref (file); } if (image) pika_display_shell_dnd_flush (shell, image); g_clear_object (&image); } static void pika_display_shell_drop_component (GtkWidget *widget, gint x, gint y, PikaImage *image, PikaChannelType component, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaImage *dest_image = pika_display_get_image (shell->display); PikaChannel *channel; PikaItem *new_item; const gchar *desc; PIKA_LOG (DND, NULL); if (shell->display->pika->busy) return; if (! dest_image) { dest_image = pika_image_new_from_component (image->pika, image, component); pika_create_display (dest_image->pika, dest_image, PIKA_UNIT_PIXEL, 1.0, G_OBJECT (pika_widget_get_monitor (widget))); g_object_unref (dest_image); return; } channel = pika_channel_new_from_component (image, component, NULL, NULL); new_item = pika_item_convert (PIKA_ITEM (channel), dest_image, PIKA_TYPE_LAYER); g_object_unref (channel); if (new_item) { PikaLayer *new_layer = PIKA_LAYER (new_item); pika_enum_get_value (PIKA_TYPE_CHANNEL_TYPE, component, NULL, NULL, &desc, NULL); pika_object_take_name (PIKA_OBJECT (new_layer), g_strdup_printf (_("%s Channel Copy"), desc)); pika_image_undo_group_start (dest_image, PIKA_UNDO_GROUP_EDIT_PASTE, _("Drop New Layer")); pika_display_shell_dnd_position_item (shell, image, new_item); pika_image_add_layer (dest_image, new_layer, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_undo_group_end (dest_image); pika_display_shell_dnd_flush (shell, dest_image); } } static void pika_display_shell_drop_pixbuf (GtkWidget *widget, gint x, gint y, GdkPixbuf *pixbuf, gpointer data) { PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data); PikaImage *image = pika_display_get_image (shell->display); PikaLayer *new_layer; gboolean has_alpha = FALSE; PIKA_LOG (DND, NULL); if (shell->display->pika->busy) return; if (! image) { image = pika_image_new_from_pixbuf (shell->display->pika, pixbuf, _("Dropped Buffer")); pika_create_display (image->pika, image, PIKA_UNIT_PIXEL, 1.0, G_OBJECT (pika_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 = pika_layer_new_from_pixbuf (pixbuf, image, pika_image_get_layer_format (image, has_alpha), _("Dropped Buffer"), PIKA_OPACITY_OPAQUE, pika_image_get_default_new_layer_mode (image)); if (new_layer) { PikaItem *new_item = PIKA_ITEM (new_layer); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE, _("Drop New Layer")); pika_display_shell_dnd_position_item (shell, image, new_item); pika_image_add_layer (image, new_layer, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_undo_group_end (image); pika_display_shell_dnd_flush (shell, image); } }