/* gtkrichtext.c * * Copyright (C) 2006 Imendio AB * Contact: Michael Natterer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ #include "config.h" #include #include "gtktextbufferrichtext.h" #include "gtktextbufferserialize.h" #include "gtkintl.h" typedef struct { gchar *mime_type; gboolean can_create_tags; GdkAtom atom; gpointer function; gpointer user_data; GDestroyNotify user_data_destroy; } GtkRichTextFormat; static GList * register_format (GList *formats, const gchar *mime_type, gpointer function, gpointer user_data, GDestroyNotify user_data_destroy, GdkAtom *atom); static GList * unregister_format (GList *formats, GdkAtom atom); static GdkAtom * get_formats (GList *formats, gint *n_formats); static void free_format (GtkRichTextFormat *format); static void free_format_list (GList *formats); static GQuark serialize_quark (void); static GQuark deserialize_quark (void); /** * gtk_text_buffer_register_serialize_format: * @buffer: a #GtkTextBuffer * @mime_type: the format’s mime-type * @function: the serialize function to register * @user_data: @function’s user_data * @user_data_destroy: a function to call when @user_data is no longer needed * * This function registers a rich text serialization @function along with * its @mime_type with the passed @buffer. * * Returns: (transfer none): the #GdkAtom that corresponds to the * newly registered format’s mime-type. * * Since: 2.10 **/ GdkAtom gtk_text_buffer_register_serialize_format (GtkTextBuffer *buffer, const gchar *mime_type, GtkTextBufferSerializeFunc function, gpointer user_data, GDestroyNotify user_data_destroy) { GList *formats; GdkAtom atom; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE); g_return_val_if_fail (mime_type != NULL && *mime_type != '\0', GDK_NONE); g_return_val_if_fail (function != NULL, GDK_NONE); formats = g_object_steal_qdata (G_OBJECT (buffer), serialize_quark ()); formats = register_format (formats, mime_type, (gpointer) function, user_data, user_data_destroy, &atom); g_object_set_qdata_full (G_OBJECT (buffer), serialize_quark (), formats, (GDestroyNotify) free_format_list); g_object_notify (G_OBJECT (buffer), "copy-target-list"); return atom; } /** * gtk_text_buffer_register_serialize_tagset: * @buffer: a #GtkTextBuffer * @tagset_name: (allow-none): an optional tagset name, on %NULL * * This function registers GTK+’s internal rich text serialization * format with the passed @buffer. The internal format does not comply * to any standard rich text format and only works between #GtkTextBuffer * instances. It is capable of serializing all of a text buffer’s tags * and embedded pixbufs. * * This function is just a wrapper around * gtk_text_buffer_register_serialize_format(). The mime type used * for registering is “application/x-gtk-text-buffer-rich-text”, or * “application/x-gtk-text-buffer-rich-text;format=@tagset_name” if a * @tagset_name was passed. * * The @tagset_name can be used to restrict the transfer of rich text * to buffers with compatible sets of tags, in order to avoid unknown * tags from being pasted. It is probably the common case to pass an * identifier != %NULL here, since the %NULL tagset requires the * receiving buffer to deal with with pasting of arbitrary tags. * * Returns: (transfer none): the #GdkAtom that corresponds to the * newly registered format’s mime-type. * * Since: 2.10 **/ GdkAtom gtk_text_buffer_register_serialize_tagset (GtkTextBuffer *buffer, const gchar *tagset_name) { gchar *mime_type; GdkAtom format; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE); g_return_val_if_fail (tagset_name == NULL || *tagset_name != '\0', GDK_NONE); if (tagset_name) { mime_type = g_strconcat ("application/x-gtk-text-buffer-rich-text;format=", tagset_name, NULL); } else { mime_type = g_strdup ("application/x-gtk-text-buffer-rich-text"); } format = gtk_text_buffer_register_serialize_format (buffer, mime_type, _gtk_text_buffer_serialize_rich_text, NULL, NULL); g_free (mime_type); return format; } /** * gtk_text_buffer_register_deserialize_format: * @buffer: a #GtkTextBuffer * @mime_type: the format’s mime-type * @function: the deserialize function to register * @user_data: @function’s user_data * @user_data_destroy: a function to call when @user_data is no longer needed * * This function registers a rich text deserialization @function along with * its @mime_type with the passed @buffer. * * Returns: (transfer none): the #GdkAtom that corresponds to the * newly registered format’s mime-type. * * Since: 2.10 **/ GdkAtom gtk_text_buffer_register_deserialize_format (GtkTextBuffer *buffer, const gchar *mime_type, GtkTextBufferDeserializeFunc function, gpointer user_data, GDestroyNotify user_data_destroy) { GList *formats; GdkAtom atom; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE); g_return_val_if_fail (mime_type != NULL && *mime_type != '\0', GDK_NONE); g_return_val_if_fail (function != NULL, GDK_NONE); formats = g_object_steal_qdata (G_OBJECT (buffer), deserialize_quark ()); formats = register_format (formats, mime_type, (gpointer) function, user_data, user_data_destroy, &atom); g_object_set_qdata_full (G_OBJECT (buffer), deserialize_quark (), formats, (GDestroyNotify) free_format_list); g_object_notify (G_OBJECT (buffer), "paste-target-list"); return atom; } /** * gtk_text_buffer_register_deserialize_tagset: * @buffer: a #GtkTextBuffer * @tagset_name: (allow-none): an optional tagset name, on %NULL * * This function registers GTK+’s internal rich text serialization * format with the passed @buffer. See * gtk_text_buffer_register_serialize_tagset() for details. * * Returns: (transfer none): the #GdkAtom that corresponds to the * newly registered format’s mime-type. * * Since: 2.10 **/ GdkAtom gtk_text_buffer_register_deserialize_tagset (GtkTextBuffer *buffer, const gchar *tagset_name) { gchar *mime_type; GdkAtom format; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE); g_return_val_if_fail (tagset_name == NULL || *tagset_name != '\0', GDK_NONE); if (tagset_name) { mime_type = g_strconcat ("application/x-gtk-text-buffer-rich-text;format=", tagset_name, NULL); } else { mime_type = g_strdup ("application/x-gtk-text-buffer-rich-text"); } format = gtk_text_buffer_register_deserialize_format (buffer, mime_type, _gtk_text_buffer_deserialize_rich_text, NULL, NULL); g_free (mime_type); return format; } /** * gtk_text_buffer_unregister_serialize_format: * @buffer: a #GtkTextBuffer * @format: a #GdkAtom representing a registered rich text format. * * This function unregisters a rich text format that was previously * registered using gtk_text_buffer_register_serialize_format() or * gtk_text_buffer_register_serialize_tagset() * * Since: 2.10 **/ void gtk_text_buffer_unregister_serialize_format (GtkTextBuffer *buffer, GdkAtom format) { GList *formats; g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); g_return_if_fail (format != GDK_NONE); formats = g_object_steal_qdata (G_OBJECT (buffer), serialize_quark ()); formats = unregister_format (formats, format); g_object_set_qdata_full (G_OBJECT (buffer), serialize_quark (), formats, (GDestroyNotify) free_format_list); g_object_notify (G_OBJECT (buffer), "copy-target-list"); } /** * gtk_text_buffer_unregister_deserialize_format: * @buffer: a #GtkTextBuffer * @format: a #GdkAtom representing a registered rich text format. * * This function unregisters a rich text format that was previously * registered using gtk_text_buffer_register_deserialize_format() or * gtk_text_buffer_register_deserialize_tagset(). * * Since: 2.10 **/ void gtk_text_buffer_unregister_deserialize_format (GtkTextBuffer *buffer, GdkAtom format) { GList *formats; g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); g_return_if_fail (format != GDK_NONE); formats = g_object_steal_qdata (G_OBJECT (buffer), deserialize_quark ()); formats = unregister_format (formats, format); g_object_set_qdata_full (G_OBJECT (buffer), deserialize_quark (), formats, (GDestroyNotify) free_format_list); g_object_notify (G_OBJECT (buffer), "paste-target-list"); } /** * gtk_text_buffer_deserialize_set_can_create_tags: * @buffer: a #GtkTextBuffer * @format: a #GdkAtom representing a registered rich text format * @can_create_tags: whether deserializing this format may create tags * * Use this function to allow a rich text deserialization function to * create new tags in the receiving buffer. Note that using this * function is almost always a bad idea, because the rich text * functions you register should know how to map the rich text format * they handler to your text buffers set of tags. * * The ability of creating new (arbitrary!) tags in the receiving buffer * is meant for special rich text formats like the internal one that * is registered using gtk_text_buffer_register_deserialize_tagset(), * because that format is essentially a dump of the internal structure * of the source buffer, including its tag names. * * You should allow creation of tags only if you know what you are * doing, e.g. if you defined a tagset name for your application * suite’s text buffers and you know that it’s fine to receive new * tags from these buffers, because you know that your application can * handle the newly created tags. * * Since: 2.10 **/ void gtk_text_buffer_deserialize_set_can_create_tags (GtkTextBuffer *buffer, GdkAtom format, gboolean can_create_tags) { GList *formats; GList *list; gchar *format_name; g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); g_return_if_fail (format != GDK_NONE); formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ()); for (list = formats; list; list = list->next) { GtkRichTextFormat *fmt = list->data; if (fmt->atom == format) { fmt->can_create_tags = can_create_tags ? TRUE : FALSE; return; } } format_name = gdk_atom_name (format); g_warning ("%s: \"%s\" is not registered as deserializable format " "with text buffer %p", G_STRFUNC, format_name ? format_name : "not a GdkAtom", buffer); g_free (format_name); } /** * gtk_text_buffer_deserialize_get_can_create_tags: * @buffer: a #GtkTextBuffer * @format: a #GdkAtom representing a registered rich text format * * This functions returns the value set with * gtk_text_buffer_deserialize_set_can_create_tags() * * Returns: whether deserializing this format may create tags * * Since: 2.10 **/ gboolean gtk_text_buffer_deserialize_get_can_create_tags (GtkTextBuffer *buffer, GdkAtom format) { GList *formats; GList *list; gchar *format_name; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE); g_return_val_if_fail (format != GDK_NONE, FALSE); formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ()); for (list = formats; list; list = list->next) { GtkRichTextFormat *fmt = list->data; if (fmt->atom == format) { return fmt->can_create_tags; } } format_name = gdk_atom_name (format); g_warning ("%s: \"%s\" is not registered as deserializable format " "with text buffer %p", G_STRFUNC, format_name ? format_name : "not a GdkAtom", buffer); g_free (format_name); return FALSE; } /** * gtk_text_buffer_get_serialize_formats: * @buffer: a #GtkTextBuffer * @n_formats: (out): return location for the number of formats * * This function returns the rich text serialize formats registered * with @buffer using gtk_text_buffer_register_serialize_format() or * gtk_text_buffer_register_serialize_tagset() * * Returns: (array length=n_formats) (transfer container): an array of * #GdkAtoms representing the registered formats. * * Since: 2.10 **/ GdkAtom * gtk_text_buffer_get_serialize_formats (GtkTextBuffer *buffer, gint *n_formats) { GList *formats; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL); g_return_val_if_fail (n_formats != NULL, NULL); formats = g_object_get_qdata (G_OBJECT (buffer), serialize_quark ()); return get_formats (formats, n_formats); } /** * gtk_text_buffer_get_deserialize_formats: * @buffer: a #GtkTextBuffer * @n_formats: (out): return location for the number of formats * * This function returns the rich text deserialize formats registered * with @buffer using gtk_text_buffer_register_deserialize_format() or * gtk_text_buffer_register_deserialize_tagset() * * Returns: (array length=n_formats) (transfer container): an array of * #GdkAtoms representing the registered formats. * * Since: 2.10 **/ GdkAtom * gtk_text_buffer_get_deserialize_formats (GtkTextBuffer *buffer, gint *n_formats) { GList *formats; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL); g_return_val_if_fail (n_formats != NULL, NULL); formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ()); return get_formats (formats, n_formats); } /** * gtk_text_buffer_serialize: * @register_buffer: the #GtkTextBuffer @format is registered with * @content_buffer: the #GtkTextBuffer to serialize * @format: the rich text format to use for serializing * @start: start of block of text to serialize * @end: end of block of test to serialize * @length: (out): return location for the length of the serialized data * * This function serializes the portion of text between @start * and @end in the rich text format represented by @format. * * @formats to be used must be registered using * gtk_text_buffer_register_serialize_format() or * gtk_text_buffer_register_serialize_tagset() beforehand. * * Returns: (array length=length) (transfer full): the serialized * data, encoded as @format * * Since: 2.10 **/ guint8 * gtk_text_buffer_serialize (GtkTextBuffer *register_buffer, GtkTextBuffer *content_buffer, GdkAtom format, const GtkTextIter *start, const GtkTextIter *end, gsize *length) { GList *formats; GList *list; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (register_buffer), NULL); g_return_val_if_fail (GTK_IS_TEXT_BUFFER (content_buffer), NULL); g_return_val_if_fail (format != GDK_NONE, NULL); g_return_val_if_fail (start != NULL, NULL); g_return_val_if_fail (end != NULL, NULL); g_return_val_if_fail (length != NULL, NULL); *length = 0; formats = g_object_get_qdata (G_OBJECT (register_buffer), serialize_quark ()); for (list = formats; list; list = list->next) { GtkRichTextFormat *fmt = list->data; if (fmt->atom == format) { GtkTextBufferSerializeFunc function = fmt->function; return function (register_buffer, content_buffer, start, end, length, fmt->user_data); } } return NULL; } /** * gtk_text_buffer_deserialize: * @register_buffer: the #GtkTextBuffer @format is registered with * @content_buffer: the #GtkTextBuffer to deserialize into * @format: the rich text format to use for deserializing * @iter: insertion point for the deserialized text * @data: (array length=length): data to deserialize * @length: length of @data * @error: return location for a #GError * * This function deserializes rich text in format @format and inserts * it at @iter. * * @formats to be used must be registered using * gtk_text_buffer_register_deserialize_format() or * gtk_text_buffer_register_deserialize_tagset() beforehand. * * Returns: %TRUE on success, %FALSE otherwise. * * Since: 2.10 **/ gboolean gtk_text_buffer_deserialize (GtkTextBuffer *register_buffer, GtkTextBuffer *content_buffer, GdkAtom format, GtkTextIter *iter, const guint8 *data, gsize length, GError **error) { GList *formats; GList *l; g_return_val_if_fail (GTK_IS_TEXT_BUFFER (register_buffer), FALSE); g_return_val_if_fail (GTK_IS_TEXT_BUFFER (content_buffer), FALSE); g_return_val_if_fail (format != GDK_NONE, FALSE); g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (length > 0, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); formats = g_object_get_qdata (G_OBJECT (register_buffer), deserialize_quark ()); for (l = formats; l; l = l->next) { GtkRichTextFormat *fmt = l->data; if (fmt->atom == format) { GtkTextBufferDeserializeFunc function = fmt->function; gboolean success; GSList *split_tags; GSList *list; GtkTextMark *left_end = NULL; GtkTextMark *right_start = NULL; GSList *left_start_list = NULL; GSList *right_end_list = NULL; /* We don't want the tags that are effective at the insertion * point to affect the pasted text, therefore we remove and * remember them, so they can be re-applied left and right of * the inserted text after pasting */ split_tags = gtk_text_iter_get_tags (iter); list = split_tags; while (list) { GtkTextTag *tag = list->data; list = list->next; /* If a tag starts at the insertion point, ignore it * because it doesn't affect the pasted text */ if (gtk_text_iter_starts_tag (iter, tag)) split_tags = g_slist_remove (split_tags, tag); } if (split_tags) { /* Need to remember text marks, because text iters * don't survive pasting */ left_end = gtk_text_buffer_create_mark (content_buffer, NULL, iter, TRUE); right_start = gtk_text_buffer_create_mark (content_buffer, NULL, iter, FALSE); for (list = split_tags; list; list = list->next) { GtkTextTag *tag = list->data; GtkTextIter *backward_toggle = gtk_text_iter_copy (iter); GtkTextIter *forward_toggle = gtk_text_iter_copy (iter); GtkTextMark *left_start = NULL; GtkTextMark *right_end = NULL; gtk_text_iter_backward_to_tag_toggle (backward_toggle, tag); left_start = gtk_text_buffer_create_mark (content_buffer, NULL, backward_toggle, FALSE); gtk_text_iter_forward_to_tag_toggle (forward_toggle, tag); right_end = gtk_text_buffer_create_mark (content_buffer, NULL, forward_toggle, TRUE); left_start_list = g_slist_prepend (left_start_list, left_start); right_end_list = g_slist_prepend (right_end_list, right_end); gtk_text_buffer_remove_tag (content_buffer, tag, backward_toggle, forward_toggle); gtk_text_iter_free (forward_toggle); gtk_text_iter_free (backward_toggle); } left_start_list = g_slist_reverse (left_start_list); right_end_list = g_slist_reverse (right_end_list); } success = function (register_buffer, content_buffer, iter, data, length, fmt->can_create_tags, fmt->user_data, error); if (!success && error != NULL && *error == NULL) g_set_error (error, 0, 0, _("Unknown error when trying to deserialize %s"), gdk_atom_name (format)); if (split_tags) { GSList *left_list; GSList *right_list; GtkTextIter left_e; GtkTextIter right_s; /* Turn the remembered marks back into iters so they * can by used to re-apply the remembered tags */ gtk_text_buffer_get_iter_at_mark (content_buffer, &left_e, left_end); gtk_text_buffer_get_iter_at_mark (content_buffer, &right_s, right_start); for (list = split_tags, left_list = left_start_list, right_list = right_end_list; list && left_list && right_list; list = list->next, left_list = left_list->next, right_list = right_list->next) { GtkTextTag *tag = list->data; GtkTextMark *left_start = left_list->data; GtkTextMark *right_end = right_list->data; GtkTextIter left_s; GtkTextIter right_e; gtk_text_buffer_get_iter_at_mark (content_buffer, &left_s, left_start); gtk_text_buffer_get_iter_at_mark (content_buffer, &right_e, right_end); gtk_text_buffer_apply_tag (content_buffer, tag, &left_s, &left_e); gtk_text_buffer_apply_tag (content_buffer, tag, &right_s, &right_e); gtk_text_buffer_delete_mark (content_buffer, left_start); gtk_text_buffer_delete_mark (content_buffer, right_end); } gtk_text_buffer_delete_mark (content_buffer, left_end); gtk_text_buffer_delete_mark (content_buffer, right_start); g_slist_free (split_tags); g_slist_free (left_start_list); g_slist_free (right_end_list); } return success; } } g_set_error (error, 0, 0, _("No deserialize function found for format %s"), gdk_atom_name (format)); return FALSE; } /* private functions */ static GList * register_format (GList *formats, const gchar *mime_type, gpointer function, gpointer user_data, GDestroyNotify user_data_destroy, GdkAtom *atom) { GtkRichTextFormat *format; *atom = gdk_atom_intern (mime_type, FALSE); formats = unregister_format (formats, *atom); format = g_slice_new0 (GtkRichTextFormat); format->mime_type = g_strdup (mime_type); format->can_create_tags = FALSE; format->atom = *atom; format->function = function; format->user_data = user_data; format->user_data_destroy = user_data_destroy; return g_list_append (formats, format); } static GList * unregister_format (GList *formats, GdkAtom atom) { GList *list; for (list = formats; list; list = list->next) { GtkRichTextFormat *format = list->data; if (format->atom == atom) { free_format (format); return g_list_delete_link (formats, list); } } return formats; } static GdkAtom * get_formats (GList *formats, gint *n_formats) { GdkAtom *array; GList *list; gint i; *n_formats = g_list_length (formats); array = g_new0 (GdkAtom, *n_formats); for (list = formats, i = 0; list; list = list->next, i++) { GtkRichTextFormat *format = list->data; array[i] = format->atom; } return array; } static void free_format (GtkRichTextFormat *format) { if (format->user_data_destroy) format->user_data_destroy (format->user_data); g_free (format->mime_type); g_slice_free (GtkRichTextFormat, format); } static void free_format_list (GList *formats) { g_list_free_full (formats, (GDestroyNotify) free_format); } static GQuark serialize_quark (void) { static GQuark quark = 0; if (! quark) quark = g_quark_from_static_string ("gtk-text-buffer-serialize-formats"); return quark; } static GQuark deserialize_quark (void) { static GQuark quark = 0; if (! quark) quark = g_quark_from_static_string ("gtk-text-buffer-deserialize-formats"); return quark; }