gtk2/gtk/gtktextbufferrichtext.c
Michael Natterer 6c1d990adc Add infrastructure for copy/paste and DND of rich text for GtkTextBuffer.
2006-03-07  Michael Natterer  <mitch@imendio.com>

	Add infrastructure for copy/paste and DND of rich text for
	GtkTextBuffer. Fixes bug #324177.

	* gtk/gtktextbufferrichtext.[ch]: new files implementing a
	per-buffer registry of rich text formats.

	* gtk/gtk.h: #include gtktextbufferrichtext.h

	* gtk/gtktextbufferserialize.[ch]: new files implementing an
	internal serialization format that can handle all of a text
	buffer's tags and pixbufs. It's not useful for anything except
	tranfer between instances of GtkTextBuffer (Anders Carlsson).

	* gtk/Makefile.am: build the new files.

	* gtk/gtkclipboard.[ch]: added convenience APIs for rich text,
	just as they exist for plain text and pixbufs.

	* gtk/gtkselection.[ch]: added rich text convenience APIs here
	too.  Return the target list from gtk_target_list_ref(). Register
	GtkTargetList as boxed type. Added
	gtk_target_table_new_from_list() and gtk_target_table_free(),
	which make converting between GtkTargetList and arrays of
	GtkTargetEntry considerably easier.

	* gtk/gtktextutil.[ch]: added _gtk_text_util_create_rich_drag_icon()
	which creates a fancy rich text icon (Matthias Clasen).

	* gtk/gtktextbuffer.[ch]: use all the new stuff above and
	implement copy and paste of rich text. Added APIs for getting the
	target lists used for copy and paste. Added public enum
	GtkTextBufferTargetInfo which contains the "info" IDs associated
	with the entries of the target lists.

	* gtk/gtktextview.c: use the new rich text APIs and
	GtkTextBuffer's new target list API to enable DND of rich text
	chunks.

	* gtk/gtk.symbols: export all the new symbols added.

	* tests/testtext.c: added rich text testing stuff.
2006-03-07 13:46:11 +00:00

705 lines
22 KiB
C

/* gtkrichtext.c
*
* Copyright (C) 2006 Imendio AB
* Contact: Michael Natterer <mitch@imendio.com>
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include "gtktextbufferrichtext.h"
#include "gtktextbufferserialize.h"
#include "gtkalias.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.
*
* Return value: 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: 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.
*
* Return value: 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 = "application/x-gtk-text-buffer-rich-text";
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_strdup_printf ("application/x-gtk-text-buffer-rich-text;format=%s",
tagset_name);
format = gtk_text_buffer_register_serialize_format (buffer, mime_type,
_gtk_text_buffer_serialize_rich_text,
NULL, NULL);
if (tagset_name)
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.
*
* Return value: 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: 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.
*
* Return value: 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 = "application/x-gtk-text-buffer-rich-text";
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_strdup_printf ("application/x-gtk-text-buffer-rich-text;format=%s",
tagset_name);
format = gtk_text_buffer_register_deserialize_format (buffer, mime_type,
_gtk_text_buffer_deserialize_rich_text,
NULL, NULL);
if (tagset_name)
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 = g_list_next (list))
{
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()
*
* Return value: 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 = g_list_next (list))
{
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: 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()
*
* Return value: an array of #GdkAtom<!-- -->s 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: 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()
*
* Return value: an array of #GdkAtom<!-- -->s 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: 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.
*
* %format<!-- -->s to be used must be registered using
* gtk_text_buffer_register_serialize_format() or
* gtk_text_buffer_register_serialize_tagset() beforehand.
*
* Return value: 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 = g_list_next (list))
{
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_serialize:
* @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: data to deserialize
* @length: length of %data
* @error: return loaction for a #GError
*
* This function deserializes rich text in format %format and inserts
* it at %iter.
*
* %format<!-- -->s to be used must be registered using
* gtk_text_buffer_register_deserialize_format() or
* gtk_text_buffer_register_deserialize_tagset() beforehand.
*
* Return value: %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 *list;
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 (list = formats; list; list = g_list_next (list))
{
GtkRichTextFormat *fmt = list->data;
if (fmt->atom == format)
{
GtkTextBufferDeserializeFunc function = fmt->function;
gboolean success;
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));
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_new0 (GtkRichTextFormat, 1);
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 = g_list_next (list))
{
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 = g_list_next (list), 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_free (format);
}
static void
free_format_list (GList *formats)
{
g_list_foreach (formats, (GFunc) free_format, NULL);
g_list_free (formats);
}
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;
}
#define __GTK_TEXT_BUFFER_RICH_TEXT_C__
#include "gtkaliasdef.c"