/* 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-1997 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 "libpikacolor/pikacolor.h" #include "libpikaconfig/pikaconfig.h" #include "widgets-types.h" #include "core/pika.h" #include "core/pika-utils.h" #include "core/pikabrush.h" #include "core/pikacontainer.h" #include "core/pikacurve.h" #include "core/pikadatafactory.h" #include "core/pikagradient.h" #include "core/pikaimage.h" #include "core/pikaimagefile.h" #include "core/pikaitem.h" #include "core/pikapalette.h" #include "core/pikapattern.h" #include "core/pikatoolinfo.h" #include "text/pikafont.h" #include "xcf/xcf.h" #include "pikaselectiondata.h" #include "pika-log.h" #include "pika-intl.h" /* local function prototypes */ static const gchar * pika_selection_data_get_name (GtkSelectionData *selection, const gchar *strfunc); static PikaObject * pika_selection_data_get_object (GtkSelectionData *selection, PikaContainer *container, PikaObject *additional); static gchar * pika_unescape_uri_string (const char *escaped, int len, const char *illegal_escaped_characters, gboolean ascii_must_not_be_escaped); /* public functions */ void pika_selection_data_set_uri_list (GtkSelectionData *selection, GList *uri_list) { GList *list; gchar *vals = NULL; g_return_if_fail (selection != NULL); g_return_if_fail (uri_list != NULL); for (list = uri_list; list; list = g_list_next (list)) { if (vals) { gchar *tmp = g_strconcat (vals, list->data, list->next ? "\n" : NULL, NULL); g_free (vals); vals = tmp; } else { vals = g_strconcat (list->data, list->next ? "\n" : NULL, NULL); } } gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) vals, strlen (vals)); g_free (vals); } GList * pika_selection_data_get_uri_list (GtkSelectionData *selection) { GList *crap_list = NULL; GList *uri_list = NULL; GList *list; gint length; const gchar *data; const gchar *buffer; g_return_val_if_fail (selection != NULL, NULL); length = gtk_selection_data_get_length (selection); if (gtk_selection_data_get_format (selection) != 8 || length < 1) { g_warning ("Received invalid file data!"); return NULL; } data = buffer = (const gchar *) gtk_selection_data_get_data (selection); PIKA_LOG (DND, "raw buffer >>%s<<", buffer); { gchar name_buffer[1024]; while (*buffer && (buffer - data < length)) { gchar *name = name_buffer; gint len = 0; while (len < sizeof (name_buffer) && *buffer && *buffer != '\n') { *name++ = *buffer++; len++; } if (len == 0) break; if (*(name - 1) == 0xd) /* gmc uses RETURN+NEWLINE as delimiter */ len--; if (len > 2) crap_list = g_list_prepend (crap_list, g_strndup (name_buffer, len)); if (*buffer) buffer++; } } if (! crap_list) return NULL; /* do various checks because file drag sources send all kinds of * arbitrary crap... */ for (list = crap_list; list; list = g_list_next (list)) { const gchar *dnd_crap = list->data; gchar *filename; gchar *hostname; gchar *uri = NULL; GError *error = NULL; PIKA_LOG (DND, "trying to convert \"%s\" to an uri", dnd_crap); filename = g_filename_from_uri (dnd_crap, &hostname, NULL); if (filename) { /* if we got a correctly encoded "file:" uri... * * (for GLib < 2.4.4, this is escaped UTF-8, * for GLib > 2.4.4, this is escaped local filename encoding) */ uri = g_filename_to_uri (filename, hostname, NULL); g_free (hostname); g_free (filename); } else if (g_file_test (dnd_crap, G_FILE_TEST_EXISTS)) { /* ...else if we got a valid local filename... */ uri = g_filename_to_uri (dnd_crap, NULL, NULL); } else { /* ...otherwise do evil things... */ const gchar *start = dnd_crap; if (g_str_has_prefix (dnd_crap, "file://")) { start += strlen ("file://"); } else if (g_str_has_prefix (dnd_crap, "file:")) { start += strlen ("file:"); } if (start != dnd_crap) { /* try if we got a "file:" uri in the wrong encoding... * * (for GLib < 2.4.4, this is escaped local filename encoding, * for GLib > 2.4.4, this is escaped UTF-8) */ gchar *unescaped_filename; if (strstr (dnd_crap, "%")) { gchar *local_filename; unescaped_filename = pika_unescape_uri_string (start, -1, "/", FALSE); /* check if we got a drop from an application that * encodes file: URIs as UTF-8 (apps linked against * GLib < 2.4.4) */ local_filename = g_filename_from_utf8 (unescaped_filename, -1, NULL, NULL, NULL); if (local_filename) { g_free (unescaped_filename); unescaped_filename = local_filename; } } else { unescaped_filename = g_strdup (start); } uri = g_filename_to_uri (unescaped_filename, NULL, &error); if (! uri) { gchar *escaped_filename = g_strescape (unescaped_filename, NULL); g_message (_("The filename '%s' couldn't be converted to a " "valid URI:\n\n%s"), escaped_filename, error->message ? error->message : _("Invalid UTF-8")); g_free (escaped_filename); g_clear_error (&error); g_free (unescaped_filename); continue; } g_free (unescaped_filename); } else { /* otherwise try the crap passed anyway, in case it's * a "http:" or whatever uri a plug-in might handle */ uri = g_strdup (dnd_crap); } } uri_list = g_list_prepend (uri_list, uri); } g_list_free_full (crap_list, (GDestroyNotify) g_free); return uri_list; } void pika_selection_data_set_color (GtkSelectionData *selection, const PikaRGB *color) { guint16 vals[4]; guchar r, g, b, a; g_return_if_fail (selection != NULL); g_return_if_fail (color != NULL); pika_rgba_get_uchar (color, &r, &g, &b, &a); vals[0] = r + (r << 8); vals[1] = g + (g << 8); vals[2] = b + (b << 8); vals[3] = a + (a << 8); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 16, (const guchar *) vals, 8); } gboolean pika_selection_data_get_color (GtkSelectionData *selection, PikaRGB *color) { const guint16 *color_vals; g_return_val_if_fail (selection != NULL, FALSE); g_return_val_if_fail (color != NULL, FALSE); if (gtk_selection_data_get_format (selection) != 16 || gtk_selection_data_get_length (selection) != 8) { g_warning ("Received invalid color data!"); return FALSE; } color_vals = (const guint16 *) gtk_selection_data_get_data (selection); pika_rgba_set_uchar (color, (guchar) (color_vals[0] >> 8), (guchar) (color_vals[1] >> 8), (guchar) (color_vals[2] >> 8), (guchar) (color_vals[3] >> 8)); return TRUE; } void pika_selection_data_set_xcf (GtkSelectionData *selection, PikaImage *image) { GMemoryOutputStream *output; g_return_if_fail (selection != NULL); g_return_if_fail (PIKA_IS_IMAGE (image)); output = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new_resizable ()); xcf_save_stream (image->pika, image, G_OUTPUT_STREAM (output), NULL, NULL, NULL); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, g_memory_output_stream_get_data (output), g_memory_output_stream_get_data_size (output)); g_object_unref (output); } PikaImage * pika_selection_data_get_xcf (GtkSelectionData *selection, Pika *pika) { GInputStream *input; PikaImage *image; gsize length; const guchar *data; GError *error = NULL; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); length = gtk_selection_data_get_length (selection); if (gtk_selection_data_get_format (selection) != 8 || length < 1) { g_warning ("Received invalid data stream!"); return NULL; } data = gtk_selection_data_get_data (selection); input = g_memory_input_stream_new_from_data (data, length, NULL); image = xcf_load_stream (pika, input, NULL, NULL, &error); if (image) { /* don't keep clipboard images in the image list */ pika_container_remove (pika->images, PIKA_OBJECT (image)); } else { g_warning ("Received invalid XCF data: %s", error->message); g_clear_error (&error); } g_object_unref (input); return image; } void pika_selection_data_set_stream (GtkSelectionData *selection, const guchar *stream, gsize stream_length) { g_return_if_fail (selection != NULL); g_return_if_fail (stream != NULL); g_return_if_fail (stream_length > 0); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) stream, stream_length); } const guchar * pika_selection_data_get_stream (GtkSelectionData *selection, gsize *stream_length) { gint length; g_return_val_if_fail (selection != NULL, NULL); g_return_val_if_fail (stream_length != NULL, NULL); length = gtk_selection_data_get_length (selection); if (gtk_selection_data_get_format (selection) != 8 || length < 1) { g_warning ("Received invalid data stream!"); return NULL; } *stream_length = length; return (const guchar *) gtk_selection_data_get_data (selection); } void pika_selection_data_set_curve (GtkSelectionData *selection, PikaCurve *curve) { gchar *str; g_return_if_fail (selection != NULL); g_return_if_fail (PIKA_IS_CURVE (curve)); str = pika_config_serialize_to_string (PIKA_CONFIG (curve), NULL); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) str, strlen (str)); g_free (str); } PikaCurve * pika_selection_data_get_curve (GtkSelectionData *selection) { PikaCurve *curve; gint length; GError *error = NULL; g_return_val_if_fail (selection != NULL, NULL); length = gtk_selection_data_get_length (selection); if (gtk_selection_data_get_format (selection) != 8 || length < 1) { g_warning ("Received invalid curve data!"); return NULL; } curve = PIKA_CURVE (pika_curve_new ("pasted curve")); if (! pika_config_deserialize_string (PIKA_CONFIG (curve), (const gchar *) gtk_selection_data_get_data (selection), length, NULL, &error)) { g_warning ("Received invalid curve data: %s", error->message); g_clear_error (&error); g_object_unref (curve); return NULL; } return curve; } void pika_selection_data_set_image (GtkSelectionData *selection, PikaImage *image) { gchar *str; g_return_if_fail (selection != NULL); g_return_if_fail (PIKA_IS_IMAGE (image)); str = g_strdup_printf ("%d:%d", pika_get_pid (), pika_image_get_id (image)); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) str, strlen (str)); g_free (str); } PikaImage * pika_selection_data_get_image (GtkSelectionData *selection, Pika *pika) { const gchar *str; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); str = pika_selection_data_get_name (selection, G_STRFUNC); if (str) { gint pid; gint ID; if (sscanf (str, "%i:%i", &pid, &ID) == 2 && pid == pika_get_pid ()) { return pika_image_get_by_id (pika, ID); } } return NULL; } void pika_selection_data_set_component (GtkSelectionData *selection, PikaImage *image, PikaChannelType channel) { gchar *str; g_return_if_fail (selection != NULL); g_return_if_fail (PIKA_IS_IMAGE (image)); str = g_strdup_printf ("%d:%d:%d", pika_get_pid (), pika_image_get_id (image), (gint) channel); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) str, strlen (str)); g_free (str); } PikaImage * pika_selection_data_get_component (GtkSelectionData *selection, Pika *pika, PikaChannelType *channel) { const gchar *str; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); if (channel) *channel = 0; str = pika_selection_data_get_name (selection, G_STRFUNC); if (str) { gint pid; gint ID; gint ch; if (sscanf (str, "%i:%i:%i", &pid, &ID, &ch) == 3 && pid == pika_get_pid ()) { PikaImage *image = pika_image_get_by_id (pika, ID); if (image && channel) *channel = ch; return image; } } return NULL; } void pika_selection_data_set_item (GtkSelectionData *selection, PikaItem *item) { gchar *str; g_return_if_fail (selection != NULL); g_return_if_fail (PIKA_IS_ITEM (item)); str = g_strdup_printf ("%d:%d", pika_get_pid (), pika_item_get_id (item)); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) str, strlen (str)); g_free (str); } PikaItem * pika_selection_data_get_item (GtkSelectionData *selection, Pika *pika) { const gchar *str; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); str = pika_selection_data_get_name (selection, G_STRFUNC); if (str) { gint pid; gint ID; if (sscanf (str, "%i:%i", &pid, &ID) == 2 && pid == pika_get_pid ()) { return pika_item_get_by_id (pika, ID); } } return NULL; } void pika_selection_data_set_item_list (GtkSelectionData *selection, GList *items) { GString *str; GList *iter; g_return_if_fail (selection != NULL); g_return_if_fail (items); for (iter = items; iter; iter = iter->next) g_return_if_fail (PIKA_IS_ITEM (iter->data)); str = g_string_new (NULL); g_string_printf (str, "%d", pika_get_pid ()); for (iter = items; iter; iter = iter->next) g_string_append_printf (str, ":%d", pika_item_get_id (iter->data)); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) str->str, str->len); g_string_free (str, TRUE); } GList * pika_selection_data_get_item_list (GtkSelectionData *selection, Pika *pika) { const gchar *str; GList *items = NULL; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); str = pika_selection_data_get_name (selection, G_STRFUNC); if (str) { gchar **tokens; gint64 pid; tokens = g_strsplit (str, ":", -1); g_return_val_if_fail (tokens[0] != NULL && tokens[1] != NULL, NULL); pid = g_ascii_strtoll (tokens[0], NULL, 10); if (pid == pika_get_pid ()) { gint i = 1; while (tokens[i]) { gint64 id = g_ascii_strtoll (tokens[i], NULL, 10); items = g_list_prepend (items, pika_item_get_by_id (pika, id)); i++; } items = g_list_reverse (items); } g_strfreev (tokens); } return items; } void pika_selection_data_set_object (GtkSelectionData *selection, PikaObject *object) { const gchar *name; g_return_if_fail (selection != NULL); g_return_if_fail (PIKA_IS_OBJECT (object)); name = pika_object_get_name (object); if (name) { gchar *str; str = g_strdup_printf ("%d:%p:%s", pika_get_pid (), object, name); gtk_selection_data_set (selection, gtk_selection_data_get_target (selection), 8, (guchar *) str, strlen (str)); g_free (str); } } PikaBrush * pika_selection_data_get_brush (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaBrush *) pika_selection_data_get_object (selection, pika_data_factory_get_container (pika->brush_factory), PIKA_OBJECT (pika_brush_get_standard (pika_get_user_context (pika)))); } PikaPattern * pika_selection_data_get_pattern (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaPattern *) pika_selection_data_get_object (selection, pika_data_factory_get_container (pika->pattern_factory), PIKA_OBJECT (pika_pattern_get_standard (pika_get_user_context (pika)))); } PikaGradient * pika_selection_data_get_gradient (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaGradient *) pika_selection_data_get_object (selection, pika_data_factory_get_container (pika->gradient_factory), PIKA_OBJECT (pika_gradient_get_standard (pika_get_user_context (pika)))); } PikaPalette * pika_selection_data_get_palette (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaPalette *) pika_selection_data_get_object (selection, pika_data_factory_get_container (pika->palette_factory), PIKA_OBJECT (pika_palette_get_standard (pika_get_user_context (pika)))); } PikaFont * pika_selection_data_get_font (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaFont *) pika_selection_data_get_object (selection, pika_data_factory_get_container (pika->font_factory), PIKA_OBJECT (pika_font_get_standard ())); } PikaBuffer * pika_selection_data_get_buffer (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaBuffer *) pika_selection_data_get_object (selection, pika->named_buffers, PIKA_OBJECT (pika_get_clipboard_buffer (pika))); } PikaImagefile * pika_selection_data_get_imagefile (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaImagefile *) pika_selection_data_get_object (selection, pika->documents, NULL); } PikaTemplate * pika_selection_data_get_template (GtkSelectionData *selection, Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); return (PikaTemplate *) pika_selection_data_get_object (selection, pika->templates, NULL); } PikaToolItem * pika_selection_data_get_tool_item (GtkSelectionData *selection, Pika *pika) { PikaToolItem *tool_item; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (selection != NULL, NULL); tool_item = (PikaToolItem *) pika_selection_data_get_object (selection, pika->tool_info_list, PIKA_OBJECT (pika_tool_info_get_standard (pika))); if (! tool_item) { tool_item = (PikaToolItem *) pika_selection_data_get_object (selection, pika->tool_item_list, NULL); } return tool_item; } /* private functions */ static const gchar * pika_selection_data_get_name (GtkSelectionData *selection, const gchar *strfunc) { const gchar *name; if (gtk_selection_data_get_format (selection) != 8 || gtk_selection_data_get_length (selection) < 1) { g_warning ("%s: received invalid selection data", strfunc); return NULL; } name = (const gchar *) gtk_selection_data_get_data (selection); if (! g_utf8_validate (name, -1, NULL)) { g_warning ("%s: received invalid selection data " "(doesn't validate as UTF-8)", strfunc); return NULL; } PIKA_LOG (DND, "name = '%s'", name); return name; } static PikaObject * pika_selection_data_get_object (GtkSelectionData *selection, PikaContainer *container, PikaObject *additional) { const gchar *str; str = pika_selection_data_get_name (selection, G_STRFUNC); if (str) { gint pid; gpointer object_addr; gint name_offset = 0; if (sscanf (str, "%i:%p:%n", &pid, &object_addr, &name_offset) >= 2 && pid == pika_get_pid () && name_offset > 0) { const gchar *name = str + name_offset; PIKA_LOG (DND, "pid = %d, addr = %p, name = '%s'", pid, object_addr, name); if (additional && strcmp (name, pika_object_get_name (additional)) == 0 && object_addr == (gpointer) additional) { return additional; } else { PikaObject *object; object = pika_container_get_child_by_name (container, name); if (object_addr == (gpointer) object) return object; } } } return NULL; } /* the next two functions are straight cut'n'paste from glib/glib/gconvert.c, * except that pika_unescape_uri_string() does not try to UTF-8 validate * the unescaped result. */ static int unescape_character (const char *scanner) { int first_digit; int second_digit; first_digit = g_ascii_xdigit_value (scanner[0]); if (first_digit < 0) return -1; second_digit = g_ascii_xdigit_value (scanner[1]); if (second_digit < 0) return -1; return (first_digit << 4) | second_digit; } static gchar * pika_unescape_uri_string (const char *escaped, int len, const char *illegal_escaped_characters, gboolean ascii_must_not_be_escaped) { const gchar *in, *in_end; gchar *out, *result; int c; if (escaped == NULL) return NULL; if (len < 0) len = strlen (escaped); result = g_malloc (len + 1); out = result; for (in = escaped, in_end = escaped + len; in < in_end; in++) { c = *in; if (c == '%') { /* catch partial escape sequences past the end of the substring */ if (in + 3 > in_end) break; c = unescape_character (in + 1); /* catch bad escape sequences and NUL characters */ if (c <= 0) break; /* catch escaped ASCII */ if (ascii_must_not_be_escaped && c <= 0x7F) break; /* catch other illegal escaped characters */ if (strchr (illegal_escaped_characters, c) != NULL) break; in += 2; } *out++ = c; } pika_assert (out - result <= len); *out = '\0'; if (in != in_end) { g_free (result); return NULL; } return result; }